All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shinesolutions.aemorchestrator.service.AemInstanceHelperService Maven / Gradle / Ivy

Go to download

Java application for orchestrating AEM infrastructure created using aem-aws-stack-builder

There is a newer version: 4.0.0
Show newest version
package com.shinesolutions.aemorchestrator.service;

import com.shinesolutions.aemorchestrator.exception.InstanceNotInHealthyStateException;
import com.shinesolutions.aemorchestrator.exception.NoPairFoundException;
import com.shinesolutions.aemorchestrator.model.EC2Instance;
import com.shinesolutions.aemorchestrator.model.EnvironmentValues;
import com.shinesolutions.aemorchestrator.model.InstanceTags;
import com.shinesolutions.aemorchestrator.util.HttpUtil;
import org.apache.http.client.ClientProtocolException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.shinesolutions.aemorchestrator.model.InstanceTags.*;

/*
 * Service used for finding URLs, IDs etc of AEM/AWS instances
 */
@Component
public class AemInstanceHelperService {

    @Value("${aem.protocol.publishDispatcher}")
    private String aemPublishDispatcherProtocol;

    @Value("${aem.protocol.publish}")
    private String aemPublishProtocol;

    @Value("${aem.protocol.authorDispatcher}")
    private String aemAuthorDispatcherProtocol;

    @Value("${aem.protocol.author}")
    private String aemAuthorProtocol;

    @Value("${aem.port.publishDispatcher}")
    private Integer aemPublishDispatcherPort;

    @Value("${aem.port.publish}")
    private Integer aemPublishPort;

    @Value("${aem.port.authorDispatcher}")
    private Integer aemAuthorDispatcherPort;

    @Value("${aem.port.author}")
    private Integer aemAuthorPort;

    @Value("${aws.snapshot.tags}")
    private List tagsToApplyToSnapshot;
    
    @Value("${aws.cloudformation.stackName.publishDispatcher}")
    private String awsPublishDispatcherStackName;

    @Resource
    private EnvironmentValues envValues;

    @Resource
    private AwsHelperService awsHelperService;

    @Resource
    private HttpUtil httpUtil;

    private static final String URL_FORMAT = "%s://%s:%s";
    private static final String AEM_HEALTH_CHECK_URL_POSTFIX = "/system/health?tags=shallow";

    /**
     * Gets the Publish Dispatcher base AEM URL for a given EC2 instance ID
     * @param instanceId EC2 instance ID
     * @return Base AEM URL (includes protocol, IP and port). E.g. http://[ip]:[port]
     */
    public String getAemUrlForPublishDispatcher(String instanceId) {
        //Publish dispatcher must be accessed via private IP
        return String.format(URL_FORMAT, aemPublishDispatcherProtocol,
            awsHelperService.getPrivateIp(instanceId), aemPublishDispatcherPort);
    }

    /**
     * Gets the Publish instance base AEM URL for a given EC2 instance ID
     * @param instanceId C2 instance ID
     * @return Base AEM URL (includes protocol, IP and port). E.g. http://[ip]:[port]
     */
    public String getAemUrlForPublish(String instanceId) {
        //Publish must be accessed via private IP
        return String.format(URL_FORMAT, aemPublishProtocol,
            awsHelperService.getPrivateIp(instanceId), aemPublishPort);
    }

    /**
     * Gets the Author base AEM URL for a given EC2 instance ID
     * @param instanceId C2 instance ID
     * @return Base AEM URL (includes protocol, IP and port). E.g. http://[ip]:[port]
     */
    public String getAemUrlForAuthorDispatcher(String instanceId) {
        //Author dispatcher can be accessed via private IP
        return String.format(URL_FORMAT, aemAuthorDispatcherProtocol,
            awsHelperService.getPrivateIp(instanceId), aemAuthorDispatcherPort);
    }

    /**
     * Gets the Author base AEM URL for the Author AWS Elastic Load Balancer
     * @return Base AEM URL (includes protocol, DNS name and port). E.g. http://[dns-name]:[port]
     */
    public String getAemUrlForAuthorElb() {
        //Author can be accessed from the load balancer
        return String.format(URL_FORMAT, aemAuthorProtocol, envValues.getElasticLoadBalancerAuthorDns(), aemAuthorPort);
    }

