/************************************************************************* * Copyright 2009-2016 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.loadbalancing.workflow; import static com.eucalyptus.loadbalancing.LoadBalancerZone.LoadBalancerZoneCoreView.name; import static com.eucalyptus.loadbalancing.LoadBalancerZone.LoadBalancerZoneCoreView.subnetId; import static com.eucalyptus.loadbalancing.service.LoadBalancingService.MAX_HEALTHCHECK_INTERVAL_SEC; import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.principal.Role; import com.eucalyptus.auth.tokens.SecurityTokenAWSCredentialsProvider; import com.eucalyptus.compute.common.*; import com.eucalyptus.loadbalancing.*; import com.eucalyptus.loadbalancing.common.LoadBalancing; import com.eucalyptus.loadbalancing.LoadBalancingSystemVpcs; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.annotation.Nullable; import com.eucalyptus.loadbalancing.common.msgs.HealthCheck; import com.eucalyptus.loadbalancing.common.msgs.PolicyDescription; import com.eucalyptus.objectstorage.client.EucaS3Client; import com.eucalyptus.objectstorage.client.EucaS3ClientFactory; import com.eucalyptus.ws.StackConfiguration; import org.apache.log4j.Logger; import com.eucalyptus.auth.Accounts; import com.eucalyptus.auth.euare.GetRolePolicyResult; import com.eucalyptus.auth.euare.InstanceProfileType; import com.eucalyptus.auth.euare.RoleType; import com.eucalyptus.auth.euare.ServerCertificateType; import com.eucalyptus.auth.principal.AccountFullName; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.autoscaling.common.msgs.AutoScalingGroupType; import com.eucalyptus.autoscaling.common.msgs.AutoScalingGroupsType; import com.eucalyptus.autoscaling.common.msgs.DescribeAutoScalingGroupsResponseType; import com.eucalyptus.autoscaling.common.msgs.DescribeAutoScalingGroupsResult; import com.eucalyptus.autoscaling.common.msgs.Instance; import com.eucalyptus.autoscaling.common.msgs.Instances; import com.eucalyptus.autoscaling.common.msgs.LaunchConfigurationType; import com.eucalyptus.cloudwatch.common.msgs.MetricData; import com.eucalyptus.component.annotation.ComponentPart; import com.eucalyptus.crypto.util.B64; import com.eucalyptus.empyrean.ServiceStatusType; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.loadbalancing.LoadBalancer.LoadBalancerCoreView; import com.eucalyptus.loadbalancing.LoadBalancer.LoadBalancerEntityTransform; import com.eucalyptus.loadbalancing.LoadBalancerBackendInstance.LoadBalancerBackendInstanceCoreView; import com.eucalyptus.loadbalancing.LoadBalancerBackendInstance.LoadBalancerBackendInstanceEntityTransform; import com.eucalyptus.loadbalancing.LoadBalancerListener.LoadBalancerListenerCoreView; import com.eucalyptus.loadbalancing.LoadBalancerListener.LoadBalancerListenerEntityTransform; import com.eucalyptus.loadbalancing.LoadBalancerListener.PROTOCOL; import com.eucalyptus.loadbalancing.LoadBalancerPolicyDescription.LoadBalancerPolicyDescriptionCoreView; import com.eucalyptus.loadbalancing.LoadBalancerSecurityGroup.LoadBalancerSecurityGroupCoreView; import com.eucalyptus.loadbalancing.LoadBalancerSecurityGroup.LoadBalancerSecurityGroupEntityTransform; import com.eucalyptus.loadbalancing.LoadBalancerZone.LoadBalancerZoneCoreView; import com.eucalyptus.loadbalancing.LoadBalancerZone.LoadBalancerZoneEntityTransform; import com.eucalyptus.loadbalancing.activities.EucalyptusActivityException; import com.eucalyptus.loadbalancing.activities.EucalyptusActivityTasks; import com.eucalyptus.loadbalancing.activities.LoadBalancerAutoScalingGroup; import com.eucalyptus.loadbalancing.activities.LoadBalancerAutoScalingGroup.LoadBalancerAutoScalingGroupCoreView; import com.eucalyptus.loadbalancing.activities.LoadBalancerAutoScalingGroup.LoadBalancerAutoScalingGroupEntityTransform; import com.eucalyptus.loadbalancing.activities.LoadBalancerServoInstance; import com.eucalyptus.loadbalancing.activities.LoadBalancerServoInstance.LoadBalancerServoInstanceCoreView; import com.eucalyptus.loadbalancing.activities.LoadBalancerServoInstance.LoadBalancerServoInstanceEntityTransform; import com.eucalyptus.loadbalancing.common.msgs.Listener; import com.eucalyptus.loadbalancing.common.msgs.LoadBalancerServoDescription; import com.eucalyptus.loadbalancing.common.msgs.PolicyAttribute; import com.eucalyptus.resources.client.Ec2Client; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.util.DNSProperties; import com.eucalyptus.util.Exceptions; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * @author Sang-Min Park (sangmin.park@hpe.com) * */ @ComponentPart(LoadBalancing.class) public class LoadBalancingActivitiesImpl implements LoadBalancingActivities { private static Logger LOG = Logger.getLogger( LoadBalancingActivitiesImpl.class ); private static int findAvailableResources(final List<ClusterInfoType> clusters, final String zoneName, final String instanceType){ // parse euca-describe-availability-zones verbose response // WARNING: this is not a standard API! for(int i =0; i<clusters.size(); i++){ final ClusterInfoType cc = clusters.get(i); if(zoneName.equals(cc.getZoneName())){ for(int j=i+1; j< clusters.size(); j++){ final ClusterInfoType candidate = clusters.get(j); if(candidate.getZoneName()!=null && candidate.getZoneName().toLowerCase().contains(instanceType.toLowerCase())){ //<zoneState>0002 / 0002 2 512 10</zoneState> final String state = candidate.getZoneState(); final String[] tokens = state.split("/"); if( tokens.length > 0 ){ try{ String strNum = tokens[0].trim().replaceFirst("0+", ""); if(strNum.length()<=0) strNum="0"; return Integer.parseInt(strNum); }catch(final NumberFormatException ex){ break; }catch(final Exception ex){ break; } } } } break; } } return Integer.MAX_VALUE; // when check fails, let's assume its abundant } @Override public boolean createLbAdmissionControl(final String accountNumber, final String lbName, final String[] zones) throws LoadBalancingActivityException { final String emi = LoadBalancingWorkerProperties.IMAGE; List<ImageDetails> images; try{ images = EucalyptusActivityTasks.getInstance().describeImagesWithVerbose(Lists.newArrayList(emi)); if(images==null || images.size()<=0 ||! images.get(0).getImageId().toLowerCase().equals(emi.toLowerCase())) throw new Exception("No loadbalancer EMI is found"); }catch(final Exception ex){ throw new LoadBalancingActivityException("failed to validate the loadbalancer EMI", ex); } // zones: is the CC found? final List<String> requestedZones = Lists.newArrayList(zones); List<ClusterInfoType> clusters; try{ clusters = EucalyptusActivityTasks.getInstance().describeAvailabilityZonesWithVerbose(); for(final ClusterInfoType cc : clusters){ requestedZones.remove(cc.getZoneName()); } }catch(final Exception ex){ throw new InvalidConfigurationRequestException("failed to validate the requested zones", ex); } if(requestedZones.size()>0){ throw new InvalidConfigurationRequestException("unknown zone is requested"); } // are there enough resources? final String instanceType = LoadBalancingWorkerProperties.INSTANCE_TYPE; int numVm = 1; try{ //// TODO: fix numVm = Integer.parseInt(LoadBalancingServiceProperties.VM_PER_ZONE); }catch(final NumberFormatException ex){ LOG.warn("unable to parse loadbalancer_num_vm"); } for(final String zone : zones){ final int capacity = findAvailableResources(clusters, zone, instanceType); if(numVm>capacity){ throw new NotEnoughResourcesException(); } } // check if the keyname is configured and exists, the key name for new ELB's should be from // loadbalancing account final String keyName = LoadBalancingWorkerProperties.KEYNAME; if ( keyName != null && !keyName.isEmpty() ) { try { Ec2Client.getInstance().describeKeyPairs(Accounts.lookupSystemAccountByAlias( AccountIdentifiers.ELB_SYSTEM_ACCOUNT ).getUserId( ), Lists.newArrayList(keyName)); } catch(Exception ex) { throw new LoadBalancingActivityException("The configured keyname is not found." + " Do you have keypair " + keyName + " that belongs to " + AccountIdentifiers.ELB_SYSTEM_ACCOUNT + " account?"); } } return true; } private static final String DEFAULT_ROLE_PATH_PREFIX = "/internal/loadbalancer"; public static final String ROLE_NAME_PREFIX = "loadbalancer-vm"; private static final String DEFAULT_ASSUME_ROLE_POLICY = "{\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}"; // FIXME: use lambda expression public static String getRoleName(final String accountNumber, final String loadbalancer) { return String.format("%s-%s-%s", ROLE_NAME_PREFIX, accountNumber, loadbalancer); } @Override public String iamRoleSetup(final String accountNumber, final String lbName) throws LoadBalancingActivityException{ RoleType role = null; final String roleName = getRoleName(accountNumber, lbName); // list-roles. try{ List<RoleType> result = EucalyptusActivityTasks.getInstance().listRoles(DEFAULT_ROLE_PATH_PREFIX); if(result != null){ for(RoleType r : result){ if(roleName.equals(r.getRoleName())){ role = r; break; } } } }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to list IAM roles", ex); } // if no role found, create a new role with assume-role policy for elb if(role==null){ /// create a new role try{ role = EucalyptusActivityTasks.getInstance().createRole(roleName, DEFAULT_ROLE_PATH_PREFIX, DEFAULT_ASSUME_ROLE_POLICY); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to create the role for ELB Vms"); } } if(role==null) throw new LoadBalancingActivityException("No role is found for LoadBalancer Vms"); return role.getRoleName(); } public static final String DEFAULT_INSTANCE_PROFILE_PATH_PREFIX="/internal/loadbalancer"; public static final String INSTANCE_PROFILE_NAME_PREFIX = "loadbalancer-vm"; private static String getInstanceProfileName (final String accountNumber, final String loadbalancer) { return String.format("%s-%s-%s", INSTANCE_PROFILE_NAME_PREFIX, accountNumber, loadbalancer); } @Override public String instanceProfileSetup(final String accountNumber, final String lbName, String roleName) throws LoadBalancingActivityException{ InstanceProfileType instanceProfile = null; final String instanceProfileName = getInstanceProfileName(accountNumber, lbName); // list instance profiles try{ // check if the instance profile for ELB VM is found List<InstanceProfileType> instanceProfiles = EucalyptusActivityTasks.getInstance().listInstanceProfiles(DEFAULT_INSTANCE_PROFILE_PATH_PREFIX); for(InstanceProfileType ip : instanceProfiles){ if(instanceProfileName.equals(ip.getInstanceProfileName())){ instanceProfile = ip; break; } } }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to list instance profiles", ex); } if(instanceProfile == null){ // if not create one try{ instanceProfile = EucalyptusActivityTasks.getInstance().createInstanceProfile(instanceProfileName, DEFAULT_INSTANCE_PROFILE_PATH_PREFIX); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to create instance profile", ex); } } if(instanceProfile == null) throw new LoadBalancingActivityException("No instance profile for loadbalancer VM is found"); try{ List<RoleType> roles = instanceProfile.getRoles().getMember(); boolean roleFound = false; for(RoleType role : roles){ if(role.getRoleName().equals(roleName)){ roleFound=true; break; } } if(!roleFound) throw new NoSuchElementException(); }catch(Exception ex){ if(roleName == null) throw new LoadBalancingActivityException("No role name is found for loadbalancer VMs"); try{ EucalyptusActivityTasks.getInstance().addRoleToInstanceProfile(instanceProfile.getInstanceProfileName(), roleName); }catch(Exception ex2){ throw new LoadBalancingActivityException("Failed to add role to the instance profile", ex2); } } return instanceProfile.getInstanceProfileName(); } static final String SERVO_ROLE_POLICY_NAME = "euca-internal-loadbalancer-vm-policy"; private static final String SERVO_ROLE_POLICY_DOCUMENT= "{\"Statement\":[{\"Action\": [\"swf:PollForActivityTask\", \"swf:RegisterActivityType\", \"swf:RespondActivityTaskCanceled\", \"swf:RespondActivityTaskCompleted\", \"swf:RespondActivityTaskFailed\", \"swf:RecordActivityTaskHeartbeat\"],\"Effect\": \"Allow\",\"Resource\": \"*\"}]}"; @Override public String iamPolicySetup(final String accountNumber, final String lbName, String roleName) throws LoadBalancingActivityException{ GetRolePolicyResult policy = null; try{ final List<String> policies = EucalyptusActivityTasks.getInstance().listRolePolicies(roleName); if(policies.contains(SERVO_ROLE_POLICY_NAME)){ policy = EucalyptusActivityTasks.getInstance().getRolePolicy(roleName, SERVO_ROLE_POLICY_NAME); } }catch(final Exception ex){ } boolean putPolicy; if(policy == null || policy.getPolicyName() == null || !policy.getPolicyName().equals(SERVO_ROLE_POLICY_NAME)){ putPolicy=true; }else if (!SERVO_ROLE_POLICY_DOCUMENT.toLowerCase().equals(policy.getPolicyDocument().toLowerCase())){ try{ EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, SERVO_ROLE_POLICY_NAME); }catch(final Exception ex){ LOG.warn("failed to delete role policy", ex); } putPolicy = true; }else{ putPolicy = false; } if(putPolicy){ try{ EucalyptusActivityTasks.getInstance().putRolePolicy(roleName, SERVO_ROLE_POLICY_NAME, SERVO_ROLE_POLICY_DOCUMENT); }catch(final Exception ex){ throw new LoadBalancingActivityException("failed to put role policy for loadbalancer vm"); } } return SERVO_ROLE_POLICY_NAME; } public static String getSecurityGroupName(final String ownerAccountNumber, final String lbName) { return String.format( "euca-internal-%s-%s", ownerAccountNumber, lbName ); } private static String generateDefaultVPCSecurityGroupName( final String vpcId ) { return String.format( "default_elb_%s", UUID.nameUUIDFromBytes( vpcId.getBytes( StandardCharsets.UTF_8 ) ).toString( ) ); } @Override public SecurityGroupSetupActivityResult securityGroupSetup(final String accountNumber, final String lbName) throws LoadBalancingActivityException { final SecurityGroupSetupActivityResult result = new SecurityGroupSetupActivityResult(); LoadBalancer lbEntity; LoadBalancerCoreView lb; try{ lbEntity = LoadBalancers.getLoadbalancer(accountNumber, lbName); lb = lbEntity.getCoreView( ); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+lbName, ex); } if ( lb.getVpcId( ) == null ) { final String groupName = getSecurityGroupName( lb.getOwnerAccountNumber(), lb.getDisplayName() ); final String groupDesc = String.format( "group for loadbalancer %s", lbName ); // check if there's an existing group with the same name boolean groupFound = false; try { List<SecurityGroupItemType> groups = EucalyptusActivityTasks.getInstance().describeSystemSecurityGroups( Lists.newArrayList( groupName ) ); if ( groups != null ) for ( final SecurityGroupItemType group : groups ) { if ( groupName.equals( group.getGroupName() ) && group.getVpcId( ) == null ) { groupFound = true; result.setGroupName(groupName); result.setGroupId(group.getGroupId()); result.setGroupOwnerAccountId(group.getAccountId()); break; } } } catch ( Exception ex ) { groupFound = false; } // create a new security group if ( !groupFound ) { try { EucalyptusActivityTasks.getInstance().createSystemSecurityGroup( groupName, groupDesc ); result.setCreatedGroupName(groupName); result.setGroupName(groupName); List<SecurityGroupItemType> groups = EucalyptusActivityTasks.getInstance().describeSystemSecurityGroups( Lists.newArrayList( groupName ) ); if ( groups != null ) for ( final SecurityGroupItemType group : groups ) { if ( groupName.equals( group.getGroupName() ) && group.getVpcId( ) == null ) { result.setCreatedGroupId(group.getGroupId()); result.setGroupId(group.getGroupId()); result.setGroupOwnerAccountId(group.getAccountId()); break; } } } catch ( Exception ex ) { throw new LoadBalancingActivityException( "Failed to create the security group for loadbalancer", ex ); } } if(result.getGroupName() == null || result.getGroupOwnerAccountId() == null) throw new LoadBalancingActivityException("Failed to create the security group for loadbalancer"); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerSecurityGroup.class ) ) { try { Entities.uniqueResult( LoadBalancerSecurityGroup.named( lbEntity, result.getGroupOwnerAccountId(), result.getGroupName() ) ); } catch( NoSuchElementException ex ){ Entities.persist( LoadBalancerSecurityGroup.create( lbEntity, result.getGroupOwnerAccountId(), result.getGroupName() ) ); } db.commit(); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while persisting security group", ex); } /// END OF NON-VPC CASE } else if ( lb.getSecurityGroupIdsToNames( ).isEmpty( ) ) { final String groupName = generateDefaultVPCSecurityGroupName( lb.getVpcId( ) ); final String groupDesc = String.format( "ELB created security group used when no security group is specified during ELB creation - modifications could impact traffic to future ELBs" ); final AccountFullName accountFullName = AccountFullName.getInstance(accountNumber); final List<SecurityGroupItemType> groups = EucalyptusActivityTasks.getInstance() .describeUserSecurityGroupsByName( accountFullName, lb.getVpcId( ), groupName ); final SecurityGroupItemType elbVpcGroup; if ( groups.isEmpty( ) ) { EucalyptusActivityTasks.getInstance().createUserSecurityGroup( accountFullName, groupName, groupDesc ); final List<SecurityGroupItemType> createdGroupList = EucalyptusActivityTasks.getInstance( ) .describeUserSecurityGroupsByName( accountFullName, lb.getVpcId( ), groupName ); elbVpcGroup = Iterables.getOnlyElement( createdGroupList ); result.setCreatedGroupId(elbVpcGroup.getGroupId( )); result.setCreatedGroupName(elbVpcGroup.getGroupName( )); } else { elbVpcGroup = Iterables.get( groups, 0 ); } Entities.asDistinctTransaction( LoadBalancer.class, new Predicate<String>( ) { @Override public boolean apply( @Nullable final String loadBalancerName ) { try { final LoadBalancer lb = Entities.uniqueResult( LoadBalancer.namedByAccountId( accountFullName.getAccountNumber( ), loadBalancerName ) ); lb.setSecurityGroupRefs( Lists.newArrayList( new LoadBalancerSecurityGroupRef( elbVpcGroup.getGroupId( ), elbVpcGroup.getGroupName( ) ) ) ); } catch ( TransactionException e ) { throw Exceptions.toUndeclared( e ); } return true; } } ).apply( lb.getDisplayName( ) ); result.setShouldRollback(false); /// In VPC, security groups are user-owned. So ELB shouldn't delete them during rollback result.setGroupId(elbVpcGroup.getGroupId( )); result.setGroupName(elbVpcGroup.getGroupName( )); result.setGroupOwnerAccountId(elbVpcGroup.getAccountId( )); } return result; } @Override public CreateTagActivityResult createLbTagCreator(final String accountNumber, final String lbName, String sgroupId) throws LoadBalancingActivityException { final String TAG_KEY = "service-type"; final String TAG_VALUE = "loadbalancing"; CreateTagActivityResult result = new CreateTagActivityResult(); // security group if(sgroupId!=null){ final boolean tagGroup; try{ final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); tagGroup = lb.getVpcId( ) == null; }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Failed to find the loadbalancer "+ lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed due to query exception", ex); } if ( tagGroup ) try{ EucalyptusActivityTasks.getInstance().createTags(TAG_KEY, TAG_VALUE, Lists.newArrayList(sgroupId)); result.setSecurityGroup(sgroupId); }catch(final Exception ex){ LOG.warn("could not tag the security group", ex); } } result.setTagKey(TAG_KEY); result.setTagValue(TAG_VALUE); return result; } private static String getLaunchConfigName( final String ownerAccountNumber, final String loadBalancerName, final String availabilityZone ) { String newLaunchConfigName = String.format("lc-euca-internal-elb-%s-%s-%s-%s", ownerAccountNumber, loadBalancerName, availabilityZone, UUID.randomUUID().toString().substring(0, 8)); if(newLaunchConfigName.length()>255) newLaunchConfigName = newLaunchConfigName.substring(0, 255); return newLaunchConfigName; } private static String getAutoScalingGroupName( final String ownerAccountNumber, final String loadBalancerName, final String availabilityZone ) { String groupName = String.format("euca-internal-elb-%s-%s-%s", ownerAccountNumber, loadBalancerName, availabilityZone ); if(groupName.length()>255) groupName = groupName.substring(0, 255); return groupName; } private static String getCredentialsString() { final String credStr = String.format("euca-%s:%s", B64.standard.encString("setup-credential"), LoadBalancingWorkerProperties.EXPIRATION_DAYS); return credStr; } private static String getLoadBalancerUserData(String initScript, final String ownerAccountNumber) { Map<String, String> kvMap = new HashMap<String, String>(); if (LoadBalancingWorkerProperties.NTP_SERVER != null){ kvMap.put("ntp_server", LoadBalancingWorkerProperties.NTP_SERVER); } if(LoadBalancingWorkerProperties.APP_COOKIE_DURATION != null){ kvMap.put("app-cookie-duration", LoadBalancingWorkerProperties.APP_COOKIE_DURATION); } kvMap.put("elb_service_url", String.format("loadbalancing.%s",DNSProperties.getDomain())); kvMap.put("euare_service_url", String.format("euare.%s", DNSProperties.getDomain())); kvMap.put("objectstorage_service_url", String.format("objectstorage.%s", DNSProperties.getDomain())); kvMap.put("simpleworkflow_service_url", String.format("simpleworkflow.%s", DNSProperties.getDomain())); kvMap.put("webservice_port", String.format("%d", StackConfiguration.PORT)); if(ownerAccountNumber!=null) kvMap.put("loadbalancer_owner_account", ownerAccountNumber); try { List<ServiceStatusType> services = EucalyptusActivityTasks.getInstance().describeServices("eucalyptus"); if(services == null || services.size()<=0) throw new EucalyptusActivityException("failed to describe eucalyptus services"); ServiceStatusType service = services.get(0); String serviceUrl = service.getServiceId().getUri(); // parse the service Url: e.g., http://192.168.0.1:8773/services/Eucalyptus String tmp = serviceUrl.replace("http://", "").replace("https://", ""); String host = tmp.substring(0, tmp.indexOf(":")); tmp = tmp.replace(host+":", ""); String port = tmp.substring(0, tmp.indexOf("/")); String path = tmp.replace(port+"/", ""); kvMap.put("eucalyptus_host", host); kvMap.put("eucalyptus_port", port); kvMap.put("eucalyptus_path", path); }catch(Exception ex){ throw Exceptions.toUndeclared(ex); } final StringBuilder sb = new StringBuilder("#!/bin/bash").append("\n"); if (initScript != null && initScript.length()>0) sb.append(initScript); sb.append("\n#System generated Load Balancer Servo config\n"); sb.append("mkdir -p /etc/load-balancer-servo/\n"); sb.append("yum -y --disablerepo \\* --enablerepo eucalyptus-service-image install load-balancer-servo\n"); sb.append("echo \""); for (String key : kvMap.keySet()) { String value = kvMap.get(key); sb.append(String.format("\n%s=%s", key, value)); } sb.append("\" > /etc/load-balancer-servo/servo.conf"); sb.append("\nchown -R servo:servo /etc/load-balancer-servo"); sb.append("\nservice load-balancer-servo start"); return sb.toString(); } private static final String TAG_KEY = "service-type"; private static final String TAG_VALUE = "loadbalancing"; @Override public AutoscalingGroupSetupActivityResult autoscalingGroupSetup(final String accountNumber, final String lbName, String instanceProfileName, String securityGroupName, List<String> zones, Map<String,String> zoneToSubnetIdMap) throws LoadBalancingActivityException { if(LoadBalancingWorkerProperties.IMAGE == null) throw new LoadBalancingActivityException("Loadbalancer's EMI is not configured"); final AutoscalingGroupSetupActivityResult activityResult = new AutoscalingGroupSetupActivityResult(); final LoadBalancer lbEntity; final LoadBalancer.LoadBalancerCoreView lb; try{ lbEntity = LoadBalancers.getLoadbalancer(accountNumber, lbName); lb = lbEntity.getCoreView( ); if ( zoneToSubnetIdMap == null ) { zoneToSubnetIdMap = CollectionUtils.putAll( Iterables.filter( lbEntity.getZones( ), Predicates.compose( Predicates.notNull( ), subnetId( ) ) ), Maps.<String, String>newHashMap( ), name( ), subnetId( ) ); } }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Failed to find the loadbalancer "+ lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed due to query exception", ex); } if( zones == null) return null; // do nothing when zone/groups are not specified for (final String availabilityZone : zones) { final String groupName = getAutoScalingGroupName( accountNumber, lbName, availabilityZone); String launchConfigName = null; boolean asgFound = false; try{ final DescribeAutoScalingGroupsResponseType response = EucalyptusActivityTasks.getInstance().describeAutoScalingGroups(Lists.newArrayList(groupName), lb.useSystemAccount()); final List<AutoScalingGroupType> groups = response.getDescribeAutoScalingGroupsResult().getAutoScalingGroups().getMember(); if(groups.size()>0 && groups.get(0).getAutoScalingGroupName().equals(groupName)){ asgFound =true; launchConfigName = groups.get(0).getLaunchConfigurationName(); } }catch(final Exception ex){ asgFound = false; } activityResult.setGroupNames(Sets.<String>newHashSet()); activityResult.setLaunchConfigNames(Sets.<String>newHashSet()); activityResult.setCreatedGroupNames(Sets.<String>newHashSet()); activityResult.setCreatedLaunchConfigNames(Sets.<String>newHashSet()); final List<String> availabilityZones = Lists.newArrayList( availabilityZone ); String vpcZoneIdentifier = null; String systemVpcZoneIdentifier = null; if ( !asgFound ) { try{ vpcZoneIdentifier = zoneToSubnetIdMap.isEmpty() ? null : Strings.emptyToNull(Joiner.on(',') .skipNulls().join(Iterables.transform(availabilityZones, Functions.forMap(zoneToSubnetIdMap)))); if (vpcZoneIdentifier != null) systemVpcZoneIdentifier = LoadBalancingSystemVpcs.getSystemVpcSubnetId(vpcZoneIdentifier); else systemVpcZoneIdentifier = null; }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to look up subnet ID", ex); } try{ Set<String> securityGroupNamesOrIds = null; if (systemVpcZoneIdentifier == null ) { securityGroupNamesOrIds = Sets.newHashSet(); if (!lb.getSecurityGroupIdsToNames().isEmpty()) { securityGroupNamesOrIds.addAll(lb.getSecurityGroupIdsToNames().keySet()); } else { if (securityGroupName != null) { securityGroupNamesOrIds.add(securityGroupName); } } } else { // if system VPC is used, use it's security group securityGroupNamesOrIds = Sets.newHashSet(); securityGroupNamesOrIds.add( LoadBalancingSystemVpcs.getSecurityGroupId(systemVpcZoneIdentifier) ); } final String KEYNAME = LoadBalancingWorkerProperties.KEYNAME; final String keyName = KEYNAME!=null && KEYNAME.length()>0 ? KEYNAME : null; final String userData = B64.standard.encString(String.format("%s\n%s", getCredentialsString(), getLoadBalancerUserData(LoadBalancingWorkerProperties.INIT_SCRIPT, lb.getOwnerAccountNumber()))); launchConfigName = getLaunchConfigName (lb.getOwnerAccountNumber(), lb.getDisplayName(), availabilityZone); EucalyptusActivityTasks.getInstance().createLaunchConfiguration(LoadBalancingWorkerProperties.IMAGE, LoadBalancingWorkerProperties.INSTANCE_TYPE, instanceProfileName, launchConfigName, securityGroupNamesOrIds, keyName, userData, zoneToSubnetIdMap.isEmpty( ) ? null : false, lb.useSystemAccount() ); activityResult.getLaunchConfigNames().add(launchConfigName); activityResult.getCreatedLaunchConfigNames().add(launchConfigName); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to create launch configuration", ex); } } activityResult.getLaunchConfigNames().add(launchConfigName); /// FIXME Integer capacity = LoadBalancingServiceProperties.getCapacityPerZone(); if(!asgFound){ // create autoscaling group with the zone and desired capacity try{ EucalyptusActivityTasks.getInstance().createAutoScalingGroup(groupName, availabilityZones, systemVpcZoneIdentifier, capacity, launchConfigName, TAG_KEY, TAG_VALUE, lb.useSystemAccount()); activityResult.getGroupNames().add(groupName); activityResult.getCreatedGroupNames().add(groupName); if (activityResult.getNumVMsPerZone() == null || activityResult.getNumVMsPerZone() == 0) { activityResult.setNumVMsPerZone(capacity); } else { activityResult.setNumVMsPerZone(activityResult.getNumVMsPerZone() + capacity); } }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to create autoscaling group", ex); } }else { try{ EucalyptusActivityTasks.getInstance().updateAutoScalingGroup(groupName, availabilityZones, capacity, launchConfigName, lb.useSystemAccount()); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to update the autoscaling group", ex); } } activityResult.getGroupNames().add(groupName); if (activityResult.getNumVMsPerZone() == null || activityResult.getNumVMsPerZone() == 0) { activityResult.setNumVMsPerZone(capacity); } else { activityResult.setNumVMsPerZone(activityResult.getNumVMsPerZone() + capacity); } // commit ASG record to the database try ( final TransactionResource db = Entities.transactionFor( LoadBalancerAutoScalingGroup.class ) ) { try { final LoadBalancerAutoScalingGroup group = Entities.uniqueResult( LoadBalancerAutoScalingGroup.named( lbEntity, availabilityZone ) ); if ( capacity != null ) group.setCapacity( capacity ); }catch(NoSuchElementException ex){ final LoadBalancerAutoScalingGroup group = LoadBalancerAutoScalingGroup.newInstance(lbEntity, availabilityZone, vpcZoneIdentifier, systemVpcZoneIdentifier, groupName, launchConfigName); if ( capacity != null ) group.setCapacity(capacity); Entities.persist(group); } db.commit(); }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to commit the database", ex); } } // end of for all zones return activityResult; } @Override public void securityGroupSetupRollback(String accountNumber, String lbName, SecurityGroupSetupActivityResult result) { if(result.getCreatedGroupName() == null || !result.getShouldRollback()) return; // set security group with the loadbalancer; update db LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); } catch(Exception ex){ return; } try{ EucalyptusActivityTasks.getInstance().deleteSystemSecurityGroup( result.getCreatedGroupName() ); }catch(Exception ex){ // when there's any servo instance referencing the security group // SecurityGroupCleanup will clean up records } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerSecurityGroup.class ) ) { final LoadBalancerSecurityGroup group = Entities.uniqueResult(LoadBalancerSecurityGroup.named(lb, result.getGroupOwnerAccountId(), result.getCreatedGroupName())); group.setState(LoadBalancerSecurityGroup.STATE.OutOfService); group.setLoadBalancer(null); Entities.persist(group); db.commit(); }catch(NoSuchElementException ex){ }catch(Exception ex){ LOG.error("failed to mark the security group OutOfService", ex); } } @Override public void createLbTagCreatorRollback(CreateTagActivityResult result) { if(result.getSecurityGroup()!=null){ try{ EucalyptusActivityTasks.getInstance().deleteTags(result.getTagKey(), result.getTagValue(), Lists.newArrayList(result.getSecurityGroup())); }catch(final Exception ex){ ; } } } @Override public void autoscalingGroupSetupRollback(String accountNumber, String lbName, AutoscalingGroupSetupActivityResult result) { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(Exception ex){ LOG.error("failed to find the loadbalancer: " + lbName); return; } if(result.getCreatedGroupNames()!=null) { for(final String asgName : result.getCreatedGroupNames()) { // delete autoscaling group try{ // terminate all instances EucalyptusActivityTasks.getInstance().deleteAutoScalingGroup(asgName, true, lb.useSystemAccount()); }catch(Exception ex){ LOG.error("failed to delete autoscaling group - "+ asgName); } } } if(result.getCreatedLaunchConfigNames()!=null) { for(final String launchConfigName : result.getCreatedLaunchConfigNames()) { // delete launch config try{ EucalyptusActivityTasks.getInstance().deleteLaunchConfiguration(launchConfigName, lb.useSystemAccount()); }catch(Exception ex){ LOG.error("failed to delete launch configuration - "+launchConfigName); } } } } @Override public List<String> enableAvailabilityZonesPersistUpdatedZones(String accountNumber, String lbName, List<String> zonesToEnable, Map<String, String> zoneToSubnetIdMap) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+ lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+ lbName, ex); } final List<String> persistedZones = Lists.newArrayList(); if(zonesToEnable!= null) { for(final String zone : zonesToEnable){ try ( final TransactionResource db = Entities.transactionFor( LoadBalancerZone.class ) ) { try { final LoadBalancerZone exist = Entities.uniqueResult( LoadBalancerZone.named( lb, zone ) ); exist.setState( LoadBalancerZone.STATE.InService ); }catch(NoSuchElementException ex){ final String subnetId = zoneToSubnetIdMap == null ? null : zoneToSubnetIdMap.get( zone ); final LoadBalancerZone newZone = LoadBalancerZone.create( lb, zone, subnetId ); newZone.setState(LoadBalancerZone.STATE.InService); Entities.persist(newZone); } persistedZones.add(zone); db.commit(); }catch(Exception ex){ throw new LoadBalancingActivityException("Error adding load balancer zone", ex ); } } } return persistedZones; } @Override public void enableAvailabilityZonesPersistUpdatedZonesRollback(String accountNumber, String lbName, List<String> zonesToRollback) { if(zonesToRollback==null || zonesToRollback.size()<=0) return; LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ LOG.warn("Could not find the loadbalancer with name="+ lbName, ex); return; }catch(Exception ex){ LOG.warn("Error while looking for loadbalancer with name="+ lbName, ex); return; } for(final LoadBalancerZoneCoreView zoneView : lb.getZones()){ if(zonesToRollback.contains(zoneView.getName())) { try ( final TransactionResource db = Entities.transactionFor( LoadBalancerZone.class ) ) { final LoadBalancerZone sample = LoadBalancerZone.named(lb, zoneView.getName()); final LoadBalancerZone update = Entities.uniqueResult(sample); update.setState( LoadBalancerZone.STATE.OutOfService ); db.commit(); }catch(final Exception ex){ LOG.error("could not mark out of state for the zone", ex); } } } } @Override public void enableAvailabilityZonesPersistBackendInstanceState( String accountNumber, String lbName, List<String> enabledZones) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ LOG.warn("Could not find the loadbalancer with name="+ lbName, ex); return; }catch(Exception ex){ LOG.warn("Error while looking for loadbalancer with name="+ lbName, ex); return; } try{ for(final String enabledZone : enabledZones) { final LoadBalancerZone zone = LoadBalancers.findZone(lb, enabledZone); for(final LoadBalancerBackendInstanceCoreView instance : zone.getBackendInstances()){ try ( final TransactionResource db = Entities.transactionFor( LoadBalancerBackendInstance.class ) ) { final LoadBalancerBackendInstance update = Entities.uniqueResult( LoadBalancerBackendInstance.named(lb, instance.getInstanceId())); update.setReasonCode( "" ); update.setDescription( "" ); db.commit(); }catch(final NoSuchElementException ex){ LOG.warn("failed to find the backend instance"); }catch(final Exception ex){ LOG.warn("failed to query the backend instance", ex); } } } }catch(final Exception ex){ LOG.warn("unable to update backend instances after enabling zone", ex); } } @Override public void createListenerCheckSSLCertificateId(String accountNumber, String lbName, Listener[] listeners) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(Exception ex){ throw new LoadBalancingActivityException("could not find the loadbalancer", ex); } for(Listener listener : listeners){ final PROTOCOL protocol = PROTOCOL.valueOf(listener.getProtocol().toUpperCase()); if(protocol.equals(PROTOCOL.HTTPS) || protocol.equals(PROTOCOL.SSL)) { final String certArn = listener.getSSLCertificateId(); if(certArn == null || certArn.length()<=0) throw new LoadBalancingActivityException("No SSLCertificateId is specified"); final String prefix = String.format("arn:aws:iam::%s:server-certificate", accountNumber); if(!certArn.startsWith(prefix)) throw new LoadBalancingActivityException("SSLCertificateId is not ARN format"); try{ final String pathAndName = certArn.replace(prefix, ""); final String certName = pathAndName.substring(pathAndName.lastIndexOf("/")+1); final ServerCertificateType cert = EucalyptusActivityTasks.getInstance().getServerCertificate(accountNumber, certName); if(cert==null) throw new LoadBalancingActivityException("No SSL certificate is found with the ARN"); if(!certArn.equals(cert.getServerCertificateMetadata().getArn())) throw new LoadBalancingActivityException("Returned certificate's ARN doesn't match the request"); }catch(final LoadBalancingActivityException ex){ throw ex; }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to get SSL server certificate", ex); } } } } public static final String SERVER_CERT_ROLE_POLICY_NAME_PREFIX = "loadbalancer-iam-policy"; public static final String ROLE_SERVER_CERT_POLICY_DOCUMENT= "{\"Statement\":[{\"Action\": [\"iam:DownloadServerCertificate\"],\"Effect\": \"Allow\",\"Resource\": \"CERT_ARN_PLACEHOLDER\"}]}"; @Override public AuthorizeSSLCertificateActivityResult createListenerAuthorizeSSLCertificate(String accountNumber, String lbName, Listener[] listeners) throws LoadBalancingActivityException { final AuthorizeSSLCertificateActivityResult result = new AuthorizeSSLCertificateActivityResult(); final Set<String> certArns = Sets.newHashSet(); final List<String> policyNames = Lists.newArrayList(); for(final Listener listener : listeners){ final PROTOCOL protocol = PROTOCOL.valueOf(listener.getProtocol().toUpperCase()); if(protocol.equals(PROTOCOL.HTTPS) || protocol.equals(PROTOCOL.SSL)) { certArns.add(listener.getSSLCertificateId()); } } if(certArns.size() <= 0) return result; LoadBalancer lb = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(Exception ex){ throw new LoadBalancingActivityException("could not find the loadbalancer", ex); } final String roleName = String.format("%s-%s-%s", ROLE_NAME_PREFIX, accountNumber, lbName); final String prefix = String.format("arn:aws:iam::%s:server-certificate", accountNumber); for (final String arn : certArns){ if(!arn.startsWith(prefix)) continue; String pathAndName = arn.replace(prefix, ""); String certName = pathAndName.substring(pathAndName.lastIndexOf("/")+1); String policyName = String.format("%s-%s-%s-%s", SERVER_CERT_ROLE_POLICY_NAME_PREFIX, accountNumber, lbName, certName); final String rolePolicyDoc = ROLE_SERVER_CERT_POLICY_DOCUMENT.replace("CERT_ARN_PLACEHOLDER", arn); try{ EucalyptusActivityTasks.getInstance().putRolePolicy(roleName, policyName, rolePolicyDoc, lb.useSystemAccount()); policyNames.add(policyName); }catch(final Exception ex){ throw new LoadBalancingActivityException("failed to authorize server certificate for SSL listener", ex); } } result.setPolicyNames(policyNames); result.setRoleName(roleName); return result; } @Override public void createListenerAuthorizeSSLCertificateRollback( String accountNumber, String lbName, AuthorizeSSLCertificateActivityResult result) { final String roleName = result.getRoleName(); final List<String> policyNames = result.getPolicyNames(); if(roleName!=null && policyNames!=null && policyNames.size()>0){ LoadBalancer lb = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(Exception ex){ return; } for(final String policyName : policyNames){ try{ EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, policyName, lb.useSystemAccount()); }catch(final Exception ex){ LOG.warn("Failed to delete role policy during listener creation rollback", ex); } } } } @Override public AuthorizeIngressRuleActivityResult createListenerAuthorizeIngressRule(String accountNumber, String lbName, Listener[] listeners) throws LoadBalancingActivityException { final AuthorizeIngressRuleActivityResult result = new AuthorizeIngressRuleActivityResult(); result.setListeners(Lists.<Listener>newArrayList()); LoadBalancer lb; String groupName = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); final LoadBalancerSecurityGroupCoreView group = lb.getGroup(); if(group!=null) groupName = group.getName(); }catch(Exception ex){ throw new LoadBalancingActivityException("could not find the loadbalancer", ex); } final Map<String,String> securityGroupIdsToNames = lb.getCoreView( ).getSecurityGroupIdsToNames( ); String protocol = "tcp"; /// Loadbalancer listeners protocols: HTTP, HTTPS, TCP, SSL -> all tcp if ( lb.getVpcId( ) == null ) { if ( groupName == null ) throw new LoadBalancingActivityException( "Group name is not found" ); for ( Listener listener : listeners ) { int port = listener.getLoadBalancerPort(); try { EucalyptusActivityTasks.getInstance().authorizeSystemSecurityGroup( groupName, protocol, port, lb.useSystemAccount() ); result.getListeners().add(listener); } catch ( Exception ex ) { throw new LoadBalancingActivityException( String.format( "failed to authorize %s, %s, %d", groupName, protocol, port ), ex ); } } } else if ( securityGroupIdsToNames.size( ) == 1 ) { if (securityGroupIdsToNames.values().contains(generateDefaultVPCSecurityGroupName(lb.getVpcId()))) { boolean isRuleEmpty = false; try { final SecurityGroupItemType defaultGroup = EucalyptusActivityTasks.getInstance() .describeUserSecurityGroupsByName(AccountFullName.getInstance(accountNumber), lb.getVpcId(), securityGroupIdsToNames.values().stream().findAny().get()) .stream().findAny().get(); if (defaultGroup.getIpPermissions() == null || defaultGroup.getIpPermissions().isEmpty()) { isRuleEmpty = true; } } catch (final Exception ex) { isRuleEmpty = false; } if (isRuleEmpty) { // the rule is created only for the first time the group is created final String groupId = Iterables.getOnlyElement(securityGroupIdsToNames.keySet()); for (Listener listener : listeners) { int port = listener.getLoadBalancerPort(); try { EucalyptusActivityTasks.getInstance().authorizeSystemSecurityGroup(groupId, protocol, port, false); } catch (Exception ex) { throw new LoadBalancingActivityException(String.format("failed to authorize %s, %s, %d", groupId, protocol, port), ex); } } } } } return result; } @Override public void createListenerAuthorizeIngressRuleRollback(String accountNumber, String lbName, AuthorizeIngressRuleActivityResult result) { final List<Listener> listeners = result.getListeners(); if (listeners == null || listeners.size() <= 0) return; LoadBalancer lb = null; String groupName = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); final LoadBalancerSecurityGroupCoreView group = lb.getGroup(); if(group!=null) groupName = group.getName(); }catch(Exception ex){ ; } if(groupName == null) return; for(Listener listener : listeners){ int port = listener.getLoadBalancerPort(); String protocol = listener.getProtocol(); protocol = protocol.toLowerCase(); try{ EucalyptusActivityTasks.getInstance().revokeSystemSecurityGroup( groupName, protocol, port, lb.useSystemAccount() ); }catch(Exception ex){ ; } } } @Override public void createListenerUpdateHealthCheckConfig(String accountNumber, String lbName, Listener[] listeners) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(final NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+ lbName, ex); }catch(final Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+ lbName, ex); } final int DEFAULT_HEALTHY_THRESHOLD = 3; final int DEFAULT_INTERVAL = 30; final int DEFAULT_TIMEOUT = 5; final int DEFAULT_UNHEALTHY_THRESHOLD = 3; /* default setting in AWS "HealthyThreshold": 10, "Interval": 30, "Target": "TCP:8000", "Timeout": 5, "UnhealthyThreshold": 2 */ try{ lb.getHealthCheckTarget(); lb.getHealthCheckInterval(); lb.getHealthCheckTimeout(); lb.getHealthCheckUnhealthyThreshold(); lb.getHealthyThreshold(); }catch(final IllegalStateException ex){ /// only when the health check is not previously configured if(listeners==null || listeners.length<=0) throw new LoadBalancingActivityException("No listener requested"); final Listener firstListener = listeners[0]; final String target = String.format( "TCP:%d", firstListener.getInstancePort() ); try ( final TransactionResource db = Entities.transactionFor( LoadBalancer.class ) ) { final LoadBalancer update = Entities.uniqueResult(lb); update.setHealthCheck( DEFAULT_HEALTHY_THRESHOLD, DEFAULT_INTERVAL, target, DEFAULT_TIMEOUT, DEFAULT_UNHEALTHY_THRESHOLD ); db.commit(); }catch(final NoSuchElementException exx){ LOG.warn("Loadbalancer not found in the database"); }catch(final Exception exx){ LOG.warn("Unable to query the loadbalancer", ex); } } } @Override public void createListenerAddDefaultSSLPolicy(String accountNumber, String lbName, Listener[] listeners) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+lbName, ex); } boolean sslListener = false; for(final Listener l : listeners) { final String protocol = l.getProtocol().toLowerCase(); if("https".equals(protocol) || "ssl".equals(protocol)) { sslListener = true; break; } } if(!sslListener) return; try{ /// this will load the sample policies into memory if(LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME == null) { LoadBalancerPolicies.getSamplePolicyDescription(); if(LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME == null) throw new LoadBalancingActivityException("Latest security policy is not found"); } boolean policyCreated = false; final Collection<LoadBalancerPolicyDescriptionCoreView> policies = lb.getPolicies(); if(policies != null) { for (final LoadBalancerPolicyDescriptionCoreView view : policies ) { if ("SSLNegotiationPolicyType".equals(view.getPolicyTypeName()) && LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME.equals(view.getPolicyName())) { policyCreated = true; break; } } } if(! policyCreated) { final PolicyAttribute attr = new PolicyAttribute(); attr.setAttributeName("Reference-Security-Policy"); attr.setAttributeValue(LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME); LoadBalancerPolicies.addLoadBalancerPolicy(lb, LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME, "SSLNegotiationPolicyType", Lists.newArrayList(attr)); try{ // reload with the newly created policy lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+ lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+ lbName, ex); } } }catch (final Exception ex) { LOG.warn("Failed to create default security policy for https/ssl listeners", ex); return; } try{ final LoadBalancerPolicyDescription policy = LoadBalancerPolicies.getLoadBalancerPolicyDescription(lb, LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME); if(policy==null) throw new LoadBalancingActivityException("No such policy is found: "+LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME); final Collection<LoadBalancerListenerCoreView> lbListeners = lb.getListeners(); for(final Listener l : listeners) { final String protocol = l.getProtocol().toLowerCase(); if("https".equals(protocol) || "ssl".equals(protocol)) { LoadBalancerListener listener = null; for(final LoadBalancerListenerCoreView view : lbListeners){ if(view.getLoadbalancerPort() == l.getLoadBalancerPort()){ listener = LoadBalancerListenerEntityTransform.INSTANCE.apply(view); break; } } if(listener == null) throw new LoadBalancingActivityException("No such listener is found"); boolean policyAttached=false; final List<LoadBalancerPolicyDescriptionCoreView> listenerPolicies = listener.getPolicies(); if(listenerPolicies!=null) { for(final LoadBalancerPolicyDescriptionCoreView listenerPolicy : listenerPolicies ) { if( "SSLNegotiationPolicyType".equals(listenerPolicy.getPolicyTypeName()) && LoadBalancerPolicies.LATEST_SECURITY_POLICY_NAME.equals(listenerPolicy.getPolicyName())) { policyAttached = true; break; } } } if(!policyAttached && listener!=null && policy!=null) { LoadBalancerPolicies.addPoliciesToListener(listener, Lists.newArrayList(policy)); } } } }catch(final Exception ex) { LOG.warn("Failed to set default security policy to https/ssl listeners", ex); } } @Override public HealthCheck lookupLoadBalancerHealthCheck(final String accountNumber, final String lbName) throws LoadBalancingActivityException { try { final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); final HealthCheck hc = new HealthCheck(); hc.setHealthyThreshold(lb.getHealthyThreshold()); hc.setInterval(lb.getHealthCheckInterval()); hc.setTarget(lb.getHealthCheckTarget()); hc.setTimeout(lb.getHealthCheckTimeout()); hc.setUnhealthyThreshold(lb.getHealthCheckUnhealthyThreshold()); return hc; }catch(final Exception ex) { throw new LoadBalancingActivityException(String.format("Failed to lookup loadbalancer (%s:%s", accountNumber, lbName)); } } @Override public Map<String, String> filterInstanceStatus(final String accountNumber, final String lbName, final String servoInstanceId, final String encodedStatus) throws LoadBalancingActivityException { if (encodedStatus == null) { return Maps.newHashMap(); } String monitoringZone = null; try { final LoadBalancerServoInstance servo = LoadBalancers.lookupServoInstance(servoInstanceId); monitoringZone = servo.getAvailabilityZone().getName(); } catch (final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup the servo instance with id="+servoInstanceId); } final Map<String, String> instanceToZone = Maps.newHashMap(); final Set<String> stoppedInstances = Sets.newHashSet(); try{ final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); lb.getBackendInstances().stream().forEach ( instance -> instanceToZone.put(instance.getInstanceId(), instance.getPartition())); stoppedInstances.addAll(lb.getBackendInstances().stream() .filter(v -> LoadBalancerBackendInstanceStates.InstanceStopped.isInstanceState(v)) .map(v -> v.getInstanceId()) .collect(Collectors.toList()) ); }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup the loadbalancer with name="+lbName); } final Set<String> validStates = Sets.newHashSet( LoadBalancerBackendInstance.STATE.InService.name(), LoadBalancerBackendInstance.STATE.OutOfService.name()); final Map<String, String> instanceToStatus = Maps.newHashMap(); try{ final Map<String,String> statusMap = VmWorkflowMarshaller.unmarshalInstances(encodedStatus); for(final String instanceId : statusMap.keySet()) { final String instanceStatus = statusMap.get(instanceId); if (!validStates.contains(instanceStatus)) continue; if (!(instanceToZone.containsKey(instanceId) && instanceToZone.get(instanceId).equals(monitoringZone))) continue; if (stoppedInstances.contains(instanceId)) continue; // EUCA-11859: do not update health check result if instance stopped instanceToStatus.put(instanceId, instanceStatus); } }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed unmarshalling instance status message", ex); } return instanceToStatus; } //TODO: SCALE @Override public void updateInstanceStatus(final String accountNumber, final String lbName, final Map<String,String> statusMap) throws LoadBalancingActivityException { // for each status, deserialize to Instance type // merge the results for each instance // update database final Set<String> validStatus = Sets.newHashSet( LoadBalancerBackendInstance.STATE.InService.name(), LoadBalancerBackendInstance.STATE.OutOfService.name()); final Map<String,String> verifiedStatusMap = statusMap.entrySet().stream() .filter( entry -> validStatus.contains(entry.getValue())) .collect(Collectors.toMap(p -> p.getKey(), p->p.getValue() )); if(verifiedStatusMap.isEmpty()) return; boolean updated = false; boolean committed = false; final int TRANSACTION_RETRY = 5; final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); for (int i = 1; i <= TRANSACTION_RETRY; i++) { try (final TransactionResource db = Entities.transactionFor(LoadBalancerBackendInstance.class)) { for (final String instanceId : verifiedStatusMap.keySet()) { final LoadBalancerBackendInstance sample = LoadBalancerBackendInstance.named(lb, instanceId); final LoadBalancerBackendInstance update = Entities.uniqueResult(sample); final String newStatus = verifiedStatusMap.get(instanceId); final LoadBalancerBackendInstance.STATE oldState = update.getBackendState(); final LoadBalancerBackendInstance.STATE newState = LoadBalancerBackendInstance.STATE.valueOf(newStatus); if (!oldState.equals(newState)) updated = true; update.setBackendState(newState); final LoadBalancerBackendInstanceStates failure = LoadBalancerBackendInstanceStates.HealthCheckFailure; final LoadBalancerBackendInstanceStates success = LoadBalancerBackendInstanceStates.HealthCheckSuccess; if (success.getState().equals(newState)) { update.setReasonCode(success.getReasonCode()); update.setDescription(success.getDescription()); } else if ( failure.getState().equals(newState)) { update.setReasonCode(failure.getReasonCode()); update.setDescription(failure.getDescription()); } update.updateInstanceStateTimestamp(); Entities.persist(update); } db.commit(); } catch (final Exception ex) { try { Thread.sleep((long) ((Math.random() * 100) * Math.pow(2, i))); }catch(final Exception ex2) { ; } continue; } committed = true; break; } if (!committed) { throw new LoadBalancingActivityException("Failed to persist instance status"); } // if changed, updating loadbalancer will cause registering instances in the servo VMs if(updated) { LoadBalancingWorkflows.updateLoadBalancer(accountNumber, lbName); } } @Override public void putCloudWatchInstanceHealth(String accountNumber, String lbName) throws LoadBalancingActivityException { List<LoadBalancerBackendInstance> backendInstances = Lists.newArrayList(); LoadBalancer lb = null; //TODO: SCALE try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); backendInstances= Lists.transform(Lists.newArrayList(lb.getBackendInstances()), LoadBalancerBackendInstanceEntityTransform.INSTANCE); }catch(final Exception ex) { LOG.error("failed to retrieve loadbalancer's backend instances", ex); return; } /// Update Cloudwatch for(final LoadBalancerBackendInstance backend: backendInstances){ final String zoneName = backend.getAvailabilityZone().getName(); if(backend.getState().equals(LoadBalancerBackendInstance.STATE.InService)){ LoadBalancerCwatchMetrics.getInstance().updateHealthy(lb.getCoreView(), zoneName, backend.getInstanceId()); }else if (backend.getState().equals(LoadBalancerBackendInstance.STATE.OutOfService)){ LoadBalancerCwatchMetrics.getInstance().updateUnHealthy(lb.getCoreView(), zoneName, backend.getInstanceId()); } } } @Override public void putCloudWatchMetrics(String accountNumber, String lbName, Map<String,String> metrics) throws LoadBalancingActivityException { if (metrics!= null) { for(final String instanceId : metrics.keySet()) { final String metric = metrics.get(instanceId); if (metric == null) continue; /// metric data from the servo VM final MetricData data = VmWorkflowMarshaller.unmarshalMetrics(metric); if(data.getMember()== null || data.getMember().size()<=0) continue; LoadBalancerZone zone = null; /// TODO: SCALE try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance sample = LoadBalancerServoInstance.named(instanceId); final LoadBalancerServoInstance entity = Entities.uniqueResult(sample); zone = LoadBalancerZoneEntityTransform.INSTANCE.apply(entity.getAvailabilityZone()); }catch(final Exception ex) { LOG.error("Failed to lookup servo instance named: " + instanceId);; } if (zone!=null) { try{ LoadBalancerCwatchMetrics.getInstance().addMetric(zone, data); }catch(Exception ex){ LOG.error("Failed to add ELB cloudwatch metric", ex); } } } } } @Override public List<String> listLoadBalancerPolicies(final String accountNumber, final String lbName) throws LoadBalancingActivityException { try { final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); final List<LoadBalancerListener> listeners = lb.getListeners().stream() .map(view -> LoadBalancerListenerEntityTransform.INSTANCE.apply(view)) .collect(Collectors.toList()); final List<LoadBalancerBackendServerDescription> backendServers = LoadBalancerBackendServers.getLoadBalancerBackendServerDescription(lb); final List<String> listenerPolicies = listeners.stream() .map(l -> l.getPolicies()) .flatMap(p -> p.stream()) .map(p -> p.getPolicyName()) .distinct() .collect(Collectors.toList()); final List<String> backendPolicies = backendServers.stream() .map(s -> s.getPolicyDescriptions()) .flatMap(p -> p.stream()) .map(p -> p.getPolicyName()) .distinct() .collect(Collectors.toList()); final List<String> publicKeyPolicies = lb.getPolicies().stream() .filter(p -> "PublicKeyPolicyType".equals(p.getPolicyTypeName())) .map(p -> p.getPolicyName()) .distinct() .collect(Collectors.toList()); final List<String> policies = Lists.newArrayList(listenerPolicies); policies.addAll(backendPolicies); policies.addAll(publicKeyPolicies); return policies.stream().distinct().collect(Collectors.toList()); }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup loadbalancer policies", ex); } } @Override public PolicyDescription getLoadBalancerPolicy(final String accountNumber, final String lbName, final String policyName) throws LoadBalancingActivityException { try { final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); final LoadBalancerPolicyDescription policy = LoadBalancerPolicies.getLoadBalancerPolicyDescription(lb, policyName); return LoadBalancerPolicies.AsPolicyDescription.INSTANCE.apply(policy); } catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup loadbalancer policies", ex); } } // TODO: SCALE @Override public Map<String, LoadBalancerServoDescription> lookupLoadBalancerDescription(final String accountNumber, final String lbName) throws LoadBalancingActivityException { final Map<String, LoadBalancerServoDescription> result = Maps.newHashMap(); try{ final LoadBalancer lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); for(final LoadBalancerZoneCoreView zoneView: lb.getZones()) { if (! LoadBalancerZone.STATE.InService.equals(zoneView.getState())) continue; final LoadBalancerServoDescription desc = LoadBalancers.getServoDescription(accountNumber, lbName, zoneView.getName()); final LoadBalancerZone zone = LoadBalancerZoneEntityTransform.INSTANCE.apply(zoneView); for(final LoadBalancerServoInstanceCoreView servoView : zone.getServoInstances()) { result.put(servoView.getInstanceId(), desc); } } } catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup loadbalancer descriptions", ex); } return result; } @Override public void deleteListenerRevokeSSLCertificatePolicy(String accountNumber, String lbName, List<Integer> portsToDelete) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(Exception ex){ throw new LoadBalancingActivityException("could not find the loadbalancer", ex); } final Set<String> allArns = Sets.newHashSet(); final Set<String> arnsToKeep = Sets.newHashSet(); for(final LoadBalancerListenerCoreView listener : lb.getListeners()){ final PROTOCOL protocol = listener.getProtocol(); if(protocol.equals(PROTOCOL.HTTPS) || protocol.equals(PROTOCOL.SSL)) { allArns.add(listener.getCertificateId()); if(! portsToDelete.contains(listener.getLoadbalancerPort())){ arnsToKeep.add(listener.getCertificateId()); } } } final Set<String> arnToDelete = Sets.difference(allArns, arnsToKeep); if(arnToDelete.size() <= 0) return; final String roleName = getRoleName(accountNumber, lbName); final String prefix = String.format("arn:aws:iam::%s:server-certificate", accountNumber); for (final String arn : arnToDelete){ if(!arn.startsWith(prefix)) continue; String pathAndName = arn.replace(prefix, ""); String certName = pathAndName.substring(pathAndName.lastIndexOf("/")+1); String policyName = String.format("%s-%s-%s-%s", SERVER_CERT_ROLE_POLICY_NAME_PREFIX, accountNumber, lbName, certName); try{ EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, policyName, lb.useSystemAccount()); }catch(final Exception ex){ LOG.warn(String.format("Failed to delete role (%s) policy (%s)", roleName, policyName), ex); } } } @Override public void deleteListenerRevokeIngressRule(String accountNumber, String lbName, List<Integer> portsToDelete) throws LoadBalancingActivityException { LoadBalancer lb; String groupName = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); final LoadBalancerSecurityGroupCoreView group = lb.getGroup(); if(group!=null) groupName = group.getName(); }catch(Exception ex){ throw new LoadBalancingActivityException("could not find the loadbalancer", ex); } if(groupName == null){ return; } String[] protocols = new String[]{"tcp"}; /// Loadbalancer listeners protocols: HTTP, HTTPS, TCP, SSL -> all tcp for(String protocol : protocols){ for(Integer port : portsToDelete){ try{ EucalyptusActivityTasks.getInstance().revokeSystemSecurityGroup( groupName, protocol, port, lb.useSystemAccount()); LOG.debug(String.format("group rule revoked (%s-%d)", groupName, port)); }catch(Exception ex){ LOG.warn("Unable to revoke the security group", ex); } } } } @Override public List<String> disableAvailabilityZonesPersistRetiredServoInstances( String accountNumber, String lbName, List<String> zonesToDisable) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+lbName, ex); } List<String> retiredInstances = Lists.newArrayList(); final List<LoadBalancerZoneCoreView> currentZones = Lists.newArrayList(lb.getZones()); for(final LoadBalancerZoneCoreView zoneView : currentZones){ if(zonesToDisable.contains(zoneView.getName())){ // the zone will be disabled LoadBalancerZone zone; try{ zone = LoadBalancerZoneEntityTransform.INSTANCE.apply(zoneView); }catch(final Exception ex){ LOG.error("unable to transform zone from the view", ex); continue; } for(final LoadBalancerServoInstanceCoreView instanceView : zone.getServoInstances()){ LoadBalancerServoInstance instance; try{ instance = LoadBalancerServoInstanceEntityTransform.INSTANCE.apply(instanceView); }catch(final Exception ex){ LOG.error("unable to transfrom servo-instance from the view", ex); continue; } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(instance); update.setState(LoadBalancerServoInstance.STATE.Retired); update.setDnsState(LoadBalancerServoInstance.DNS_STATE.Deregistered); Entities.persist(update); db.commit(); retiredInstances.add(update.getInstanceId()); }catch(final NoSuchElementException ex){ LOG.warn("Failed to update the servo instance's state: no such instance found"); }catch(final Exception ex){ LOG.warn("Failed to update the servo instance's state", ex); } } } } return retiredInstances; } @Override public void disableAvailabilityZonesPersistRetiredServoInstancesRollback( String accountNumber, String lbName, List<String> updatedInstanceIds) { if(updatedInstanceIds == null || updatedInstanceIds.size()<=0) return; for(final String instanceId : updatedInstanceIds){ try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance sample = LoadBalancerServoInstance.named(instanceId); final LoadBalancerServoInstance update = Entities.uniqueResult(sample); update.setState(LoadBalancerServoInstance.STATE.InService); Entities.persist(update); db.commit(); }catch(final NoSuchElementException ex){ LOG.warn("Failed to update the servo instance's state: no such instance found"); }catch(final Exception ex){ LOG.warn("Failed to update the servo instance's state", ex); } } } @Override public List<String> disableAvailabilityZonesUpdateAutoScalingGroup( String accountNumber, String lbName, List<String> zonesToDisable) throws LoadBalancingActivityException { final List<String> updatedZones = Lists.newArrayList(); LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+lbName, ex); } final Collection<LoadBalancerAutoScalingGroupCoreView> groups = lb.getAutoScaleGroups(); for(final LoadBalancerAutoScalingGroupCoreView group : groups) { if (! zonesToDisable.contains(group.getAvailabilityZone())) continue; final String groupName = group.getName(); final int capacity = 0; try{ EucalyptusActivityTasks.getInstance().updateAutoScalingGroup(groupName, null, capacity, lb.useSystemAccount()); }catch(final Exception ex) { LOG.error("Failed to change the capacity of ELB's autoscaling group", ex); } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerAutoScalingGroup.class ) ){ final LoadBalancerAutoScalingGroup update = Entities.uniqueResult(LoadBalancerAutoScalingGroup.named(lb, group.getAvailabilityZone())); update.setCapacity(capacity); Entities.persist(update); db.commit(); }catch(NoSuchElementException ex){ LOG.error("failed to find the autoscaling group record", ex); }catch(Exception ex){ LOG.error("failed to update the autoscaling group record", ex); } updatedZones.add(group.getAvailabilityZone()); } return updatedZones; } @Override public void disableAvailabilityZonesUpdateAutoScalingGroupRollback( String accountNumber, String lbName, List<String> updatedZones) { if(updatedZones == null || updatedZones.size()<=0) return; LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ LOG.error("Could not find the loadbalancer with name="+lbName, ex); return; }catch(Exception ex){ LOG.error("Error while looking for loadbalancer with name="+lbName, ex); return; } final Collection<LoadBalancerAutoScalingGroupCoreView> groups = lb.getAutoScaleGroups(); for(final LoadBalancerAutoScalingGroupCoreView group : groups) { if (! updatedZones.contains(group.getAvailabilityZone())) continue; final String groupName = group.getName(); final int capacity = LoadBalancingServiceProperties.getCapacityPerZone(); try{ EucalyptusActivityTasks.getInstance().updateAutoScalingGroup(groupName, null, capacity, lb.useSystemAccount()); }catch(final Exception ex) { LOG.error("Failed to change the capacity of ELB's autoscaling group", ex); } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerAutoScalingGroup.class ) ){ final LoadBalancerAutoScalingGroup update = Entities.uniqueResult(LoadBalancerAutoScalingGroup.named(lb, group.getAvailabilityZone())); update.setCapacity(capacity); Entities.persist(update); db.commit(); }catch(NoSuchElementException ex){ LOG.error("failed to find the autoscaling group record", ex); }catch(Exception ex){ LOG.error("failed to update the autoscaling group record", ex); } } } @Override public void disableAvailabilityZonesPersistUpdatedZones(String accountNumber, String lbName, List<String> zonesToDisable) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+lbName, ex); } if (zonesToDisable==null) return; for(final String zone : zonesToDisable) { try ( final TransactionResource db = Entities.transactionFor( LoadBalancerZone.class ) ) { final LoadBalancerZone update = Entities.uniqueResult(LoadBalancerZone.named(lb, zone)); update.setState( LoadBalancerZone.STATE.OutOfService ); db.commit(); }catch(final Exception ex){ LOG.debug( "Error updating state for load balancer zone", ex ); } } } @Override public void disableAvailabilityZonesPersistBackendInstanceState( String accountNumber, String lbName, List<String> zonesToDisable) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Could not find the loadbalancer with name="+lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Error while looking for loadbalancer with name="+lbName, ex); } if(zonesToDisable == null || zonesToDisable.size()<=0) return; for(final String removedZone : zonesToDisable){ final LoadBalancerZone zone = LoadBalancers.findZone(lb, removedZone); for(final LoadBalancerBackendInstanceCoreView instance : zone.getBackendInstances()){ try ( TransactionResource db = Entities.transactionFor( LoadBalancerBackendInstance.class ) ){ final LoadBalancerBackendInstance update = Entities.uniqueResult( LoadBalancerBackendInstance.named(lb, instance.getInstanceId())); final LoadBalancerBackendInstanceStates azDisabled = LoadBalancerBackendInstanceStates.AvailabilityZoneDisabled; update.setState(azDisabled.getState()); update.setReasonCode(azDisabled.getReasonCode()); update.setDescription(azDisabled.getDescription()); Entities.persist(update); db.commit(); }catch(final NoSuchElementException ex){ LOG.warn("failed to find the backend instance"); }catch(final Exception ex){ LOG.warn("failed to query the backend instance", ex); } } } } @Override public void deleteLoadBalancerDeactivateDns(String accountNumber, String lbName) { LoadBalancer lb; final List<LoadBalancerServoInstanceCoreView> servos = Lists.newArrayList(); try{ lb= LoadBalancers.getLoadbalancer(accountNumber, lbName); if(lb.getZones()!=null){ for(final LoadBalancerZoneCoreView zoneView : lb.getZones()){ LoadBalancerZone zone; try{ zone = LoadBalancerZoneEntityTransform.INSTANCE.apply(zoneView); }catch(final Exception ex){ continue; } servos.addAll(zone.getServoInstances()); } } }catch(NoSuchElementException ex){ return; }catch(Exception ex){ LOG.warn("Failed to find the loadbalancer", ex); return; } for(final LoadBalancerServoInstanceCoreView instance: servos){ final String address = instance.getAddress(); if(address==null || address.length()<=0) continue; try{ try ( final TransactionResource db = Entities.transactionFor(LoadBalancerServoInstance.class)){ try{ final LoadBalancerServoInstance entity = Entities.uniqueResult(LoadBalancerServoInstance.named(instance.getInstanceId())); entity.setDnsState(LoadBalancerServoInstance.DNS_STATE.Deregistered); Entities.persist(entity); db.commit(); }catch(final Exception ex){ LOG.error(String.format("failed to set servo instance(%s)'s dns state to deregistered", instance.getInstanceId()), ex); } } }catch(Exception ex){ LOG.error("Error updating DNS registration state for balancer " + lbName, ex); } } } @Override public void deleteLoadBalancerDeleteScalingGroup(String accountNumber, String lbName) throws LoadBalancingActivityException { LoadBalancer lb; try{ lb= LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ return; }catch(Exception ex){ LOG.warn("Failed to find the loadbalancer named " + lbName, ex); return; } final Collection<LoadBalancerAutoScalingGroupCoreView> groups = lb.getAutoScaleGroups(); if(groups == null || groups.isEmpty()){ LOG.warn(String.format("Loadbalancer %s had no autoscale group associated with it", lb.getDisplayName())); return; } for (final LoadBalancerAutoScalingGroupCoreView group : groups) { final String groupName = group.getName(); String launchConfigName = null; try{ final DescribeAutoScalingGroupsResponseType resp = EucalyptusActivityTasks.getInstance().describeAutoScalingGroups(Lists.newArrayList(groupName), lb.useSystemAccount()); final AutoScalingGroupType asgType = resp.getDescribeAutoScalingGroupsResult().getAutoScalingGroups().getMember().get(0); launchConfigName = asgType.getLaunchConfigurationName(); }catch(final Exception ex){ LOG.warn(String.format("Unable to find the launch config associated with %s", groupName)); } try{ EucalyptusActivityTasks.getInstance().updateAutoScalingGroup(groupName, null, 0, lb.useSystemAccount()); }catch(final Exception ex){ LOG.warn(String.format("Unable to set desired capacity for %s", groupName), ex); } boolean error=false; final int NUM_DELETE_ASG_RETRY = 4; for(int i=0; i<NUM_DELETE_ASG_RETRY; i++){ try{ EucalyptusActivityTasks.getInstance().deleteAutoScalingGroup(groupName, true, lb.useSystemAccount()); error = false; // will terminate all instances }catch(final Exception ex){ error = true; LOG.warn(String.format("Failed to delete autoscale group (%d'th attempt): %s", (i+1), groupName)); try{ long sleepMs = (i+1) * 500; Thread.sleep(sleepMs); }catch(final Exception ex2){ } } if(!error) break; } if(error){ throw new LoadBalancingActivityException("Failed to delete autoscaling group; retry in a few seconds"); } if(launchConfigName!=null){ try{ EucalyptusActivityTasks.getInstance().deleteLaunchConfiguration(launchConfigName, lb.useSystemAccount()); }catch(Exception ex){ LOG.warn("Failed to delete launch configuration " + launchConfigName, ex); } } LoadBalancerAutoScalingGroup scaleGroup = null; try{ scaleGroup = LoadBalancerAutoScalingGroupEntityTransform.INSTANCE.apply(group); }catch(final Exception ex){ LOG.error("falied to update servo instance record", ex); } if(scaleGroup==null) return; try ( TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { for(final LoadBalancerServoInstanceCoreView instanceView : scaleGroup.getServos()){ LoadBalancerServoInstance instance; try{ instance=LoadBalancerServoInstanceEntityTransform.INSTANCE.apply(instanceView); }catch(final Exception ex){ continue; } final LoadBalancerServoInstance found = Entities.uniqueResult(instance); found.setAvailabilityZone(null); found.setAutoScalingGroup(null); // InService --> Retired // Pending --> Retired // OutOfService --> Retired // Error --> Retired found.setState(LoadBalancerServoInstance.STATE.Retired); Entities.persist(found); } db.commit(); }catch(final Exception ex){ LOG.error("Failed to update servo instance record", ex); } } // AutoScalingGroup record will be deleted later by clean-up workflow } @Override public void deleteLoadBalancerDeleteInstanceProfile(String accountNumber, String lbName) { final String instanceProfileName = getInstanceProfileName(accountNumber, lbName); final String roleName = getRoleName(accountNumber, lbName); LoadBalancer lb = null; try{ lb= LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ return; }catch(Exception ex){ LOG.warn("Failed to find the loadbalancer named " + lbName, ex); return; } try{ EucalyptusActivityTasks.getInstance().removeRoleFromInstanceProfile(instanceProfileName, roleName, lb.useSystemAccount()); }catch(final Exception ex){ LOG.error(String.format("Failed to remove role(%s) from the instance profile(%s)", roleName, instanceProfileName), ex); } // remove instance profile try{ EucalyptusActivityTasks.getInstance().deleteInstanceProfile(instanceProfileName, lb.useSystemAccount()); }catch(final Exception ex){ LOG.error(String.format("Failed to delete instance profile (%s)", instanceProfileName), ex); } } @Override public void deleteLoadBalancerDeleteIamRole(String accountNumber, String lbName) { final String roleName = getRoleName(accountNumber, lbName); LoadBalancer lb = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(NoSuchElementException ex){ return; }catch(Exception ex){ LOG.warn("Failed to find the loadbalancer named " + lbName, ex); return; } List<String> rolePolicies = null; try{ rolePolicies = EucalyptusActivityTasks.getInstance().listRolePolicies(roleName); }catch(final Exception ex){ LOG.warn("Failed to list role policies to delete", ex); } if(rolePolicies != null) { for(final String policy : rolePolicies) { // delete role policy try{ EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, policy, lb.useSystemAccount()); }catch(final Exception ex){ LOG.error("Failed to delete role policy: " + policy, ex); } } } // delete role try{ EucalyptusActivityTasks.getInstance().deleteRole(roleName, lb.useSystemAccount()); }catch(final Exception ex){ LOG.error("failed to delete role: " + roleName, ex); } } @Override public void deleteLoadBalancerDeleteSecurityGroup(String accountNumber, String lbName) { LoadBalancer lb; LoadBalancerSecurityGroupCoreView groupView = null; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); if(lb.getGroup()!=null){ groupView = lb.getGroup(); } }catch(NoSuchElementException ex){ return; }catch(Exception ex){ LOG.error("Error while looking for loadbalancer with name="+lbName, ex); return; } if ( lb.getVpcId( ) == null ) { LoadBalancerSecurityGroup group; try { group = LoadBalancerSecurityGroupEntityTransform.INSTANCE.apply( groupView ); } catch ( final Exception ex ) { LOG.error( "Erorr while looking for loadbalancer group", ex ); return; } try ( TransactionResource db = Entities.transactionFor( LoadBalancerSecurityGroup.class ) ) { final LoadBalancerSecurityGroup update = Entities.uniqueResult( group ); update.setLoadBalancer( null ); // this allows the loadbalancer to be deleted update.setState( LoadBalancerSecurityGroup.STATE.OutOfService ); Entities.persist( update ); db.commit(); } catch ( Exception ex ) { LOG.warn( "Could not disassociate the group from loadbalancer" ); } // the actual security group is delete during the clean-up workflow } } private static EucaS3Client getS3Client (final String roleName) throws AuthException { try { final Role lbRole = Accounts.lookupRoleByName( Accounts.lookupAccountIdByAlias( AccountIdentifiers.ELB_SYSTEM_ACCOUNT), roleName); final SecurityTokenAWSCredentialsProvider roleCredentialProvider = SecurityTokenAWSCredentialsProvider.forUserOrRole(Accounts.lookupPrincipalByRoleId(lbRole.getRoleId())); return EucaS3ClientFactory.getEucaS3Client(roleCredentialProvider); }catch (AuthException ex) { LOG.error("Failed to get credentials for loadbalancing role", ex); }catch (Exception ex) { LOG.error("Failed to get credentials for loadbalancing role", ex); } return null; } final String ACCESSLOG_ROLE_POLICY_NAME = "euca-internal-loadbalancer-vm-policy-accesslog"; @Override public AccessLogPolicyActivityResult modifyLoadBalancerAttributesCreateAccessLogPolicy( final String accountNumber, final String lbName, final Boolean accessLogEnabled, final String s3BucketName, final String s3BucketPrefix, final Integer emitInterval) throws LoadBalancingActivityException { final String ACCESSLOG_ROLE_POLICY_DOCUMENT= "{\"Statement\":" + "[ {" + "\"Action\": [\"s3:PutObject\"]," + "\"Effect\": \"Allow\"," + "\"Resource\": [\"arn:aws:s3:::BUCKETNAME_PLACEHOLDER/BUCKETPREFIX_PLACEHOLDER\"]" + "}]}"; AccessLogPolicyActivityResult result = new AccessLogPolicyActivityResult(); result.setShouldRollback(false); if (!accessLogEnabled) return result; final String bucketName = s3BucketName; final String bucketPrefix = com.google.common.base.Objects.firstNonNull(s3BucketPrefix, ""); final String roleName = getRoleName(accountNumber, lbName); final String policyName = ACCESSLOG_ROLE_POLICY_NAME; try{ final List<String> policies = EucalyptusActivityTasks.getInstance().listRolePolicies(roleName); if(policies.contains(policyName)){ EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, policyName); } }catch(final Exception ex){ ; } String policyDocument = ACCESSLOG_ROLE_POLICY_DOCUMENT.replace("BUCKETNAME_PLACEHOLDER", bucketName); if (bucketPrefix.length() > 0) { policyDocument = policyDocument.replace("BUCKETPREFIX_PLACEHOLDER", bucketPrefix+"/*"); }else{ policyDocument = policyDocument.replace("BUCKETPREFIX_PLACEHOLDER", "*"); } try{ EucalyptusActivityTasks.getInstance().putRolePolicy(roleName, policyName, policyDocument); result.setRoleName(roleName); result.setPolicyName(policyName); result.setShouldRollback(true); }catch(final Exception ex){ throw new LoadBalancingActivityException("failed to put role policy for loadbalancer vm's access to S3 buckets"); } try { final EucaS3Client s3c = getS3Client(roleName); final String key = s3BucketPrefix != null && !s3BucketPrefix.isEmpty() ? String.format("%s/AWSLogs/%s/ELBAccessLogTestFile", s3BucketPrefix, accountNumber) : String.format("AWSLogs/%s/ELBAccessLogTestFile", accountNumber); final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); final String content = String.format("Enable AccessLog for ELB: %s at %s", lbName, df.format(new Date())); final PutObjectRequest req = new PutObjectRequest(bucketName, key, new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), new ObjectMetadata()) .withCannedAcl(CannedAccessControlList.BucketOwnerFullControl); s3c.putObject(req); } catch (final Exception ex) { LOG.warn("Failed to put test key to the access log bucket"); } return result; } @Override public void modifyLoadBalancerAttributesCreateAccessLogPolicyRollback( final String accountNumber, final String lbName, final AccessLogPolicyActivityResult result) { if(!result.getShouldRollback()) return; try{ EucalyptusActivityTasks.getInstance().deleteRolePolicy(result.getRoleName(), result.getPolicyName()); }catch(final Exception ex) { ; } } @Override public void modifyLoadBalancerAttributesDeleteAccessLogPolicy( final String accountNumber, final String lbName, final Boolean accessLogEnabled, final String s3BucketName, final String s3BucketPrefix, final Integer emitInterval) throws LoadBalancingActivityException { if (accessLogEnabled) return; final String roleName = getRoleName(accountNumber, lbName); final String policyName = ACCESSLOG_ROLE_POLICY_NAME; try{ EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, policyName); }catch(final Exception ex) { ; } } @Override public void modifyLoadBalancerAttributesPersistAttributes( final String accountNumber, final String lbName, final Boolean accessLogEnabled, final String s3BucketName, final String s3BucketPrefix, Integer emitInterval) throws LoadBalancingActivityException { String bucketName = null; String bucketPrefix = null; if(accessLogEnabled) { bucketName = s3BucketName; bucketPrefix = com.google.common.base.Objects.firstNonNull(s3BucketPrefix, ""); emitInterval = com.google.common.base.Objects.firstNonNull(emitInterval, 60); } else { bucketName = ""; bucketPrefix = ""; emitInterval = 60; } try ( final TransactionResource db = Entities.transactionFor(LoadBalancer.class) ) { final LoadBalancer lb = Entities.uniqueResult( LoadBalancer.namedByAccountId(accountNumber, lbName)); lb.setAccessLogEnabled(accessLogEnabled); lb.setAccessLogEmitInterval(emitInterval); lb.setAccessLogS3BucketName(bucketName); lb.setAccessLogS3BucketPrefix(bucketPrefix); Entities.persist(lb); db.commit(); }catch(final NoSuchElementException ex) { throw new LoadBalancingActivityException("No such loadbalancer is found"); }catch(final Exception ex) { throw new LoadBalancingActivityException("Unknown error occured while saving entity", ex); } } private String lookupSecondaryNetworkInterface(final String instanceId) { try{ final Optional<InstanceNetworkInterfaceSetItemType> optEni = LoadBalancingSystemVpcs.getUserVpcInterface(instanceId); if(optEni.isPresent()) { return optEni.get().getNetworkInterfaceId(); } return null; }catch(final Exception ex) { LOG.error("Failed to lookup secondary network interface for instance: " + instanceId); return null; } } @Override public void modifyServicePropertiesValidateRequest(final String emi, final String instanceType, final String keyname, final String initScript) throws LoadBalancingActivityException { if(emi!=null){ try{ final List<ImageDetails> images = EucalyptusActivityTasks.getInstance().describeImagesWithVerbose(Lists.newArrayList(emi)); if(images == null || images.size()<=0) throw new LoadBalancingActivityException("No such EMI is found in the system"); if(! images.get(0).getImageId().toLowerCase().equals(emi.toLowerCase())) throw new LoadBalancingActivityException("No such EMI is found in the system"); }catch(final LoadBalancingActivityException ex){ throw ex; }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to verify EMI in the system"); } } // validate instance type if(instanceType!=null){ try{ final List<VmTypeDetails> vmTypes = EucalyptusActivityTasks.getInstance().describeInstanceTypes(Lists.newArrayList(instanceType)); if(vmTypes.size()<=0) throw new LoadBalancingActivityException("Invalid instance type -- " + instanceType); }catch(final LoadBalancingActivityException ex){ throw ex; }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to verify instance type -- " + instanceType); } } } @Override public void applySecurityGroupUpdateSecurityGroup(final String accountNumber, final String lbName, final Map<String, String> groupIdToNames) throws LoadBalancingActivityException { final LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer( accountNumber, lbName ); }catch(NoSuchElementException ex){ throw new LoadBalancingActivityException("Failed to find the loadbalancer " + lbName, ex); }catch(Exception ex){ throw new LoadBalancingActivityException("Unable to access loadbalancer metadata", ex); } for(final LoadBalancerAutoScalingGroupCoreView group : lb.getAutoScaleGroups()) { final String groupName = group.getName(); final DescribeAutoScalingGroupsResponseType response = EucalyptusActivityTasks.getInstance().describeAutoScalingGroups( Lists.newArrayList( groupName ), lb.useSystemAccount() ); final DescribeAutoScalingGroupsResult describeAutoScalingGroupsResult = response.getDescribeAutoScalingGroupsResult(); if ( describeAutoScalingGroupsResult != null ) { final AutoScalingGroupsType autoScalingGroupsType = describeAutoScalingGroupsResult.getAutoScalingGroups( ); if ( autoScalingGroupsType != null && autoScalingGroupsType.getMember( ) != null && !autoScalingGroupsType.getMember( ).isEmpty( ) && autoScalingGroupsType.getMember( ).get( 0 ).getInstances( ) != null ) { for ( final Instance instance : autoScalingGroupsType.getMember( ).get( 0 ).getInstances( ).getMember( ) ) { final String userVpcEni = lookupSecondaryNetworkInterface(instance.getInstanceId()); if (userVpcEni == null) { throw new LoadBalancingActivityException("Failed to lookup user VPC network interface"); } try { final List<String> sgroupIds = Lists.newArrayList(groupIdToNames.keySet()); EucalyptusActivityTasks.getInstance().modifyNetworkInterfaceSecurityGroups(userVpcEni, sgroupIds); }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to set security groups to network interface", ex); } } } } } } @Override public void modifyServicePropertiesUpdateScalingGroup(final String emi, final String instanceType, final String keyname, final String initScript) throws LoadBalancingActivityException { final List<LoadBalancer> lbs = LoadBalancers.listLoadbalancers(); for(final LoadBalancer lb : lbs){ final Collection<LoadBalancerAutoScalingGroupCoreView> groups = lb.getAutoScaleGroups(); for(final LoadBalancerAutoScalingGroupCoreView asg : groups) { if(asg==null || asg.getName()==null) continue; final String asgName = asg.getName(); try{ AutoScalingGroupType asgType = null; try{ final DescribeAutoScalingGroupsResponseType resp = EucalyptusActivityTasks.getInstance().describeAutoScalingGroups(Lists.newArrayList(asgName), lb.useSystemAccount()); if(resp.getDescribeAutoScalingGroupsResult() != null && resp.getDescribeAutoScalingGroupsResult().getAutoScalingGroups()!=null && resp.getDescribeAutoScalingGroupsResult().getAutoScalingGroups().getMember()!=null && resp.getDescribeAutoScalingGroupsResult().getAutoScalingGroups().getMember().size()>0){ asgType = resp.getDescribeAutoScalingGroupsResult().getAutoScalingGroups().getMember().get(0); } }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to find the scaling group: "+asgName); } if(asgType!=null) { final LaunchConfigurationType lc = EucalyptusActivityTasks.getInstance().describeLaunchConfiguration(asgType.getLaunchConfigurationName(), lb.useSystemAccount()); String launchConfigName; do{ launchConfigName = getLaunchConfigName( lb.getOwnerAccountNumber(), lb.getDisplayName(), asg.getAvailabilityZone()); }while(launchConfigName.equals(asgType.getLaunchConfigurationName())); final String newEmi = emi != null? emi : lc.getImageId(); final String newType = instanceType != null? instanceType : lc.getInstanceType(); String newKeyname = keyname != null ? keyname : lc.getKeyName(); final String newUserdata = B64.standard.encString(String.format( "%s\n%s", getCredentialsString(), getLoadBalancerUserData(initScript, lb.getOwnerAccountNumber()))); try{ EucalyptusActivityTasks.getInstance().createLaunchConfiguration(newEmi, newType, lc.getIamInstanceProfile(), launchConfigName, lc.getSecurityGroups().getMember(), newKeyname, newUserdata, lc.getAssociatePublicIpAddress( ), lb.useSystemAccount() ); }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to create new launch config", ex); } try{ EucalyptusActivityTasks.getInstance().updateAutoScalingGroup(asgName, null,asgType.getDesiredCapacity(), launchConfigName, lb.useSystemAccount()); }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to update the autoscaling group", ex); } try{ EucalyptusActivityTasks.getInstance().deleteLaunchConfiguration(asgType.getLaunchConfigurationName(), lb.useSystemAccount()); }catch(final Exception ex){ LOG.warn("unable to delete the old launch configuration", ex); } // copy all tags from new image to ASG if ( emi != null ) { try { final List<ImageDetails> images = EucalyptusActivityTasks.getInstance().describeImagesWithVerbose(Lists.newArrayList(emi)); // image should exist at this point for(ResourceTag tag:images.get(0).getTagSet()){ EucalyptusActivityTasks.getInstance().createOrUpdateAutoscalingTags(tag.getKey(), tag.getValue(), asgName, lb.useSystemAccount()); } } catch (final Exception ex) { LOG.warn("unable to propagate tags from image to ASG", ex); } } LOG.debug(String.format("autoscaling group '%s' was updated", asgName)); } }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to apply ELB property changes", ex); } } // for all autoscaling groups of LB } // for all LBs } private LoadBalancerServoInstance newInstance(final Instance instance, final LoadBalancerAutoScalingGroup group) throws Exception { final String instanceId = instance.getInstanceId(); final LoadBalancerCoreView lbView = group.getLoadBalancer(); LoadBalancer lb; try{ lb=LoadBalancerEntityTransform.INSTANCE.apply(lbView); }catch(final Exception ex){ LOG.error("unable to transfrom loadbalancer from the viewer", ex); throw ex; } LoadBalancerZoneCoreView zoneView = null; for(final LoadBalancerZoneCoreView z : lb.getZones()){ if(z.getName().equals(instance.getAvailabilityZone())){ zoneView = z; break; } } if(zoneView == null) throw new Exception("No availability zone with name="+instance.getAvailabilityZone()+" found for loadbalancer "+lb.getDisplayName()); final LoadBalancerSecurityGroupCoreView sgroupView = lb.getGroup(); if(sgroupView == null && lb.getVpcId()==null) throw new Exception("No security group is found for loadbalancer "+lb.getDisplayName()); LoadBalancerZone zone; LoadBalancerSecurityGroup sgroup; try{ zone = LoadBalancerZoneEntityTransform.INSTANCE.apply(zoneView); sgroup = sgroupView == null ? null : LoadBalancerSecurityGroupEntityTransform.INSTANCE.apply(sgroupView); }catch(final Exception ex){ LOG.error("Unable to transform entity", ex); throw ex; } // for faster inclusion into DNS response, update status as soon as servo is running String ipAddr = null; String privateIpAddr = null; try{ List<RunningInstancesItemType> result = null; result = EucalyptusActivityTasks.getInstance().describeSystemInstancesWithVerbose(Lists.newArrayList(instance.getInstanceId())); if(result!=null && result.size()>0){ ipAddr = result.get(0).getIpAddress(); privateIpAddr = result.get(0).getPrivateIpAddress(); } }catch(Exception ex){ LOG.warn("failed to run describe-instances", ex); } final LoadBalancerServoInstance newInstance = LoadBalancerServoInstance.newInstance(zone, sgroup, group, Integer.parseInt(LoadBalancingWorkerProperties.EXPIRATION_DAYS), instanceId); if("Healthy".equals(instance.getHealthStatus()) && "InService".equals(instance.getLifecycleState())) newInstance.setState(LoadBalancerServoInstance.STATE.InService); newInstance.setAddress(ipAddr); newInstance.setPrivateIp(privateIpAddr); if (!(ipAddr == null && privateIpAddr == null)) newInstance.setDnsState(LoadBalancerServoInstance.DNS_STATE.Registered); return newInstance; } private void updateIpAddressesInVpc(final String instanceId) { final List<Optional<String>> userVpcInterfaceAddresses = LoadBalancingSystemVpcs.getUserVpcInterfaceIps(instanceId); if(userVpcInterfaceAddresses!=null) { final Optional<String> publicIp = userVpcInterfaceAddresses.get(0); final Optional<String> privateIp = userVpcInterfaceAddresses.get(1); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(LoadBalancerServoInstance.named(instanceId)); if(publicIp.isPresent()) update.setAddress(publicIp.get()); if(privateIp.isPresent()) update.setPrivateIp(privateIp.get()); Entities.persist(update); db.commit(); }catch(final Exception ex) { LOG.error("Failed to update instance's IP addresses", ex); } } } @Override public void checkServoInstances() throws LoadBalancingActivityException { final int NUM_ASGS_TO_DESCRIBE = 8; // lookup all LoadBalancerAutoScalingGroup records List<LoadBalancerAutoScalingGroup> groups = Lists.newArrayList(); Map<String, LoadBalancerAutoScalingGroup> allGroupMap = new ConcurrentHashMap<>(); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerAutoScalingGroup.class ) ) { groups = Entities.query(LoadBalancerAutoScalingGroup.named(), true); for(LoadBalancerAutoScalingGroup g : groups){ allGroupMap.put(g.getName(), g); } }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to query loadbalancer autoscaing groups", ex); } final Map<String, LoadBalancerAutoScalingGroup> groupToQuery = allGroupMap; // describe as group and find the unknown instance Ids List<AutoScalingGroupType> queriedGroups = Lists.newArrayList(); for(final List<String> partition : Iterables.partition(groupToQuery.keySet(), NUM_ASGS_TO_DESCRIBE)) { try{ DescribeAutoScalingGroupsResponseType response = EucalyptusActivityTasks.getInstance().describeAutoScalingGroupsWithVerbose(partition); DescribeAutoScalingGroupsResult result = response.getDescribeAutoScalingGroupsResult(); AutoScalingGroupsType asgroups = result.getAutoScalingGroups(); queriedGroups.addAll(asgroups.getMember()); }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to describe autoscaling groups", ex); } } /// lookup all servoInstances in the DB Map<String, LoadBalancerServoInstance> servoMap = new ConcurrentHashMap<>(); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final List<LoadBalancerServoInstance> result = Entities.query(LoadBalancerServoInstance.named(), true); for(LoadBalancerServoInstance inst : result){ servoMap.put(inst.getInstanceId(), inst); } }catch(Exception ex){ throw new LoadBalancingActivityException("Failed to lookup existing servo instances in DB", ex); } /// for all found instances that's not in the servo instance DB /// create servo record final List<LoadBalancerServoInstance> newServos = Lists.newArrayList(); final Map<String, Instance> foundInstances = new ConcurrentHashMap<>(); for(final AutoScalingGroupType asg : queriedGroups){ Instances instances = asg.getInstances(); if(instances!=null && instances.getMember() != null && instances.getMember().size() >0){ for(final Instance instance : instances.getMember()){ final String instanceId = instance.getInstanceId(); foundInstances.put(instanceId, instance); if(!servoMap.containsKey(instanceId)){ /// new instance found try{ final LoadBalancerAutoScalingGroup group= allGroupMap.get(asg.getAutoScalingGroupName()); if(group==null) throw new IllegalArgumentException("The group with name "+ asg.getAutoScalingGroupName()+ " not found in the database"); final LoadBalancerServoInstance newInstance = newInstance(instance, group); newServos.add(newInstance); /// persist later }catch(final Exception ex) { LOG.error("Failed to construct servo instance entity", ex); continue; } } } } } // CASE 1: NEW INSTANCES WITHIN THE AS GROUP FOUND if(newServos.size()>0){ try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { for(LoadBalancerServoInstance instance : newServos){ Entities.persist(instance); } db.commit(); }catch(Exception ex){ LOG.error("Failed to persist the servo instance record", ex); } if (LoadBalancingSystemVpcs.isCloudVpc().isPresent() && LoadBalancingSystemVpcs.isCloudVpc().get()) { try { newServos.stream() .filter(instance -> LoadBalancerServoInstance.STATE.InService.equals( instance.getState())) .forEach(instance -> LoadBalancingSystemVpcs.setupUserVpcInterface(instance.getInstanceId())); } catch (final Exception ex) { LOG.error("Failed to attach secondary network interface to ELB instances", ex); } try { // if servo is in VPC, update ip addresses using the secondary interface's address newServos.stream() .filter(instance -> LoadBalancerServoInstance.STATE.InService.equals( instance.getState())) .forEach(instance -> { updateIpAddressesInVpc(instance.getInstanceId()); }); } catch (final Exception ex) { LOG.error("Failed to retrieve IP addresses of secondary network interface"); } } } List<LoadBalancerServoInstanceCoreView> servoRecords = Lists.newArrayList(); for(String groupName : groupToQuery.keySet()){ final LoadBalancerAutoScalingGroup group = groupToQuery.get(groupName); servoRecords.addAll(group.getServos()); } //final List<LoadBalancerServoInstance> registerDnsARec = Lists.newArrayList(); for(LoadBalancerServoInstanceCoreView instanceView : servoRecords){ /// CASE 2: EXISTING SERVO INSTANCES ARE NOT FOUND IN THE ASG QUERY RESPONSE if(! foundInstances.containsKey(instanceView.getInstanceId()) && ! instanceView.getState().equals(LoadBalancerServoInstance.STATE.Retired)){ LoadBalancerServoInstance instance; try{ instance = LoadBalancerServoInstanceEntityTransform.INSTANCE.apply(instanceView); }catch(final Exception ex){ LOG.error("Failed to transform servo instance from the view", ex); continue; } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(instance); update.setState(LoadBalancerServoInstance.STATE.Error); Entities.persist(update); db.commit(); }catch(Exception ex){ LOG.error(String.format("Failed to mark the servo instance's state to ERROR (%s)", instance.getInstanceId())); } } else if (foundInstances.containsKey(instanceView.getInstanceId())) { /// CASE 3: INSTANCE STATE UPDATED Instance instanceCurrent = foundInstances.get(instanceView.getInstanceId()); final String healthState = instanceCurrent.getHealthStatus(); final String lifecycleState = instanceCurrent.getLifecycleState(); LoadBalancerServoInstance.STATE curState = instanceView.getState(); LoadBalancerServoInstance.STATE newState = curState; if(healthState != null && ! healthState.equals("Healthy")){ newState = LoadBalancerServoInstance.STATE.Error; }else if (lifecycleState != null){ switch ( lifecycleState ) { case "Pending": newState = LoadBalancerServoInstance.STATE.Pending; break; case "Quarantined": newState = LoadBalancerServoInstance.STATE.Error; break; case "InService": newState = LoadBalancerServoInstance.STATE.InService; break; case "Terminating": case "Terminated": newState = LoadBalancerServoInstance.STATE.OutOfService; break; } } if(!curState.equals(LoadBalancerServoInstance.STATE.Retired) && !curState.equals(newState)){ LoadBalancerServoInstance instance; try{ instance = LoadBalancerServoInstanceEntityTransform.INSTANCE.apply(instanceView); }catch(final Exception ex){ LOG.error("Failed to transform servo instance from the view", ex); continue; } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(instance); update.setState(newState); Entities.persist(update); db.commit(); }catch(Exception ex){ LOG.error(String.format("Failed to commit the servo instance's state change (%s)", instance.getInstanceId())); } if (LoadBalancerServoInstance.STATE.InService.equals(newState)) { try { if(LoadBalancingSystemVpcs.isCloudVpc().isPresent() && LoadBalancingSystemVpcs.isCloudVpc().get()) { LoadBalancingSystemVpcs.setupUserVpcInterface(instance.getInstanceId()); updateIpAddressesInVpc(instance.getInstanceId()); } }catch(final Exception ex) { LOG.error("Failed to attach secondary network interface to ELB instances", ex); } } } } } } // make sure InService servo instance has its IP registered to DNS // also make sure Error or OutOfService servo instance has its IP deregistered from DNS @Override public void checkServoInstanceDns() throws LoadBalancingActivityException { /// determine the servo instances to query final List<LoadBalancerServoInstance> allInstances = Lists.newArrayList(); //final List<LoadBalancerServoInstance> stateOutdated = Lists.newArrayList(); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { allInstances.addAll( Entities.query(LoadBalancerServoInstance.named())); }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to query servo instances in DB"); } final List<LoadBalancerServoInstance> stateOutdated = allInstances; for(final LoadBalancerServoInstance instance : stateOutdated){ if(LoadBalancerServoInstance.STATE.InService.equals(instance.getState())){ if(!LoadBalancerServoInstance.DNS_STATE.Registered.equals(instance.getDnsState())){ String ipAddr = null; String privateIpAddr = null; final Optional<Boolean> vpcTest = LoadBalancingSystemVpcs.isCloudVpc(); if (vpcTest.isPresent() && vpcTest.get()) { /// in vpc mode final List<Optional<String>> userVpcInterfaceAddresses = LoadBalancingSystemVpcs.getUserVpcInterfaceIps(instance.getInstanceId()); if(userVpcInterfaceAddresses!=null) { final Optional<String> optPublicIp = userVpcInterfaceAddresses.get(0); final Optional<String> optPrivateIp = userVpcInterfaceAddresses.get(1); if(optPublicIp.isPresent()) ipAddr = optPublicIp.get(); if(optPrivateIp.isPresent()) privateIpAddr = optPrivateIp.get(); } }else { /// in classic mode if (instance.getAddress() == null) { try { List<RunningInstancesItemType> result = null; result = EucalyptusActivityTasks.getInstance().describeSystemInstancesWithVerbose(Lists.newArrayList(instance.getInstanceId())); if (result != null && result.size() > 0) { ipAddr = result.get(0).getIpAddress(); privateIpAddr = result.get(0).getPrivateIpAddress(); } } catch (Exception ex) { LOG.warn("Failed to run describe-instances", ex); continue; } } else { ipAddr = instance.getAddress(); privateIpAddr = instance.getPrivateIp(); } } try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(instance); update.setAddress(ipAddr); update.setPrivateIp(privateIpAddr); if (!(ipAddr == null && privateIpAddr == null) ) update.setDnsState(LoadBalancerServoInstance.DNS_STATE.Registered); db.commit(); }catch(NoSuchElementException ex){ LOG.warn("Failed to find the servo instance named "+instance.getInstanceId(), ex); }catch(Exception ex){ LOG.warn("Failed to update servo instance's ip address", ex); } } }else if (LoadBalancerServoInstance.STATE.OutOfService.equals(instance.getState()) || LoadBalancerServoInstance.STATE.Error.equals(instance.getState())){ if(!LoadBalancerServoInstance.DNS_STATE.Deregistered.equals(instance.getDnsState())){ try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(instance); update.setDnsState(LoadBalancerServoInstance.DNS_STATE.Deregistered); db.commit(); }catch(NoSuchElementException ex){ LOG.warn("Failed to find the servo instance named "+instance.getInstanceId(), ex); }catch(Exception ex){ LOG.warn("Failed to update servo instance's ip address", ex); } } } } } @Override public void checkServoElasticIp() throws LoadBalancingActivityException { // EUCA-12956 if(! LoadBalancingWorkerProperties.useElasticIp()) { return; } final List<LoadBalancerServoInstance> allInstances = Lists.newArrayList(); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { allInstances.addAll( Entities.query(LoadBalancerServoInstance.named())); }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to query servo instances in DB"); } final Map<String, LoadBalancerServoInstance> inServiceInstances = allInstances.stream() .filter( vm -> LoadBalancerServoInstance.STATE.InService.equals(vm.getState()) ) .filter( vm -> LoadBalancerServoInstance.DNS_STATE.Registered.equals(vm.getDnsState()) ) .collect( Collectors.toMap( LoadBalancerServoInstance::getInstanceId, vm -> vm) ); final Optional<Boolean> vpcTest = LoadBalancingSystemVpcs.isCloudVpc(); for(final String instanceId : inServiceInstances.keySet()) { String ipAddr = null; String privateIpAddr = null; try { if (vpcTest.isPresent() && vpcTest.get()) { /// in vpc mode final List<Optional<String>> userVpcInterfaceAddresses = LoadBalancingSystemVpcs.getUserVpcInterfaceIps(instanceId); if (userVpcInterfaceAddresses != null) { final Optional<String> optPublicIp = userVpcInterfaceAddresses.get(0); final Optional<String> optPrivateIp = userVpcInterfaceAddresses.get(1); if (optPublicIp.isPresent()) ipAddr = optPublicIp.get(); if (optPrivateIp.isPresent()) privateIpAddr = optPrivateIp.get(); } } else { /// in classic mode final List<RunningInstancesItemType> result = EucalyptusActivityTasks.getInstance().describeSystemInstancesWithVerbose( Lists.newArrayList(instanceId)); if (result != null && result.size() > 0) { ipAddr = result.get(0).getIpAddress(); privateIpAddr = result.get(0).getPrivateIpAddress(); } } } catch (final Exception ex) { LOG.warn("Failed to describe loadbalancer worker instances", ex); continue; } // there's an external change in elastic or private IP if((ipAddr != null && !ipAddr.equals(inServiceInstances.get(instanceId).getAddress())) || privateIpAddr!= null && !privateIpAddr.equals(inServiceInstances.get(instanceId).getPrivateIp())) { try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance update = Entities.uniqueResult(LoadBalancerServoInstance.named(instanceId)); if (ipAddr!=null) { update.setAddress(ipAddr); } if (privateIpAddr!=null) { update.setPrivateIp(ipAddr); } db.commit(); }catch(final NoSuchElementException ex){ LOG.warn("Failed to find the servo instance named "+instanceId, ex); }catch(final Exception ex){ LOG.warn("Failed to update servo instance's ip address", ex); } } } } /* * Note that the backend instance check does not affect the health check result of the instances. * the health check is left to the "ping" mechanism by the servo. the state update here is the mean * by which to include only the non-faulty instances in the list delivered to servo. */ @Override public void checkBackendInstances() throws LoadBalancingActivityException { final int NUM_INSTANCES_TO_DESCRIBE = 8; /// determine backend instances to query (an instance can be registered to multiple ELBs) final Map<String, List<LoadBalancerBackendInstance>> allInstances = Maps.newHashMap(); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerBackendInstance.class ) ) { final List<LoadBalancerBackendInstance> instances = Entities.query(LoadBalancerBackendInstance.named()); for (final LoadBalancerBackendInstance instance : instances) { if(!allInstances.containsKey(instance.getInstanceId())) { allInstances.put(instance.getInstanceId(), Lists.newArrayList()); } allInstances.get(instance.getInstanceId()).add(instance); } }catch(final Exception ex){ throw new LoadBalancingActivityException("Failed to query backend instances", ex); } final List<RunningInstancesItemType> queryResult = Lists.newArrayList(); for(final List<String> partition : Iterables.partition(allInstances.keySet(), NUM_INSTANCES_TO_DESCRIBE)) { try{ queryResult.addAll( EucalyptusActivityTasks.getInstance().describeSystemInstancesWithVerbose(partition)); }catch(final Exception ex){ LOG.warn("Failed to query instances", ex); break; } } //EUCA-9919: remove registered instances when terminated final Set<String> terminatedInstances = Sets.newHashSet(); final Map<String, LoadBalancerBackendInstanceStates> stateMap = new HashMap<>(); final Map<String, RunningInstancesItemType> runningInstances = new HashMap<String, RunningInstancesItemType>(); for(final RunningInstancesItemType instance : queryResult){ final String state = instance.getStateName(); if("pending".equals(state)) stateMap.put(instance.getInstanceId(), LoadBalancerBackendInstanceStates.InitialRegistration); else if("running".equals(state)){ runningInstances.put(instance.getInstanceId(), instance); }else if("shutting-down".equals(state)) stateMap.put(instance.getInstanceId(), LoadBalancerBackendInstanceStates.InstanceInvalidState); else if("terminated".equals(state)) { stateMap.put(instance.getInstanceId(), LoadBalancerBackendInstanceStates.InstanceInvalidState); terminatedInstances.add(instance.getInstanceId()); }else if("stopping".equals(state)) stateMap.put(instance.getInstanceId(), LoadBalancerBackendInstanceStates.InstanceStopped); else if("stopped".equals(state)) stateMap.put(instance.getInstanceId(), LoadBalancerBackendInstanceStates.InstanceStopped); } final Set<LoadBalancerBackendInstance> backendsToDelete = Sets.newHashSet(); for(final String instanceId : allInstances.keySet()) { for (final LoadBalancerBackendInstance be : allInstances.get(instanceId)) { if (terminatedInstances.contains(instanceId)) { // case 1: instance terminated backendsToDelete.add(be); continue; } if (stateMap.containsKey(instanceId)) { // case 2: instance not in running state try (final TransactionResource db = Entities.transactionFor(LoadBalancerBackendInstance.class)) { final LoadBalancerBackendInstanceStates trueState = stateMap.get(be.getInstanceId()); final LoadBalancerBackendInstance update = Entities.uniqueResult(be); update.setBackendState(trueState.getState()); update.setReasonCode(trueState.getReasonCode()); update.setDescription(trueState.getDescription()); Entities.persist(update); db.commit(); } catch (final Exception ex) { ; } } else if (runningInstances.containsKey(instanceId)) { // case 3: instance running // case 3.a: check if instance was re-started (EUCA-11859) if (LoadBalancerBackendInstanceStates.InstanceStopped.isInstanceState(be)) { final LoadBalancerBackendInstanceStates registration = LoadBalancerBackendInstanceStates.InitialRegistration; try (final TransactionResource db = Entities.transactionFor(LoadBalancerBackendInstance.class)) { final LoadBalancerBackendInstance update = Entities.uniqueResult(be); update.setBackendState(registration.getState()); update.setReasonCode(registration.getReasonCode()); update.setDescription(registration.getDescription()); Entities.persist(update); db.commit(); } catch (final Exception ex) { ; } } // case 3.b: check instance's IP address change String instanceIpAddress = null; if (be.getLoadBalancer().getVpcId() == null) instanceIpAddress = runningInstances.get(instanceId).getIpAddress(); else instanceIpAddress = runningInstances.get(instanceId).getPrivateIpAddress(); if (instanceIpAddress == null) { LOG.warn(String.format("Failed to determine ELB backend instance's IP address: %s", instanceId)); } else if (!instanceIpAddress.equals(be.getIpAddress())) { try (final TransactionResource db = Entities.transactionFor(LoadBalancerBackendInstance.class)) { final LoadBalancerBackendInstance update = Entities.uniqueResult(be); update.setIpAddress(instanceIpAddress); update.setPartition(runningInstances.get(instanceId).getPlacement()); Entities.persist(update); db.commit(); } catch (final Exception ex) { ; } } } } } for(final LoadBalancerBackendInstance be : backendsToDelete) { try ( final TransactionResource db = Entities.transactionFor( LoadBalancerBackendInstance.class ) ) { final LoadBalancerBackendInstance entity = Entities.uniqueResult(be); Entities.delete(entity); LOG.info("Instance "+be.getInstanceId()+" is terminated and removed from ELB"); db.commit(); }catch(final Exception ex) { ; } } /// mark outdated instances as Error final int HealthUpdateTimeoutSec = 3 * MAX_HEALTHCHECK_INTERVAL_SEC; /// 6 minutes final Predicate<LoadBalancerBackendInstance> unreachableLoadbalancer = (instance) -> { if (LoadBalancerBackendInstanceStates.UnrechableLoadBalancer.isInstanceState(instance)) return false; if (LoadBalancerBackendInstanceStates.InstanceStopped.isInstanceState(instance)) return false; final long currentTime = System.currentTimeMillis(); Date lastUpdated = instance.instanceStateLastUpdated(); if (lastUpdated == null) lastUpdated = instance.getCreationTimestamp(); final int diffSec = (int) ((currentTime - lastUpdated.getTime()) / 1000.0); return diffSec > HealthUpdateTimeoutSec; }; final Set<LoadBalancerBackendInstance> outdatedInstances = allInstances.values().stream() .flatMap(Collection::stream) .filter(v -> !backendsToDelete.contains(v)) // DB records deleted already .filter(v -> unreachableLoadbalancer.apply(v)) .collect(Collectors.toSet()); if (! outdatedInstances.isEmpty()) { final LoadBalancerBackendInstanceStates unreachable = LoadBalancerBackendInstanceStates.UnrechableLoadBalancer; try (TransactionResource db = Entities.transactionFor(LoadBalancerBackendInstance.class)) { for (final LoadBalancerBackendInstance instance : outdatedInstances) { final LoadBalancerBackendInstance update = Entities.uniqueResult(instance); update.setState(unreachable.getState()); update.setReasonCode(unreachable.getReasonCode()); update.setDescription(unreachable.getDescription()); Entities.persist(update); } db.commit(); } catch (final Exception ex) { ; } } } @Override public void cleanupSecurityGroups() throws LoadBalancingActivityException { /// find all security group whose member instances are empty List<LoadBalancerSecurityGroup> allGroups = null; try ( TransactionResource db = Entities.transactionFor( LoadBalancerSecurityGroup.class ) ) { allGroups = Entities.query(LoadBalancerSecurityGroup.withState(LoadBalancerSecurityGroup.STATE.OutOfService)); }catch(Exception ex){ /* retry later */ } if(allGroups==null || allGroups.size()<=0) return; final List<LoadBalancerSecurityGroup> toDelete = Lists.newArrayList(); for(LoadBalancerSecurityGroup group : allGroups){ Collection<LoadBalancerServoInstanceCoreView> instances = group.getServoInstances(); if(instances == null || instances.size()<=0) toDelete.add(group); } /// delete them from euca for(final LoadBalancerSecurityGroup group : toDelete){ boolean deleted = false; try{ final List<SecurityGroupItemType> existingGroups = EucalyptusActivityTasks.getInstance().describeSystemSecurityGroups( Lists.newArrayList(group.getName()), true); if(existingGroups == null || existingGroups.size()<=0) deleted =true; else { EucalyptusActivityTasks.getInstance().deleteSystemSecurityGroup( group.getName(), true); LOG.info("Deleted security group: "+group.getName()); deleted = true; } }catch(final Exception ex){ try{ final List<SecurityGroupItemType> existingGroups = EucalyptusActivityTasks.getInstance().describeSystemSecurityGroups( Lists.newArrayList(group.getName()), false); if(existingGroups == null || existingGroups.size()<=0) deleted =true; else{ EucalyptusActivityTasks.getInstance().deleteSystemSecurityGroup( group.getName(), false); LOG.info("Deleted security group: "+group.getName()); deleted = true; } }catch(final Exception ex2) { LOG.warn("Failed to delete the security group from eucalyptus",ex2); } } if (deleted) { try ( final TransactionResource db = Entities.transactionFor( LoadBalancerSecurityGroup.class ) ) { final LoadBalancerSecurityGroup g = Entities.uniqueResult(group); Entities.delete(g); db.commit(); }catch(NoSuchElementException ex){ ; }catch(Exception ex){ LOG.warn("Failed to delete the securty group records from database", ex); } } } } @Override public void cleanupServoInstances() throws LoadBalancingActivityException { // find all OutOfService instances List<LoadBalancerServoInstance> retired=null; try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { LoadBalancerServoInstance sample = LoadBalancerServoInstance.withState(LoadBalancerServoInstance.STATE.Retired.name()); retired = Entities.query(sample); sample = LoadBalancerServoInstance.withState(LoadBalancerServoInstance.STATE.Error.name()); retired.addAll(Entities.query(sample)); }catch(Exception ex){ LOG.warn("Failed to query loadbalancer servo instance", ex); } if(retired == null || retired.size()<=0) return; /// for each: // describe instances final List<String> param = Lists.newArrayList(); final Map<String, String> latestState = Maps.newHashMap(); for(final LoadBalancerServoInstance instance : retired){ /// call describe instance String instanceId = instance.getInstanceId(); if(instanceId == null) continue; param.clear(); param.add(instanceId); String instanceState; try{ List<RunningInstancesItemType> result =null; result = EucalyptusActivityTasks.getInstance().describeSystemInstancesWithVerbose(param); if (result.isEmpty()) instanceState= "terminated"; else instanceState = result.get(0).getStateName(); }catch(final Exception ex){ LOG.warn("Failed to query instances", ex); continue; } latestState.put(instanceId, instanceState); } // if state==terminated or describe instances return no result, // delete the database record for(String instanceId : latestState.keySet()){ String state = latestState.get(instanceId); if(state.equals("terminated")){ try ( final TransactionResource db2 = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { LoadBalancerServoInstance toDelete = Entities.uniqueResult(LoadBalancerServoInstance.named(instanceId)); Entities.delete(toDelete); db2.commit(); }catch(Exception ex){ LOG.warn( "Unable to delete load balancer servo instance: ", ex ); } } } } @Override public void recycleFailedServoInstances() throws LoadBalancingActivityException { /// when SWF activity fails on the VM more than the threshold (property), the VM is terminated /// and the autoscaling group replaces it with the new VM. /// The terminated Vms will be cleaned up later by checkServoInstances() and cleanupServoInstances() final int failureThreshold = Integer.parseInt(LoadBalancingWorkerProperties.FAILURE_THRESHOLD_FOR_RECYCLE); if (failureThreshold <= 0) { return; } try{ final List<LoadBalancerServoInstance> inServiceInstances = Lists.newArrayList(); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { LoadBalancerServoInstance sample = LoadBalancerServoInstance.withState(LoadBalancerServoInstance.STATE.InService.name()); inServiceInstances.addAll(Entities.query(sample)); } final Date oneHourAgo = new Date(System.currentTimeMillis() - (1 * 60 * 60 * 1000)); final List<String> unhealthyInstances = inServiceInstances.stream() .filter(vm -> vm.getActivityFailureCount() >= failureThreshold) .map(vm -> vm.getInstanceId()) .collect(Collectors.toList()); final List<String> newInstances = inServiceInstances.stream() .filter(vm -> vm.getActivityFailureUpdateTime() == null) .map(vm -> vm.getInstanceId()) .collect(Collectors.toList()); final List<String> temporallyFailedInstances = inServiceInstances.stream() .filter(vm -> vm.getActivityFailureCount() < failureThreshold && vm.getActivityFailureCount() > 0 && (vm.getActivityFailureUpdateTime()!=null && vm.getActivityFailureUpdateTime().before(oneHourAgo))) .map(vm -> vm.getInstanceId()) .collect(Collectors.toList()); try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { for (final String instanceId : newInstances) { final LoadBalancerServoInstance update = Entities.uniqueResult(LoadBalancerServoInstance.named(instanceId)); update.setActivityFailureUpdateTime(new Date(System.currentTimeMillis())); Entities.persist(update); } for (final String instanceId : temporallyFailedInstances) { final LoadBalancerServoInstance update = Entities.uniqueResult(LoadBalancerServoInstance.named(instanceId)); update.setActivityFailureCount(0); update.setActivityFailureUpdateTime(new Date(System.currentTimeMillis())); Entities.persist(update); } db.commit(); } for (final String instanceId : unhealthyInstances) { EucalyptusActivityTasks.getInstance().terminateInstances(Lists.newArrayList(instanceId)); LOG.debug(String.format("Unhealthy loadbalancer VM is detected and terminated (%s)", instanceId)); } } catch(final Exception ex) { LOG.error("Failed to recycle unhealthy worker VMs", ex); } } @Override public void runContinousWorkflows() throws LoadBalancingActivityException { List<LoadBalancer> loadbalancers = null; try{ loadbalancers = LoadBalancers.listLoadbalancers(); }catch(final Exception ex) { LOG.error("Failed to list all loadbalancers", ex); return; } for(final LoadBalancer lb : loadbalancers) { final String accountId = lb.getOwnerAccountNumber(); final String lbName = lb.getDisplayName(); try{ LoadBalancingWorkflows.runUpdateLoadBalancer(accountId, lbName); LoadBalancingWorkflows.runInstanceStatusPolling(accountId, lbName); LoadBalancingWorkflows.runCloudWatchPutMetric(accountId, lbName); }catch(final Exception ex) { LOG.error("Failed to run continous workflows for loadbalancers", ex); } } } @Override public List<String> lookupServoInstances(final String accountNumber, final String lbName) throws LoadBalancingActivityException { final LoadBalancer lb; try{ lb = LoadBalancers.getLoadbalancer(accountNumber, lbName); }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup loadbalancer"); } final List<String> instances = Lists.newArrayList(); try{ for(final LoadBalancerZoneCoreView zoneView : lb.getZones()) { if(LoadBalancerZone.STATE.OutOfService.equals(zoneView.getState())) continue; final LoadBalancerZone zone = LoadBalancerZoneEntityTransform.INSTANCE.apply(zoneView); instances.addAll(Collections2.transform(zone.getServoInstances(), new Function<LoadBalancerServoInstanceCoreView, String>(){ @Override public String apply(LoadBalancerServoInstanceCoreView arg0) { return arg0.getInstanceId(); } })); } }catch(final Exception ex) { throw new LoadBalancingActivityException("Failed to lookup servo instance records", ex); } return instances; } // to make sure that all ELB VMs have the right role policy @Override public void upgrade4_4() throws LoadBalancingActivityException { final List<LoadBalancer> oldLBs = LoadBalancers.listLoadbalancers().stream() .filter( lb -> !LoadBalancers.v4_4_0.apply(lb) ) .collect(Collectors.toList()); for (final LoadBalancer lb : oldLBs) { final String accountNumber = lb.getOwnerAccountNumber(); final String lbName = lb.getDisplayName(); final String roleName = getRoleName(accountNumber, lbName); try { GetRolePolicyResult policy = null; final List<String> policies = EucalyptusActivityTasks.getInstance().listRolePolicies(roleName); if (policies.contains(SERVO_ROLE_POLICY_NAME)) { policy = EucalyptusActivityTasks.getInstance().getRolePolicy(roleName, SERVO_ROLE_POLICY_NAME); } final boolean policyAllowsSwf = true ? policy != null && SERVO_ROLE_POLICY_DOCUMENT.toLowerCase().equals(policy.getPolicyDocument().toLowerCase()) : false; if (!policyAllowsSwf) { if (policy != null) { EucalyptusActivityTasks.getInstance().deleteRolePolicy(roleName, policy.getPolicyName()); } EucalyptusActivityTasks.getInstance().putRolePolicy(roleName, SERVO_ROLE_POLICY_NAME, SERVO_ROLE_POLICY_DOCUMENT); LOG.info(String.format("IAM role policy was updated for loadbalancer (%s-%s)", accountNumber, lbName)); } } catch (final Exception ex) { LOG.warn(String.format("Failed to upgrade old loadbalancer (%s-%s) to 4.4", accountNumber, lbName), ex); } } } @Override public void recordInstanceTaskFailure(final String instanceId) throws LoadBalancingActivityException { try { try ( final TransactionResource db = Entities.transactionFor( LoadBalancerServoInstance.class ) ) { final LoadBalancerServoInstance sample = LoadBalancerServoInstance.named(instanceId); final LoadBalancerServoInstance entity = Entities.uniqueResult(sample); entity.setActivityFailureCount(entity.getActivityFailureCount() + 1); Entities.persist(entity); db.commit(); } }catch(final Exception ex) { LOG.warn(String.format("Failed to mark the VM (%s) as failed", instanceId), ex); } } }