diff --git a/.gitignore b/.gitignore index 4d6d1b1..3024a64 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ *.dmg *.gz *.iso -*.jar *.rar *.tar *.zip diff --git a/README.md b/README.md index dd2f2d5..d7aa237 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ A Selenium Grid plugin for integrating your project with AWS (Amazon Web Service #### Details New nodes will be started as needed depending on the browser requested: -* Chrome - New c3.large instances will be started capable of running 6 threads (tests) per instance. If your test run requests 30 threads, this will result in 5 nodes being started up, where as 31 are requested, this will result in 6 nodes started up. Note that these can also run firefox tests as well -* Firefox - New t2.micro instances will be started capable of running 1 thread (test) per instance. Selenium window focus issues were the reason behind running 1 thread per virtual machine. These instances can run chrome tests as well. +* Chrome - New c4.large instances will be started capable of running 5 threads (tests) per instance. If your test run requests 30 threads, this will result in 6 nodes being started up, where as 31 are requested, this will result in 7 nodes started up. Note that these can also run firefox tests as well +* Firefox - New t2.small instances will be started capable of running 1 thread (test) per instance. Selenium window focus issues were the reason behind running 1 thread per virtual machine. These instances can run chrome tests as well. * Internet Explorer - IE is currently not supported. There are plans to add IE support in an upcoming release. AWS bills by rounding up to the next hour, so if you leave a machine on for 5 minutes, you get billed for the full hour (60 minutes). Based on this logic, SeleniumGridScaler's automatic termination logic will terminate nodes at the end of the current billing cycle, taking into account current test load. So if there is enough test load, say 12 tests running and only 12 threads are online (2 chrome instances each capable of running 6 for a total of 12), all of the nodes will stay on. If the current time crosses into the next billing cycle (e.g. they're on for 1 hour and 5 minutes), SeleniumGridScaler will not attempt to terminate them until the end of that next billing cycle (will attempt to terminate at 1 hour and 55 minutes instead of prematurely terminating paid for resources). ## Requirements -A Java 7 or later runtime is required in order to run this application +A Java 8 or later runtime is required in order to run this application ## Installation/Startup Download the automation-grid jar file. To start up the hub process, start the jar with the java jar command like so. Note, you must register the Scaler servlet (AutomationTestRunServlet) with GridLauncher. @@ -58,6 +58,7 @@ In your test run, you must send a GET HTTP request to the servlet, with the requ * browser - This is the browser you want to run in. Currently supported values, are chrome, firefox, and internetexplorer * threadCount - Number of threads you want to run for your tests * uuid - Unique test run identifier +* os - (Optional) This represents Selenium's Platform that you want to run in (can be Platform.ANY as well). Linux/Unix is currently the only supported platform. If you don't specify a platform, the logic will default to Platform.ANY and linux nodes will be started. If you wanted to do a test run with 10 threads in chrome with a test run UUID of 'testRun1', the HTTP request would look something like this @@ -137,6 +138,8 @@ Possible values to override/implement: Please see AWS documentation for more information on how to use/setup these things * tags (optional) - If you want to add any [tags](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html) to your instances as they're started, prefix your comma separated key/value pairs with 'tag' (e.g. 'tagDepartment=Department,QA' would add a tag with a key of 'Department' and a value of 'QA') +## Current Issues +Currently Windows is not supported. There was an AMI I built out but forgot to make public before changing jobs. So, if you want to run in Windows, you'll basically need to mimic the startup/configuration logic done in ~/grid/grid_start_node.sh on the linux AMI included in aws.properties.default ## Contributing diff --git a/automation-grid.jar b/automation-grid.jar new file mode 100644 index 0000000..e954baa Binary files /dev/null and b/automation-grid.jar differ diff --git a/pom.xml b/pom.xml index e1fcba2..6f17694 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ Matthew Hardin mhardin.github@gmail.com - RetailMeNot, inc + Netflix https://github.com/mhardin @@ -26,7 +26,25 @@ org.seleniumhq.selenium selenium-server - 2.42.2 + 2.52.0 + + + commons-codec + commons-codec + + + commons-logging + commons-logging + + + org.seleniumhq.selenium + selenium-htmlunit-driver + + + org.apache.commons + commons-lang3 + + com.amazonaws @@ -43,12 +61,12 @@ org.apache.commons commons-lang3 - 3.1 + 3.4 commons-codec commons-codec - 1.4 + 1.3 junit @@ -60,6 +78,12 @@ ch.qos.logback logback-classic 1.0.9 + + + org.slf4j + slf4j-api + + ch.qos.logback @@ -77,6 +101,11 @@ 1.4.1 test + + org.apache.commons + commons-collections4 + 4.0 + @@ -124,12 +153,16 @@ org.apache.maven.plugins maven-surefire-plugin 2.12 + + perthread + 1 + org.codehaus.mojo cobertura-maven-plugin - 2.6 + 2.7 @@ -174,8 +207,8 @@ 2.4 true - 1.7 - 1.7 + 1.8 + 1.8 @@ -192,6 +225,25 @@ + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4 + + + enforce + + + + + + + enforce + + + + org.apache.maven.plugins maven-javadoc-plugin @@ -202,6 +254,9 @@ jar + + -Xdoclint:none + @@ -213,15 +268,15 @@ org.codehaus.mojo cobertura-maven-plugin - 2.5.1 + 2.7 - scm:git:git@github.com:RetailMeNot/SeleniumGridScaler.git - scm:git:git@github.com:RetailMeNot/SeleniumGridScaler.git - scm:git:git@github.com:RetailMeNot/SeleniumGridScaler.git + scm:git:git@github.com:mhardin/SeleniumGridScaler.git + scm:git:git@github.com:mhardin/SeleniumGridScaler.git + scm:git:git@github.com:mhardin/SeleniumGridScaler.git 0.9 diff --git a/src/main/java/com/rmn/qa/AutomationCapabilityMatcher.java b/src/main/java/com/rmn/qa/AutomationCapabilityMatcher.java index 8d3473f..7e678dc 100644 --- a/src/main/java/com/rmn/qa/AutomationCapabilityMatcher.java +++ b/src/main/java/com/rmn/qa/AutomationCapabilityMatcher.java @@ -11,15 +11,16 @@ */ package com.rmn.qa; -import com.google.common.annotations.VisibleForTesting; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import org.apache.commons.lang3.StringUtils; import org.openqa.grid.internal.utils.DefaultCapabilityMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import com.google.common.annotations.VisibleForTesting; /** * Custom CapabilityMatcher which will not match a node that is marked as Expired/Terminated, which will happen @@ -73,7 +74,7 @@ public boolean matches(Map nodeCapability,Map re // If the run that spun up these hubs is still happening, just perform the default matching behavior // as that run is the one that requested these nodes. AutomationDynamicNode node = context.getNode(instanceId); - if(node != null && (node.getStatus() == AutomationDynamicNode.STATUS.EXPIRED || node.getStatus() == AutomationDynamicNode.STATUS.TERMINATED) ) { + if(node != null && (node.getStatus() != AutomationDynamicNode.STATUS.RUNNING) ) { log.debug(String.format("Node [%s] will not be used to match a request as it is expired/terminated",instanceId)); // If the run that spun these hubs up is not in progress AND this node has been flagged to shutdown, // do not match this node up to fulfill a test request diff --git a/src/main/java/com/rmn/qa/AutomationConstants.java b/src/main/java/com/rmn/qa/AutomationConstants.java index 7929751..4338bfa 100644 --- a/src/main/java/com/rmn/qa/AutomationConstants.java +++ b/src/main/java/com/rmn/qa/AutomationConstants.java @@ -34,6 +34,7 @@ public interface AutomationConstants { String EXTRA_CAPABILITIES_PROPERTY_NAME="extraCapabilities"; String AWS_ACCESS_KEY="awsAccessKey"; String AWS_PRIVATE_KEY="awsSecretKey"; + String AWS_TOKEN="awsToken"; String AWS_DEFAULT_RESOURCE_NAME= "aws.properties.default"; String REAPER_THREAD_CONFIG = "useReaperThread"; } diff --git a/src/main/java/com/rmn/qa/AutomationContext.java b/src/main/java/com/rmn/qa/AutomationContext.java index b9f2bac..99705eb 100644 --- a/src/main/java/com/rmn/qa/AutomationContext.java +++ b/src/main/java/com/rmn/qa/AutomationContext.java @@ -37,7 +37,7 @@ public static AutomationRunContext getContext() { /** * Clears out the previous context. Used for unit testing */ - public static void refreshContext() { + public static synchronized void refreshContext() { AutomationContext.context = new AutomationRunContext(); } diff --git a/src/main/java/com/rmn/qa/AutomationDynamicNode.java b/src/main/java/com/rmn/qa/AutomationDynamicNode.java index 1928eff..de52bf4 100644 --- a/src/main/java/com/rmn/qa/AutomationDynamicNode.java +++ b/src/main/java/com/rmn/qa/AutomationDynamicNode.java @@ -14,6 +14,8 @@ import java.util.Calendar; import java.util.Date; +import org.openqa.selenium.Platform; + /** * Represents a dynamically started node that is used to run tests */ @@ -33,7 +35,8 @@ public enum STATUS { RUNNING,EXPIRED,TERMINATED }; private static final int NODE_INTERVAL_LIFETIME = 55; // 55 minutes // TODO: Refactor this to be AutomationRunRequest - private final String uuid,instanceId,browser,os; + private final String uuid, instanceId, browser, ipAddress, instanceType; + private final Platform platform; private Date startDate,endDate; private final int nodeCapacity; private STATUS status; @@ -43,18 +46,28 @@ public enum STATUS { RUNNING,EXPIRED,TERMINATED }; * @param uuid UUID of the test run that created this node * @param instanceId Instance ID of the instance this node represents * @param browser Requested browser of the test run that created this node - * @param os Requested OS of the test run that created this node + * @param platform Requested OS of the test run that created this node * @param startDate Date that this node was started * @param nodeCapacity Maximum test capacity that this node can run */ - public AutomationDynamicNode(String uuid,String instanceId,String browser, String os,Date startDate,int nodeCapacity){ + public AutomationDynamicNode(String uuid,String instanceId,String browser, Platform platform, Date startDate, int nodeCapacity){ + this(uuid, instanceId, browser, platform, null, startDate, nodeCapacity); + } + + public AutomationDynamicNode(String uuid,String instanceId,String browser, Platform platform, String ipAddress, Date startDate, int nodeCapacity){ + this(uuid, instanceId, browser, platform, ipAddress, startDate, nodeCapacity, null); + } + + public AutomationDynamicNode(String uuid,String instanceId,String browser, Platform platform, String ipAddress, Date startDate, int nodeCapacity, String instanceType){ this.uuid = uuid; this.instanceId = instanceId; this.browser = browser; - this.os = os; + this.platform = platform; + this.ipAddress = ipAddress; this.startDate = startDate; this.endDate = computeEndDate(startDate); this.nodeCapacity = nodeCapacity; + this.instanceType = instanceType; this.status = STATUS.RUNNING; } @@ -106,11 +119,19 @@ public String getBrowser() { } /** - * Returns the OS of this node + * Returns the Platform of this node * @return */ - public String getOs() { - return os; + public Platform getPlatform() { + return platform; + } + + /** + * Returns the private IP address of this node + * @return + */ + public String getIpAddress() { + return ipAddress; } /** @@ -156,6 +177,14 @@ public int getNodeCapacity() { return nodeCapacity; } + /** + * Returns the instance type of this node + * @return + */ + public String getInstanceType() { + return instanceType; + } + /** * Returns the current status of this node. * @return @@ -199,6 +228,11 @@ public int hashCode() { @Override public String toString() { - return String.format("Node - UUID: %s Instance ID: %s Created Date: %s",uuid,instanceId, startDate); + return "AutomationDynamicNode{" + + "uuid='" + uuid + '\'' + + ", instanceId='" + instanceId + '\'' + + ", startDate=" + startDate + + ", ipAddress='" + ipAddress + '\'' + + '}'; } } diff --git a/src/main/java/com/rmn/qa/AutomationRunContext.java b/src/main/java/com/rmn/qa/AutomationRunContext.java index f45cf6e..a54c448 100644 --- a/src/main/java/com/rmn/qa/AutomationRunContext.java +++ b/src/main/java/com/rmn/qa/AutomationRunContext.java @@ -22,12 +22,12 @@ import org.openqa.grid.internal.RemoteProxy; import org.openqa.grid.internal.TestSession; import org.openqa.grid.internal.TestSlot; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; +import com.rmn.qa.aws.VmManager; /** * Context object used to keep track of registered runs and dynamic nodes. @@ -38,11 +38,13 @@ public final class AutomationRunContext { private static final Logger log = LoggerFactory.getLogger(AutomationRunContext.class); + public static final int PENDING_NODE_EXPIRATION_TIME_IN_MINUTES = 20; private static final int CLEANUP_LIFE_LENGTH_IN_SECONDS = 90; // 1.5 minutes private Map requests = Maps.newConcurrentMap(); private Map nodes = Maps.newConcurrentMap(); + private Map pendingStartupNodes = Maps.newConcurrentMap(); // Nodes that are currently starting up and have not registered yet - private int totalNodeCount; + private volatile int totalNodeCount; @VisibleForTesting AutomationRunContext() { } @@ -285,7 +287,7 @@ private boolean isRunOld(final AutomationRunRequest runRequest) { } else { return System.currentTimeMillis() > runRequest.getCreatedDate().getTime() + (AutomationRunContext.CLEANUP_LIFE_LENGTH_IN_SECONDS - * 1000); + * 1000); } } @@ -307,6 +309,71 @@ public void setTotalNodeCount(final int totalNodeCount) { this.totalNodeCount = totalNodeCount; } + /** + * Adds the specified node to the pending node collection + * @param node + */ + public void addPendingNode(AutomationDynamicNode node) { + pendingStartupNodes.put(node.getInstanceId(), node); + } + + /** + * Removes the specified node from the pending node collection + * @param amiId + */ + public void removePendingNode(String amiId) { + pendingStartupNodes.remove(amiId); + } + + /** + * Checks if the specified node exists in the pending node collection + * @param amiId + * @return True if the node exists, false otherwise + */ + public boolean pendingNodeExists(String amiId) { + return pendingStartupNodes.containsKey(amiId); + } + + /** + * Removes nodes from the pending collection if they haven't come online after 20 minutes + * @param vmManager + */ + public void removeExpiredPendingNodes(VmManager vmManager) { + Iterator> iterator = getPendingStartupNodes().entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + AutomationDynamicNode node = entry.getValue(); + if ((System.currentTimeMillis() - node.getStartDate().getTime()) > PENDING_NODE_EXPIRATION_TIME_IN_MINUTES * 60000) { // 20 minutes + log.error(String.format("Node %s was pending longer than 20 minutes. Removing from pending set.", node)); + try { + if (!vmManager.terminateInstance(node.getInstanceId())) { + log.warn(String.format("Error terminating pending node %s that never came online", node)); + } + node.updateStatus(AutomationDynamicNode.STATUS.TERMINATED); + } catch (Exception e) { + log.warn(String.format("Exception terminating pending node %s that never came online", node), e); + } + iterator.remove(); + } + } + } + + /** + * Returns the pending nodes collection + * @return + */ + private Map getPendingStartupNodes() { + return pendingStartupNodes; + } + + /** + * Returns true if there are no nodes that are pending startup + * @return + */ + public boolean noPendingNodesExist() { + return pendingStartupNodes.isEmpty(); + } + /** * Returns the number of additional threads this hub can support. This considers all test runs that are in progress * diff --git a/src/main/java/com/rmn/qa/AutomationRunRequest.java b/src/main/java/com/rmn/qa/AutomationRunRequest.java index 925b0c5..ec93566 100644 --- a/src/main/java/com/rmn/qa/AutomationRunRequest.java +++ b/src/main/java/com/rmn/qa/AutomationRunRequest.java @@ -12,14 +12,19 @@ package com.rmn.qa; -import com.google.common.annotations.VisibleForTesting; -import org.apache.commons.lang3.StringUtils; -import org.openqa.selenium.remote.CapabilityType; - import java.util.Calendar; import java.util.Date; import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.CapabilityType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; +import com.rmn.qa.task.AbstractAutomationCleanupTask; + /** * Represents a run request which will typically be sent in by a test run requesting resources. Used * to encapsulate various run parameters @@ -27,11 +32,13 @@ */ public final class AutomationRunRequest { + private static final Logger log = LoggerFactory.getLogger(AbstractAutomationCleanupTask.class); + private final String uuid; private final Integer threadCount; private final String browser; private final String browserVersion; - private final String os; + private final Platform platform; private final Date createdDate; // Require callers to have required variables through constructor below @@ -63,10 +70,10 @@ public AutomationRunRequest(String uuid, Integer threadCount,String browser) { * @param threadCount Number of threads for the requesting test run * @param browser Browser for the requesting test run * @param browserVersion Browser version for the requesting test run - * @param os OS for the requesting test run + * @param platform Platform for the requesting test run */ - public AutomationRunRequest(String runUuid, Integer threadCount, String browser, String browserVersion, String os) { - this(runUuid, threadCount, browser, browserVersion, os, new Date()); + public AutomationRunRequest(String runUuid, Integer threadCount, String browser, String browserVersion, Platform platform) { + this(runUuid, threadCount, browser, browserVersion, platform, new Date()); } /** @@ -75,16 +82,16 @@ public AutomationRunRequest(String runUuid, Integer threadCount, String browser, * @param threadCount Number of threads for the requesting test run * @param browser Browser for the requesting test run * @param browserVersion Browser version for the requesting test run - * @param os OS for the requesting test run + * @param platform Platform for the requesting test run * @param createdDate Date that the test run request was received */ @VisibleForTesting - public AutomationRunRequest(String runUuid, Integer threadCount, String browser,String browserVersion, String os, Date createdDate) { + public AutomationRunRequest(String runUuid, Integer threadCount, String browser,String browserVersion, Platform platform, Date createdDate) { this.uuid = runUuid; this.threadCount = threadCount; this.browser = browser; this.browserVersion = browserVersion; - this.os = os; + this.platform = platform; this.createdDate = createdDate; } @@ -99,8 +106,9 @@ public static AutomationRunRequest requestFromCapabilities(Map ca if(capabilities.containsKey(CapabilityType.VERSION)) { capabilityBrowserVersion = (String)capabilities.get(CapabilityType.VERSION); } - String capabilityOs = (String)capabilities.get(CapabilityType.PLATFORM); - return new AutomationRunRequest(null,null,capabilityBrowser,capabilityBrowserVersion,capabilityOs); + Object platform = capabilities.get(CapabilityType.PLATFORM); + Platform capabilityPlatform = AutomationUtils.getPlatformFromObject(platform); + return new AutomationRunRequest(null,null,capabilityBrowser,capabilityBrowserVersion,capabilityPlatform); } /** @@ -134,10 +142,10 @@ public String getBrowser() { public String getBrowserVersion() { return browserVersion; } /** - * Returns the OS (e.g. 'linux') + * Returns the Platform (e.g. 'Platform.WINDOWS') * @return */ - public String getOs() { return os; } + public Platform getPlatform() { return platform; } /** * Returns the created date for this run request * @return @@ -170,8 +178,8 @@ public String toString() { if(!StringUtils.isEmpty(browser)) { builder.append(" - Browser: ").append(browser); } - if(!StringUtils.isEmpty(os)) { - builder.append(" - OS: ").append(os); + if(platform != null) { + builder.append(" - Platform: ").append(platform); } return builder.toString(); } @@ -184,18 +192,16 @@ public String toString() { public boolean matchesCapabilities(Map capabilities) { String capabilityBrowser = (String)capabilities.get(CapabilityType.BROWSER_NAME); String capabilityBrowserVersion = (String)capabilities.get(CapabilityType.VERSION); - String capabilityOs = (String)capabilities.get(CapabilityType.PLATFORM); + Object capabilityPlatformObject = capabilities.get(CapabilityType.PLATFORM); + Platform capabilityPlatform = AutomationUtils.getPlatformFromObject(capabilityPlatformObject); if(!AutomationUtils.lowerCaseMatch(browser, capabilityBrowser)) { return false; } if(browserVersion != null && !AutomationUtils.lowerCaseMatch(browserVersion,capabilityBrowserVersion)) { return false; } - if(os != null && !AutomationUtils.lowerCaseMatch(os, capabilityOs)) { - // If either OS has 'ANY' for the platform, that means it should be a match regardless and we don't have to count this as a non-match - if(!AutomationUtils.lowerCaseMatch(os,"any") && !AutomationUtils.lowerCaseMatch(capabilityOs,"any")) { - return false; - } + if(platform != null && !AutomationUtils.firstPlatformCanBeFulfilledBySecondPlatform(platform, capabilityPlatform)) { + return false; } return true; } @@ -212,11 +218,8 @@ public boolean matchesCapabilities(AutomationRunRequest otherRequest) { if(browserVersion != null && browserVersion != otherRequest.getBrowserVersion()) { return false; } - if(os != null && !AutomationUtils.lowerCaseMatch(os, otherRequest.getOs())) { - // If either OS has 'ANY' for the platform, that means it should be a match regardless and we don't have to count this as a non-match - if(!AutomationUtils.lowerCaseMatch(os,"any") && !AutomationUtils.lowerCaseMatch(otherRequest.getOs(),"any")) { - return false; - } + if(platform != null && !AutomationUtils.firstPlatformCanBeFulfilledBySecondPlatform(platform, otherRequest.getPlatform())) { + return false; } return true; } @@ -231,7 +234,7 @@ public boolean equals(Object o) { if (!browser.equals(that.browser)) return false; if (browserVersion != null ? !browserVersion.equals(that.browserVersion) : that.browserVersion != null) return false; - if (os != null ? !os.equals(that.os) : that.os != null) return false; + if (platform != null ? !platform.equals(that.platform) : that.platform != null) return false; return true; } @@ -240,7 +243,7 @@ public boolean equals(Object o) { public int hashCode() { int result = browser.hashCode(); result = 31 * result + (browserVersion != null ? browserVersion.hashCode() : 0); - result = 31 * result + (os != null ? os.hashCode() : 0); + result = 31 * result + (platform != null ? platform.hashCode() : 0); return result; } } diff --git a/src/main/java/com/rmn/qa/AutomationUtils.java b/src/main/java/com/rmn/qa/AutomationUtils.java index 9012de1..2887c82 100644 --- a/src/main/java/com/rmn/qa/AutomationUtils.java +++ b/src/main/java/com/rmn/qa/AutomationUtils.java @@ -15,12 +15,20 @@ import java.util.Calendar; import java.util.Date; +import org.apache.commons.lang3.StringUtils; +import org.openqa.selenium.Platform; +import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.remote.BrowserType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Util methods * @author mhardin */ public final class AutomationUtils { + private static final Logger log = LoggerFactory.getLogger(AutomationUtils.class); /** * Modifies the specified date * @param dateToModify Date to modify @@ -44,7 +52,7 @@ public static Date modifyDate(Date dateToModify,int unitsToModify,int unitType) * @return */ public static boolean isCurrentTimeAfterDate(Date dateToCheck,int unitsToCheckWith,int unitType) { - Date targetDate = AutomationUtils.modifyDate(dateToCheck,unitsToCheckWith,unitType); + Date targetDate = AutomationUtils.modifyDate(dateToCheck, unitsToCheckWith, unitType); return new Date().after(targetDate); } @@ -59,4 +67,114 @@ public static boolean lowerCaseMatch(String string1, String string2) { return string2.equals(string1.toLowerCase().replace(" ", "")); } + /** + * Returns the Selenium platform object from an unknown object, which could be a string or an actual object. + * If the platform can not be determined, null will be returned + * @param platformObj + * @return + */ + public static Platform getPlatformFromObject(Object platformObj) { + if (platformObj == null) { + return null; + } + Platform parsedPlatform = null; + if (platformObj instanceof Platform) { + parsedPlatform = ((Platform) platformObj); + } else if (platformObj instanceof String) { + String platformString = (String) platformObj; + if (StringUtils.isEmpty(platformString)) { + return null; + } + try { + parsedPlatform = Platform.fromString(platformString); + } catch (WebDriverException e) { + log.error("Error parsing out platform for string: " + platformObj, e); + } + } else { + // Do nothing and return null for unexpected type + log.warn("Platform was not an expected type: " + platformObj); + } + return parsedPlatform; + } + + /** + * Returns true if the requested browser and platform can be used within AMIs, and false otherwise + * @param browserPair + * @return + */ + public static boolean browserAndPlatformSupported(BrowserPlatformPair browserPair) { + // If no platform is defined or the user specifies linux, perform the browser check. + if(AutomationUtils.isPlatformUnix(browserPair.getPlatform())){ + return lowerCaseMatch(BrowserType.CHROME,browserPair.getBrowser()) + || lowerCaseMatch(BrowserType.FIREFOX,browserPair.getBrowser()); + } else if (browserPair.getPlatform() == Platform.ANY || AutomationUtils.isPlatformWindows(browserPair.getPlatform())) { + return lowerCaseMatch(BrowserType.CHROME, browserPair.getBrowser()) + || lowerCaseMatch(BrowserType.FIREFOX, browserPair.getBrowser()) + || lowerCaseMatch("internetexplorer", browserPair.getBrowser()); + } else { + return false; + } + } + + /** + * Returns true if the platform is from the Windows family + * @param platform + * @return + */ + public static boolean isPlatformWindows(Platform platform) { + return getUnderlyingFamily(platform) == Platform.WINDOWS; + } + + /** + * Returns true if the platform is from the Unix family + * @param platform + * @return + */ + public static boolean isPlatformUnix(Platform platform) { + return getUnderlyingFamily(platform) == Platform.UNIX; + } + + /** + * Returns true if the platforms match. If either platform is Platform.ANY, this will return true. + * @param platformOne + * @param platformTwo + * @return + */ + // Even though we're handling null in here, this shouldn't really happen unless someone has a mis-configured test request or + // manually connects a node to this hub with a mis-configured JSON file. Unfortunately, there is not a lot we can do to prevent + // this besides handling it here + public static boolean firstPlatformCanBeFulfilledBySecondPlatform(Platform platformOne, Platform platformTwo) { + // If either platform is ANY, this means this request should match + if (platformOne == Platform.ANY || platformTwo == Platform.ANY) { + return true; + } else if (platformOne != null && platformTwo == null) { + // If the first platform is requesting a specific platform, and the 2nd platform is null, + // return false for a match as we can't determine if they do in fact match + return false; + } else if (platformOne == null) { + return true; + } else { + // At the end of the day, we only support basic platforms (Windows or Linux), so group + // each platform into the family and compare + Platform platformOneFamily = getUnderlyingFamily(platformOne); + Platform platformTwoFamily = getUnderlyingFamily(platformTwo); + return platformOneFamily == platformTwoFamily; + } + } + + /** + * Returns the underlying 'family' of the specified platform + * @param platform + * @return + */ + public static Platform getUnderlyingFamily(Platform platform) { + if (platform == null) { + return null; + } + if (platform == Platform.UNIX || platform == Platform.WINDOWS || platform == Platform.MAC || platform == Platform.ANY) { + return platform; + } else { + return getUnderlyingFamily(platform.family()); + } + } } diff --git a/src/main/java/com/rmn/qa/BrowserPlatformPair.java b/src/main/java/com/rmn/qa/BrowserPlatformPair.java new file mode 100644 index 0000000..65b0a2a --- /dev/null +++ b/src/main/java/com/rmn/qa/BrowserPlatformPair.java @@ -0,0 +1,62 @@ +package com.rmn.qa; + +import org.openqa.selenium.Platform; + +/** + * Created by jchan on 5/16/16. + */ +public class BrowserPlatformPair { + + private String browser; + private Platform platform; + + public BrowserPlatformPair(String browser, Platform platform){ + this.browser = browser; + this.platform = platform; + } + public void setBrowser(String browser){ + this.browser = browser; + } + public void setPlatform(Platform platform){ + this.platform = platform; + } + public String getBrowser(){ + return this.browser; + } + public Platform getPlatform(){ + return this.platform; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BrowserPlatformPair that = (BrowserPlatformPair) o; + + if (browser != null ? !browser.equals(that.browser) : that.browser != null) { + return false; + } + return platform == that.platform; + + } + + @Override + public int hashCode() { + int result = browser != null ? browser.hashCode() : 0; + result = 31 * result + (platform != null ? platform.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "BrowserPlatformPair{" + + "browser='" + browser + '\'' + + ", platform=" + platform + + '}'; + } +} diff --git a/src/main/java/com/rmn/qa/aws/AwsTagReporter.java b/src/main/java/com/rmn/qa/aws/AwsTagReporter.java index ca126bb..056e26d 100644 --- a/src/main/java/com/rmn/qa/aws/AwsTagReporter.java +++ b/src/main/java/com/rmn/qa/aws/AwsTagReporter.java @@ -11,6 +11,17 @@ */ package com.rmn.qa.aws; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.AmazonServiceException; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.CreateTagsRequest; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; @@ -18,15 +29,6 @@ import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Tag; import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Properties; -import java.util.Set; public class AwsTagReporter extends Thread { @@ -67,7 +69,7 @@ public void run() { instancesFound = true; } } catch(Throwable t) { - log.error("Error finding instances. Sleeping."); + log.warn("Error finding instances. Sleeping for 500ms."); try { sleep(); } catch (InterruptedException e) { @@ -96,7 +98,7 @@ private void associateTags(Collection instances) { setTagsForInstance(instanceId); } log.info("Tags added!"); - } catch(IndexOutOfBoundsException | ClassCastException e) { + } catch(IndexOutOfBoundsException | ClassCastException | AmazonServiceException e) { log.error("Error adding tags. Please make sure your tag syntax is correct (refer to the readme)",e); } } diff --git a/src/main/java/com/rmn/qa/aws/AwsVmManager.java b/src/main/java/com/rmn/qa/aws/AwsVmManager.java index bea31de..9009d83 100644 --- a/src/main/java/com/rmn/qa/aws/AwsVmManager.java +++ b/src/main/java/com/rmn/qa/aws/AwsVmManager.java @@ -18,25 +18,27 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; - import java.text.DateFormat; import java.text.SimpleDateFormat; - -import java.util.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import java.util.TimeZone; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.codec.binary.Base64; - +import org.openqa.selenium.Platform; import org.openqa.selenium.remote.BrowserType; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.amazonaws.AmazonServiceException; - -import com.amazonaws.auth.BasicAWSCredentials; - +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.Instance; @@ -47,11 +49,10 @@ import com.amazonaws.services.ec2.model.RunInstancesResult; import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import com.amazonaws.services.ec2.model.TerminateInstancesResult; - import com.google.common.annotations.VisibleForTesting; - import com.rmn.qa.AutomationConstants; import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BrowserPlatformPair; import com.rmn.qa.NodesCouldNotBeStartedException; /** @@ -61,17 +62,21 @@ public class AwsVmManager implements VmManager { private static final Logger log = LoggerFactory.getLogger(AwsVmManager.class); public static final DateFormat NODE_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); - + public static final Platform DEFAULT_PLATFORM = Platform.UNIX; private AmazonEC2Client client; - BasicAWSCredentials credentials; + @VisibleForTesting AWSCredentials credentials; private Properties awsProperties; - public static final int CHROME_THREAD_COUNT = 6; - public static final int FIREFOX_IE_THREAD_COUNT = 1; - + + // How many chrome browser processes are allowed per EC2 instance. + public static int CHROME_THREAD_COUNT = 5; + + // How many firefox browser processes are allowed per EC2 instance. + public static int FIREFOX_IE_THREAD_COUNT = 1; + private String region; - static { + static { // Read and write dates from node config in UTC format NODE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); } @@ -94,6 +99,37 @@ public AwsVmManager() { log.info("Falling back to IAM roles for authorization since no credentials provided in system properties", e); client = new AmazonEC2Client(); } + AwsVmManager.setRegion(client, awsProperties, region); + + // Allow the user to override the default settings for browser + // processes per EC2 operating system instance. + String maxChromeThreads = awsProperties.getProperty("node_max_processes_chrome"); + if ((null != maxChromeThreads) && (0 != maxChromeThreads.length())) { + int newMaxChromeThreads = Integer.parseInt(maxChromeThreads); + // Sanity check the maximum chrome instances setting. Hard max of 128 sounds good. + if ((newMaxChromeThreads >= 1) && (newMaxChromeThreads <= 128)) { + log.info("Overriding factory default maximum chrome threads with: " + newMaxChromeThreads); + CHROME_THREAD_COUNT = newMaxChromeThreads; + } else { + log.error("Invalid property setting for 'node_max_processes_chrome', ignoring '" + maxChromeThreads + "', using factory default of " + CHROME_THREAD_COUNT); + } + } + + String maxFirefoxThreads = awsProperties.getProperty("node_max_processes_firefox"); + if ((null != maxFirefoxThreads) && (0 != maxFirefoxThreads.length())) { + int newMaxFirefoxThreads = Integer.parseInt(maxFirefoxThreads); + // Sanity check the maximum firefox instances setting. Hard max of 128 sounds good. + if ((newMaxFirefoxThreads >= 1) && (newMaxFirefoxThreads <= 128)) { + log.info("Overriding factory default maximum chrome threads with: " + newMaxFirefoxThreads); + FIREFOX_IE_THREAD_COUNT = newMaxFirefoxThreads; + } else { + log.error("Invalid property setting for 'node_max_processes_firefox', ignoring '" + maxFirefoxThreads + "', using factory default of " + FIREFOX_IE_THREAD_COUNT); + } + } + + } + + public static void setRegion(AmazonEC2Client client, Properties awsProperties, String region) { client.setEndpoint(awsProperties.getProperty(region + "_endpoint")); } @@ -142,14 +178,23 @@ Properties initAWSProperties() { return properties; } + @VisibleForTesting + AmazonEC2Client getClient() { + return client; + } + + protected Properties getAwsProperties() { + return awsProperties; + } + /** * Retrieves AWS {@link com.amazonaws.auth.BasicAWSCredentials credentials} from the configuration file. * * @return */ @VisibleForTesting - BasicAWSCredentials getCredentials() { - + AWSCredentials getCredentials() { + Properties awsProperties = getAwsProperties(); // Give the system property credentials precedence over ones found in the config file String accessKey = System.getProperty(AutomationConstants.AWS_ACCESS_KEY); if (accessKey == null) { @@ -171,7 +216,13 @@ BasicAWSCredentials getCredentials() { } } - return new BasicAWSCredentials(accessKey, privateKey); + // Token is not required, so do not throw an exception if it is not present + String token = System.getProperty(AutomationConstants.AWS_TOKEN); + if (token == null) { + token = awsProperties.getProperty(AutomationConstants.AWS_TOKEN); + } + + return new BasicSessionCredentials(accessKey, privateKey, token); } public List launchNodes(final String amiId, final String instanceType, final int numberToStart, @@ -186,6 +237,7 @@ public List launchNodes(final String amiId, final String instanceType, log.info("Setting image id: " + runRequest.getImageId()); log.info("Setting instance type: " + runRequest.getInstanceType()); + Properties awsProperties = getAwsProperties(); String subnetKey = awsProperties.getProperty(region + "_subnet_id"); if (subnetKey != null) { log.info("Setting subnet: " + subnetKey); @@ -232,20 +284,23 @@ public List launchNodes(final String amiId, final String instanceType, * {@inheritDoc} */ @Override - public List launchNodes(final String uuid, String os, final String browser, final String hubHostName, - final int nodeCount, final int maxSessions) throws NodesCouldNotBeStartedException { - - // Unspecified OS will default to Linux - if (null == os) { - if (AutomationUtils.lowerCaseMatch(browser, "internet explorer")) { - os = "windows"; - } else { - os = "linux"; - } + public List launchNodes(final String uuid, Platform platform, final String browser, final String hubHostName, + final int nodeCount, final int maxSessions) throws NodesCouldNotBeStartedException { + // If platform is null or ANY, go ahead and default to any + if (platform == null) { + platform = Platform.ANY; } - - String userData = getUserData(uuid, hubHostName, browser, os, maxSessions); - String amiId = awsProperties.getProperty(getAmiIdForOs(os, browser)); + BrowserPlatformPair browserPlatformPair = new BrowserPlatformPair(browser, platform); + if (!AutomationUtils.browserAndPlatformSupported(browserPlatformPair)) { + throw new RuntimeException("Unsupported browser/platform: " + browserPlatformPair); + } + // After we have verified the platform is supported, go ahead and set it to the default platform + if (platform == platform.ANY) { + platform = DEFAULT_PLATFORM; + } + String userData = getUserData(uuid, hubHostName, browser, platform, maxSessions); + Properties awsProperties = getAwsProperties(); + String amiId = awsProperties.getProperty(getAmiIdForOs(platform, browser)); String instanceType = awsProperties.getProperty("node_instance_type_" + browser); return this.launchNodes(amiId, instanceType, nodeCount, userData, false); } @@ -265,10 +320,11 @@ private RunInstancesResult getResults(final RunInstancesRequest request, int req throws NodesCouldNotBeStartedException { RunInstancesResult runInstancesResult; try { - if(client == null){ + AmazonEC2Client localClient = getClient(); + if(localClient == null){ throw new RuntimeException("The client is not initialized"); } - runInstancesResult = client.runInstances(request); + runInstancesResult = localClient.runInstances(request); } catch (AmazonServiceException e) { // If there is insufficient capacity in this subnet / availability zone, then we want to try other @@ -278,6 +334,7 @@ private RunInstancesResult getResults(final RunInstancesRequest request, int req log.error(String.format("Insufficient capacity in subnet [%s]: %s", request.getSubnetId(), e)); requestNumber = requestNumber + 1; + Properties awsProperties = getAwsProperties(); String fallBackSubnetId = awsProperties.getProperty(region + "_subnet_fallback_id_" + requestNumber); // Make sure and only try to recursively loop so as long as we have a valid fallback subnet id. Logic @@ -312,26 +369,28 @@ private RunInstancesResult getResults(final RunInstancesRequest request, int req */ @VisibleForTesting void associateTags(final String threadName, final Collection instances) { - Thread reportThread = new AwsTagReporter(threadName, client, instances, awsProperties); + Thread reportThread = new AwsTagReporter(threadName, getClient(), instances, getAwsProperties()); reportThread.start(); } /** * Gets the instance ID based on the OS that is chosen. * - * @param os OS for the requested test run + * @param platform OS for the requested test run * @param browser Browser for the requested test run * * @return */ - private String getAmiIdForOs(final String os, final String browser) { + private String getAmiIdForOs(Platform platform, final String browser) { String requestedProperty; - if (os.equals("windows") || browser.equals(BrowserType.IE)) { + if (AutomationUtils.isPlatformWindows(platform) || browser.equals(BrowserType.IE)) { + // We only want to run on Windows if the caller is specifically asking for it, + // or if they want to run in IE (only supported on Windows) requestedProperty = region + "_windows_node_ami"; - } else if (os.equals("linux")) { + } else if (AutomationUtils.isPlatformUnix(platform)) { requestedProperty = region + "_linux_node_ami"; } else { - throw new RuntimeException("Unsupported OS: " + os); + throw new RuntimeException("Unsupported OS: " + platform); } return requestedProperty; @@ -346,10 +405,22 @@ public boolean terminateInstance(final String instanceId) { TerminateInstancesRequest terminateRequest = new TerminateInstancesRequest(); terminateRequest.withInstanceIds(instanceId); - if(client == null){ + AmazonEC2Client localClient = getClient(); + if(localClient == null){ throw new RuntimeException("The client is not initialized"); } - TerminateInstancesResult result = client.terminateInstances(terminateRequest); + TerminateInstancesResult result; + try{ + result = localClient.terminateInstances(terminateRequest); + } catch(AmazonServiceException ase) { + // If the node was terminated outside of this plugin, handle the error appropriately + if (ase.getErrorCode().equals("InvalidInstanceID.NotFound")) { + log.error("Node not found when attempting to remove: " + instanceId); + return false; + } else { + throw ase; + } + } List stateChanges = result.getTerminatingInstances(); boolean terminatedInstance = false; for (InstanceStateChange stateChange : stateChanges) { @@ -371,12 +442,13 @@ public boolean terminateInstance(final String instanceId) { return false; } + log.info(String.format("Node [%s] successfully terminated", instanceId)); return true; } @Override public List describeInstances(final DescribeInstancesRequest describeInstancesRequest) { - return client.describeInstances(describeInstancesRequest).getReservations(); + return getClient().describeInstances(describeInstancesRequest).getReservations(); } /** @@ -385,23 +457,23 @@ public List describeInstances(final DescribeInstancesRequest descri * @param uuid UUID of the test run * @param hubHostName Resolvable host name of the hub the node will register with * @param browser Browser for the requested test run - * @param os OS for the requested test run + * @param platform OS for the requested test run * @param maxSessions Maximum simultaneous test sessions * * @return */ @VisibleForTesting - String getUserData(final String uuid, final String hubHostName, final String browser, final String os, + String getUserData(final String uuid, final String hubHostName, final String browser, final Platform platform, final int maxSessions) { - try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - ZipOutputStream zos = new ZipOutputStream(outputStream); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(outputStream); ) { // Pull the node config out so we can write it to the zip file ZipEntry nodeConfigZipEntry = new ZipEntry("nodeConfigTemplate.json"); zos.putNextEntry(nodeConfigZipEntry); - String nodeConfigContents = getNodeConfig(uuid, hubHostName, browser, os, maxSessions); + String nodeConfigContents = getNodeConfig(uuid, hubHostName, browser, platform, maxSessions); zos.write(nodeConfigContents.getBytes()); zos.closeEntry(); @@ -429,15 +501,15 @@ String getUserData(final String uuid, final String hubHostName, final String bro * @return */ @VisibleForTesting - String getNodeConfig(final String uuid, final String hostName, final String browser, final String os, - final int maxSessions) { + String getNodeConfig(final String uuid, final String hostName, final String browser, final Platform platform, + final int maxSessions) { String resourceName; - if (os.equals("windows")) { + if (AutomationUtils.isPlatformWindows(platform)) { resourceName = AutomationConstants.WINDOWS_PROPERTY_NAME; - } else if (os.equals("linux")) { + } else if (AutomationUtils.isPlatformUnix(platform)) { resourceName = AutomationConstants.LINUX_PROPERTY_NAME; } else { - throw new RuntimeException("Unexpected OS for prop config: " + os); + throw new RuntimeException("Unexpected OS for prop config: " + platform); } String nodeConfig = getFileContents(resourceName); @@ -448,7 +520,7 @@ String getNodeConfig(final String uuid, final String hostName, final String brow nodeConfig = nodeConfig.replaceAll("", String.valueOf(AwsVmManager.CHROME_THREAD_COUNT)); nodeConfig = nodeConfig.replaceAll("", uuid); nodeConfig = nodeConfig.replaceAll("", browser); - nodeConfig = nodeConfig.replaceAll("", os); + nodeConfig = nodeConfig.replaceAll("", platform.toString()); Date createdDate = Calendar.getInstance().getTime(); diff --git a/src/main/java/com/rmn/qa/aws/VmManager.java b/src/main/java/com/rmn/qa/aws/VmManager.java index 421e8e8..24b8ba3 100644 --- a/src/main/java/com/rmn/qa/aws/VmManager.java +++ b/src/main/java/com/rmn/qa/aws/VmManager.java @@ -11,19 +11,21 @@ */ package com.rmn.qa.aws; +import java.util.List; + +import org.openqa.selenium.Platform; + import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Reservation; import com.rmn.qa.NodesCouldNotBeStartedException; -import java.util.List; - public interface VmManager { /** * Launches the specified instances * @param uuid UUID of the requesting test run - * @param os OS of the requesting test run + * @param platform Platform of the requesting test run * @param browser Browser of the requesting test run * @param hubHostName Hub host name for the nodes to register with * @param nodeCount Number of nodes to be started @@ -31,7 +33,7 @@ public interface VmManager { * @return */ // TODO Refactor into AutomationRunRequest - List launchNodes(String uuid, String os, String browser, String hubHostName, int nodeCount, int maxSessions) throws NodesCouldNotBeStartedException; + List launchNodes(String uuid, Platform platform, String browser, String hubHostName, int nodeCount, int maxSessions) throws NodesCouldNotBeStartedException; /** * Terminates the specified instance diff --git a/src/main/java/com/rmn/qa/servlet/AutomationStatusServlet.java b/src/main/java/com/rmn/qa/servlet/AutomationStatusServlet.java index 61c5820..a8f85a9 100644 --- a/src/main/java/com/rmn/qa/servlet/AutomationStatusServlet.java +++ b/src/main/java/com/rmn/qa/servlet/AutomationStatusServlet.java @@ -11,19 +11,27 @@ */ package com.rmn.qa.servlet; -import com.google.common.io.ByteStreams; -import com.rmn.qa.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.openqa.grid.internal.Registry; import org.openqa.grid.web.servlet.RegistryBasedServlet; +import org.openqa.selenium.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; +import com.google.common.io.ByteStreams; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationRequestMatcher; +import com.rmn.qa.AutomationRunContext; +import com.rmn.qa.AutomationRunRequest; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.RequestMatcher; /** * Legacy API to pull free threads for a given browser @@ -60,13 +68,14 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t String browserRequested = request.getParameter("browser"); - // OS is optional - String os = request.getParameter("os"); if (browserRequested == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Parameter 'browser' must be passed in as a query string parameter"); return; } - AutomationRunRequest runRequest = new AutomationRunRequest(AutomationStatusServlet.class.getSimpleName(),null,browserRequested,null,os); + // OS is optional + String os = request.getParameter("os"); + Platform platformRequested = AutomationUtils.getPlatformFromObject(os); + AutomationRunRequest runRequest = new AutomationRunRequest(AutomationStatusServlet.class.getSimpleName(),null,browserRequested,null,platformRequested); log.info(String.format("Legacy server request received. Browser [%s]", browserRequested)); AutomationRunContext context = AutomationContext.getContext(); // If a run is already going on with this browser, return an error code @@ -78,7 +87,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t int availableNodes = requestMatcher.getNumFreeThreadsForParameters(getRegistry().getAllProxies(),runRequest); response.setStatus(HttpServletResponse.SC_OK); // Add the browser so we know the nodes are occupied - context.addRun(new AutomationRunRequest(browserRequested,availableNodes,browserRequested,null,os)); + context.addRun(new AutomationRunRequest(browserRequested,availableNodes,browserRequested,null,platformRequested)); try (InputStream in = new ByteArrayInputStream(String.valueOf(availableNodes).getBytes("UTF-8"))){ ByteStreams.copy(in, response.getOutputStream()); } finally { diff --git a/src/main/java/com/rmn/qa/servlet/AutomationTestRunServlet.java b/src/main/java/com/rmn/qa/servlet/AutomationTestRunServlet.java index 8bdc3a5..b9b7309 100644 --- a/src/main/java/com/rmn/qa/servlet/AutomationTestRunServlet.java +++ b/src/main/java/com/rmn/qa/servlet/AutomationTestRunServlet.java @@ -11,41 +11,52 @@ */ package com.rmn.qa.servlet; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.openqa.grid.internal.ProxySet; +import org.openqa.grid.internal.Registry; +import org.openqa.grid.selenium.GridLauncher; +import org.openqa.grid.web.servlet.RegistryBasedServlet; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.BrowserType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.amazonaws.services.ec2.model.Instance; +import com.google.common.collect.Lists; import com.rmn.qa.AutomationConstants; import com.rmn.qa.AutomationContext; import com.rmn.qa.AutomationDynamicNode; import com.rmn.qa.AutomationRequestMatcher; import com.rmn.qa.AutomationRunRequest; import com.rmn.qa.AutomationUtils; -import com.rmn.qa.RequestMatcher; -import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.BrowserPlatformPair; import com.rmn.qa.NodesCouldNotBeStartedException; +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.RequestMatcher; import com.rmn.qa.aws.AwsVmManager; import com.rmn.qa.aws.VmManager; import com.rmn.qa.task.AutomationHubCleanupTask; import com.rmn.qa.task.AutomationNodeCleanupTask; -import com.rmn.qa.task.AutomationNodeRegistryTask; +import com.rmn.qa.task.AutomationOrphanedNodeRegistryTask; +import com.rmn.qa.task.AutomationPendingNodeRegistryTask; import com.rmn.qa.task.AutomationReaperTask; import com.rmn.qa.task.AutomationRunCleanupTask; -import org.openqa.grid.internal.ProxySet; -import org.openqa.grid.internal.Registry; -import org.openqa.grid.selenium.GridLauncher; -import org.openqa.grid.web.servlet.RegistryBasedServlet; -import org.openqa.selenium.remote.BrowserType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Date; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import com.rmn.qa.task.AutomationScaleNodeTask; /** * Servlet used to register new {@link com.rmn.qa.AutomationRunRequest runs} as well as delete existing {@link com.rmn.qa.AutomationRunRequest runs}. @@ -56,14 +67,6 @@ public class AutomationTestRunServlet extends RegistryBasedServlet implements Re private static final long serialVersionUID = 8484071790930378855L; private static final Logger log = LoggerFactory.getLogger(AutomationTestRunServlet.class); - // Start times - private static final long START_DELAY_IN_SECONDS = 60L; // 60 start delay for run and node cleanup tasks - private static final long EXPIRED_POLLING_TIME_IN_SECONDS = 15L; // Look for nodes to clean up every 15 seconds - private static final long HUB_TERMINATE_START_DELAY_IN_MINUTES= 5L; // Delay 5 minutes to start trying to shutdown the hub - // Polling times - private static final long HUB_TERMINATION_POLLING_TIME_IN_MINUTES = 1L; // Look every minute to shutdown the hub - private static final long NODE_REGISTRATION_POLLING_TIME_IN_MINUTES = 15L; // Look every 15 minutes for new unregistered nodes - private static final long TEST_RUN_CLEANUP_POLLING_TIME_IN_SECONDS = 60L; // Look for runs to clean up every 60 seconds // We override these for unit testing private VmManager ec2; @@ -83,7 +86,7 @@ public AutomationTestRunServlet() { * @param ec2 EC2 implementation that you wish to use * @param requestMatcher RequestMatcher implementation you wish you use */ - public AutomationTestRunServlet(Registry registry, boolean initThreads, VmManager ec2,RequestMatcher requestMatcher) { + public AutomationTestRunServlet(Registry registry, boolean initThreads, VmManager ec2, RequestMatcher requestMatcher) { super(registry); setManageEc2(ec2); setRequestMatcher(requestMatcher); @@ -97,18 +100,24 @@ private void initCleanupThreads() { // Wrapper to lazily fetch the Registry object as this is not populated at instantiation time // Spin up a scheduled thread to poll for unused test runs and clean up them Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationRunCleanupTask(this), - AutomationTestRunServlet.START_DELAY_IN_SECONDS, AutomationTestRunServlet.TEST_RUN_CLEANUP_POLLING_TIME_IN_SECONDS, TimeUnit.SECONDS); + 60L, 60L, TimeUnit.SECONDS); // Spin up a scheduled thread to clean up and terminate nodes that were spun up Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationNodeCleanupTask(this,ec2,requestMatcher), - AutomationTestRunServlet.START_DELAY_IN_SECONDS,AutomationTestRunServlet.EXPIRED_POLLING_TIME_IN_SECONDS, TimeUnit.SECONDS); + 60L, 15L, TimeUnit.SECONDS); // Spin up a scheduled thread to register unregistered dynamic nodes (will happen if hub gets shut down) - Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationNodeRegistryTask(this), - AutomationTestRunServlet.HUB_TERMINATE_START_DELAY_IN_MINUTES,AutomationTestRunServlet.NODE_REGISTRATION_POLLING_TIME_IN_MINUTES, TimeUnit.MINUTES); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationOrphanedNodeRegistryTask(this), + 1L, 5L, TimeUnit.MINUTES); + // Spin up a scheduled thread to track nodes that are pending startup + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationPendingNodeRegistryTask(this, ec2), + 60L, 15L, TimeUnit.SECONDS); + // Spin up a scheduled thread to analyzed queued requests to scale up capacity + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationScaleNodeTask(this, ec2), + 60L, 15L, TimeUnit.SECONDS); String instanceId = System.getProperty(AutomationConstants.INSTANCE_ID); if(instanceId != null && instanceId.length() > 0) { log.info("Instance ID detected. Hub termination thread will be started."); Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationHubCleanupTask(this,ec2,instanceId), - AutomationTestRunServlet.HUB_TERMINATE_START_DELAY_IN_MINUTES,AutomationTestRunServlet.HUB_TERMINATION_POLLING_TIME_IN_MINUTES, TimeUnit.MINUTES); + 5L, 1L, TimeUnit.MINUTES); } else { log.info("Hub is not a dynamic hub -- termination logic will not be started"); } @@ -117,7 +126,7 @@ private void initCleanupThreads() { if(!"false".equalsIgnoreCase(runReaperThread)) { // Spin up a scheduled thread to terminate orphaned instances Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new AutomationReaperTask(this,ec2), - AutomationTestRunServlet.HUB_TERMINATE_START_DELAY_IN_MINUTES,AutomationTestRunServlet.NODE_REGISTRATION_POLLING_TIME_IN_MINUTES, TimeUnit.MINUTES); + 1L, 15L, TimeUnit.MINUTES); } else { log.info("Reaper thread not running due to config flag."); } @@ -161,6 +170,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) String osRequested = request.getParameter("os"); String threadCount = request.getParameter("threadCount"); String uuid = request.getParameter(AutomationConstants.UUID); + BrowserPlatformPair browserPlatformPairRequest; + Platform requestedPlatform; + // Return a 400 if any of the required parameters are not passed in // Check for uuid first as this is the most important variable if (uuid == null) { @@ -181,9 +193,24 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); return; } + if (StringUtils.isEmpty(osRequested)) { + requestedPlatform = Platform.ANY; + } else { + requestedPlatform = AutomationUtils.getPlatformFromObject(osRequested); + if (requestedPlatform == null) { + String msg = "Parameter 'os' does not have a valid Selenium Platform equivalent: " + osRequested; + log.error(msg); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); + return; + } + } + Integer threadCountRequested = Integer.valueOf(threadCount); - AutomationRunRequest runRequest = new AutomationRunRequest(uuid, threadCountRequested, browserRequested, browserVersion, osRequested); - log.info(String.format("Server request received. Browser [%s] - Requested Node Count [%s] - Request UUID [%s]", browserRequested, threadCountRequested, uuid)); + + AutomationRunRequest runRequest = new AutomationRunRequest(uuid, threadCountRequested, browserRequested, browserVersion, requestedPlatform); + browserPlatformPairRequest = new BrowserPlatformPair(browserRequested, requestedPlatform); + + log.info(String.format("Server request [%s] received.", runRequest)); boolean amisNeeded; int amiThreadsToStart=0; int currentlyAvailableNodes; @@ -205,12 +232,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) amiThreadsToStart = threadCountRequested - currentlyAvailableNodes; } // If the browser requested is not supported by AMIs, we need to not unnecessarily spin up AMIs - if(amisNeeded && !browserSupportedByAmis(browserRequested)) { - response.sendError(HttpServletResponse.SC_GONE,"Request cannot be fulfilled and browser is not supported by AMIs"); + if(amisNeeded && !AutomationUtils.browserAndPlatformSupported(browserPlatformPairRequest)) { + response.sendError(HttpServletResponse.SC_GONE,"Request cannot be fulfilled and browser and platform is not supported by AMIs"); return; } // Add the run to our context so we can track it - AutomationRunRequest newRunRequest = new AutomationRunRequest(uuid, threadCountRequested, browserRequested, browserVersion, osRequested); + AutomationRunRequest newRunRequest = new AutomationRunRequest(uuid, threadCountRequested, browserRequested, browserVersion, requestedPlatform); boolean addSuccessful = AutomationContext.getContext().addRun(newRunRequest); if(!addSuccessful) { log.warn(String.format("Test run already exists for the same UUID [%s]", uuid)); @@ -222,7 +249,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) // Start up AMIs as that will be required log.warn(String.format("Insufficient nodes to fulfill request. New AMIs will be queued up. Requested [%s] - Available [%s] - Request UUID [%s]", threadCountRequested, currentlyAvailableNodes, uuid)); try{ - startNodes(uuid,amiThreadsToStart,browserRequested,osRequested); + AutomationTestRunServlet.startNodes(ec2, uuid, amiThreadsToStart, browserRequested, requestedPlatform); } catch(NodesCouldNotBeStartedException e) { // Make sure and de-register the run if the AMI startup was not successful AutomationContext.getContext().deleteRun(uuid); @@ -241,12 +268,30 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } } + // Convenience method for deleting all/specific nodes when developing locally + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + log.warn("Call received to delete all instances"); + Map nodes = AutomationContext.getContext().getNodes(); + if (nodes != null && !CollectionUtils.isEmpty(nodes.keySet())) { + Iterator iterator = nodes.keySet().iterator(); + while (iterator.hasNext()) { + String instanceId = iterator.next(); + log.warn("Terminating instance: " + instanceId); + ec2.terminateInstance(instanceId); + iterator.remove(); + } + } else { + log.warn("No nodes to terminate."); + } + } + /** * Starts up AMIs * @param threadCountRequested * @return */ - private void startNodes(String uuid,int threadCountRequested, String browser, String os) throws NodesCouldNotBeStartedException { + public static List startNodes(VmManager ec2, String uuid,int threadCountRequested, String browser, Platform platform) throws NodesCouldNotBeStartedException { log.info(String.format("%d threads requested",threadCountRequested)); try{ String localhostname; @@ -279,32 +324,26 @@ private void startNodes(String uuid,int threadCountRequested, String browser, St machinesNeeded++; } log.info(String.format("%s nodes will be started for run [%s]",machinesNeeded,uuid)); - List instances = ec2.launchNodes(uuid, os, browser, localhostname, - machinesNeeded, numThreadsPerMachine); + List instances = ec2.launchNodes(uuid, platform, browser, localhostname, machinesNeeded, numThreadsPerMachine); log.info(String.format("%d instances started", instances.size())); // Reuse the start date since all the nodes were created within the same request Date startDate = new Date(); + List createdNodes = Lists.newArrayList(); for(Instance instance : instances) { + AutomationDynamicNode createdNode = new AutomationDynamicNode(uuid, instance.getInstanceId(), browser, platform, instance.getPrivateIpAddress(), startDate, numThreadsPerMachine, instance.getInstanceType()); + // Add the node as pending startup to our context so we can track it in AutomationPendingNodeRegistryTask + AutomationContext.getContext().addPendingNode(createdNode); log.info("Node instance id: " + instance.getInstanceId()); - AutomationContext.getContext().addNode( - new AutomationDynamicNode(uuid, instance.getInstanceId(), browser, os, startDate, - numThreadsPerMachine)); + AutomationContext.getContext().addNode(createdNode); + createdNodes.add(createdNode); } + return createdNodes; } catch(Exception e) { - log.error("Error trying to start nodes: " + e); + log.error("Error trying to start nodes: ",e); throw new NodesCouldNotBeStartedException("Error trying to start nodes",e); } } - /** - * Returns true if the requested browser can be used within AMIs, and false otherwise - * @param browser - * @return - */ - private boolean browserSupportedByAmis(String browser) { - return AutomationUtils.lowerCaseMatch(BrowserType.CHROME,browser) || AutomationUtils.lowerCaseMatch(BrowserType.FIREFOX,browser) || AutomationUtils.lowerCaseMatch("internetexplorer",browser); - } - @Override public Registry retrieveRegistry() { return getRegistry(); diff --git a/src/main/java/com/rmn/qa/servlet/StatusServlet.java b/src/main/java/com/rmn/qa/servlet/StatusServlet.java index 098eedb..cc17fb5 100644 --- a/src/main/java/com/rmn/qa/servlet/StatusServlet.java +++ b/src/main/java/com/rmn/qa/servlet/StatusServlet.java @@ -11,8 +11,20 @@ */ package com.rmn.qa.servlet; -import com.google.common.io.ByteStreams; -import com.rmn.qa.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; import org.openqa.grid.common.GridDocHelper; import org.openqa.grid.internal.Registry; import org.openqa.grid.internal.RemoteProxy; @@ -28,13 +40,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; +import com.google.common.io.ByteStreams; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRequestMatcher; +import com.rmn.qa.AutomationRunRequest; /** * Front end to monitor what is currently happening on the proxies. The display is defined by @@ -122,7 +133,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if(instanceId != null) { AutomationDynamicNode node = AutomationContext.getContext().getNode((String)instanceId); if(node != null) { - localBuilder.append("
EC2 dynamic node."); + localBuilder.append("
EC2 dynamic node " + node.getInstanceId()); + if (!StringUtils.isEmpty(node.getInstanceType())) { + localBuilder.append("
Instance Type " + node.getInstanceType()); + } localBuilder.append("
Start time " + node.getStartDate()); localBuilder.append("
Current shutdown time ").append(node.getEndDate()); localBuilder.append("
Requested test run ").append(node.getUuid()); @@ -258,7 +272,7 @@ private String getConfigInfo(boolean verbose) { } private String key(String key) { - return "" + key + " : "; + return "" + key + " : "; } private String prettyHtmlPrint(GridHubConfiguration config) { diff --git a/src/main/java/com/rmn/qa/task/AbstractAutomationCleanupTask.java b/src/main/java/com/rmn/qa/task/AbstractAutomationCleanupTask.java index 2bf69f5..1a57866 100644 --- a/src/main/java/com/rmn/qa/task/AbstractAutomationCleanupTask.java +++ b/src/main/java/com/rmn/qa/task/AbstractAutomationCleanupTask.java @@ -11,10 +11,11 @@ */ package com.rmn.qa.task; -import com.rmn.qa.RegistryRetriever; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.rmn.qa.RegistryRetriever; + /** * Base task class that has exception/running handling for other tasks to extend with their * own implementations diff --git a/src/main/java/com/rmn/qa/task/AutomationNodeCleanupTask.java b/src/main/java/com/rmn/qa/task/AutomationNodeCleanupTask.java index ab8e1e0..039d526 100644 --- a/src/main/java/com/rmn/qa/task/AutomationNodeCleanupTask.java +++ b/src/main/java/com/rmn/qa/task/AutomationNodeCleanupTask.java @@ -11,16 +11,36 @@ */ package com.rmn.qa.task; -import com.google.common.annotations.VisibleForTesting; -import com.rmn.qa.*; -import com.rmn.qa.aws.VmManager; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.openqa.grid.internal.ProxySet; import org.openqa.grid.internal.RemoteProxy; import org.openqa.grid.internal.TestSlot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import com.google.common.annotations.VisibleForTesting; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRunContext; +import com.rmn.qa.AutomationRunRequest; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.RequestMatcher; +import com.rmn.qa.aws.VmManager; /** * Cleanup task which moves {@link com.rmn.qa.AutomationDynamicNode nodes} into the correct status depending on their lifecycle. The purpose of this @@ -42,7 +62,7 @@ public class AutomationNodeCleanupTask extends AbstractAutomationCleanupTask { * @param ec2 EC2 implementation * @param requestMatcher Request matcher implementation */ - public AutomationNodeCleanupTask(RegistryRetriever registryRetriever,VmManager ec2,RequestMatcher requestMatcher) { + public AutomationNodeCleanupTask(RegistryRetriever registryRetriever, VmManager ec2, RequestMatcher requestMatcher) { super(registryRetriever); this.ec2 = ec2; this.requestMatcher = requestMatcher; @@ -62,6 +82,11 @@ protected ProxySet getProxySet() { return registryRetriever.retrieveRegistry().getAllProxies(); } + @VisibleForTesting + protected void removeProxy(RemoteProxy proxy) { + registryRetriever.retrieveRegistry().removeIfPresent(proxy); + } + // We're going to continuously iterate over registered nodes with the hub. If they're expired, we're going to mark them for removal. // If nodes marked for removal are used into the next billing cycle, then we're going to move their end date back again and put them back // into the running queue @@ -83,23 +108,26 @@ public void doWork() { log.info(String.format("Updating node %s to 'EXPIRED' status. Start date [%s] End date [%s]",instanceId,node.getStartDate(),node.getEndDate())); node.updateStatus(AutomationDynamicNode.STATUS.EXPIRED); } - } else if(nodeStatus == AutomationDynamicNode.STATUS.EXPIRED) { + } else if (nodeStatus == AutomationDynamicNode.STATUS.EXPIRED) { // See if we're in the next billing cycle (create + 55 + 6, which should equal 61 minutes and would safely be in the next billing cycle) - if(AutomationUtils.isCurrentTimeAfterDate(node.getEndDate(), 6,Calendar.MINUTE)) { + if (AutomationUtils.isCurrentTimeAfterDate(node.getEndDate(), 6, Calendar.MINUTE)) { node.incrementEndDateByOneHour(); - log.info(String.format("Node [%s] was still running after initial allotted time. Resetting status and increasing end date to %s.",instanceId, node.getEndDate())); + log.info(String.format("Node [%s] was still running after initial allotted time. Resetting status and increasing end date to %s.", instanceId, node.getEndDate())); node.updateStatus(AutomationDynamicNode.STATUS.RUNNING); - } else if(isNodeCurrentlyEmpty(instanceId)) { - log.info(String.format("Terminating node %s and updating status to 'TERMINATED'",instanceId)); + } else if (isNodeCurrentlyEmpty(instanceId)) { + log.info(String.format("Terminating node %s and updating status to 'TERMINATED'", instanceId)); // Delete node ec2.terminateInstance(instanceId); + // Also remove the node from Selenium's tracking set as there have been cases where the node sticks around + // and slows down the console as the node can on longer be pinged + removeFromProxy(getProxySet(), instanceId); node.updateStatus(AutomationDynamicNode.STATUS.TERMINATED); } - } else if(nodeStatus == AutomationDynamicNode.STATUS.TERMINATED) { + } else if (nodeStatus == AutomationDynamicNode.STATUS.TERMINATED) { // If the current time is more than 30 minutes after the node end date, we should remove it from being tracked - if(System.currentTimeMillis() > node.getEndDate().getTime() + (30 * 60 * 1000) ) { + if (System.currentTimeMillis() > node.getEndDate().getTime() + (30 * 60 * 1000)) { // Remove it, and this will remove from tracking since we're referencing the collection - log.info(String.format("Removing node [%s] from internal tracking set",instanceId)); + log.info(String.format("Removing node [%s] from internal tracking set", instanceId)); iterator.remove(); } } @@ -107,6 +135,28 @@ public void doWork() { } } + /** + * Removes the specified instance from the ProxySet, if it exists + * @param proxySet + * @param instanceId + */ + private void removeFromProxy(ProxySet proxySet, String instanceId) { + Iterator iterator = proxySet.iterator(); + while(iterator.hasNext()) { + RemoteProxy proxy = iterator.next(); + Map config = proxy.getConfig(); + if(config.containsKey(AutomationConstants.INSTANCE_ID)) { + String nodeInstanceId = (String)config.get(AutomationConstants.INSTANCE_ID); + if (instanceId.equals(nodeInstanceId)) { + log.info("Node found to remove from proxy set: " + instanceId); + removeProxy(proxy); + return; + } + } + } + log.warn("Node not found to remove from proxy set: " + instanceId); + } + /** * Returns true if the specified node can be safely shut down, false otherwise * @param node @@ -174,7 +224,7 @@ private boolean canNodeShutDown(AutomationDynamicNode node) { log.info(String.format("Tests are not in progress. Node: %s Request: %s Free Slots: %s Node Slots: %s",node.getInstanceId(),request,freeSlotsForBrowser,finalNum)); } } else { - log.info(String.format("Load suitable for Request [%s]. Free slots [%s] Node slots [%s]",request,freeSlotsForBrowser,finalNum)); + log.info(String.format("Load suitable for node shutdown. Request [%s]. Node [%s] Free slots [%s] Node slots [%s]",request, node, freeSlotsForBrowser,finalNum)); } } // If we iterated over every browser for the node and load was not heavy enough, we can safely shut this node down @@ -188,7 +238,8 @@ private boolean canNodeShutDown(AutomationDynamicNode node) { */ public boolean isNodeCurrentlyEmpty(String instanceToFind) { ProxySet proxySet = getProxySet(); - for(RemoteProxy proxy : proxySet){ + boolean nodeEmpty = true; + for (RemoteProxy proxy : proxySet) { List slots = proxy.getTestSlots(); Object instanceId = proxy.getConfig().get(AutomationConstants.INSTANCE_ID); // If the instance id's do not match, this means this is not the node we are looking for @@ -200,14 +251,64 @@ public boolean isNodeCurrentlyEmpty(String instanceToFind) { for (TestSlot testSlot : slots) { // If we find a running session, this means the node is occupied, so we should return false if(testSlot.getSession() != null) { - return false; + nodeEmpty = false; + break; } } - // If we reached this point, this means we found our target node AND it had no sessions, meaning the node was empty - return true; + if (!nodeEmpty) { + return checkNodeForHungSessions(instanceToFind); + } else { + // If we reached this point, this means we found our target node AND it had no sessions, meaning the node was empty + return true; + } } // If we didn't find a matching node, we're going to say the nodes is empty so we can terminate it log.warn("No matching node was found in the proxy set. Instance id: " + instanceToFind); return true; } + + /** + * Checks the instance in question to see if any browser slots are 'hung' that would otherwise block terminating the instance + * @param instanceToFind + * @return + */ + private boolean checkNodeForHungSessions(String instanceToFind) { + AutomationDynamicNode node = AutomationContext.getContext().getNode(instanceToFind); + // If the IP is null, there isn't anything we can do with the node and we have to treat it as not empty + if (node.getIpAddress() == null) { + return false; + } else { + String url = String.format("http://%s:5555/wd/hub/sessions", node.getIpAddress()); + log.info("Orphaned nodes URL: " + url); + try { + log.info("Attempting to retrieve in progress sessions before termination for node: " + node.getInstanceId()); + URL obj = new URL(url); + HttpURLConnection con = (HttpURLConnection) obj.openConnection(); + con.setConnectTimeout(10000); + con.setReadTimeout(10000); + + int responseCode = con.getResponseCode(); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer responseBuffer = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + responseBuffer.append(inputLine); + } + in.close(); + String response = responseBuffer.toString(); + if (response != null && !response.contains("capabilities")) { + log.info("Node had hung sessions but will be terminated anyways."); + return true; + } + } catch(SocketTimeoutException ste) { + log.warn("Timeout attempting to retrieve in progress sessions for node: " + node.getInstanceId(), ste); + } catch (Exception e) { + log.warn("Error retrieving sessions from node", e); + // We don't need an explicit return here as we can just reuse the one below + } + return false; + } + } } diff --git a/src/main/java/com/rmn/qa/task/AutomationNodeRegistryTask.java b/src/main/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTask.java similarity index 81% rename from src/main/java/com/rmn/qa/task/AutomationNodeRegistryTask.java rename to src/main/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTask.java index f653d19..8d73263 100644 --- a/src/main/java/com/rmn/qa/task/AutomationNodeRegistryTask.java +++ b/src/main/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTask.java @@ -11,34 +11,41 @@ */ package com.rmn.qa.task; -import com.google.common.annotations.VisibleForTesting; -import com.rmn.qa.*; -import com.rmn.qa.aws.AwsVmManager; +import java.text.ParseException; +import java.util.Date; +import java.util.Map; + import org.openqa.grid.internal.ProxySet; import org.openqa.grid.internal.RemoteProxy; +import org.openqa.selenium.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.ParseException; -import java.util.Date; -import java.util.Map; +import com.google.common.annotations.VisibleForTesting; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRunContext; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.aws.AwsVmManager; /** - * Registry task which registers unregistered dynamic {@link com.rmn.qa.AutomationDynamicNode nodes}. This can happen if the hub process restarts for whatever reason + * Registry task which registers orphaned dynamic {@link com.rmn.qa.AutomationDynamicNode nodes}. This can happen if the hub process restarts for whatever reason * and loses track of previously registered nodes * @author mhardin */ -public class AutomationNodeRegistryTask extends AbstractAutomationCleanupTask { +public class AutomationOrphanedNodeRegistryTask extends AbstractAutomationCleanupTask { - private static final Logger log = LoggerFactory.getLogger(AutomationNodeRegistryTask.class); + private static final Logger log = LoggerFactory.getLogger(AutomationOrphanedNodeRegistryTask.class); @VisibleForTesting - static final String NAME = "Node Registry Task"; + static final String NAME = "Orphaned Node Registry Task"; /** * Constructs a registry task with the specified context retrieval mechanism * @param registryRetriever Represents the retrieval mechanism you wish to use */ - public AutomationNodeRegistryTask(RegistryRetriever registryRetriever) { + public AutomationOrphanedNodeRegistryTask(RegistryRetriever registryRetriever) { super(registryRetriever); } @@ -52,7 +59,7 @@ protected ProxySet getProxySet() { @Override public String getDescription() { - return AutomationNodeRegistryTask.NAME; + return AutomationOrphanedNodeRegistryTask.NAME; } // We're going to continuously iterate over registered nodes with the hub. If they're expired, we're going to mark them for removal. @@ -60,7 +67,6 @@ public String getDescription() { // into the running queue @Override public void doWork() { - log.info("Looking for unregistered nodes"); ProxySet proxySet = getProxySet(); if(proxySet != null && proxySet.size() > 0) { for(RemoteProxy proxy : proxySet) { @@ -82,7 +88,8 @@ public void doWork() { int threadCount = (Integer)config.get(AutomationConstants.CONFIG_MAX_SESSION); String browser = (String)config.get(AutomationConstants.CONFIG_BROWSER); String os = (String)config.get(AutomationConstants.CONFIG_OS); - AutomationDynamicNode node = new AutomationDynamicNode(uuid,instanceId,browser,os,createdDate,threadCount); + Platform platform = AutomationUtils.getPlatformFromObject(os); + AutomationDynamicNode node = new AutomationDynamicNode(uuid, instanceId, browser, platform, createdDate, threadCount); log.info("Unregistered dynamic node found: " + node); context.addNode(node); } diff --git a/src/main/java/com/rmn/qa/task/AutomationPendingNodeRegistryTask.java b/src/main/java/com/rmn/qa/task/AutomationPendingNodeRegistryTask.java new file mode 100644 index 0000000..d9dcb03 --- /dev/null +++ b/src/main/java/com/rmn/qa/task/AutomationPendingNodeRegistryTask.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 RetailMeNot, 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, either version 3 of the License, or + * (at your option) any later version. + * 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. + */ +package com.rmn.qa.task; + +import java.util.Map; + +import org.openqa.grid.internal.ProxySet; +import org.openqa.grid.internal.RemoteProxy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRunContext; +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.aws.VmManager; + +/** + * Registry task which registers dynamic {@link AutomationDynamicNode nodes} as they come online + * + * @author mhardin + */ +public class AutomationPendingNodeRegistryTask extends AbstractAutomationCleanupTask { + + private static final Logger log = LoggerFactory.getLogger(AutomationPendingNodeRegistryTask.class); + @VisibleForTesting + static final String NAME = "Pending Node Registry Task"; + private VmManager vmManager; + + /** + * Constructs a registry task with the specified context retrieval mechanism + * + * @param registryRetriever Represents the retrieval mechanism you wish to use + */ + public AutomationPendingNodeRegistryTask(RegistryRetriever registryRetriever, VmManager vmManager) { + super(registryRetriever); + this.vmManager = vmManager; + } + + /** + * Returns the ProxySet to be used for cleanup purposes. + * + * @return + */ + protected ProxySet getProxySet() { + return registryRetriever.retrieveRegistry().getAllProxies(); + } + + @Override + public String getDescription() { + return AutomationPendingNodeRegistryTask.NAME; + } + + // We're going to continuously iterate over registered nodes with the hub. If they're expired, we're going to mark them for removal. + // If nodes marked for removal are used into the next billing cycle, then we're going to move their end date back again and put them back + // into the running queue + @Override + public void doWork() { + ProxySet proxySet = getProxySet(); + if (proxySet != null && !proxySet.isEmpty()) { + for (RemoteProxy proxy : proxySet) { + Map config = proxy.getConfig(); + // If the config has an instanceId in it, this means this node was dynamically started and we should + // track it if we are not already + if (config.containsKey(AutomationConstants.INSTANCE_ID)) { + String instanceId = (String) config.get(AutomationConstants.INSTANCE_ID); + AutomationRunContext context = AutomationContext.getContext(); + // If this node is already in our context, that means we are already tracking this node to terminate + if (context.pendingNodeExists(instanceId)) { + log.info(String.format("Pending node %s found in the running state. Removing from pending set", instanceId)); + context.removePendingNode(instanceId); + } + } + } + } + // Remove any pending nodes that haven't come online after a configured amount of time + AutomationContext.getContext().removeExpiredPendingNodes(vmManager); + } +} diff --git a/src/main/java/com/rmn/qa/task/AutomationReaperTask.java b/src/main/java/com/rmn/qa/task/AutomationReaperTask.java index 18c5b27..47c57a2 100644 --- a/src/main/java/com/rmn/qa/task/AutomationReaperTask.java +++ b/src/main/java/com/rmn/qa/task/AutomationReaperTask.java @@ -1,5 +1,12 @@ package com.rmn.qa.task; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.Filter; import com.amazonaws.services.ec2.model.Instance; @@ -9,12 +16,6 @@ import com.rmn.qa.AutomationUtils; import com.rmn.qa.RegistryRetriever; import com.rmn.qa.aws.VmManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Calendar; -import java.util.Date; -import java.util.List; public class AutomationReaperTask extends AbstractAutomationCleanupTask { @@ -42,10 +43,10 @@ public void doWork() { for(Reservation reservation : reservations) { for(Instance instance : reservation.getInstances()) { // Look for orphaned nodes - Date threshold = AutomationUtils.modifyDate(new Date(),-30, Calendar.MINUTE); + Date threshold = AutomationUtils.modifyDate(new Date(),-90, Calendar.MINUTE); String instanceId = instance.getInstanceId(); // If we found a node old enough AND we're not internally tracking it, this means this is an orphaned node and we should terminate it - if(threshold.after(instance.getLaunchTime()) && !AutomationContext.getContext().nodeExists(instanceId)) { + if(threshold.after(instance.getLaunchTime()) && !AutomationContext.getContext().nodeExists(instanceId) && instance.getState().getCode() != 48) { // 48 == terminated log.info("Terminating orphaned node: " + instanceId); ec2.terminateInstance(instanceId); } diff --git a/src/main/java/com/rmn/qa/task/AutomationScaleNodeTask.java b/src/main/java/com/rmn/qa/task/AutomationScaleNodeTask.java new file mode 100644 index 0000000..8359749 --- /dev/null +++ b/src/main/java/com/rmn/qa/task/AutomationScaleNodeTask.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2014 RetailMeNot, 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, either version 3 of the License, or + * (at your option) any later version. + * 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. + */ +package com.rmn.qa.task; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.openqa.grid.internal.ProxySet; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.CapabilityType; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BrowserPlatformPair; +import com.rmn.qa.NodesCouldNotBeStartedException; +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.aws.VmManager; +import com.rmn.qa.servlet.AutomationTestRunServlet; + +/** + * Registry task which registers unregistered dynamic {@link AutomationDynamicNode nodes}. This can happen if the hub process restarts for whatever reason + * and loses track of previously registered nodes + * @author mhardin + */ +public class AutomationScaleNodeTask extends AbstractAutomationCleanupTask { + + private static final Logger log = LoggerFactory.getLogger(AutomationScaleNodeTask.class); + private static final int QUEUED_REQUEST_THRESHOLD_IN_MS = 5000; + private Map queuedBrowsersPlatforms = Maps.newHashMap(); + private Map pendingStartupCapacity = Maps.newHashMap(); + private VmManager vmManager; + @VisibleForTesting + static final String NAME = "Automation Scale Node Task"; + + /** + * Constructs a registry task with the specified context retrieval mechanism + * @param registryRetriever Represents the retrieval mechanism you wish to use + */ + public AutomationScaleNodeTask(RegistryRetriever registryRetriever, VmManager vmManager) { + super(registryRetriever); + this.vmManager = vmManager; + } + + /** + * Returns the ProxySet to be used for cleanup purposes. + * @return + */ + protected ProxySet getProxySet() { + return registryRetriever.retrieveRegistry().getAllProxies(); + } + + @Override + public String getDescription() { + return AutomationScaleNodeTask.NAME; + } + + @VisibleForTesting + Iterable getDesiredCapabilities() { + return registryRetriever.retrieveRegistry().getDesiredCapabilities(); + } + + @VisibleForTesting + List startNodes(VmManager vmManager, int browsersToStart, String browser, Platform platform) throws NodesCouldNotBeStartedException { + return AutomationTestRunServlet.startNodes(vmManager, "AD-HOC", browsersToStart, browser, platform); + } + + /** + * Returns true if nodePendingDate is more than 5 seconds older than the current time + */ + @VisibleForTesting + boolean haveTestRequestsBeenQueuedForLongEnough(Date nowDate, Date nodePendingDate) { + return nowDate.getTime() - nodePendingDate.getTime() > QUEUED_REQUEST_THRESHOLD_IN_MS; + } + + /** + * Returns true if the platform is not specified, is all platforms, or is linux + * @param platformPair + * @return + */ + private boolean isEligibleToScale(BrowserPlatformPair platformPair) { + return AutomationUtils.browserAndPlatformSupported(platformPair); + } + + // We're going to continuously iterate over queued test requests with the hub. If there are no nodes pending startup, we're basically going to calculate load + // over time to see if we need to spin up new instances ad-hoc + @Override + public void doWork() { + log.warn("Doing node scale work"); + // Iterate over all queued requests and track browser/platform combinations that are eligible to scale capacity for + Iterator pendingCapabilities = getDesiredCapabilities().iterator(); + if (pendingCapabilities.hasNext()) { + log.info("Analyzing currently queued requests"); + while (pendingCapabilities.hasNext()) { + DesiredCapabilities capabilities = pendingCapabilities.next(); + String browser = (String) capabilities.getCapability(CapabilityType.BROWSER_NAME); + Object platformObject = capabilities.getCapability(CapabilityType.PLATFORM); + Platform platform = AutomationUtils.getPlatformFromObject(platformObject); + // If a valid platform wasn't able to be parsed from the queued test request, go ahead and default to Platform.ANY, + // as Platform is not required for this plugin + if (platform == null) { + // Default to ANY here and let AwsVmManager dictate what ANY translates to + platform = Platform.ANY; + } + // Group all platforms by their underlying family + platform = AutomationUtils.getUnderlyingFamily(platform); + BrowserPlatformPair desiredPair = new BrowserPlatformPair(browser, platform); + + // Don't attempt to calculate load for browsers & platform families we cannot start + if (!isEligibleToScale(desiredPair)) { + log.warn("Unsupported browser and platform pair, browser: " + browser + " platform family: " + platform.family()); + continue; + } + + // Handle requests for specific browser platforms. + queuedBrowsersPlatforms.computeIfAbsent(new BrowserPlatformPair(browser, platform), s -> new Date()); + } + } + // Now, iterate over eligible browser/platform combinations that we're tracking and attempt to scale up + Iterator queuedBrowsersIterator = queuedBrowsersPlatforms.keySet().iterator(); + while(queuedBrowsersIterator.hasNext()) { + BrowserPlatformPair originalBrowserPlatformRequest = queuedBrowsersIterator.next(); + Date currentTime = new Date(); + Date timeBrowserPlatformQueued = queuedBrowsersPlatforms.get(originalBrowserPlatformRequest); + if (haveTestRequestsBeenQueuedForLongEnough(currentTime, timeBrowserPlatformQueued)) { // If we've had pending queued requests for this browser for at least 5 seconds + pendingCapabilities = getDesiredCapabilities().iterator(); + if (pendingCapabilities.hasNext()) { + int browsersToStart = 0; + while (pendingCapabilities.hasNext()) { + DesiredCapabilities currentlyQueuedCapabilities = pendingCapabilities.next(); + String currentlyQueuedBrowser = (String) currentlyQueuedCapabilities.getCapability(CapabilityType.BROWSER_NAME); + Object platformObject = currentlyQueuedCapabilities.getCapability(CapabilityType.PLATFORM); + Platform currentlyQueuedPlatform = AutomationUtils.getPlatformFromObject(platformObject); + // If a valid platform wasn't able to be parsed from the queued test request, go ahead and default to Platform.ANY, + // as Platform is not required for this plugin + if (currentlyQueuedPlatform == null) { + currentlyQueuedPlatform = Platform.ANY; + } + // Group all platforms by their underlying family + currentlyQueuedPlatform = AutomationUtils.getUnderlyingFamily(currentlyQueuedPlatform); + if (originalBrowserPlatformRequest.equals(new BrowserPlatformPair(currentlyQueuedBrowser, currentlyQueuedPlatform))) { + browsersToStart++; + } + } + this.startNodesForBrowserPlatform(originalBrowserPlatformRequest, browsersToStart); + } + // Regardless of if we spun up browsers or not, clear this count out + queuedBrowsersIterator.remove(); + } + } + } + + /** + * Starts up the specified number of browsers for the specified browser/platform pair. Takes into account nodes that are pending startup + * @param browserPlatform + * @param browsersToStart + */ + private void startNodesForBrowserPlatform(BrowserPlatformPair browserPlatform, int browsersToStart) { + // If there are queued up browser requests, go ahead and subtract nodes pending startup from the count to account for the pending capacity + if (browsersToStart > 0 && pendingStartupCapacity.containsKey(browserPlatform)) { + ScaleCapacityContext pendingCapacityContext = pendingStartupCapacity.get(browserPlatform); + // Go ahead and clear out any nodes that have started up + pendingCapacityContext.clearPendingNodes(); + // This represents capacity that is pending startup and we need to subtract it from the total amount of nodes that we want to start + // so we do not get an excess of capacity + int pendingCapacity = pendingCapacityContext.getTotalCapacityCount(); + if (pendingCapacity > 0) { + log.warn(String.format("Subtracting %d capacity from queued load %s for browser/platform %s", pendingCapacity, browsersToStart, browserPlatform)); + } + browsersToStart = browsersToStart - pendingCapacity; + } + if (browsersToStart > 0) { + log.info(String.format("Spinning up %d threads for browser/platform %s based on current test load", browsersToStart, browserPlatform)); + try { + List createdNodes = this.startNodes(vmManager, browsersToStart, browserPlatform.getBrowser(), browserPlatform.getPlatform()); + // Grab the scale context object for this browser/platform pair + ScaleCapacityContext contextForBrowserPair = pendingStartupCapacity.computeIfAbsent(browserPlatform, browserPlatformPair -> new ScaleCapacityContext()); + // Add all the created nodes to the context object so we can compute load programmatically for pending browsers + contextForBrowserPair.addAll(createdNodes); + } catch (NodesCouldNotBeStartedException e) { + throw new RuntimeException("Error scaling up nodes", e); + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/rmn/qa/task/ScaleCapacityContext.java b/src/main/java/com/rmn/qa/task/ScaleCapacityContext.java new file mode 100644 index 0000000..da77468 --- /dev/null +++ b/src/main/java/com/rmn/qa/task/ScaleCapacityContext.java @@ -0,0 +1,47 @@ +package com.rmn.qa.task; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import com.beust.jcommander.internal.Lists; +import com.google.common.annotations.VisibleForTesting; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; + +/** + * Created by matthew on 6/23/16. + */ +public class ScaleCapacityContext { + + @VisibleForTesting + List nodesPendingStartup = Lists.newArrayList(); + + public int getTotalCapacityCount() { + return nodesPendingStartup.stream().mapToInt(AutomationDynamicNode::getNodeCapacity).sum(); + } + + public void addAll(Collection nodes) { + nodesPendingStartup.addAll(nodes); + } + + /** + * Clears out any nodes that are no longer in the 'pending' state + */ + public void clearPendingNodes() { + Iterator iterator = nodesPendingStartup.iterator(); + while(iterator.hasNext()) { + AutomationDynamicNode pendingNode = iterator.next(); + if (!AutomationContext.getContext().pendingNodeExists(pendingNode.getInstanceId())) { + iterator.remove(); + } + } + } + + @Override + public String toString() { + return "ScaleCapacityContext{" + + "nodesPendingStartup=" + nodesPendingStartup + + '}'; + } +} diff --git a/src/main/resources/aws.properties.default b/src/main/resources/aws.properties.default index 7cfe57c..c3167e9 100644 --- a/src/main/resources/aws.properties.default +++ b/src/main/resources/aws.properties.default @@ -15,10 +15,10 @@ sa-east-1_endpoint=https://ec2.sa-east-1.amazonaws.com # Node AMI Info us-east-1_linux_node_ami=ami-d216a3ba -node_instance_type_chrome=c3.large +node_instance_type_chrome=c4.large # Firefox and IE will only run in 1 browser per VM so we'll use micros for them -node_instance_type_firefox=t2.micro -node_instance_type_internetexplorer=t2.micro +node_instance_type_firefox=t2.small +node_instance_type_internetexplorer=t2.small us-east-1_windows_node_ami=ami-dcd869b4 diff --git a/src/main/resources/grid_start_node.sh b/src/main/resources/grid_start_node.sh new file mode 100755 index 0000000..73224ad --- /dev/null +++ b/src/main/resources/grid_start_node.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# ###################################################################################### +# Shell script to configure a Selenium Grid worker node and add it to the grid. +# This is executed by this command in the crontab, as seen with "cronatab -l": +# @reboot /home/ubuntu/grid/grid_start_node.sh >> /home/ubuntu/grid/process.log 2>&1 +# +# ###################################################################################### +PATH=/sbin:/usr/sbin:/bin:/usr/bin +cd /home/ubuntu/grid/ + +# Call into AWS and figure out what our EC2 instance ID is. +# Sometimes cron kicks off this script before the AWS/EC2 networking stack is awake. +# We need to wait, polling for when the instance ID details beome available. +echo "Grid Automation process started, looking up our EC2 Instance at `date`" +export EC2_INSTANCE_ID= +while [ -z "${EC2_INSTANCE_ID}" ] ; do + # Wait 1 seconds to let the network settle down on boot up and let AWS get all of + # the user-data zip file artifiacts in place. + sleep 1 + export EC2_INSTANCE_ID="`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`" +done + +echo "Retreived our EC2 Instance ID: $EC2_INSTANCE_ID at `date`" + +# Pull down the user data, which will be a zip file containing necessary information +# This is placed in the user-data fild for the instance by the getUserData() and launchNodes() methods. +export NODE_TEMPLATE="/home/ubuntu/grid/nodeConfigTemplate.json" +curl http://169.254.169.254/latest/user-data -o /home/ubuntu/grid/data.zip + +# Now, unzip the data downloaded from the userdata +unzip -o /home/ubuntu/grid/data.zip -d /home/ubuntu/grid/ + +# Replace the instance ID in the node config file +sed "s//$EC2_INSTANCE_ID/g" $NODE_TEMPLATE > /home/ubuntu/grid/nodeConfig.json + +# Finally, run the java process in a window so browsers can run +xvfb-run --auto-servernum --server-args='-screen 0, 1600x1200x24' \ + java -jar /home/ubuntu/grid/selenium-server-node.jar -role node \ + -nodeConfig /home/ubuntu/grid/nodeConfig.json \ + -Dwebdriver.chrome.driver="/home/ubuntu/grid/chromedriver" \ + -log /home/ubuntu/grid/grid.log & + diff --git a/src/main/resources/hub.static.json b/src/main/resources/hub.static.json index 9909122..851b05b 100644 --- a/src/main/resources/hub.static.json +++ b/src/main/resources/hub.static.json @@ -6,6 +6,7 @@ "throwOnCapabilityNotPresent": false, "nodePolling": 5000, "unregisterIfStillDownAfter": 5000, + "nodeStatusCheckTimeout": 3000, "cleanUpCycle": 5000, "timeout": 80000, "servlets": ["com.rmn.qa.servlet.AutomationTestRunServlet","com.rmn.qa.servlet.StatusServlet"], diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 23e2a1d..da2c983 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -55,7 +55,15 @@ - + + + + + + + + + @@ -87,6 +95,14 @@ + + + + + + + + diff --git a/src/main/resources/node.linux.json b/src/main/resources/node.linux.json index ee86d8a..0488bd6 100644 --- a/src/main/resources/node.linux.json +++ b/src/main/resources/node.linux.json @@ -12,7 +12,7 @@ "maxInstances": , "instanceId": "", "seleniumProtocol": "WebDriver" - }, + } ], "configuration": { diff --git a/src/main/resources/node.windows.json b/src/main/resources/node.windows.json index d955df8..f3ee021 100644 --- a/src/main/resources/node.windows.json +++ b/src/main/resources/node.windows.json @@ -18,7 +18,7 @@ "maxInstances": , "instanceId": "", "seleniumProtocol": "WebDriver" - }, + } ], "configuration": { diff --git a/src/main/resources/nodeConfigTemplate.json b/src/main/resources/nodeConfigTemplate.json deleted file mode 100755 index 586b5c8..0000000 --- a/src/main/resources/nodeConfigTemplate.json +++ /dev/null @@ -1,29 +0,0 @@ -{ "capabilities": - [ - { - "browserName": "firefox", - "maxInstances": , - "instanceId": "", - "seleniumProtocol": "WebDriver" - }, - { - "browserName": "chrome", - "maxInstances": , - "instanceId": "", - "seleniumProtocol": "WebDriver" - }, - ], - "configuration": - { - "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy", - "maxSession": , - "uuid": "", - "instanceId": "", - "createdDate": "", - "createdBrowser": "", - "createdOs": "", - "register": true, - "registerCycle": 5000, - "hubHost": "" - } -} \ No newline at end of file diff --git a/src/main/resources/patch_etc_hosts.sh b/src/main/resources/patch_etc_hosts.sh new file mode 100755 index 0000000..452a7f1 --- /dev/null +++ b/src/main/resources/patch_etc_hosts.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# ########################################################################### +# A shell script to patch the /etc/hosts file for new grid node hosts. +# Stock hosts files on EC2 usually only contain the loopback addresses and +# are missing the actual host name and the DHCP address binding. This can +# delay certain network operations. +# This script patches the /etc/hosts file to contain the hostname on both the +# loopback and DHCP based VPC IP address. +# ########################################################################### +PATH=/sbin:/usr/sbin:/bin:/usr/bin + +# Make certain we can find our host's EC2 instance ID. This ensures that the +# network stack is online and that it can get out to the rest of the netowrk. +export EC2_INSTANCE_ID= +while [ -z "${EC2_INSTANCE_ID}" ] ; do + # Wait 1 seconds to let the network settle down on boot up and let AWS get all of + # the user-data zip file artifiacts in place. + sleep 1 + export EC2_INSTANCE_ID="`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`" +done + +# Now we are certain the network stack is online, what is the host's IP? +LOCALIP=`ifconfig | grep "inet addr" | grep -v "127.0.0" | cut -d':' -f2 | cut -d' ' -f1` +HOSTNAME=`hostname` + +# Count the number of instacnces of the local ip in /etc/hosts. If this comes +# back as zero then we know the local IP address needs to be added to the +# /etc/hosts file. +HASLOCALIP=`grep -c $LOCALIP /etc/hosts` + +# Count the number of instances of the host name in /etc/hosts. If this comes +# back as zero then we know the localhost line needs to be patched to include +# the local host name. +HASHOSTNAME=`grep -c $HOSTNAME /etc/hosts` + +# Add the local hostname to /etc/hosts. +if [ "0" -eq "$HASHOSTNAME" ] ; then + sudo sed -i -e "s/localhost/localhost $HOSTNAME/" /etc/hosts +fi + +# Add the local IP to /etc/hosts. +if [ "0" -eq "$HASLOCALIP" ] ; then + sudo bash -c "echo '# hostname added to support SeleniumGridScaler. ' >> /etc/hosts" + sudo bash -c "echo \"$LOCALIP $HOSTNAME\" >> /etc/hosts" +fi + +# Add an /etc/hosts entry for the Hub IP address. +HASHUBNODEADDR=`grep -c hubHost /home/ubuntu/grid/nodeConfig.json` +while [ "0" -eq "$HASHUBNODEADDR" ] ; do + # Wait for the node setup script to pull down the hub's IP address. + sleep 1 + export HASHUBNODEADDR=`grep -c hubHost /home/ubuntu/grid/nodeConfig.json` +done + +# Lookup the IP address of the hubNode in the nodeConfig.json file. +HUBNODEADDR=`grep hubHost /home/ubuntu/grid/nodeConfig.json | cut -d'"' -f4` + +# Only add the entry if it is actually necessary to do so. +ETCHOSTSHUBCOUNT=`grep -c $HUBNODEADDR /etc/hosts` +if [ "0" -eq "$ETCHOSTSHUBCOUNT" ] ; then + sudo bash -c "echo $HUBNODEADDR hubnode gridhead >> /etc/hosts" +fi diff --git a/src/test/java/com/rmn/qa/AbstractAutomationCleanupTaskTest.java b/src/test/java/com/rmn/qa/AbstractAutomationCleanupTaskTest.java index 2a3c074..c683420 100644 --- a/src/test/java/com/rmn/qa/AbstractAutomationCleanupTaskTest.java +++ b/src/test/java/com/rmn/qa/AbstractAutomationCleanupTaskTest.java @@ -12,14 +12,16 @@ package com.rmn.qa; +import org.junit.Test; + import com.rmn.qa.task.AbstractAutomationCleanupTask; + import junit.framework.Assert; -import org.junit.Test; /** * Created by mhardin on 4/25/14. */ -public class AbstractAutomationCleanupTaskTest { +public class AbstractAutomationCleanupTaskTest extends BaseTest { @Test public void testExceptionHandled() { diff --git a/src/test/java/com/rmn/qa/AutomationCapabilityMatcherTest.java b/src/test/java/com/rmn/qa/AutomationCapabilityMatcherTest.java index 2a8d5ac..e6b8cf0 100644 --- a/src/test/java/com/rmn/qa/AutomationCapabilityMatcherTest.java +++ b/src/test/java/com/rmn/qa/AutomationCapabilityMatcherTest.java @@ -12,20 +12,23 @@ package com.rmn.qa; -import com.google.common.collect.ImmutableMap; -import junit.framework.Assert; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + import org.junit.After; import org.junit.Test; +import org.openqa.selenium.Platform; import org.openqa.selenium.remote.CapabilityType; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import com.google.common.collect.ImmutableMap; + +import junit.framework.Assert; /** * Created by mhardin on 4/25/14. */ -public class AutomationCapabilityMatcherTest { +public class AutomationCapabilityMatcherTest extends BaseTest { @Test public void testMatches() { @@ -42,10 +45,10 @@ public void testNodeNotInContext() { AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); Map nodeCapability = new HashMap(); nodeCapability.put(CapabilityType.BROWSER_NAME,"firefox"); - nodeCapability.put(AutomationConstants.INSTANCE_ID,"foo"); + nodeCapability.put(AutomationConstants.INSTANCE_ID,"foobar"); Map testCapability = new HashMap(); testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); - AutomationDynamicNode node = new AutomationDynamicNode("uuid","id","browser","os", new Date(),10); + AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser", Platform.LINUX, new Date(),10); node.updateStatus(AutomationDynamicNode.STATUS.EXPIRED); Assert.assertTrue("Capabilities should match as node is not in context",matcher.matches(nodeCapability,testCapability)); } @@ -58,7 +61,7 @@ public void testExpiredNode() { nodeCapability.put(AutomationConstants.INSTANCE_ID,"foo"); Map testCapability = new HashMap(); testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); - AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser","os", new Date(),10); + AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser",Platform.LINUX, new Date(),10); AutomationContext.getContext().addNode(node); node.updateStatus(AutomationDynamicNode.STATUS.EXPIRED); Assert.assertFalse("Capabilities should match as node is not in context", matcher.matches(nodeCapability, testCapability)); @@ -72,7 +75,7 @@ public void testTerminatedNode() { nodeCapability.put(AutomationConstants.INSTANCE_ID,"foo"); Map testCapability = new HashMap(); testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); - AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser","os", new Date(),10); + AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser",Platform.LINUX, new Date(),10); AutomationContext.getContext().addNode(node); node.updateStatus(AutomationDynamicNode.STATUS.TERMINATED); Assert.assertFalse("Capabilities should match as node is not in context",matcher.matches(nodeCapability,testCapability)); diff --git a/src/test/java/com/rmn/qa/AutomationDynamicNodeTest.java b/src/test/java/com/rmn/qa/AutomationDynamicNodeTest.java index 7999cde..5f8d06f 100644 --- a/src/test/java/com/rmn/qa/AutomationDynamicNodeTest.java +++ b/src/test/java/com/rmn/qa/AutomationDynamicNodeTest.java @@ -12,14 +12,15 @@ package com.rmn.qa; +import org.junit.Test; + import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Warning; -import org.junit.Test; /** * Created by mhardin on 5/1/14. */ -public class AutomationDynamicNodeTest { +public class AutomationDynamicNodeTest extends BaseTest { @Test public void equalsContract() { diff --git a/src/test/java/com/rmn/qa/AutomationRequestMatcherTest.java b/src/test/java/com/rmn/qa/AutomationRequestMatcherTest.java index 3017488..26850e7 100644 --- a/src/test/java/com/rmn/qa/AutomationRequestMatcherTest.java +++ b/src/test/java/com/rmn/qa/AutomationRequestMatcherTest.java @@ -12,24 +12,27 @@ package com.rmn.qa; -import junit.framework.Assert; -import org.junit.After; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; + import org.junit.Test; import org.openqa.grid.common.SeleniumProtocol; import org.openqa.grid.internal.ProxySet; import org.openqa.grid.internal.TestSlot; +import org.openqa.selenium.Platform; import org.openqa.selenium.remote.CapabilityType; -import javax.servlet.ServletException; -import java.io.IOException; -import java.util.*; +import com.google.common.collect.Maps; -public class AutomationRequestMatcherTest { +import junit.framework.Assert; - @After - public void cleanUp() { - AutomationContext.refreshContext(); - } +public class AutomationRequestMatcherTest extends BaseTest { @Test // Tests that a node in the Expired state is not considered as a free resource @@ -43,11 +46,11 @@ public void testRequestNodeExpiredState() throws IOException, ServletException{ ProxySet proxySet = new ProxySet(false); MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID, nodeId); proxy.setConfig(config); - List testSlots = new ArrayList(); - Map capabilities = new HashMap(); + List testSlots = new ArrayList<>(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,browser); testSlots.add(new TestSlot(proxy, SeleniumProtocol.WebDriver, null, capabilities)); proxy.setTestSlots(testSlots); @@ -71,10 +74,10 @@ public void testRequestNodeTerminatedNoInstanceId() throws IOException, ServletE MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); proxy.setMaxNumberOfConcurrentTestSessions(5); - Map config = new HashMap<>(); + Map config = Maps.newHashMap(); proxy.setConfig(config); - List testSlots = new ArrayList(); - Map capabilities = new HashMap(); + List testSlots = new ArrayList<>(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,browser); testSlots.add(new TestSlot(proxy, SeleniumProtocol.WebDriver, null, capabilities)); proxy.setTestSlots(testSlots); @@ -98,11 +101,11 @@ public void testRequestNodeTerminatedState() throws IOException, ServletExceptio MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(5); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap<>(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID, nodeId); proxy.setConfig(config); - List testSlots = new ArrayList(); - Map capabilities = new HashMap(); + List testSlots = new ArrayList<>(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,browser); testSlots.add(new TestSlot(proxy, SeleniumProtocol.WebDriver, null, capabilities)); proxy.setTestSlots(testSlots); @@ -117,7 +120,7 @@ public void testRequestNodeTerminatedState() throws IOException, ServletExceptio // Tests that OS matching on a node works correctly public void testRequestMatchingOs() throws IOException, ServletException { String browser = "firefox"; - String os = "linux"; + Platform os = Platform.LINUX; String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation AutomationDynamicNode node = new AutomationDynamicNode("testUuid",nodeId,null,null,new Date(),50); @@ -126,10 +129,10 @@ public void testRequestMatchingOs() throws IOException, ServletException { MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap<>(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap<>(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); capabilities.put(CapabilityType.PLATFORM,os); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); @@ -145,7 +148,7 @@ public void testRequestMatchingOs() throws IOException, ServletException { // Tests that OS NOT matching on a node works correctly public void testRequestNonMatchingOs() throws IOException, ServletException { String browser = "firefox"; - String os = "linux"; + Platform os = Platform.LINUX; String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation AutomationDynamicNode node = new AutomationDynamicNode("testUuid",nodeId,null,null,new Date(),50); @@ -154,12 +157,12 @@ public void testRequestNonMatchingOs() throws IOException, ServletException { MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); - capabilities.put(CapabilityType.PLATFORM,"doesntMatch"); + capabilities.put(CapabilityType.PLATFORM,Platform.WINDOWS); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); proxySet.add(proxy); @@ -171,7 +174,7 @@ public void testRequestNonMatchingOs() throws IOException, ServletException { @Test // Happy path that browsers matching shows correct free node count - public void testRequestMatchingBrowsers() throws IOException, ServletException{ + public void testRequestMatchingBrowsers() throws IOException, ServletException{ String browser = "firefox"; String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -181,10 +184,10 @@ public void testRequestMatchingBrowsers() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,browser); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -207,10 +210,10 @@ public void testRequestNonMatchingBrowsers() throws IOException, ServletExceptio MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"doesntMatch"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -233,10 +236,10 @@ public void testRequestAllTestSlotsIncluded() throws IOException, ServletExcepti MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(10); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -259,10 +262,10 @@ public void testRequestAllTestSlotsIncludedGreaterNodeLimit() throws IOException MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(15); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -285,10 +288,10 @@ public void testRequestAllTestSlotsIncludedLessThanNodeLimit() throws IOExceptio MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(5); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -313,10 +316,10 @@ public void testRequestNewRunNotStartedDifferentBrowser() throws IOException, Se MockRemoteProxy nonMatchingProxy = new MockRemoteProxy(); nonMatchingProxy.setMaxNumberOfConcurrentTestSessions(50); nonMatchingProxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); nonMatchingProxy.setConfig(config); - Map nonMatchingCapabilities = new HashMap(); + Map nonMatchingCapabilities = Maps.newHashMap(); nonMatchingCapabilities.put(CapabilityType.BROWSER_NAME, nonMatchingBrowser); TestSlot nonMatchingTestSlot = new TestSlot(nonMatchingProxy, SeleniumProtocol.WebDriver,null,nonMatchingCapabilities); nonMatchingProxy.setMultipleTestSlots(nonMatchingTestSlot, 10); @@ -329,7 +332,7 @@ public void testRequestNewRunNotStartedDifferentBrowser() throws IOException, Se matchingProxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); config.put(AutomationConstants.INSTANCE_ID,nodeId); matchingProxy.setConfig(config); - Map matchingCapabilities = new HashMap(); + Map matchingCapabilities = Maps.newHashMap(); matchingCapabilities.put(CapabilityType.BROWSER_NAME, matchingBrowser); TestSlot matchingTestSlot = new TestSlot(nonMatchingProxy, SeleniumProtocol.WebDriver,null,matchingCapabilities); matchingProxy.setMultipleTestSlots(matchingTestSlot, 10); @@ -353,10 +356,10 @@ public void testRequestNewRunNotStarted() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID, nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -380,10 +383,10 @@ public void testRequestNewRunInProgress() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(15); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); // Associate the run UUID with the test slots to mimic a test run that is partially underway capabilities.put(AutomationConstants.UUID,runId); @@ -391,7 +394,7 @@ public void testRequestNewRunInProgress() throws IOException, ServletException{ // Assign a session to the test slot testSlot.getNewSession(capabilities); proxy.setMultipleTestSlots(testSlot,5); - Map capabilities2 = new HashMap(); + Map capabilities2 = Maps.newHashMap(); capabilities2.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot2 = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities2); proxy.setMultipleTestSlots(testSlot2, 10); @@ -415,10 +418,10 @@ public void testRequestOldRunInProgress() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); // Associate the run UUID with the test slots to mimic a test run that is still in progress capabilities.put(AutomationConstants.UUID,runId); @@ -426,7 +429,7 @@ public void testRequestOldRunInProgress() throws IOException, ServletException{ // Assign a session to the test slot testSlot.getNewSession(capabilities); proxy.setMultipleTestSlots(testSlot, 5); - Map capabilities2 = new HashMap(); + Map capabilities2 = Maps.newHashMap(); capabilities2.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot2 = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities2); proxy.setMultipleTestSlots(testSlot2, 5); @@ -451,10 +454,10 @@ public void testRequestOldRunFinished() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(50); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map capabilities = new HashMap(); + Map capabilities = Maps.newHashMap(); capabilities.put(CapabilityType.BROWSER_NAME,"firefox"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); proxy.setMultipleTestSlots(testSlot, 10); @@ -476,17 +479,17 @@ public void testMultipleBrowsersInUse() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(10); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map nonMatchingCapabilities = new HashMap(); + Map nonMatchingCapabilities = Maps.newHashMap(); nonMatchingCapabilities.put(CapabilityType.BROWSER_NAME, "chrome"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,nonMatchingCapabilities); testSlot.getNewSession(nonMatchingCapabilities); proxy.setMultipleTestSlots(testSlot, 5); proxySet.add(proxy); - Map matchingCapabilities = new HashMap(); + Map matchingCapabilities = Maps.newHashMap(); matchingCapabilities.put(CapabilityType.BROWSER_NAME, browser); TestSlot testSlot2 = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,matchingCapabilities); proxy.setMultipleTestSlots(testSlot2, 5); @@ -510,17 +513,17 @@ public void testNew2() throws IOException, ServletException{ MockRemoteProxy proxy = new MockRemoteProxy(); proxy.setMaxNumberOfConcurrentTestSessions(6); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config = new HashMap(); + Map config = Maps.newHashMap(); config.put(AutomationConstants.INSTANCE_ID,nodeId); proxy.setConfig(config); - Map nonMatchingCapabilities = new HashMap(); + Map nonMatchingCapabilities = Maps.newHashMap(); nonMatchingCapabilities.put(CapabilityType.BROWSER_NAME, "chrome"); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,nonMatchingCapabilities); testSlot.getNewSession(nonMatchingCapabilities); proxy.setMultipleTestSlots(testSlot, 6); proxySet.add(proxy); - Map matchingCapabilities = new HashMap(); + Map matchingCapabilities = Maps.newHashMap(); matchingCapabilities.put(CapabilityType.BROWSER_NAME, browser); TestSlot testSlot2 = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,matchingCapabilities); proxy.setMultipleTestSlots(testSlot2, 1); @@ -529,10 +532,10 @@ public void testNew2() throws IOException, ServletException{ MockRemoteProxy proxy2 = new MockRemoteProxy(); proxy2.setMaxNumberOfConcurrentTestSessions(12); proxy2.setCapabilityMatcher(new AutomationCapabilityMatcher()); - Map config2 = new HashMap(); + Map config2 = Maps.newHashMap(); config2.put(AutomationConstants.INSTANCE_ID,nodeId); proxy2.setConfig(config2); - Map nonMatchingCapabilities2 = new HashMap(); + Map nonMatchingCapabilities2 = Maps.newHashMap(); nonMatchingCapabilities2.put(CapabilityType.BROWSER_NAME, "chrome"); TestSlot testSlot3 = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,nonMatchingCapabilities2); testSlot2.getNewSession(nonMatchingCapabilities2); @@ -543,7 +546,6 @@ public void testNew2() throws IOException, ServletException{ proxySet.add(proxy); - proxySet.add(proxy2); AutomationContext.getContext().setTotalNodeCount(50); diff --git a/src/test/java/com/rmn/qa/AutomationRunContextTest.java b/src/test/java/com/rmn/qa/AutomationRunContextTest.java index 8a31f2a..2a88a68 100644 --- a/src/test/java/com/rmn/qa/AutomationRunContextTest.java +++ b/src/test/java/com/rmn/qa/AutomationRunContextTest.java @@ -12,39 +12,35 @@ package com.rmn.qa; -import junit.framework.Assert; -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; -import org.junit.After; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.junit.Test; import org.openqa.grid.common.SeleniumProtocol; import org.openqa.grid.internal.ProxySet; import org.openqa.grid.internal.TestSlot; import org.openqa.grid.internal.utils.CapabilityMatcher; +import org.openqa.selenium.Platform; import org.openqa.selenium.remote.CapabilityType; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import junit.framework.Assert; +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; /** * Created by mhardin on 5/1/14. */ -public class AutomationRunContextTest { - - @After() - public void cleanUp() { - AutomationContext.refreshContext(); - } +public class AutomationRunContextTest extends BaseTest { @Test // Tests that an old run gets cleaned up (removed) public void testOldRun() { - AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"firefox","10","linux",AutomationUtils.modifyDate(new Date(),-5, Calendar.MINUTE)); - AutomationRunContext context = AutomationContext.getContext(); + AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"firefox","10", Platform.LINUX,AutomationUtils.modifyDate(new Date(),-5, Calendar.MINUTE)); + AutomationRunContext context = AutomationContext.getContext(); context.addRun(oldRequest); Assert.assertTrue("Run should exist", context.hasRun(oldRequest.getUuid())); @@ -71,7 +67,7 @@ public void testNewRun() { @Test // Tests that a new run does not get cleaned up (removed) public void testNewRunIE() { - AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"internetexplorer","10","linux",AutomationUtils.modifyDate(new Date(),-7, Calendar.MINUTE)); + AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"internetexplorer","10",Platform.LINUX,AutomationUtils.modifyDate(new Date(),-7, Calendar.MINUTE)); AutomationRunContext context = AutomationContext.getContext(); context.addRun(oldRequest); @@ -86,7 +82,7 @@ public void testNewRunIE() { @Test // Tests that a new run does not get cleaned up (removed) public void testOldRunIE() { - AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"internetexplorer","10","linux",AutomationUtils.modifyDate(new Date(),-15, Calendar.MINUTE)); + AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"internetexplorer","10",Platform.LINUX,AutomationUtils.modifyDate(new Date(),-15, Calendar.MINUTE)); AutomationRunContext context = AutomationContext.getContext(); context.addRun(oldRequest); @@ -102,7 +98,7 @@ public void testOldRunIE() { // Tests that a run with slots does not get removed public void testActiveSession() { String uuid = "uuid"; - AutomationRunRequest request = new AutomationRunRequest(uuid,10,"firefox","10","linux",AutomationUtils.modifyDate(new Date(),-1, Calendar.HOUR)); + AutomationRunRequest request = new AutomationRunRequest(uuid,10,"firefox","10",Platform.LINUX,AutomationUtils.modifyDate(new Date(),-1, Calendar.HOUR)); AutomationRunContext context = AutomationContext.getContext(); context.addRun(request); @@ -129,7 +125,7 @@ public void testActiveSession() { // Tests that a run with slots does not get removed public void testNoSessions() { String uuid = "uuid"; - AutomationRunRequest request = new AutomationRunRequest(uuid,10,"firefox","10","linux",AutomationUtils.modifyDate(new Date(),-1, Calendar.HOUR)); + AutomationRunRequest request = new AutomationRunRequest(uuid,10,"firefox","10",Platform.LINUX,AutomationUtils.modifyDate(new Date(),-1, Calendar.HOUR)); AutomationRunContext context = AutomationContext.getContext(); context.addRun(request); @@ -250,7 +246,7 @@ public void testOldRunInProgress() { capabilities.put(CapabilityType.PLATFORM,"linux"); capabilities.put(CapabilityType.BROWSER_NAME,"chrome"); capabilities.put(AutomationConstants.UUID,uuid); - AutomationRunRequest request = new AutomationRunRequest(uuid,10,"chrome","23","linux", AutomationUtils.modifyDate(new Date(),-5,Calendar.MINUTE)); + AutomationRunRequest request = new AutomationRunRequest(uuid,10,"chrome","23",Platform.LINUX, AutomationUtils.modifyDate(new Date(),-5,Calendar.MINUTE)); runContext.addRun(request); TestSlot testSlot = new TestSlot(proxy, SeleniumProtocol.WebDriver,null,capabilities); testSlot.getNewSession(capabilities); diff --git a/src/test/java/com/rmn/qa/AutomationRunRequestTest.java b/src/test/java/com/rmn/qa/AutomationRunRequestTest.java index 833e284..759fda4 100644 --- a/src/test/java/com/rmn/qa/AutomationRunRequestTest.java +++ b/src/test/java/com/rmn/qa/AutomationRunRequestTest.java @@ -12,19 +12,23 @@ package com.rmn.qa; -import junit.framework.Assert; -import org.junit.Test; -import org.openqa.selenium.remote.CapabilityType; - import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; +import org.junit.Test; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.CapabilityType; + +import com.google.common.collect.Maps; + +import junit.framework.Assert; + /** * Created by mhardin on 4/24/14. */ -public class AutomationRunRequestTest { +public class AutomationRunRequestTest extends BaseTest { @Test // Tests that two run request objects match each other @@ -32,7 +36,7 @@ public void testMatchesOtherRunRequest() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; + Platform os = Platform.LINUX; AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,browserVersion,os); Assert.assertTrue("Run requests should match",first.matchesCapabilities(second)); @@ -44,10 +48,10 @@ public void testMatchesOtherRunRequestBadBrowser() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; + Platform os = Platform.LINUX; AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); AutomationRunRequest second = new AutomationRunRequest(uuid,null,"badBrowser",browserVersion,os); - Assert.assertFalse("Run requests should NOT match due to browser",first.matchesCapabilities(second)); + Assert.assertFalse("Run requests should NOT match due to browser", first.matchesCapabilities(second)); } @Test // Tests that two run request objects do match each other due to mismatching browser versions @@ -55,7 +59,7 @@ public void testMatchesOtherRunRequestBadBrowserVersion() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; + Platform os = Platform.LINUX; AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,"12432",os); Assert.assertFalse("Run requests should NOT match due to browser version", first.matchesCapabilities(second)); @@ -66,9 +70,9 @@ public void testMatchesOtherRunRequestBadOs() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; + Platform os = Platform.LINUX; AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); - AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,browserVersion,"badOs"); + AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,browserVersion,Platform.MAC); Assert.assertFalse("Run requests should NOT match due to browser",first.matchesCapabilities(second)); } @@ -78,10 +82,22 @@ public void testMatchesOtherRunRequestOptionalParameters() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = null; - String os = null; + Platform os = null; AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); - AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,"20","linux"); - Assert.assertTrue("Run requests should match",first.matchesCapabilities(second)); + AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,"20",Platform.LINUX); + Assert.assertTrue("Run requests should match", first.matchesCapabilities(second)); + } + + @Test + // Tests that two run request objects do NOT match each other since the optional fields are on the first object + public void testDoesntMatchesOtherRunRequestNonOptionalParametersNull() { + String uuid = "testUuid"; + String browser = "firefox"; + String browserVersion = null; + Platform os = null; + AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,"20",Platform.LINUX); + AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,browserVersion,os); + Assert.assertFalse("Run requests should not match", first.matchesCapabilities(second)); } @Test @@ -90,8 +106,8 @@ public void testDoesntMatchesOtherRunRequestNonOptionalParameters() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = null; - String os = null; - AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,"20","linux"); + Platform os = Platform.WINDOWS; + AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,"20",Platform.LINUX); AutomationRunRequest second = new AutomationRunRequest(uuid,null,browser,browserVersion,os); Assert.assertFalse("Run requests should match", first.matchesCapabilities(second)); } @@ -102,8 +118,8 @@ public void testMatchesCapabilities() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; - Map map = new HashMap(); + Platform os = Platform.LINUX; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,browserVersion); map.put(CapabilityType.PLATFORM,os); @@ -117,8 +133,8 @@ public void testMatchesCapabilitiesBadBrowser() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; - Map map = new HashMap(); + Platform os = Platform.LINUX; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,browserVersion); map.put(CapabilityType.PLATFORM,os); @@ -132,8 +148,8 @@ public void testMatchesCapabilitiesBadVersion() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; - Map map = new HashMap(); + Platform os = Platform.LINUX; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,browserVersion); map.put(CapabilityType.PLATFORM,os); @@ -147,12 +163,12 @@ public void testMatchesCapabilitiesBadOs() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "20"; - String os = "linux"; - Map map = new HashMap(); + Platform os = Platform.LINUX; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,browserVersion); map.put(CapabilityType.PLATFORM,os); - AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,"badOs"); + AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,Platform.WINDOWS); Assert.assertFalse("Capabilities should match", first.matchesCapabilities(map)); } @@ -162,11 +178,11 @@ public void testMatchesCapabilitiesOptionalParameters() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = null; - String os = null; - Map map = new HashMap(); + Platform os = null; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,"20"); - map.put(CapabilityType.PLATFORM,"badOs"); + map.put(CapabilityType.PLATFORM,Platform.MAC); AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); Assert.assertTrue("Capabilities should match",first.matchesCapabilities(map)); } @@ -177,11 +193,11 @@ public void testMatchesCapabilitiesNonOptionalParameters() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "21"; - String os = "linux"; - Map map = new HashMap(); + Platform os = Platform.LINUX; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,"20"); - map.put(CapabilityType.PLATFORM,"badOs"); + map.put(CapabilityType.PLATFORM,Platform.MAC); AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,os); Assert.assertFalse("Capabilities should NOT match",first.matchesCapabilities(map)); } @@ -192,8 +208,8 @@ public void testMatchesFactoryMethod() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "25"; - String os = "linux"; - Map map = new HashMap(); + Platform os = Platform.LINUX; + Map map = Maps.newHashMap(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,browserVersion); map.put(CapabilityType.PLATFORM,os); @@ -223,12 +239,12 @@ public void testOsMatchesAnyRequests() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "25"; - String os = "ANY"; + Platform os = Platform.ANY; Map map = new HashMap<>(); map.put(CapabilityType.BROWSER_NAME,browser); map.put(CapabilityType.VERSION,browserVersion); - map.put(CapabilityType.PLATFORM,os); - AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,"linux"); + map.put(CapabilityType.PLATFORM,os.toString()); + AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,Platform.LINUX); AutomationRunRequest second = AutomationRunRequest.requestFromCapabilities(map); Assert.assertTrue("Requests should be equal", first.matchesCapabilities(second)); Assert.assertTrue("Requests should be equal", second.matchesCapabilities(first)); @@ -240,12 +256,12 @@ public void testOsMatchesAnyCapability() { String uuid = "testUuid"; String browser = "firefox"; String browserVersion = "25"; - String os = "ANY"; + Platform os = Platform.ANY; Map capabilities = new HashMap<>(); capabilities.put(CapabilityType.BROWSER_NAME, browser); capabilities.put(CapabilityType.VERSION, browserVersion); - capabilities.put(CapabilityType.PLATFORM, os); - AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,"linux"); + capabilities.put(CapabilityType.PLATFORM, os.toString()); + AutomationRunRequest first = new AutomationRunRequest(uuid,null,browser,browserVersion,Platform.LINUX); Assert.assertTrue("Requests should be equal", first.matchesCapabilities(capabilities)); } } diff --git a/src/test/java/com/rmn/qa/AutomationUtilsTest.java b/src/test/java/com/rmn/qa/AutomationUtilsTest.java index e2f2000..3ae6bc6 100644 --- a/src/test/java/com/rmn/qa/AutomationUtilsTest.java +++ b/src/test/java/com/rmn/qa/AutomationUtilsTest.java @@ -12,13 +12,14 @@ package com.rmn.qa; -import junit.framework.Assert; -import org.junit.Test; - import java.util.Calendar; import java.util.Date; -public class AutomationUtilsTest { +import org.junit.Test; + +import junit.framework.Assert; + +public class AutomationUtilsTest extends BaseTest { @Test public void testDifferentCasedEquals() { diff --git a/src/test/java/com/rmn/qa/BaseTest.java b/src/test/java/com/rmn/qa/BaseTest.java new file mode 100644 index 0000000..a514c58 --- /dev/null +++ b/src/test/java/com/rmn/qa/BaseTest.java @@ -0,0 +1,15 @@ +package com.rmn.qa; + +import org.junit.After; + +/** + * Created by matthew on 5/26/16. + */ +public abstract class BaseTest { + + @After + // Since AutomationContext is a shared singleton, make sure and clear it after every test + public void afterTest() { + AutomationContext.refreshContext(); + } +} diff --git a/src/test/java/com/rmn/qa/MockHttpServletRequest.java b/src/test/java/com/rmn/qa/MockHttpServletRequest.java index bbfff70..b2d9c30 100644 --- a/src/test/java/com/rmn/qa/MockHttpServletRequest.java +++ b/src/test/java/com/rmn/qa/MockHttpServletRequest.java @@ -12,20 +12,31 @@ package com.rmn.qa; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletInputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.Principal; +import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + /** * Created by mhardin on 2/6/14. */ @@ -38,6 +49,76 @@ public String getAuthType() { return null; } + @Override + public String changeSessionId() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + return null; + } + + @Override + public Part getPart(String name) throws IOException, ServletException { + return null; + } + + @Override + public T upgrade(Class handlerClass) throws IOException, ServletException { + return null; + } + + @Override + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { + return false; + } + + @Override + public void login(String username, String password) throws ServletException { + + } + + @Override + public void logout() throws ServletException { + + } + + @Override + public Collection getParts() throws IOException, ServletException { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public long getContentLengthLong() { + return 0; + } + @Override public Cookie[] getCookies() { return new Cookie[0]; @@ -306,4 +387,9 @@ public String getLocalAddr() { public int getLocalPort() { return 0; } + + @Override + public ServletContext getServletContext() { + return null; + } } diff --git a/src/test/java/com/rmn/qa/MockHttpServletResponse.java b/src/test/java/com/rmn/qa/MockHttpServletResponse.java index c0ef17a..b97c862 100644 --- a/src/test/java/com/rmn/qa/MockHttpServletResponse.java +++ b/src/test/java/com/rmn/qa/MockHttpServletResponse.java @@ -12,13 +12,15 @@ package com.rmn.qa; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; +import java.util.Collection; import java.util.Locale; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + /** * Created by mhardin on 2/6/14. */ @@ -33,6 +35,31 @@ public void addCookie(Cookie cookie) { } + @Override + public int getStatus() { + return 0; + } + + @Override + public Collection getHeaderNames() { + return null; + } + + @Override + public void setContentLengthLong(long len) { + + } + + @Override + public Collection getHeaders(String name) { + return null; + } + + @Override + public String getHeader(String name) { + return null; + } + @Override public boolean containsHeader(String name) { return false; diff --git a/src/test/java/com/rmn/qa/MockRemoteProxy.java b/src/test/java/com/rmn/qa/MockRemoteProxy.java index 151e807..4435026 100644 --- a/src/test/java/com/rmn/qa/MockRemoteProxy.java +++ b/src/test/java/com/rmn/qa/MockRemoteProxy.java @@ -12,7 +12,8 @@ package com.rmn.qa; -import org.json.JSONObject; +import com.google.gson.JsonObject; +import com.google.gson.JsonElement; import org.openqa.grid.common.RegistrationRequest; import org.openqa.grid.common.exception.GridException; import org.openqa.grid.internal.Registry; @@ -136,7 +137,7 @@ public HttpClientFactory getHttpClientFactory() { } @Override - public JSONObject getStatus() throws GridException { + public JsonObject getStatus() throws GridException { return null; } diff --git a/src/test/java/com/rmn/qa/MockVmManager.java b/src/test/java/com/rmn/qa/MockVmManager.java index f4f76ae..6463abc 100644 --- a/src/test/java/com/rmn/qa/MockVmManager.java +++ b/src/test/java/com/rmn/qa/MockVmManager.java @@ -12,14 +12,16 @@ package com.rmn.qa; +import java.util.Arrays; +import java.util.List; + +import org.openqa.selenium.Platform; + import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Reservation; import com.rmn.qa.aws.VmManager; -import java.util.ArrayList; -import java.util.List; - public class MockVmManager implements VmManager { private boolean nodesLaunched = false; @@ -31,7 +33,7 @@ public class MockVmManager implements VmManager { @Override - public List launchNodes(String uuid, String os, String browser, String hubHostName, int nodeCount, int maxSessions) { + public List launchNodes(String uuid, Platform platform, String browser, String hubHostName, int nodeCount, int maxSessions) { if(throwException) { throw new RuntimeException("Can't start nodes"); } @@ -40,9 +42,7 @@ public List launchNodes(String uuid, String os, String browser, String this.browser = browser; Instance instance = new Instance(); instance.setInstanceId("instanceId"); - List instances = new ArrayList(); - instances.add(instance); - return instances; + return Arrays.asList(instance); } @Override diff --git a/src/test/java/com/rmn/qa/TestBrowserPlatformPair.java b/src/test/java/com/rmn/qa/TestBrowserPlatformPair.java new file mode 100644 index 0000000..f799621 --- /dev/null +++ b/src/test/java/com/rmn/qa/TestBrowserPlatformPair.java @@ -0,0 +1,48 @@ +package com.rmn.qa; + +import org.junit.Test; +import org.openqa.selenium.Platform; + +import junit.framework.Assert; + +/** + * Created by matthew on 5/26/16. + */ +public class TestBrowserPlatformPair extends BaseTest { + + @Test + public void getGet() { + BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); + Assert.assertEquals("Browsers should be equal", "chrome", one.getBrowser()); + Assert.assertEquals("Platforms should be equal", Platform.LINUX, one.getPlatform()); + } + + @Test + public void testEquals() { + BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); + BrowserPlatformPair two = new BrowserPlatformPair("chrome", Platform.LINUX); + Assert.assertTrue("Objects should be equal", one.equals(two)); + } + + @Test + public void testNotEqualsPlatform() { + BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); + BrowserPlatformPair two = new BrowserPlatformPair("chrome", Platform.UNIX); + Assert.assertFalse("Objects should not be equal due to non-matching platform", one.equals(two)); + } + + @Test + public void testNotEqualsPlatformDifferentFamily() { + BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.UNIX); + BrowserPlatformPair two = new BrowserPlatformPair("chrome", Platform.WINDOWS); + Assert.assertFalse("Objects should not be equal due to non-matching platform", one.equals(two)); + } + + @Test + public void testNotEqualsBrowser() { + BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); + BrowserPlatformPair two = new BrowserPlatformPair("firefox", Platform.LINUX); + Assert.assertFalse("Objects should not be equal due to non-matching platform", one.equals(two)); + } + +} diff --git a/src/test/java/com/rmn/qa/aws/AwsTagReporterTest.java b/src/test/java/com/rmn/qa/aws/AwsTagReporterTest.java index ad472e4..2e41b9f 100644 --- a/src/test/java/com/rmn/qa/aws/AwsTagReporterTest.java +++ b/src/test/java/com/rmn/qa/aws/AwsTagReporterTest.java @@ -12,17 +12,20 @@ package com.rmn.qa.aws; +import java.util.Arrays; +import java.util.Collection; +import java.util.Properties; + +import org.junit.Test; + import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Reservation; -import junit.framework.Assert; -import org.junit.Test; +import com.rmn.qa.BaseTest; -import java.util.Arrays; -import java.util.Collection; -import java.util.Properties; +import junit.framework.Assert; -public class AwsTagReporterTest { +public class AwsTagReporterTest extends BaseTest { @Test public void testTagsAssociated() { diff --git a/src/test/java/com/rmn/qa/aws/MockManageVm.java b/src/test/java/com/rmn/qa/aws/MockManageVm.java index 6273d61..620d12c 100644 --- a/src/test/java/com/rmn/qa/aws/MockManageVm.java +++ b/src/test/java/com/rmn/qa/aws/MockManageVm.java @@ -12,14 +12,18 @@ package com.rmn.qa.aws; +import java.util.Properties; + +import org.openqa.selenium.Platform; + +import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.ec2.AmazonEC2Client; -import java.util.Properties; - public class MockManageVm extends AwsVmManager { private String userData; + private AWSCredentials awsCredentials; public MockManageVm() { super(); @@ -35,7 +39,7 @@ public MockManageVm(AmazonEC2Client client, Properties properties, String region } @Override - String getUserData(String uuid, String hubHostName, String browser, String os, int maxSessions) { + String getUserData(String uuid, String hubHostName, String browser, Platform platform, int maxSessions) { return userData; } @@ -43,7 +47,11 @@ public void setUserData(String userData) { this.userData = userData; } - public void setCredentials(BasicAWSCredentials basicAWSCredentials) { + public void setCredentials(AWSCredentials basicAWSCredentials) { this.credentials = basicAWSCredentials; } + + public void setAwsCredentials(AWSCredentials awsCredentials) { + this.awsCredentials = awsCredentials; + } } diff --git a/src/test/java/com/rmn/qa/aws/VmManagerTest.java b/src/test/java/com/rmn/qa/aws/VmManagerTest.java index eff009b..a236e01 100644 --- a/src/test/java/com/rmn/qa/aws/VmManagerTest.java +++ b/src/test/java/com/rmn/qa/aws/VmManagerTest.java @@ -12,8 +12,22 @@ package com.rmn.qa.aws; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.collections4.CollectionUtils; +import org.junit.After; +import org.junit.Test; +import org.openqa.selenium.Platform; + import com.amazonaws.AmazonServiceException; -import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.auth.AWSCredentials; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.InstanceState; import com.amazonaws.services.ec2.model.InstanceStateChange; @@ -23,22 +37,12 @@ import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import com.amazonaws.services.ec2.model.TerminateInstancesResult; import com.rmn.qa.AutomationConstants; +import com.rmn.qa.BaseTest; import com.rmn.qa.NodesCouldNotBeStartedException; -import junit.framework.Assert; -import org.apache.commons.collections.CollectionUtils; -import org.junit.After; -import org.junit.Test; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Properties; +import junit.framework.Assert; -public class VmManagerTest { +public class VmManagerTest extends BaseTest { @After public void clear() { @@ -93,7 +97,7 @@ public void testKeysSet() { properties.setProperty(AutomationConstants.AWS_PRIVATE_KEY,privateKey); String region = "east"; AwsVmManager AwsVmManager = new AwsVmManager(client,properties,region); - BasicAWSCredentials credentials = AwsVmManager.getCredentials(); + AWSCredentials credentials = AwsVmManager.getCredentials(); Assert.assertEquals("Access key IDs should match",accessKey,credentials.getAWSAccessKeyId()); Assert.assertEquals("Access key IDs should match",privateKey,credentials.getAWSSecretKey()); } @@ -110,7 +114,7 @@ public void testSystemPropertyPrecedence() { properties.setProperty(AutomationConstants.AWS_PRIVATE_KEY,"moreGibberish"); String region = "east"; AwsVmManager AwsVmManager = new com.rmn.qa.aws.AwsVmManager(client,properties,region); - BasicAWSCredentials credentials = AwsVmManager.getCredentials(); + AWSCredentials credentials = AwsVmManager.getCredentials(); Assert.assertEquals("Access key IDs should match", accessKey, credentials.getAWSAccessKeyId()); Assert.assertEquals("Access key IDs should match", privateKey, credentials.getAWSSecretKey()); } @@ -125,12 +129,13 @@ public void testLaunchNodes() throws NodesCouldNotBeStartedException{ runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os="windows"; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.WINDOWS; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; manageEC2.setUserData(userData); - List instances = manageEC2.launchNodes(uuid,os,browser,null,threadCount,maxSessions); + manageEC2.launchNodes(uuid,os,browser,null,threadCount,maxSessions); RunInstancesRequest request = client.getRunInstancesRequest(); Assert.assertEquals("Min count should match thread count requested", threadCount, request.getMinCount()); Assert.assertEquals("Max count should match thread count requested", threadCount, request.getMaxCount()); @@ -150,7 +155,8 @@ public void testLaunchNodesOptionalFieldsSet() throws NodesCouldNotBeStartedExc runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os=null; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = null; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -183,7 +189,8 @@ public void testLaunchNodesMultipleSecurityGroups() throws NodesCouldNotBeStart runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os=null; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.ANY; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -225,7 +232,8 @@ public void testLaunchNodesIe() throws NodesCouldNotBeStartedException { runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="internet explorer",os=null; + String region = "east", uuid="uuid",browser="internet explorer"; + Platform os = null; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -258,7 +266,8 @@ public void testLaunchNodesBadOs() throws NodesCouldNotBeStartedException{ runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os="badOs"; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.MAC; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -271,7 +280,7 @@ public void testLaunchNodesBadOs() throws NodesCouldNotBeStartedException{ try{ manageEC2.launchNodes(uuid,os,browser,null,threadCount,maxSessions); } catch(RuntimeException e) { - Assert.assertTrue("Failure message should be correct",e.getMessage().contains(os)); + Assert.assertTrue("Failure message should be correct",e.getMessage().contains(os.toString())); return; } Assert.fail("Call should fail due to invalid OS"); @@ -374,7 +383,7 @@ public void testS3Config() { String region = "east"; MockManageVm manageEC2 = new MockManageVm(client,properties,region); - BasicAWSCredentials credentials = manageEC2.getCredentials(); + AWSCredentials credentials = manageEC2.getCredentials(); manageEC2.setCredentials(credentials); String s3Config = manageEC2.getS3Config(); Assert.assertTrue("Access key should have been injected into the file", s3Config.contains(accessKey)); @@ -429,13 +438,14 @@ public void testInvalidCustomProperty() { // Test that the correct node config file is generated for a windows os public void testGetNodeConfigWindows() { MockManageVm manageEC2 = new MockManageVm(null,null,null); - String uuid="uuid",hostName="hostName",browser="chrome",os="windows"; + String uuid="uuid",hostName="hostName",browser="chrome"; + Platform os = Platform.WINDOWS; int maxSessions = 5; String nodeConfig = manageEC2.getNodeConfig(uuid,hostName,browser,os,maxSessions); Assert.assertTrue("Max sessions should have been passed in",nodeConfig.contains(String.valueOf(maxSessions))); Assert.assertTrue("UUID should have been passed in",nodeConfig.contains(uuid)); Assert.assertTrue("Browser should have been passed in",nodeConfig.contains(browser)); - Assert.assertTrue("OS should have been passed in",nodeConfig.contains(os)); + Assert.assertTrue("OS should have been passed in",nodeConfig.contains(os.toString())); Assert.assertTrue("Host name should have been passed in",nodeConfig.contains(hostName)); Assert.assertTrue("IE thread count should have been passed in", nodeConfig.contains(String.valueOf(AwsVmManager.FIREFOX_IE_THREAD_COUNT))); } @@ -444,13 +454,14 @@ public void testGetNodeConfigWindows() { // Test that the correct node config file is generated for a linux os public void testGetNodeConfigLinux() { MockManageVm manageEC2 = new MockManageVm(null,null,null); - String uuid="uuid",hostName="hostName",browser="chrome",os="linux"; + String uuid="uuid",hostName="hostName",browser="chrome"; + Platform os = Platform.LINUX; int maxSessions = 5; String nodeConfig = manageEC2.getNodeConfig(uuid,hostName,browser,os,maxSessions); Assert.assertTrue("Max sessions should have been passed in",nodeConfig.contains(String.valueOf(maxSessions))); Assert.assertTrue("UUID should have been passed in",nodeConfig.contains(uuid)); Assert.assertTrue("Browser should have been passed in",nodeConfig.contains(browser)); - Assert.assertTrue("OS should have been passed in",nodeConfig.contains(os)); + Assert.assertTrue("OS should have been passed in",nodeConfig.contains(os.toString())); Assert.assertTrue("Host name should have been passed in",nodeConfig.contains(hostName)); Assert.assertTrue("IE thread count should have been passed in", nodeConfig.contains(String.valueOf(AwsVmManager.CHROME_THREAD_COUNT))); } @@ -459,12 +470,13 @@ public void testGetNodeConfigLinux() { // Test that the correct exception is throw when you specified a bad OS for the node config public void testGetNodeConfigBadOs() { MockManageVm manageEC2 = new MockManageVm(null,null,null); - String uuid="uuid",hostName="hostName",browser="chrome",os="badOs"; + String uuid="uuid",hostName="hostName",browser="chrome"; + Platform os = Platform.MAC; int maxSessions = 5; try{ manageEC2.getNodeConfig(uuid,hostName,browser,os,maxSessions); } catch(RuntimeException e) { - Assert.assertTrue("Failure message should be correct",e.getMessage().contains(os)); + Assert.assertTrue("Failure message should be correct",e.getMessage().contains(os.toString())); return; } Assert.fail("Node config should not work due to bad OS"); @@ -483,7 +495,8 @@ public void testSubnetNoFallBack() throws NodesCouldNotBeStartedException { runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os="linux"; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.LINUX; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -515,7 +528,8 @@ public void testSubnetFallsBackSuccessfully() throws NodesCouldNotBeStartedExcep runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os="linux"; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.LINUX; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -542,7 +556,8 @@ public void testSubnetFallBackUnknownError() throws NodesCouldNotBeStartedExcept runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os="linux"; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.LINUX; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -575,7 +590,8 @@ public void testSubnetInfiniteLoop() throws NodesCouldNotBeStartedException { runInstancesResult.setReservation(reservation); client.setRunInstances(runInstancesResult); Properties properties = new Properties(); - String region = "east", uuid="uuid",browser="chrome",os="linux"; + String region = "east", uuid="uuid",browser="chrome"; + Platform os = Platform.LINUX; Integer threadCount = 5,maxSessions=5; MockManageVm manageEC2 = new MockManageVm(client,properties,region); String userData = "userData"; @@ -601,10 +617,9 @@ public void testSubnetInfiniteLoop() throws NodesCouldNotBeStartedException { } @Test - //Tests that the client is initialized and exception is not thrown + //Tests that if the client is not initialized, an exception with appropriate message is thrown public void testClientInitialized(){ AwsVmManager manageEC2 = new AwsVmManager(); - String region = "east"; try{ manageEC2.launchNodes("foo", "bar", 4, "userData", false); } catch(Exception e) { diff --git a/src/test/java/com/rmn/qa/servlet/AutomationTestRunServletTest.java b/src/test/java/com/rmn/qa/servlet/AutomationTestRunServletTest.java index 1c98a0c..98f1176 100644 --- a/src/test/java/com/rmn/qa/servlet/AutomationTestRunServletTest.java +++ b/src/test/java/com/rmn/qa/servlet/AutomationTestRunServletTest.java @@ -12,26 +12,37 @@ package com.rmn.qa.servlet; -import com.rmn.qa.*; -import junit.framework.Assert; -import org.junit.After; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; + import org.junit.Test; import org.openqa.grid.common.SeleniumProtocol; import org.openqa.grid.internal.ProxySet; import org.openqa.grid.internal.TestSlot; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.BrowserType; import org.openqa.selenium.remote.CapabilityType; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.*; +import com.rmn.qa.AutomationCapabilityMatcher; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRunRequest; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockHttpServletRequest; +import com.rmn.qa.MockHttpServletResponse; +import com.rmn.qa.MockRemoteProxy; +import com.rmn.qa.MockRequestMatcher; +import com.rmn.qa.MockVmManager; -public class AutomationTestRunServletTest { +import junit.framework.Assert; - @After - public void cleanUp() { - AutomationContext.refreshContext(); - } +public class AutomationTestRunServletTest extends BaseTest { @Test // Makes sure that the query string parameter 'uuid' is required @@ -86,6 +97,7 @@ public void testHubThreadCountMaxedOut() throws IOException, ServletException{ MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","firefox"); + request.setParameter("os", Platform.ANY.toString()); request.setParameter("threadCount", "50"); AutomationContext.getContext().addRun(new AutomationRunRequest("runUUid",50,"firefox")); AutomationContext.getContext().setTotalNodeCount(50); @@ -105,6 +117,7 @@ public void testHubThreadCountMaxedOutMultipleRuns() throws IOException, Servlet MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","firefox"); + request.setParameter("os", Platform.ANY.toString()); request.setParameter("threadCount","50"); AutomationContext.getContext().addRun(new AutomationRunRequest("runUUid",25,"firefox")); AutomationContext.getContext().addRun(new AutomationRunRequest("runUUid",25,"firefox")); @@ -128,6 +141,7 @@ public void testRequestCanFulfill() throws IOException, ServletException{ MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","firefox"); + request.setParameter("os", Platform.ANY.toString()); request.setParameter("threadCount","10"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -164,6 +178,7 @@ public void testDuplicateUuids() throws IOException, ServletException{ String uuid = "testUUid"; request.setParameter("uuid",uuid); request.setParameter("browser","firefox"); + request.setParameter("os", Platform.ANY.toString()); request.setParameter("threadCount","10"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -202,6 +217,7 @@ public void testRequestCanFulfillSpinUpNodes() throws IOException, ServletExcept MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","firefox"); + request.setParameter("os", Platform.ANY.toString()); request.setParameter("threadCount","6"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -237,6 +253,7 @@ public void testRequestCanFulfillSpinUpNodesChrome() throws IOException, Servlet MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","chrome"); + request.setParameter("os", Platform.LINUX.toString()); request.setParameter("threadCount","7"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -273,6 +290,7 @@ public void testRequestCanFulfillSpinUpNodesIe() throws IOException, ServletExce MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","internetexplorer"); + request.setParameter("os", Platform.WINDOWS.toString()); request.setParameter("threadCount","1"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -307,7 +325,8 @@ public void testRequestCantFulfillUnsupportedBrowser() throws IOException, Servl MockAutomationTestRunServlet servlet = new MockAutomationTestRunServlet(null,false, manageEc2,matcher); MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); - request.setParameter("browser","unknown"); + request.setParameter("browser", BrowserType.SAFARI.toString()); + request.setParameter("os", Platform.WINDOWS.toString()); request.setParameter("threadCount","7"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation @@ -344,6 +363,7 @@ public void testInitCleanupThreads() throws IOException, ServletException{ MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","chrome"); + request.setParameter("os", Platform.WINDOWS.toString()); request.setParameter("threadCount","7"); request.setParameter("browserVersion","21"); String nodeId = "nodeId"; @@ -379,6 +399,7 @@ public void testInitCleanupThreadsNoInstanceId() throws IOException, ServletExce MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","chrome"); + request.setParameter("os", Platform.WINDOWS.toString()); request.setParameter("threadCount","7"); request.setParameter("browserVersion","21"); String nodeId = "nodeId"; @@ -416,6 +437,7 @@ public void testRequestStartNodesFailed() throws IOException, ServletException{ MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("uuid","testUuid"); request.setParameter("browser","firefox"); + request.setParameter("os", Platform.WINDOWS.toString()); request.setParameter("threadCount","10"); String nodeId = "nodeId"; // Add a node that is not running to make sure its not included in the available calculation diff --git a/src/test/java/com/rmn/qa/task/AutomationHubCleanupTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationHubCleanupTaskTest.java index f44e24f..53e3676 100644 --- a/src/test/java/com/rmn/qa/task/AutomationHubCleanupTaskTest.java +++ b/src/test/java/com/rmn/qa/task/AutomationHubCleanupTaskTest.java @@ -12,19 +12,22 @@ package com.rmn.qa.task; -import com.rmn.qa.AutomationUtils; -import com.rmn.qa.MockVmManager; -import com.rmn.qa.MockRemoteProxy; -import com.rmn.qa.aws.AwsVmManager; -import junit.framework.Assert; +import java.util.Calendar; +import java.util.Date; + import org.junit.After; import org.junit.Test; import org.openqa.grid.internal.ProxySet; -import java.util.Calendar; -import java.util.Date; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockRemoteProxy; +import com.rmn.qa.MockVmManager; +import com.rmn.qa.aws.AwsVmManager; + +import junit.framework.Assert; -public class AutomationHubCleanupTaskTest { +public class AutomationHubCleanupTaskTest extends BaseTest { @After public void afterTest() { diff --git a/src/test/java/com/rmn/qa/task/AutomationNodeCleanupTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationNodeCleanupTaskTest.java index 15219ba..17fde34 100644 --- a/src/test/java/com/rmn/qa/task/AutomationNodeCleanupTaskTest.java +++ b/src/test/java/com/rmn/qa/task/AutomationNodeCleanupTaskTest.java @@ -12,23 +12,34 @@ package com.rmn.qa.task; -import com.rmn.qa.*; -import junit.framework.Assert; -import org.junit.After; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.junit.Test; import org.openqa.grid.common.SeleniumProtocol; import org.openqa.grid.internal.ProxySet; import org.openqa.grid.internal.TestSlot; import org.openqa.selenium.remote.CapabilityType; -import java.util.*; +import com.rmn.qa.AutomationCapabilityMatcher; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRequestMatcher; +import com.rmn.qa.AutomationRunRequest; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockRemoteProxy; +import com.rmn.qa.MockRequestMatcher; +import com.rmn.qa.MockVmManager; -public class AutomationNodeCleanupTaskTest { +import junit.framework.Assert; - @After - public void tearDown() { - AutomationContext.refreshContext(); - } +public class AutomationNodeCleanupTaskTest extends BaseTest { @Test // Tests that the hard coded name of the task is correct @@ -303,9 +314,33 @@ public void testNodeRemovedAfterTime() { Assert.assertEquals("Status should change to expired first", AutomationDynamicNode.STATUS.EXPIRED, node.getStatus()); task.run(); Assert.assertEquals("Node should be terminated as it was empty", AutomationDynamicNode.STATUS.TERMINATED, node.getStatus()); - Assert.assertNotNull("Node should be tracked",AutomationContext.getContext().getNode(node.getInstanceId())); + Assert.assertNotNull("Node should be tracked", AutomationContext.getContext().getNode(node.getInstanceId())); node.setEndDate(AutomationUtils.modifyDate(new Date(), -45, Calendar.MINUTE)); task.run(); Assert.assertNull("Node should not be tracked after its been terminated for 30 minutes", AutomationContext.getContext().getNode(node.getInstanceId())); } + + @Test + // Tests that a terminated node is removed from internal tracking as well as from Selenium's ProxySet + public void testNodeRemovedFromInternalTrackingAndProxy() { + ProxySet proxySet = new ProxySet(false); + MockAutomationNodeCleanupTask task = new MockAutomationNodeCleanupTask(null,new MockVmManager(),new MockRequestMatcher()); + MockRemoteProxy proxy = new MockRemoteProxy(); + proxySet.add(proxy); + task.setProxySet(proxySet); + AutomationDynamicNode node = new AutomationDynamicNode("testUuid","dummyId",null,null,AutomationUtils.modifyDate(new Date(),-56, Calendar.MINUTE),10); + proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); + Map config = new HashMap<>(); + config.put(AutomationConstants.INSTANCE_ID,node.getInstanceId()); + proxy.setConfig(config); + AutomationContext.getContext().addNode(node); + proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); + Assert.assertFalse("Proxy should not be removed yet", task.isProxyRemoved()); + task.run(); + Assert.assertEquals("Status should change to expired first", AutomationDynamicNode.STATUS.EXPIRED, node.getStatus()); + task.run(); + Assert.assertEquals("Node should be terminated as it was empty", AutomationDynamicNode.STATUS.TERMINATED, node.getStatus()); + Assert.assertNotNull("Node should still be tracked after its terminated", AutomationContext.getContext().getNode(node.getInstanceId())); + Assert.assertTrue("Proxy should have been removed yet", task.isProxyRemoved()); + } } diff --git a/src/test/java/com/rmn/qa/task/AutomationNodeRegistryTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTaskTest.java similarity index 84% rename from src/test/java/com/rmn/qa/task/AutomationNodeRegistryTaskTest.java rename to src/test/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTaskTest.java index fb05556..f211abd 100644 --- a/src/test/java/com/rmn/qa/task/AutomationNodeRegistryTaskTest.java +++ b/src/test/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTaskTest.java @@ -12,54 +12,56 @@ package com.rmn.qa.task; -import com.rmn.qa.*; -import com.rmn.qa.aws.AwsVmManager; -import junit.framework.Assert; -import org.junit.After; -import org.junit.Test; -import org.openqa.grid.internal.ProxySet; - import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; +import org.junit.Test; +import org.openqa.grid.internal.ProxySet; +import org.openqa.selenium.Platform; + +import com.rmn.qa.AutomationCapabilityMatcher; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockRemoteProxy; +import com.rmn.qa.aws.AwsVmManager; + +import junit.framework.Assert; + /** * Created by mhardin on 4/24/14. */ -public class AutomationNodeRegistryTaskTest { - - @After - public void tearDown() { - AutomationContext.refreshContext(); - } +public class AutomationOrphanedNodeRegistryTaskTest extends BaseTest { @Test // Tests that the hardcoded name of the task is correct public void testTaskName() { - AutomationNodeRegistryTask task = new AutomationNodeRegistryTask(null); - Assert.assertEquals("Name should be the same", AutomationNodeRegistryTask.NAME, task.getDescription()); + AutomationOrphanedNodeRegistryTask task = new AutomationOrphanedNodeRegistryTask(null); + Assert.assertEquals("Name should be the same", AutomationOrphanedNodeRegistryTask.NAME, task.getDescription()); } @Test // Test that a node not in the context gets picked up and registered by the task public void testRegisterNewNode() { - MockAutomationNodeRegistryTask task = new MockAutomationNodeRegistryTask(null); + MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); ProxySet proxySet = new ProxySet(false); MockRemoteProxy proxy = new MockRemoteProxy(); proxySet.add(proxy); - Map config = new HashMap(); + Map config = new HashMap<>(); String instanceId = "instanceId"; String uuid="testUuid"; int threadCount = 10; String browser = "firefox"; - String os = "linux"; + Platform os = Platform.LINUX; config.put(AutomationConstants.INSTANCE_ID,instanceId); config.put(AutomationConstants.UUID,uuid); config.put(AutomationConstants.CONFIG_MAX_SESSION, threadCount); config.put(AutomationConstants.CONFIG_BROWSER, browser); - config.put(AutomationConstants.CONFIG_OS, os); + config.put(AutomationConstants.CONFIG_OS, os.toString()); config.put(AutomationConstants.CONFIG_CREATED_DATE, AwsVmManager.NODE_DATE_FORMAT.format(new Date())); proxy.setConfig(config); proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); @@ -71,22 +73,22 @@ public void testRegisterNewNode() { Assert.assertEquals("UUID should match", uuid,existingNode.getUuid()); Assert.assertEquals("Browser should match", browser,existingNode.getBrowser()); Assert.assertEquals("Thread count should match", threadCount,existingNode.getNodeCapacity()); - Assert.assertEquals("OS should match", os, existingNode.getOs()); + Assert.assertEquals("OS should match", os, existingNode.getPlatform()); } @Test // Test if a node already exists in the context that the task does not override that node public void testNodeAlreadyExists() { - MockAutomationNodeRegistryTask task = new MockAutomationNodeRegistryTask(null); + MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); ProxySet proxySet = new ProxySet(false); MockRemoteProxy proxy = new MockRemoteProxy(); proxySet.add(proxy); - Map config = new HashMap(); + Map config = new HashMap<>(); String instanceId = "instanceId"; String uuid="testUuid"; int threadCount = 10; String browser = "firefox"; - String os = "linux"; + Platform os = Platform.LINUX; config.put(AutomationConstants.INSTANCE_ID,instanceId); config.put(AutomationConstants.UUID,"fake"); config.put(AutomationConstants.CONFIG_MAX_SESSION, 1); @@ -105,17 +107,17 @@ public void testNodeAlreadyExists() { Assert.assertEquals("UUID should match the previously existing node", uuid, newNode.getUuid()); Assert.assertEquals("Browser should match the previously existing node", browser,newNode.getBrowser()); Assert.assertEquals("Thread count should match the previously existing node", threadCount, newNode.getNodeCapacity()); - Assert.assertEquals("OS should match the previously existing node", os, newNode.getOs()); + Assert.assertEquals("OS should match the previously existing node", os, newNode.getPlatform()); } @Test // Test that a node without an instance id does not get registered public void testRegisterNodeWithoutInstanceId() { - MockAutomationNodeRegistryTask task = new MockAutomationNodeRegistryTask(null); + MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); ProxySet proxySet = new ProxySet(false); MockRemoteProxy proxy = new MockRemoteProxy(); proxySet.add(proxy); - Map config = new HashMap(); + Map config = new HashMap<>(); String uuid="testUuid"; int threadCount = 10; String browser = "firefox"; @@ -136,7 +138,7 @@ public void testRegisterNodeWithoutInstanceId() { @Test // Test that an empty proxy set does not result in any added nodes after the register task runs public void testEmptyProxySet() { - MockAutomationNodeRegistryTask task = new MockAutomationNodeRegistryTask(null); + MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); ProxySet proxySet = new ProxySet(false); task.setProxySet(proxySet); Assert.assertEquals("Node should not be registered before the task runs",0,AutomationContext.getContext().getNodes().size()); @@ -147,7 +149,7 @@ public void testEmptyProxySet() { @Test // Test that a null proxy set does not result in any added nodes public void testNullEmptyProxySet() { - MockAutomationNodeRegistryTask task = new MockAutomationNodeRegistryTask(null); + MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); ProxySet proxySet = new ProxySet(false); task.setProxySet(proxySet); Assert.assertEquals("Node should not be registered before the task runs",0,AutomationContext.getContext().getNodes().size()); @@ -158,7 +160,7 @@ public void testNullEmptyProxySet() { @Test // Test that a node with a bad date does not register successfully after the task runs public void testBadDateFormat() { - MockAutomationNodeRegistryTask task = new MockAutomationNodeRegistryTask(null); + MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); ProxySet proxySet = new ProxySet(false); MockRemoteProxy proxy = new MockRemoteProxy(); proxySet.add(proxy); diff --git a/src/test/java/com/rmn/qa/task/AutomationPendingNodeRegistryTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationPendingNodeRegistryTaskTest.java new file mode 100644 index 0000000..735201a --- /dev/null +++ b/src/test/java/com/rmn/qa/task/AutomationPendingNodeRegistryTaskTest.java @@ -0,0 +1,148 @@ +package com.rmn.qa.task; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.openqa.grid.internal.ProxySet; + +import com.rmn.qa.AutomationCapabilityMatcher; +import com.rmn.qa.AutomationConstants; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.AutomationRunContext; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockRemoteProxy; +import com.rmn.qa.MockVmManager; + +import junit.framework.Assert; + +/** + * Created by matthew on 3/18/16. + */ +public class AutomationPendingNodeRegistryTaskTest extends BaseTest { + + @Test + public void testPendingNodeRemoved() { + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); + ProxySet proxySet = new ProxySet(false); + MockRemoteProxy proxy = new MockRemoteProxy(); + proxySet.add(proxy); + Map config = new HashMap<>(); + String instanceId = "instanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); + AutomationContext.getContext().addPendingNode(node); + config.put(AutomationConstants.INSTANCE_ID, instanceId); + proxy.setConfig(config); + proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); + task.setProxySet(proxySet); + + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); + task.doWork(); + Assert.assertFalse("Instance should not show as pending after task has run", AutomationContext.getContext().pendingNodeExists(instanceId)); + } + + @Test + public void testPendingNodeNotRemovedMismatchInstanceId() { + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); + ProxySet proxySet = new ProxySet(false); + MockRemoteProxy proxy = new MockRemoteProxy(); + proxySet.add(proxy); + Map config = new HashMap<>(); + String instanceId = "instanceId"; + String instanceIdDifferent = "differentInstanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceIdDifferent, null, null, null, new Date(), 1); + AutomationContext.getContext().addPendingNode(node); + config.put(AutomationConstants.INSTANCE_ID, instanceId); + proxy.setConfig(config); + proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); + task.setProxySet(proxySet); + + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceIdDifferent)); + task.doWork(); + Assert.assertTrue("Instance should still be pending as it hasn't come online", AutomationContext.getContext().pendingNodeExists(instanceIdDifferent)); + Assert.assertFalse("Instance should not be pending as it never was pending to begin with", AutomationContext.getContext().pendingNodeExists(instanceId)); + + } + + @Test + public void testPendingNodeNotRemovedNoConfigId() { + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); + ProxySet proxySet = new ProxySet(false); + MockRemoteProxy proxy = new MockRemoteProxy(); + proxySet.add(proxy); + Map config = new HashMap<>(); + String instanceId = "instanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); + AutomationContext.getContext().addPendingNode(node); + proxy.setConfig(config); + proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); + task.setProxySet(proxySet); + + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); + task.doWork(); + Assert.assertTrue("Node should still be pending after task runs as there was no instance id in the config", AutomationContext.getContext().pendingNodeExists(instanceId)); + } + + @Test + public void testPendingNodeNotRemovedNullProxySet() { + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); + ProxySet proxySet = new ProxySet(false); + String instanceId = "instanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); + AutomationContext.getContext().addPendingNode(node); + task.setProxySet(proxySet); + + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); + task.doWork(); + Assert.assertTrue("Node should still be pending after task runs as there was no instance id in the config", AutomationContext.getContext().pendingNodeExists(instanceId)); + } + + @Test + public void testPendingNodeNotRemovedEmptyProxySet() { + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); + ProxySet proxySet = new ProxySet(false); + String instanceId = "instanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); + AutomationContext.getContext().addPendingNode(node); + task.setProxySet(proxySet); + + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); + task.doWork(); + Assert.assertTrue("Node should still be pending after task runs as there was no instance id in the config", AutomationContext.getContext().pendingNodeExists(instanceId)); + } + + @Test + // Make sure the pending node gets removed after its been pending for too long + public void testPendingNodeRemovedAfterTimeout() { + MockVmManager vmManager = new MockVmManager(); + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null, vmManager); + String instanceId = "instanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, AutomationUtils.modifyDate(new Date(), - (AutomationRunContext.PENDING_NODE_EXPIRATION_TIME_IN_MINUTES + 1), Calendar.MINUTE), 1); + AutomationContext.getContext().addPendingNode(node); + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); + task.doWork(); + Assert.assertTrue("Instance should be terminated", vmManager.isTerminated()); + Assert.assertFalse("Instance should not show as pending after task has run", AutomationContext.getContext().pendingNodeExists(instanceId)); + Assert.assertEquals("Instance should have a terminated status", AutomationDynamicNode.STATUS.TERMINATED, node.getStatus()); + } + + @Test + // Make sure the pending node gets removed after its been pending for too long + public void testPendingNodeNotRemovedBeforeTimeout() { + MockVmManager vmManager = new MockVmManager(); + MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null, vmManager); + String instanceId = "instanceId"; + AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, AutomationUtils.modifyDate(new Date(), - (AutomationRunContext.PENDING_NODE_EXPIRATION_TIME_IN_MINUTES - 1), Calendar.MINUTE), 1); + AutomationContext.getContext().addPendingNode(node); + Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); + task.doWork(); + Assert.assertFalse("Instance should not be terminated as timeout wasn't hit", vmManager.isTerminated()); + Assert.assertTrue("Node should still be in the pending set as it wasn't removed", AutomationContext.getContext().pendingNodeExists(instanceId)); + Assert.assertEquals("Instance should have a terminated status", AutomationDynamicNode.STATUS.RUNNING, node.getStatus()); + } + +} diff --git a/src/test/java/com/rmn/qa/task/AutomationReaperTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationReaperTaskTest.java index 2f548c9..19e2e7c 100644 --- a/src/test/java/com/rmn/qa/task/AutomationReaperTaskTest.java +++ b/src/test/java/com/rmn/qa/task/AutomationReaperTaskTest.java @@ -1,28 +1,33 @@ package com.rmn.qa.task; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; + +import org.junit.Test; + import com.amazonaws.services.ec2.model.Instance; +import com.amazonaws.services.ec2.model.InstanceState; import com.amazonaws.services.ec2.model.Reservation; import com.rmn.qa.AutomationContext; import com.rmn.qa.AutomationDynamicNode; import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BaseTest; import com.rmn.qa.MockVmManager; -import junit.framework.Assert; -import org.junit.Test; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; +import junit.framework.Assert; -public class AutomationReaperTaskTest { +public class AutomationReaperTaskTest extends BaseTest { @Test public void testShutdown() { MockVmManager ec2 = new MockVmManager(); Reservation reservation = new Reservation(); - Instance instance = new Instance(); - String instanceId = "foo"; - instance.setInstanceId(instanceId); - instance.setLaunchTime(AutomationUtils.modifyDate(new Date(),-5,Calendar.HOUR)); + String instanceId = "foobar"; + Instance instance = new Instance() + .withState(new InstanceState().withCode(45)) + .withInstanceId(instanceId) + .withLaunchTime(AutomationUtils.modifyDate(new Date(),-5,Calendar.HOUR)); reservation.setInstances(Arrays.asList(instance)); ec2.setReservations(Arrays.asList(reservation)); AutomationReaperTask task = new AutomationReaperTask(null,ec2); diff --git a/src/test/java/com/rmn/qa/task/AutomationRunCleanupTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationRunCleanupTaskTest.java index c4511e6..4e59b0a 100644 --- a/src/test/java/com/rmn/qa/task/AutomationRunCleanupTaskTest.java +++ b/src/test/java/com/rmn/qa/task/AutomationRunCleanupTaskTest.java @@ -12,23 +12,31 @@ package com.rmn.qa.task; -import com.rmn.qa.*; -import junit.framework.Assert; +import java.util.Calendar; +import java.util.Date; + import org.junit.Test; import org.openqa.grid.internal.ProxySet; +import org.openqa.selenium.Platform; -import java.util.Calendar; -import java.util.Date; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationRunContext; +import com.rmn.qa.AutomationRunRequest; +import com.rmn.qa.AutomationUtils; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockRemoteProxy; + +import junit.framework.Assert; /** * Created by mhardin on 5/1/14. */ -public class AutomationRunCleanupTaskTest { +public class AutomationRunCleanupTaskTest extends BaseTest { @Test // Tests that an old run not in progress is no longer registered public void testCleanup() { - AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"firefox","10","linux", AutomationUtils.modifyDate(new Date(), -1, Calendar.HOUR)); + AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"firefox","10", Platform.LINUX, AutomationUtils.modifyDate(new Date(), -1, Calendar.HOUR)); AutomationRunContext context = AutomationContext.getContext(); context.addRun(oldRequest); diff --git a/src/test/java/com/rmn/qa/task/AutomationScaleNodeTaskTest.java b/src/test/java/com/rmn/qa/task/AutomationScaleNodeTaskTest.java new file mode 100644 index 0000000..b4bc546 --- /dev/null +++ b/src/test/java/com/rmn/qa/task/AutomationScaleNodeTaskTest.java @@ -0,0 +1,346 @@ +package com.rmn.qa.task; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; + +import org.junit.Test; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.CapabilityType; +import org.openqa.selenium.remote.DesiredCapabilities; + +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.BaseTest; +import com.rmn.qa.MockRequestMatcher; +import com.rmn.qa.MockVmManager; + +import junit.framework.Assert; + +/** + * Created by matthew on 3/18/16. + */ +public class AutomationScaleNodeTaskTest extends BaseTest { + + + @Test + // Handle + public void testNoQueuedRequests() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + task.setDesiredCapabilities(Collections.emptyList()); + task.run(); + Assert.assertEquals("No nodes should have been started", 0, task.getNodesStarted().size()); + } + + @Test + // Unsupported browser on a queued request should not result in any scaled capacity + public void testUnsupportedBrowserNoNodesStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "safari"); + task.setDesiredCapabilities(Arrays.asList(capabilities)); + task.run(); + Assert.assertEquals("No nodes should have been started", 0, task.getNodesStarted().size()); + } + + @Test + // If an unsupported platform is queued, no node should be started + public void testUnsupportedPlatformNoNodesStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setPlatform(Platform.MAC); + task.setDesiredCapabilities(Arrays.asList(capabilities)); + task.run(); + Assert.assertEquals("No nodes should have been started", 0, task.getNodesStarted().size()); + } + + @Test + // Not sure if this will ever happen in the wild, but if no platform is specified, make sure we're defaulting to ANY + public void testNullPlatformNodeStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + task.setDesiredCapabilities(Arrays.asList(capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 1, task.getNodesStarted().size()); + Assert.assertNotNull("Nodes should have been started", task.getNodesStarted().get(0)); + Assert.assertEquals("Started browser should be correct", "chrome", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.ANY, task.getPlatformStarted().get(0)); + Assert.assertEquals("Number of nodes started should be correct", 1, task.getNumThreadsStarted().get(0).intValue()); + } + + @Test + // If a queued request specifies ANY, we should handle starting up a new node + public void testAnyPlatformNodeStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.ANY); + task.setDesiredCapabilities(Arrays.asList(capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 1, task.getNodesStarted().size()); + Assert.assertNotNull("Nodes should have been started", task.getNodesStarted().get(0)); + Assert.assertEquals("Started browser should be correct", "chrome", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.ANY, task.getPlatformStarted().get(0)); + Assert.assertEquals("Number of nodes started should be correct", 1, task.getNumThreadsStarted().get(0).intValue()); + } + + @Test + // If multiple requests have different platforms from the same family, only one type of platform should start + public void testMultiplePlatformsStartOneTypeOfNode() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + DesiredCapabilities capabilities2 = new DesiredCapabilities(); + capabilities2.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities2.setCapability(CapabilityType.PLATFORM, Platform.UNIX); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities2)); + task.run(); + Assert.assertEquals("Number of threads should be correct", 1, task.getNumThreadsStarted().size()); + Assert.assertEquals("Nodes should have been started", 2, task.getNumThreadsStarted().get(0).intValue()); + Assert.assertEquals("Started browser should be correct", "chrome", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.UNIX, task.getPlatformStarted().get(0)); + } + @Test + // If multiple requests have different platforms from different families, multiple types of nodes should start + public void testDifferentPlatformsStartDifferentNodes() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + DesiredCapabilities capabilities2 = new DesiredCapabilities(); + capabilities2.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities2.setCapability(CapabilityType.PLATFORM, Platform.WINDOWS); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities2)); + task.run(); + Assert.assertEquals("Nodes should have been started", 2, task.getNodesStarted().size()); + Assert.assertNotNull("Nodes should have been started", task.getNodesStarted().get(0)); + Assert.assertNotNull("Nodes should have been started", task.getNodesStarted().get(1)); + Assert.assertEquals("Started browser should be correct", 2, task.getBrowserStarted().stream().filter(browser -> "chrome".equals(browser)).count()); + Assert.assertEquals("Started platform should be correct", 1, task.getPlatformStarted().stream().filter(platform -> platform == Platform.WINDOWS).count()); + Assert.assertEquals("Started platform should be correct", 1, task.getPlatformStarted().stream().filter(platform -> platform == Platform.UNIX).count()); + Assert.assertEquals("Number of nodes started should be correct", 1, task.getNumThreadsStarted().get(0).intValue()); + Assert.assertEquals("Number of nodes started should be correct", 1, task.getNumThreadsStarted().get(1).intValue()); + } + + @Test + // Happy path, make sure queued requests for linux result in started instances + public void testLinuxPlatformNodeStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + task.setDesiredCapabilities(Arrays.asList(capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 1, task.getNodesStarted().size()); + Assert.assertNotNull("Nodes should have been started", task.getNodesStarted().get(0)); + Assert.assertEquals("Started browser should be correct", "chrome", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.UNIX, task.getPlatformStarted().get(0)); + Assert.assertEquals("Number of nodes started should be correct", 1, task.getNumThreadsStarted().get(0).intValue()); + } + + @Test + // Test that if there are nodes pending startup that haven't come online, their load is correctly factored into future + // scale activity + public void testPendingLoadSubtractsFromQueuedRequests() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 2, task.getNodesStarted().size()); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities, capabilities, capabilities)); + // Clear out previous counts from the mock task so they don't interfere with the new run + task.clear(); + task.run(); + Assert.assertEquals("Only 2 nodes should start as pending count should be subtracted from count", 2, task.getNodesStarted().size()); + } + + @Test + // Test that if there are nodes pending startup that haven't come online, their load is correctly factored into future + // scale activity + public void testUnrelatedPendingLoadDoesntSubtractsFromQueuedRequests() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + // Add 2 nodes to our pending context list and make sure this doesn't affect our scale node logic. Only pending nodes that + // originate from AutomationScaleNodeTask should be included in the count + AutomationContext.getContext().addPendingNode(new AutomationDynamicNode(null, "foo", null, null, null, new Date(), 1)); + AutomationContext.getContext().addPendingNode(new AutomationDynamicNode(null, "bar", null, null, null, new Date(), 1)); + task.run(); + Assert.assertEquals("Nodes should have been started", 2, task.getNodesStarted().size()); + } + + @Test + // Make sure after a node has registers with the hub, that it is no longer subtracted from scale activity + public void testPendingLoadDoesntSubtractsFromQueuedRequestsNodesStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 2, task.getNodesStarted().size()); + // Clear all nodes from pending to emulate the nodes starting up + task.getNodesStarted().stream().forEach(node -> AutomationContext.getContext().removePendingNode(node.getInstanceId())); + // Clear out previous counts from the mock task so they don't interfere with the new run + task.clear(); + task.run(); + Assert.assertEquals("Only 2 nodes should start as pending count should be subtracted from count", 2, task.getNodesStarted().size()); + } + + @Test + // Make sure that nodes pending startup don't count towards other non-matching queued requests + public void testPendingLoadDoesntSubtractsFromQueuedRequests() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 2, task.getNodesStarted().size()); + // Change the platform so we have different requests coming in + capabilities.setPlatform(Platform.WINDOWS); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities, capabilities, capabilities)); + // Clear out previous counts from the mock task so they don't interfere with the new run + task.clear(); + task.run(); + Assert.assertEquals("Only 2 nodes should start as pending count should be subtracted from count", 4, task.getNodesStarted().size()); + } + + @Test + // Make sure a higher specified platform dumbs down to its correct family (e.g. LINUX -> UNIX) + public void testFamilyPlatformNodeStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.LINUX); + task.setDesiredCapabilities(Arrays.asList(capabilities)); + task.run(); + Assert.assertEquals("Nodes should have been started", 1, task.getNodesStarted().size()); + Assert.assertNotNull("Nodes should have been started", task.getNodesStarted().get(0)); + Assert.assertEquals("Started browser should be correct", "chrome", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.UNIX, task.getPlatformStarted().get(0)); + Assert.assertEquals("Number of nodes started should be correct", 1, task.getNumThreadsStarted().get(0).intValue()); + } + + @Test + // Tests that multiple queued requests result in multiple nodes started in one task pass + public void testMultipleNodesStartedAtOnce() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.ANY); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("Number of threads should be correct", 1, task.getNumThreadsStarted().size()); + Assert.assertEquals("Nodes should have been started", 2, task.getNumThreadsStarted().get(0).intValue()); + Assert.assertEquals("Started browser should be correct", "chrome", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.ANY, task.getPlatformStarted().get(0)); + } + + @Test + // Unsupported platform should not result in any scaled nodes + public void testUnsupportedPlatformNodeNotStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + capabilities.setCapability(CapabilityType.PLATFORM, Platform.MAC); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("No nodes should have been started", 0, task.getNodesStarted().size()); + } + + @Test + // If a certain browser/platform hasn't been queued long enough, no nodes should be scaled up + public void testNotEnoughTimePassedNoNewNodeStarted() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + task.setNodeOldEnoughToStart(false); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("No nodes should have been started", 0, task.getNodesStarted().size()); + } + + @Test + // Make sure if a previously tracked browser was seen by the task, that nothing starts up for that browser if there + // aren't any matching queued test requests + public void testPreviouslyTrackedBrowserDoesntStart() { + MockVmManager manageEc2 = new MockVmManager(); + MockRequestMatcher matcher = new MockRequestMatcher(); + matcher.setThreadsToReturn(10); + MockAutomationScaleNodeTask task = new MockAutomationScaleNodeTask(null, manageEc2); + task.setNodeOldEnoughToStart(false); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome"); + task.setDesiredCapabilities(Arrays.asList(capabilities, capabilities)); + task.run(); + Assert.assertEquals("No nodes should have been started", 0, task.getNodesStarted().size()); + task.setNodeOldEnoughToStart(true); + DesiredCapabilities nonMatchingBrowser = new DesiredCapabilities(); + nonMatchingBrowser.setBrowserName("firefox"); + task.setDesiredCapabilities(Arrays.asList(nonMatchingBrowser, nonMatchingBrowser)); + task.run(); + // Firefox, not chrome, should start up + Assert.assertEquals("Number of threads should be correct", 1, task.getNumThreadsStarted().size()); + Assert.assertEquals("Nodes should have been started", 2, task.getNumThreadsStarted().get(0).intValue()); + Assert.assertEquals("Started browser should be correct", "firefox", task.getBrowserStarted().get(0)); + Assert.assertEquals("Started platform should be correct", Platform.ANY, task.getPlatformStarted().get(0)); + } + + +} diff --git a/src/test/java/com/rmn/qa/task/MockAutomationNodeCleanupTask.java b/src/test/java/com/rmn/qa/task/MockAutomationNodeCleanupTask.java index c1afdb2..6d809f3 100644 --- a/src/test/java/com/rmn/qa/task/MockAutomationNodeCleanupTask.java +++ b/src/test/java/com/rmn/qa/task/MockAutomationNodeCleanupTask.java @@ -12,14 +12,17 @@ package com.rmn.qa.task; -import com.rmn.qa.RequestMatcher; +import org.openqa.grid.internal.ProxySet; +import org.openqa.grid.internal.RemoteProxy; + import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.RequestMatcher; import com.rmn.qa.aws.VmManager; -import org.openqa.grid.internal.ProxySet; public class MockAutomationNodeCleanupTask extends AutomationNodeCleanupTask { private ProxySet proxySet; + private boolean proxyRemoved = false; public MockAutomationNodeCleanupTask(RegistryRetriever retrieveContext, VmManager ec2, RequestMatcher requestMatcher) { super(retrieveContext, ec2, requestMatcher); @@ -33,4 +36,13 @@ protected ProxySet getProxySet() { public void setProxySet(ProxySet proxySet) { this.proxySet = proxySet; } + + @Override + protected void removeProxy(RemoteProxy proxy) { + proxyRemoved = true; + } + + public boolean isProxyRemoved() { + return proxyRemoved; + } } diff --git a/src/test/java/com/rmn/qa/task/MockAutomationNodeRegistryTask.java b/src/test/java/com/rmn/qa/task/MockAutomationOrphanedNodeRegistryTask.java similarity index 83% rename from src/test/java/com/rmn/qa/task/MockAutomationNodeRegistryTask.java rename to src/test/java/com/rmn/qa/task/MockAutomationOrphanedNodeRegistryTask.java index 1632d72..979a704 100644 --- a/src/test/java/com/rmn/qa/task/MockAutomationNodeRegistryTask.java +++ b/src/test/java/com/rmn/qa/task/MockAutomationOrphanedNodeRegistryTask.java @@ -12,17 +12,18 @@ package com.rmn.qa.task; -import com.rmn.qa.RegistryRetriever; import org.openqa.grid.internal.ProxySet; +import com.rmn.qa.RegistryRetriever; + /** * Created by mhardin on 4/24/14. */ -public class MockAutomationNodeRegistryTask extends AutomationNodeRegistryTask { +public class MockAutomationOrphanedNodeRegistryTask extends AutomationOrphanedNodeRegistryTask { private ProxySet proxySet; - public MockAutomationNodeRegistryTask(RegistryRetriever retrieveContext) { + public MockAutomationOrphanedNodeRegistryTask(RegistryRetriever retrieveContext) { super(retrieveContext); } diff --git a/src/test/java/com/rmn/qa/task/MockAutomationPendingNodeRegistryTask.java b/src/test/java/com/rmn/qa/task/MockAutomationPendingNodeRegistryTask.java new file mode 100644 index 0000000..19c9ff2 --- /dev/null +++ b/src/test/java/com/rmn/qa/task/MockAutomationPendingNodeRegistryTask.java @@ -0,0 +1,32 @@ +package com.rmn.qa.task; + +import org.openqa.grid.internal.ProxySet; + +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.aws.MockManageVm; +import com.rmn.qa.aws.VmManager; + +/** + * Created by matthew on 3/18/16. + */ +public class MockAutomationPendingNodeRegistryTask extends AutomationPendingNodeRegistryTask { + + private ProxySet proxySet; + + public MockAutomationPendingNodeRegistryTask(RegistryRetriever registryRetriever) { + super(registryRetriever, new MockManageVm()); + } + + public MockAutomationPendingNodeRegistryTask(RegistryRetriever registryRetriever, VmManager vmManager) { + super(registryRetriever, vmManager); + } + + @Override + protected ProxySet getProxySet() { + return proxySet; + } + + public void setProxySet(ProxySet proxySet) { + this.proxySet = proxySet; + } +} diff --git a/src/test/java/com/rmn/qa/task/MockAutomationScaleNodeTask.java b/src/test/java/com/rmn/qa/task/MockAutomationScaleNodeTask.java new file mode 100644 index 0000000..8ae083b --- /dev/null +++ b/src/test/java/com/rmn/qa/task/MockAutomationScaleNodeTask.java @@ -0,0 +1,101 @@ +package com.rmn.qa.task; + +import java.util.Date; +import java.util.List; + +import org.openqa.grid.internal.ProxySet; +import org.openqa.selenium.Platform; +import org.openqa.selenium.remote.DesiredCapabilities; + +import com.beust.jcommander.internal.Lists; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.MockVmManager; +import com.rmn.qa.NodesCouldNotBeStartedException; +import com.rmn.qa.RegistryRetriever; +import com.rmn.qa.aws.VmManager; + +/** + * Created by matthew on 3/18/16. + */ +public class MockAutomationScaleNodeTask extends AutomationScaleNodeTask { + + private ProxySet proxySet; + private Iterable desiredCapabilities; + private List nodesStarted = Lists.newArrayList(); + private List browserStarted = Lists.newArrayList(); + private List platformStarted = Lists.newArrayList(); + private List numThreadsStarted = Lists.newArrayList(); + private boolean nodeOldEnoughToStart = true; + + public MockAutomationScaleNodeTask(RegistryRetriever retriever, MockVmManager vmManager) { + super(retriever, vmManager); + } + + @Override + public ProxySet getProxySet() { + return proxySet; + } + + public void setProxySet(ProxySet proxySet) { + this.proxySet = proxySet; + } + + @Override + Iterable getDesiredCapabilities() { + return desiredCapabilities; + } + + public void setDesiredCapabilities(Iterable desiredCapabilities) { + this.desiredCapabilities = desiredCapabilities; + } + + @Override + List startNodes(VmManager vmManager, int browsersToStart, String browser, Platform platform) throws NodesCouldNotBeStartedException { + List createdNodes = Lists.newArrayList(); + numThreadsStarted.add(browsersToStart); + browserStarted.add(browser); + platformStarted.add(platform); + for (int i=0;i getNodesStarted() { + return nodesStarted; + } + + public List getBrowserStarted() { + return browserStarted; + } + + public List getPlatformStarted() { + return platformStarted; + } + + public List getNumThreadsStarted() { + return numThreadsStarted; + } + + public void clear() { + nodesStarted = Lists.newArrayList(); + browserStarted = Lists.newArrayList(); + platformStarted = Lists.newArrayList(); + numThreadsStarted = Lists.newArrayList(); + } +} + diff --git a/src/test/java/com/rmn/qa/task/ScaleCapacityContextTest.java b/src/test/java/com/rmn/qa/task/ScaleCapacityContextTest.java new file mode 100644 index 0000000..87ff06f --- /dev/null +++ b/src/test/java/com/rmn/qa/task/ScaleCapacityContextTest.java @@ -0,0 +1,53 @@ +package com.rmn.qa.task; + +import java.util.Date; + +import org.junit.Test; + +import com.beust.jcommander.internal.Lists; +import com.rmn.qa.AutomationContext; +import com.rmn.qa.AutomationDynamicNode; +import com.rmn.qa.BaseTest; + +import junit.framework.Assert; + +/** + * Created by matthew on 3/18/16. + */ +public class ScaleCapacityContextTest extends BaseTest { + + @Test + public void testCapacityCount() { + ScaleCapacityContext context = new ScaleCapacityContext(); + AutomationDynamicNode firstNode = new AutomationDynamicNode(null, "foo", null, null, new Date(), 1); + AutomationDynamicNode secondNode = new AutomationDynamicNode(null, "bar", null, null, new Date(), 3); + int expectedCount = firstNode.getNodeCapacity() + secondNode.getNodeCapacity(); + context.addAll(Lists.newArrayList(firstNode, secondNode)); + Assert.assertEquals("Total capacity count should be correct", expectedCount, context.getTotalCapacityCount()); + } + + @Test + public void clearPendingNodes() { + ScaleCapacityContext context = new ScaleCapacityContext(); + AutomationDynamicNode firstNode = new AutomationDynamicNode(null, "foo", null, null, new Date(), 1); + AutomationDynamicNode secondNode = new AutomationDynamicNode(null, "bar", null, null, new Date(), 1); + context.addAll(Lists.newArrayList(firstNode, secondNode)); + Assert.assertEquals("Size should be correct as nodes should exist in context", 2, context.nodesPendingStartup.size()); + context.clearPendingNodes(); + Assert.assertEquals("Size should be empty as nodes should be removed", 0, context.nodesPendingStartup.size()); + } + + @Test + public void nodesDontClear() { + ScaleCapacityContext context = new ScaleCapacityContext(); + AutomationDynamicNode firstNode = new AutomationDynamicNode(null, "foo", null, null, new Date(), 1); + AutomationDynamicNode secondNode = new AutomationDynamicNode(null, "bar", null, null, new Date(), 1); + context.addAll(Lists.newArrayList(firstNode, secondNode)); + Assert.assertEquals("Size should be correct as nodes should exist in context", 2, context.nodesPendingStartup.size()); + AutomationContext.getContext().addPendingNode(firstNode); + AutomationContext.getContext().addPendingNode(secondNode); + context.clearPendingNodes(); + Assert.assertEquals("Size should be the same as nodes should exist in context", 2, context.nodesPendingStartup.size()); + } + +}