    /**
     * Helper method for determining if the Author ELB is in a healthy state
     * @return true if the Author ELB is in a healthy state, false if not
     * @throws IOException (normally if can't connect)
     * @throws ClientProtocolException if there's an error in the HTTP protocol
     * @throws GeneralSecurityException for any SSL related issue
     */
    public boolean isAuthorElbHealthy() throws ClientProtocolException, IOException, GeneralSecurityException {
        String url = getAemUrlForAuthorElb() + AEM_HEALTH_CHECK_URL_POSTFIX;

        return httpUtil.isHttpGetResponseOk(url);
    }
    
    /**
     * Helper method for determining if the given Publish instance is in a healthy state
     * @param instanceId the publish AWS instance id
     * @return true if the Publish instance is in a healthy state
     * @throws ClientProtocolException if there's an error in the HTTP protocol
     * @throws IOException (normally if can't connect)
     * @throws GeneralSecurityException for any SSL related issue
     */
    public boolean isPubishHealthy(String instanceId) throws ClientProtocolException, IOException, 
        GeneralSecurityException {
        String url = getAemUrlForPublish(instanceId) + AEM_HEALTH_CHECK_URL_POSTFIX;

        return httpUtil.isHttpGetResponseOk(url);
    }
    
    /**
     * Blocking method that continually checks to see if a given Publish instance is in a healthy state.
     * Will stop blocking once the Publish instance is deemed to be in a healthy state, or will
     * throw an exception if not
     * @param instanceId of the Publish instance
     * @throws InstanceNotInHealthyStateException thrown if reaches waiting period time out
     */
    @Retryable(maxAttempts=10, value=InstanceNotInHealthyStateException.class, backoff=@Backoff(delay=5000))
    public void waitForPublishToBeHealthy(String instanceId) throws InstanceNotInHealthyStateException {
        try {
            if(!isPubishHealthy(instanceId)) {
                throw new InstanceNotInHealthyStateException(instanceId);
            }
        } catch (Exception e) {
            throw new InstanceNotInHealthyStateException(instanceId, e);
        }
    }

    /**
     * Gets the Publish EC2 instance ID that is paired to the given Publish Dispatcher.
     * Uses a tag on the instance to find the pair
     * @param dispatcherInstanceId EC2 instance ID of Publish Dispatcher
     * @return Publish EC2 instance ID. If no pair found, then returns null
     */
    public String getPublishIdForPairedDispatcher(String dispatcherInstanceId) {
        List publishIds = awsHelperService.getInstanceIdsForAutoScalingGroup(
            envValues.getAutoScaleGroupNameForPublish());

        return publishIds.stream().filter(p -> dispatcherInstanceId.equals(
            awsHelperService.getTags(p).get(PAIR_INSTANCE_ID.getTagName()))).findFirst().orElse(null);
    }

    /**
     * Gets the Publish Dispatcher EC2 instance ID that is paired to the given Publish instance.
     * Uses a tag on the instance to find the pair
     * @param publishInstanceId the Publish EC2 instance ID
     * @return Publish Dispatcher EC2 instance ID. If no pair found, then returns null
     */
    public String getDispatcherIdForPairedPublish(String publishInstanceId) {
        List dispatcherIds = awsHelperService.getInstanceIdsForAutoScalingGroup(
            envValues.getAutoScaleGroupNameForPublishDispatcher());

        return dispatcherIds.stream().filter(d -> publishInstanceId.equals(
            awsHelperService.getTags(d).get(PAIR_INSTANCE_ID.getTagName()))).findFirst().orElse(null);
    }

    /**
     * Gets the desired capacity of the Publish auto scaling group
     * @return Desired capacity for Publish auto scaling group
     */
    public int getAutoScalingGroupDesiredCapacityForPublish() {
        return awsHelperService.getAutoScalingGroupDesiredCapacity(envValues.getAutoScaleGroupNameForPublish());
    }

    /**
     * Gets the desired capacity of the Publish Dispatcher auto scaling group
     * @return Desired capacity for Publish Dispatcher auto scaling group
     */
    public int getAutoScalingGroupDesiredCapacityForPublishDispatcher() {
        return awsHelperService.getAutoScalingGroupDesiredCapacity(envValues.getAutoScaleGroupNameForPublishDispatcher());
    }

    /**
     * Sets the desired capacity of the Publish auto scaling group.
     * NOTE: Changing the desired capacity will cause the auto scaling group to either add or remove instances
     * @param desiredCapacity the new desired capacity
     */
    public void setAutoScalingGroupDesiredCapacityForPublish(int desiredCapacity) {
        awsHelperService.setAutoScalingGroupDesiredCapacity(envValues.getAutoScaleGroupNameForPublish(), desiredCapacity);
    }

    /**
     * Finds an active Publish instance (excluding the given instance ID) suitable for taking a snapshot from
     * @param excludeInstanceId the publish instance ID to exclude
     * from the search (generally the new Publish instance that needs the snapshot)
     * @return Active publish instance ID to get snapshot from, null if can't be found
     */
    public String getPublishIdToSnapshotFrom(String excludeInstanceId) {

        List publishIds = awsHelperService.getInstanceIdsForAutoScalingGroup( envValues.getAutoScaleGroupNameForPublish());

        return publishIds.stream().filter(s -> !s.equals(excludeInstanceId))
                .filter(i -> awsHelperService.getTags(i).get(InstanceTags.SNAPSHOT_ID.getTagName()) != null)
                .sorted((o1, o2) -> {

                    Date launchTime1 = awsHelperService.getLaunchTime(o1);
                    Date launchTime2 = awsHelperService.getLaunchTime(o2);

                    final int launchTimeCompareTo = launchTime1.compareTo(launchTime2);

                    if (launchTimeCompareTo == 0) {
                        return o1.compareTo(o2);
                    }

                    return launchTimeCompareTo;
                })
                .findFirst().orElse(null);
    }
    
    /**
     * If no Publish instances have been set up via snapshot, then the first one will not require a snapshot 
     * (nothing to snapshot from). The method helps determine if it is the first publish instance to be set up 
     * after startup.
     * @return true if no instance son the Publish group have the SnapshotId tag, false otherwise
     */
    public boolean isFirstPublishInstance() {
        List publishIds = awsHelperService.getInstanceIdsForAutoScalingGroup(
            envValues.getAutoScaleGroupNameForPublish());
        
        //Check if any of the instances on the group have the SnapshotId tag, if not then it's the first
        return publishIds.stream().filter(i -> awsHelperService.getTags(i).get(
            InstanceTags.SNAPSHOT_ID.getTagName()) != null).findFirst().orElse(null) == null;
    }

    /**
     * Tags the given instance with a given snapshot ID
     * @param instanceId the EC2 instance to tag
     * @param snapshotId the snapshot ID to place in the tag
     */
    public void tagInstanceWithSnapshotId(String instanceId, String snapshotId) {
        Map tags = new HashMap();
        tags.put(SNAPSHOT_ID.getTagName(), snapshotId);
        awsHelperService.addTags(instanceId, tags);
    }

    /**
     * Tags the given Author Dispatcher instance with Author DNS host name
     * @param authorDispatcherInstanceId the EC2 Author Dispatcher instance ID
     */
    public void tagAuthorDispatcherWithAuthorHost(String authorDispatcherInstanceId) {
        Map authorTags = new HashMap();
        authorTags.put(AEM_AUTHOR_HOST.getTagName(), envValues.getElasticLoadBalancerAuthorDns());
        awsHelperService.addTags(authorDispatcherInstanceId, authorTags);
    }

    /**
     * Creates and tags a snapshot resource with select tags taken from the publish instance.
     * The tags to use are defined via properties (aws.snapshot.tags)
     * @param instanceId the publish EC2 instance ID from which the snapshot was taken
     * @param volumeId of where the snapshot will be stored
     * @return snapshot ID
     */
    public String createPublishSnapshot(String instanceId, String volumeId) {
        Map activePublishTags = awsHelperService.getTags(instanceId);

        Map tagsForSnapshot = activePublishTags.entrySet().stream()
            .filter(map -> tagsToApplyToSnapshot.contains(map.getKey()))
            .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
        
        tagsForSnapshot.put(SNAPSHOT_TYPE.getTagName(), "orchestration");
        tagsForSnapshot.put(NAME.getTagName(), "AEM publish Snapshot " + instanceId);
        
        String snapshotId = awsHelperService.createSnapshot(volumeId,
            "Orchestration AEM snapshot of publish instance " + instanceId + " and volume " + volumeId);
        
        awsHelperService.addTags(snapshotId, tagsForSnapshot);

        return snapshotId;
    }

    /**
     * Adds pair ID EC2 tags to both the Publish Dispatcher and Publish instance that point to each other.
     * @param publishId the EC2 Publish instance ID
     * @param dispatcherId the Publish Dispatcher EC2 instance ID
     */
    public void pairPublishWithDispatcher(String publishId, String dispatcherId) {
        Map publishTags = new HashMap();
        publishTags.put(AEM_PUBLISH_DISPATCHER_HOST.getTagName(), awsHelperService.getPrivateIp(dispatcherId));
        publishTags.put(PAIR_INSTANCE_ID.getTagName(), dispatcherId);
        awsHelperService.addTags(publishId, publishTags);

        Map dispatcherTags = new HashMap();
        dispatcherTags.put(AEM_PUBLISH_HOST.getTagName(), awsHelperService.getPrivateIp(publishId));
        dispatcherTags.put(PAIR_INSTANCE_ID.getTagName(), publishId);
        awsHelperService.addTags(dispatcherId, dispatcherTags);
    }

    /**
     * Looks at all Publish Dispatcher instances on the auto scaling group and retrieves the
     * first one missing a pair ID tag (unpaired).
     * @param instanceId the Publish instance ID
     * @return Publish Dispatcher instance ID tag
     * @throws NoPairFoundException if can't find unpaired Publish Dispatcher
     */
    @Retryable(maxAttempts=10, value=NoPairFoundException.class, backoff=@Backoff(delay=5000))
    public String findUnpairedPublishDispatcher(String instanceId) throws NoPairFoundException {
        String unpairedDispatcher = null;
        
        List dispatcherIds = awsHelperService.getInstancesForAutoScalingGroup(
            envValues.getAutoScaleGroupNameForPublishDispatcher());
        //Filter the list to get all possible eligible candidates
        dispatcherIds = dispatcherIds.stream().filter(d -> 
            isViablePair(instanceId, d.getInstanceId())).collect(Collectors.toList());
        
        if(dispatcherIds.size() > 1) {
            String publishAZ = awsHelperService.getAvailabilityZone(instanceId);
            
            //If there are many candidates, then pick the one with the same AZ or else use first
            unpairedDispatcher = (dispatcherIds.stream().filter(i -> i.getAvailabilityZone().equalsIgnoreCase(publishAZ))
                .findFirst().orElse(dispatcherIds.get(0))).getInstanceId();
        } else if (dispatcherIds.size() == 1) {
            unpairedDispatcher = dispatcherIds.get(0).getInstanceId();
        } else {
            throw new NoPairFoundException(instanceId);
        }
        
        return unpairedDispatcher;
    }
    
    private boolean isViablePair(String instanceId, String dispatcherInstanceId) {
        Map tags = awsHelperService.getTags(dispatcherInstanceId);
        return !tags.containsKey(PAIR_INSTANCE_ID.getTagName()) || //Either it's missing a pairing tag
            tags.get(PAIR_INSTANCE_ID.getTagName()).equals(instanceId); //Or it's already paired to the instance
    }
    
    /**
     * Creates a CloudWatch content health alarm for a given publish instance
     * @param instanceId of the publish instance
     */
    public void createContentHealthAlarmForPublisher(String instanceId) {
        awsHelperService.createContentHealthCheckAlarm(
            getContentHealthCheckAlarmName(instanceId), 
            "Content Health Alarm for Publish Instance " + instanceId,
            instanceId,
            awsPublishDispatcherStackName,
            envValues.getTopicArn());
    }
    
    /**
     * Deletes a CloudWatch content health alarm for a given publish instance ID
     * @param instanceId of the publish instance
     */
    public void deleteContentHealthAlarmForPublisher(String instanceId) {
        awsHelperService.deleteAlarm(getContentHealthCheckAlarmName(instanceId));
    }
    
    
    private String getContentHealthCheckAlarmName(String instanceId) {
        return "contentHealthCheck-" + instanceId;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy