├── .gitignore ├── .travis.yml ├── Dockerfile ├── HowToSetupAVPC.md ├── README.md ├── automation-grid.jar ├── license.txt ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── rmn │ │ │ └── qa │ │ │ ├── AutomationCapabilityMatcher.java │ │ │ ├── AutomationConstants.java │ │ │ ├── AutomationContext.java │ │ │ ├── AutomationDynamicNode.java │ │ │ ├── AutomationRequestMatcher.java │ │ │ ├── AutomationRunContext.java │ │ │ ├── AutomationRunRequest.java │ │ │ ├── AutomationUtils.java │ │ │ ├── BrowserPlatformPair.java │ │ │ ├── NodesCouldNotBeStartedException.java │ │ │ ├── RegistryRetriever.java │ │ │ ├── RequestMatcher.java │ │ │ ├── aws │ │ │ ├── AwsTagReporter.java │ │ │ ├── AwsVmManager.java │ │ │ └── VmManager.java │ │ │ ├── servlet │ │ │ ├── AutomationStatusServlet.java │ │ │ ├── AutomationTestRunServlet.java │ │ │ └── StatusServlet.java │ │ │ └── task │ │ │ ├── AbstractAutomationCleanupTask.java │ │ │ ├── AutomationHubCleanupTask.java │ │ │ ├── AutomationNodeCleanupTask.java │ │ │ ├── AutomationOrphanedNodeRegistryTask.java │ │ │ ├── AutomationPendingNodeRegistryTask.java │ │ │ ├── AutomationReaperTask.java │ │ │ ├── AutomationRunCleanupTask.java │ │ │ ├── AutomationScaleNodeTask.java │ │ │ └── ScaleCapacityContext.java │ └── resources │ │ ├── .s3cfg │ │ ├── aws.properties.default │ │ ├── hub.static.json │ │ ├── logback.xml │ │ ├── node.linux.json │ │ └── node.windows.json └── test │ └── java │ └── com │ └── rmn │ └── qa │ ├── AbstractAutomationCleanupTaskTest.java │ ├── AutomationCapabilityMatcherTest.java │ ├── AutomationDynamicNodeTest.java │ ├── AutomationRequestMatcherTest.java │ ├── AutomationRunContextTest.java │ ├── AutomationRunRequestTest.java │ ├── AutomationUtilsTest.java │ ├── BaseTest.java │ ├── MockHttpServletRequest.java │ ├── MockHttpServletResponse.java │ ├── MockRemoteProxy.java │ ├── MockRequestMatcher.java │ ├── MockVmManager.java │ ├── TestBrowserPlatformPair.java │ ├── aws │ ├── AwsTagReporterTest.java │ ├── MockAmazonEc2Client.java │ ├── MockManageVm.java │ └── VmManagerTest.java │ ├── servlet │ ├── AutomationTestRunServletTest.java │ └── MockAutomationTestRunServlet.java │ └── task │ ├── AutomationHubCleanupTaskTest.java │ ├── AutomationNodeCleanupTaskTest.java │ ├── AutomationOrphanedNodeRegistryTaskTest.java │ ├── AutomationPendingNodeRegistryTaskTest.java │ ├── AutomationReaperTaskTest.java │ ├── AutomationRunCleanupTaskTest.java │ ├── AutomationScaleNodeTaskTest.java │ ├── MockAutomationHubCleanupTask.java │ ├── MockAutomationNodeCleanupTask.java │ ├── MockAutomationOrphanedNodeRegistryTask.java │ ├── MockAutomationPendingNodeRegistryTask.java │ ├── MockAutomationRunCleanupTask.java │ ├── MockAutomationScaleNodeTask.java │ └── ScaleCapacityContextTest.java └── startGrid /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.rar 19 | *.tar 20 | *.zip 21 | 22 | # Logs, databases, and properties # 23 | ###################### 24 | *.log* 25 | *.sql 26 | *.sqlite 27 | *.properties 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | Icon? 37 | ehthumbs.db 38 | Thumbs.db 39 | 40 | # IntelliJ # 41 | ############ 42 | .idea 43 | *.iml 44 | 45 | # Eclipse cruft # 46 | ################# 47 | .project 48 | .metadata 49 | bin/** 50 | tmp/** 51 | tmp/**/* 52 | *.tmp 53 | *.bak 54 | *.swp 55 | *~.nib 56 | local.properties 57 | .classpath 58 | .settings/ 59 | .loadpath 60 | 61 | # External tool builders 62 | .externalToolBuilders/ 63 | 64 | # Locally stored "Eclipse launch configurations" 65 | *.launch 66 | 67 | # CDT-specific 68 | .cproject 69 | 70 | # PDT-specific 71 | .buildpath 72 | 73 | # Maven stuff 74 | target/ 75 | test-output/ 76 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk7 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:7-jdk 2 | ADD target /target 3 | ADD startGrid /usr/bin/startGrid 4 | RUN chmod +x /usr/bin/startGrid 5 | CMD ["/usr/bin/startGrid"] 6 | -------------------------------------------------------------------------------- /HowToSetupAVPC.md: -------------------------------------------------------------------------------- 1 | 1.) In your Amazon Web Services console, click the 'VPC' link. This should take you to the VPC Dashboard. 2 | 3 | 2.) Click 'Start VPC Wizard' at the top of the page. You should now be on the Step 1: Select a VPC Configuration page. 4 | 5 | 3.) Select the 'VPC with Public and Private Subnets' page. Click the 'Select' button on the right. You should now be on Step 2 6 | 7 | 4.) Give the VPC a name, such as "Selenium VPC". Name the Public subnet to be "Selenium Public Subnet" and the Private subnet to be "Selenium Private Subnet". Leave all other values as default. 8 | 9 | 5.) Click 'Create VPC' and then wait a few moments for the wizard to complete. Click the OK button after it finishes. 10 | 11 | 6.) In the VPC Dashboard, click on 'Your VPCs'. Find the VPC you created above, and note the VPC ID. 12 | 13 | 7.) In the VPC Dashboard, click on 'Subnets'. Find the private subnet you created above, and note its subnet ID (e.g. subnet-9ed94444). This will be the subnet you use to launch your nodes in in your properties file. The value will be for the \_linux\_node\_ami value, where is the name of your region, like east or west. So if you're in the 'east' region, the key would be east\_linux\_node\_ami. 14 | 15 | 8.) Select the public subnet you created named 'Selenium Public Subnet' and click on 'Inbound Rules'. You will want to add a rule by clicking 'Edit' and then 'Add another rule'. Type should be 'SSH (22)' for SSH access. If your externally facing IP address was 43.68.39.29, you would put 43.68.39.29/30 for the Source, so basically add "/30" to your externally facing IP address. If you need access to any additional ports, you should add them here. 16 | 17 | 9.) In the VPC Dashboard, click on 'Security Groups'. Find the security group for your VPC ID in step 6. This will be used for the \_security\_group value in your configuration file. 18 | 19 | 10.) Click 'Services' at the top, then click on 'EC2'. Now click on 'Instances' in the left hand side. Find the running instance that represents the NAT the wizard created (should be associated with your VPC ID from step 6). Select the instance, and there should be a public ip address under 'Public IP' in the 'Description' section at the bottom. **This will be the IP address that you must get whitelisted in your networking setup in order for the nodes to be able to hit your test environment. This is very important or your tests will not be able to run successfully.** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SeleniumGridScaler [![Build Status](https://travis-ci.org/RetailMeNot/SeleniumGridScaler.svg?branch=master)](https://travis-ci.org/RetailMeNot/SeleniumGridScaler) 2 | 3 | *** 4 | #### Overview 5 | 6 | A Selenium Grid plugin for integrating your project with AWS (Amazon Web Services) to allow for resources (EC2 'nodes') to dynamically spin up/down (autoscale) depending on current test load. The jar should be pulled down and installed to serve as a hub for the desired Selenium Grid setup. 7 | 8 | #### Details 9 | New nodes will be started as needed depending on the browser requested: 10 | 11 | * 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 12 | * 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. 13 | * Internet Explorer - IE is currently not supported. There are plans to add IE support in an upcoming release. 14 | 15 | 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). 16 | 17 | ## Requirements 18 | A Java 8 or later runtime is required in order to run this application 19 | 20 | ## Installation/Startup 21 | 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. 22 | 23 | java -DawsAccessKey=foo -DawsSecretKey=bar -DipAddress="10.10.28.205" -cp automation-grid.jar org.openqa.grid.selenium.GridLauncher -role hub -servlets "com.rmn.qa.servlet.AutomationTestRunServlet","com.rmn.qa.servlet.StatusServlet" -hubConfig hub.json 24 | 25 | 26 | The -hubConfig configuration key and value are optional to specify Selenium Hub configuration information. Please see the 'Selenium Configuration' section below for more information. After starting the hub process, you should be able to run your tests against the hub in the **Running Your Tests** section below 27 | 28 | ## Installation/Startup w/Docker 29 | Clone source code repository, and build target jars with: 30 | 31 | mvn install 32 | 33 | After building target jars, build docker image: 34 | 35 | docker build -t selenium-grid-ec2 36 | 37 | After the docker image has been built, you can start a new docker container with it: 38 | 39 | docker run -d -p 4444:4444 -e AWS_ACCESS_KEY={REPLACE_WITH_YOUR_KEY} -e AWS_SECRET_KEY={REPLACE_WITH_SECRET_KEY selenium-grid-ec2 40 | 41 | ## Startup Configuration 42 | Certain configuration values can be passed in to change the default behavior of SeleniumGridScaler 43 | 44 | System properties 45 | 46 | * awsAccessKey (most likely required) - This should be the Access Key ID for your AWS account. If not set permissions are derived from IAM role. 47 | * awsSecretKey (most likely required) - This should be the Secret Key for your AWS account. If not set permissions are derived from IAM role. 48 | * ipAddress (required) - Resolvable host name or ip address of the hub for started nodes to connect to. Note 'localhost' or '127.0.0.1' will not work as this will not be resolvable from the context of another machine 49 | * totalNodeCount - Maximum number of nodes that can connect to your hub. Defaults to 150 if not specified 50 | * extraCapabilities - CSV list of capabilities you want to be considered if specified client side (e.g. adding 'target' to this list will require any capabilities coming in with the 'target' key present to match the value in the node config) 51 | * useReaperThread - Set this to 'false' if you do not want the reaper thread running. The reaper thread will automatically terminate instances that are not registered with the hub to prevent unused instances from accumulating in AWS over time. This can be useful to disable if you're trying to debug a node that the plugin started and killed the java process on it. 52 | 53 | More information can be found [here](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html) on AWS Access 54 | 55 | ## Running Your Tests 56 | In your test run, you must send a GET HTTP request to the servlet, with the required query string parameters, to ensure the requested resources are available. 57 | 58 | * browser - This is the browser you want to run in. Currently supported values, are chrome, firefox, and internetexplorer 59 | * threadCount - Number of threads you want to run for your tests 60 | * uuid - Unique test run identifier 61 | * 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. 62 | 63 | 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 64 | 65 | http://hubIpAddress:4444/grid/admin/AutomationTestRunServlet?uuid=testRun1&threadCount=10&browser=chrome 66 | 67 | Possible response codes: 68 | 69 | * Returns a 201 if the request can be fulfilled but AMIs must be started 70 | * Returns a 202 if the request can be fulfilled with current capacity 71 | * Returns a 400 if the required parameters are not passed in. 72 | * Returns a 409 if the server is at full node capacity 73 | * Returns a 500 for an unexpected error condition (see log for details) 74 | 75 | After a successful response code (201 or 202), you should be able to run your test run against the hub end point like so. 76 | Note that the test run UUID used in the API call above must be passed in via a desired capability or you may run into unintended side effects 77 | 78 | 79 | 80 | URL url = null; 81 | try { 82 | url = new URL("http://servername:4444/wd/hub"); 83 | } catch (MalformedURLException e) { 84 | e.printStackTrace(); 85 | } 86 | capabilities.setBrowserName("chrome"); 87 | // The test run UUID associated with the test needs to be passed in 88 | // so SeleniumGridScaler can associate in progress tests with test runs 89 | capabilities.setCapability("uuid","testRun1"); 90 | WebDriver driver = new RemoteWebDriver(url,capabilities); 91 | 92 | ## Selenium Configuration 93 | Note that on top of the SeleniumGridScaler configuration, you can use all existing configurations for Selenium. Available selenium configurations values can be found [here](https://code.google.com/p/selenium/source/browse/java/server/src/org/openqa/grid/common/defaults/GridParameters.properties) and an example of passing them in can be seen in the **Running Your Tests** section above 94 | 95 | ## Test Access 96 | If you're trying to test against a website thats not publicly available on the internet, such as a private testing environment behind a firewall, you will need to setup up some sort of solution to allow your nodes have to access your test environment(s). Two ways of granting access have been included below. 97 | 98 | * **Whitelist IP Addresses** One way to accomplish access for your dynamically started nodes to have access to your test environments is by white listing IP addresses in AWS. This [guide](https://github.com/RetailMeNot/SeleniumGridScaler/blob/master/HowToSetupAVPC.md) is a great way to show how to setup two separate subnets, and route all traffic through a single NAT machine so that you only have to whitelist the IP address of the NAT machine, and not for every individual node. Essentially, you'll create a VPC with 2 subnets, one private and one public, each occupying half the range of the VPC (or whatever you prefer). The NAT will live in the public subnet, with the nodes living in the private subnet. You will then configure a security group to route all external traffic (outside the VPC range) in the private subnet through the NAT and associate this security group with your nodes when you start them up. You will also want to create an internet gateway and associate it with your VPC so your machines can have access to the internet. 99 | 100 | * **Setup a Tunnel** Another way to configure access for your nodes to hit your test environments would be to setup a network tunnel of some sort. You would then need to modify the AMI to run this tunnel on startup or have it running 24/7. You will probably need to work with your IT department to accomplish this. 101 | 102 | ## Logging 103 | Logging will be saved to a log file found in in /log/automation.log. If you would like to change the location, you can override the directory and log file name with the 'logLocation' system property (e.g. -DlogLocation=/log/myLogName.log) The current logging levels is set to INFO or higher. 104 | 105 | ## Installation to Develop Locally 106 | 107 | The SeleniumGridScaler is a Maven project, which means when you have downloaded it and opened it in your favorite 108 | Maven-enabled IDE, you should be able to run "mvn install", and a SNAPSHOT jar file will be deployed to your 109 | local Maven repo. 110 | 111 | Once the jar has been deployed, you should be able to include a small snippet in your project pom file to point to your installed SNAPSHOT to develop and test any changes you may want to make: 112 | 113 | 114 | com.retailmenot 115 | selenium-grid-scaler 116 | 1.1-SNAPSHOT 117 | 118 | 119 | If you're currently using Maven, and your repos, paths, and IDE are all set up correctly, you should be able 120 | to address the classes in this project immediately (you may need to update your Maven dependencies). 121 | 122 | If you would prefer to simply build a jar file and include it into your classpath, run "mvn package", and 123 | the jar file should appear in the target folder under the main folder and then you can add it to your sources for your project. 124 | 125 | ## AWS Customization 126 | If you would like to specify custom attributes for the instances that will be started in AWS, you can do so by creating a .properties file 127 | and passing it in via a system property 'propertyFileLocation' for the file location (e.g. -DpropertyFileLocation=/myPath/customAws.properties). Please see aws.properties.sample for an idea of how the format should look. 128 | Possible values to override/implement: 129 | 130 | * region - Represents the region you are running in, such as 'east' or 'west'. 131 | * _endpoint - Endpoint address for the region's API 132 | * \_linux\_node_ami - AMI ID for linux instances to be started 133 | * \_windows\_node_ami - AMI ID for windows instances to be started 134 | * \_security\_group (optional) - Security group ID for the instance to use 135 | * \_subnet_id (optional) - VPC ID for the instance to use 136 | * \_subnet\_fallback\_id\_1 (optional) - Set of fallback subnet IDs to use if the availability zone in the above subnet is full. Naming should have a 1, 2, 3, etc suffix for the naming convention. See aws.properties.sample for an example 137 | * \_key_name (optional) - Key name to associate with the instance 138 | Please see AWS documentation for more information on how to use/setup these things 139 | * 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') 140 | 141 | ## Current Issues 142 | 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 143 | 144 | ## Contributing 145 | 146 | All pull requests are greatly appreciated! This project is intended for end users of Selenium Grid, but we've only implemented functionality we've needed to handle our current needs. If you need new features, open an issue on github, or better yet, contribute your own features! 147 | 148 | The SeleniumGridScaler is a Maven/Java project, using JUnit for unit tests, Cobertura for coverage checks, and Selenium for web browser interaction. 149 | 150 | ## Contact 151 | For any questions, concerns, or problems, please feel free to contact the author Matthew Hardin at mhardin.github@gmail.com 152 | 153 | ## License 154 | 155 | This project has been released under the GPL license. Please see the license.txt file for more details. 156 | -------------------------------------------------------------------------------- /automation-grid.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhardin/SeleniumGridScaler/7dc58dadb24340de13260c42455bb476660b4ce4/automation-grid.jar -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationCapabilityMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa; 13 | 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | import org.apache.commons.lang3.StringUtils; 19 | import org.openqa.grid.internal.utils.DefaultCapabilityMatcher; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import com.google.common.annotations.VisibleForTesting; 24 | 25 | /** 26 | * Custom CapabilityMatcher which will not match a node that is marked as Expired/Terminated, which will happen 27 | * via any running {@link com.rmn.qa.task.AutomationNodeCleanupTask AutomationNodeCleanupTasks} 28 | * @author mhardin 29 | */ 30 | public class AutomationCapabilityMatcher extends DefaultCapabilityMatcher { 31 | 32 | private static final Logger log = LoggerFactory.getLogger(AutomationCapabilityMatcher.class); 33 | 34 | // We can add additional capability keys we want to check here 35 | @VisibleForTesting 36 | final Set additionalConsiderations = new HashSet<>(); 37 | 38 | public AutomationCapabilityMatcher() { 39 | super(); 40 | String propertyValue = System.getProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME); 41 | if(!StringUtils.isEmpty(propertyValue)) { 42 | if(propertyValue.contains(",")) { 43 | String[] capabilities = propertyValue.split(","); 44 | for(String capability : capabilities) { 45 | log.info("Adding capability: " + capability); 46 | additionalConsiderations.add(capability); 47 | } 48 | } else { 49 | log.info("Adding capability from property value: " + propertyValue); 50 | additionalConsiderations.add(propertyValue); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | public boolean matches(Map nodeCapability,Map requestedCapability) { 57 | // First we need to check any additional capabilities that may exist in the requested set. We're iterating over 58 | // additionalConsiderations as its likely to be a smaller collection for now than the requestedCapabilities 59 | for(String s : additionalConsiderations) { 60 | Object requestedCapabilityValue = requestedCapability.get(s); 61 | if(requestedCapabilityValue != null) { 62 | if(!requestedCapabilityValue.equals(nodeCapability.get(s))) { 63 | return false; 64 | } 65 | } 66 | } 67 | // If neither expected config value exists, go ahead and default to the default matching behavior 68 | // as this node is most likely not a dynamically started node 69 | if(!nodeCapability.containsKey(AutomationConstants.INSTANCE_ID)) { 70 | return super.matches(nodeCapability,requestedCapability); 71 | } 72 | String instanceId = (String)nodeCapability.get(AutomationConstants.INSTANCE_ID); 73 | AutomationRunContext context = AutomationContext.getContext(); 74 | // If the run that spun up these hubs is still happening, just perform the default matching behavior 75 | // as that run is the one that requested these nodes. 76 | AutomationDynamicNode node = context.getNode(instanceId); 77 | if(node != null && (node.getStatus() != AutomationDynamicNode.STATUS.RUNNING) ) { 78 | log.debug(String.format("Node [%s] will not be used to match a request as it is expired/terminated",instanceId)); 79 | // If the run that spun these hubs up is not in progress AND this node has been flagged to shutdown, 80 | // do not match this node up to fulfill a test request 81 | return false; 82 | } else { 83 | // If the node couldn't be retrieved or was not expired/terminated, then we should just use the default matching behavior 84 | return super.matches(nodeCapability,requestedCapability); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa; 13 | 14 | /** 15 | * Constants in use across various hub/node configs 16 | * @author mhardin 17 | */ 18 | public interface AutomationConstants { 19 | 20 | // These NODE_CONFIG_* constants represent key names in the node configs 21 | String CONFIG_CREATED_DATE = "createdDate"; 22 | String CONFIG_MAX_SESSION = "maxSession"; 23 | String CONFIG_BROWSER = "createdBrowser"; 24 | String CONFIG_OS = "createdOs"; 25 | // This is the value that will be in the desired capabilities that our node registers with 26 | // Runtime value of the hub instance id that gets passed in as a system property. Also used in the node config 27 | String INSTANCE_ID = "instanceId"; 28 | // IP address of the hub that will be passed in as a system property 29 | String IP_ADDRESS = "ipAddress"; 30 | // Test run UUID 31 | String UUID = "uuid"; 32 | String WINDOWS_PROPERTY_NAME="node.windows.json"; 33 | String LINUX_PROPERTY_NAME="node.linux.json"; 34 | String EXTRA_CAPABILITIES_PROPERTY_NAME="extraCapabilities"; 35 | String AWS_ACCESS_KEY="awsAccessKey"; 36 | String AWS_PRIVATE_KEY="awsSecretKey"; 37 | String AWS_TOKEN="awsToken"; 38 | String AWS_DEFAULT_RESOURCE_NAME= "aws.properties.default"; 39 | String REAPER_THREAD_CONFIG = "useReaperThread"; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * Context to store a singleton {@link com.rmn.qa.AutomationRunContext AutomationRunContext} object 19 | * @author mhardin 20 | */ 21 | public class AutomationContext { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(AutomationContext.class); 24 | private static final int DEFAULT_MAX_THREAD_COUNT = 150; 25 | private static AutomationRunContext context = new AutomationRunContext(); 26 | 27 | // Singleton to maintain a context object 28 | 29 | /** 30 | * Returns the singleton {@link com.rmn.qa.AutomationRunContext context} instance 31 | * @return 32 | */ 33 | public static AutomationRunContext getContext() { 34 | return AutomationContext.context; 35 | } 36 | 37 | /** 38 | * Clears out the previous context. Used for unit testing 39 | */ 40 | public static synchronized void refreshContext() { 41 | AutomationContext.context = new AutomationRunContext(); 42 | } 43 | 44 | static { 45 | String totalNodeCount = System.getProperty("totalNodeCount"); 46 | // Default to 150 if node count was not passed in 47 | if(totalNodeCount == null) { 48 | getContext().setTotalNodeCount(AutomationContext.DEFAULT_MAX_THREAD_COUNT); 49 | log.warn("Defaulting node count to " + AutomationContext.DEFAULT_MAX_THREAD_COUNT); 50 | } else { 51 | getContext().setTotalNodeCount(Integer.parseInt(totalNodeCount)); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationDynamicNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa; 13 | 14 | import java.util.Calendar; 15 | import java.util.Date; 16 | 17 | import org.openqa.selenium.Platform; 18 | 19 | /** 20 | * Represents a dynamically started node that is used to run tests 21 | */ 22 | public final class AutomationDynamicNode implements Comparable { 23 | 24 | /**
 25 |      *  RUNNING    - node is running and no further action needs to be taken
 26 |      *  EXPIRED    - node has passed its expiration date and needs to be terminated.  A node will not
 27 |      *               be marked expired if there is sufficient load on the system to require the node resources
 28 |      *  TERMINATED - node has been successfully terminated through the EC2 API
 29 |      * 
30 | **/ 31 | public enum STATUS { RUNNING,EXPIRED,TERMINATED }; 32 | 33 | // This is used to set how far past the created date that the node will 34 | // be marked for termination 35 | private static final int NODE_INTERVAL_LIFETIME = 55; // 55 minutes 36 | 37 | // TODO: Refactor this to be AutomationRunRequest 38 | private final String uuid, instanceId, browser, ipAddress, instanceType; 39 | private final Platform platform; 40 | private Date startDate,endDate; 41 | private final int nodeCapacity; 42 | private STATUS status; 43 | 44 | /** 45 | * Constructor to create a new node representing instance 46 | * @param uuid UUID of the test run that created this node 47 | * @param instanceId Instance ID of the instance this node represents 48 | * @param browser Requested browser of the test run that created this node 49 | * @param platform Requested OS of the test run that created this node 50 | * @param startDate Date that this node was started 51 | * @param nodeCapacity Maximum test capacity that this node can run 52 | */ 53 | public AutomationDynamicNode(String uuid,String instanceId,String browser, Platform platform, Date startDate, int nodeCapacity){ 54 | this(uuid, instanceId, browser, platform, null, startDate, nodeCapacity); 55 | } 56 | 57 | public AutomationDynamicNode(String uuid,String instanceId,String browser, Platform platform, String ipAddress, Date startDate, int nodeCapacity){ 58 | this(uuid, instanceId, browser, platform, ipAddress, startDate, nodeCapacity, null); 59 | } 60 | 61 | public AutomationDynamicNode(String uuid,String instanceId,String browser, Platform platform, String ipAddress, Date startDate, int nodeCapacity, String instanceType){ 62 | this.uuid = uuid; 63 | this.instanceId = instanceId; 64 | this.browser = browser; 65 | this.platform = platform; 66 | this.ipAddress = ipAddress; 67 | this.startDate = startDate; 68 | this.endDate = computeEndDate(startDate); 69 | this.nodeCapacity = nodeCapacity; 70 | this.instanceType = instanceType; 71 | this.status = STATUS.RUNNING; 72 | } 73 | 74 | /** 75 | * Updates the status of this node. 76 | * @param status 77 | */ 78 | public void updateStatus(STATUS status) { 79 | this.status = status; 80 | } 81 | 82 | /** 83 | * Computes the end date for this node based on the pre configured 84 | * end time 85 | * @param dateStarted 86 | * @return 87 | */ 88 | private Date computeEndDate(Date dateStarted) { 89 | Calendar c = Calendar.getInstance(); 90 | c.setTime(dateStarted); 91 | c.add(Calendar.MINUTE, AutomationDynamicNode.NODE_INTERVAL_LIFETIME); // number of days to add 92 | return c.getTime(); 93 | } 94 | 95 | /** 96 | * Returns the UUID for this node (will be the UUID of the run that 97 | * resulted in this node being started) 98 | * @return 99 | */ 100 | public String getUuid() { 101 | return uuid; 102 | } 103 | 104 | /** 105 | * Returns the instance id of this node 106 | * @return 107 | */ 108 | public String getInstanceId() { 109 | return instanceId; 110 | } 111 | 112 | /** 113 | * Returns the browser of this node. Will be the browser of the 114 | * run that resulted in this node being started 115 | * @return 116 | */ 117 | public String getBrowser() { 118 | return browser; 119 | } 120 | 121 | /** 122 | * Returns the Platform of this node 123 | * @return 124 | */ 125 | public Platform getPlatform() { 126 | return platform; 127 | } 128 | 129 | /** 130 | * Returns the private IP address of this node 131 | * @return 132 | */ 133 | public String getIpAddress() { 134 | return ipAddress; 135 | } 136 | 137 | /** 138 | * Returns the date that his node was started. This will 139 | * be the time that the node was requested and not necessarily started 140 | * @return 141 | */ 142 | public Date getStartDate() { 143 | return startDate; 144 | } 145 | 146 | /** 147 | * Returns the currently scheduled end date of this node. Note 148 | * that this can change as this node end date gets pushed back. 149 | * @return 150 | */ 151 | public Date getEndDate() { 152 | return endDate; 153 | } 154 | 155 | /** 156 | * Sets the end date for this node. 157 | * @param endDate Date which will be set for the end date 158 | */ 159 | public void setEndDate(Date endDate) { 160 | this.endDate = endDate; 161 | } 162 | 163 | /** 164 | * Increments the end date by an hour. Useful for moving the end date into the next Amazon billing cycle 165 | */ 166 | public void incrementEndDateByOneHour() { 167 | // Add 60 minutes so we're as close to the hour as we can be instead of adding 55 again 168 | setEndDate(AutomationUtils.modifyDate(getEndDate(),60,Calendar.MINUTE)); 169 | } 170 | 171 | /** 172 | * Returns the total node capacity of this node (total number of browsers 173 | * that can run simultaneously) 174 | * @return 175 | */ 176 | public int getNodeCapacity() { 177 | return nodeCapacity; 178 | } 179 | 180 | /** 181 | * Returns the instance type of this node 182 | * @return 183 | */ 184 | public String getInstanceType() { 185 | return instanceType; 186 | } 187 | 188 | /** 189 | * Returns the current status of this node. 190 | * @return 191 | */ 192 | public STATUS getStatus(){ 193 | return status; 194 | } 195 | 196 | @Override 197 | public int compareTo(AutomationDynamicNode o) { 198 | return this.startDate.compareTo(getStartDate()); 199 | } 200 | 201 | @Override 202 | public boolean equals(Object o) { 203 | if (this == o) { 204 | return true; 205 | } 206 | if (o == null || getClass() != o.getClass()) { 207 | return false; 208 | } 209 | 210 | AutomationDynamicNode that = (AutomationDynamicNode) o; 211 | 212 | if (!instanceId.equals(that.instanceId)) { 213 | return false; 214 | } 215 | if (!uuid.equals(that.uuid)) { 216 | return false; 217 | } 218 | 219 | return true; 220 | } 221 | 222 | @Override 223 | public int hashCode() { 224 | int result = uuid.hashCode(); 225 | result = 31 * result + instanceId.hashCode(); 226 | return result; 227 | } 228 | 229 | @Override 230 | public String toString() { 231 | return "AutomationDynamicNode{" + 232 | "uuid='" + uuid + '\'' + 233 | ", instanceId='" + instanceId + '\'' + 234 | ", startDate=" + startDate + 235 | ", ipAddress='" + ipAddress + '\'' + 236 | '}'; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationRequestMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa; 13 | 14 | import org.openqa.grid.internal.ProxySet; 15 | import org.openqa.grid.internal.RemoteProxy; 16 | import org.openqa.grid.internal.TestSession; 17 | import org.openqa.grid.internal.TestSlot; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.util.Calendar; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | /** 26 | * Computes how many free/available resources there are for a given browser, browser version, and OS 27 | * @author mhardin 28 | */ 29 | public class AutomationRequestMatcher implements RequestMatcher { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(AutomationRequestMatcher.class); 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | public int getNumFreeThreadsForParameters(ProxySet proxySet, AutomationRunRequest runRequest) { 38 | // This will keep a count of the number of instances that can run our requested test 39 | int totalFreeSlots = 0; 40 | // Current runs registered with the hub. Make a copy of the set so we don't muck with the original set of registered runs 41 | Map currentRuns = new HashMap<>(); 42 | // Add them all into our map so we can keep track of both the runs we need to delete from our free count as well as 43 | // tests that are in progress for the run. 44 | for(String key : AutomationContext.getContext().getRunUuids()) { 45 | currentRuns.put(key,0); 46 | } 47 | for(RemoteProxy proxy : proxySet) { 48 | int matchingCapableSlots = 0; 49 | int runningSessions = 0; 50 | int matchingRunningSessions = 0; 51 | int maxNodeThreadsAvailable = proxy.getMaxNumberOfConcurrentTestSessions(); 52 | String instanceId = proxy.getConfig().custom.get(AutomationConstants.INSTANCE_ID); 53 | boolean nodeMarkedForTermination = false; 54 | if(instanceId != null) { 55 | AutomationDynamicNode node = AutomationContext.getContext().getNode(instanceId); 56 | // If this node has been spun up and it is no longer in the running state, go to the next test slot 57 | // as we cannot consider this node to be a free resource 58 | if(node != null) {// There really shouldn't ever be a null node here but adding the check regardless 59 | if(node.getStatus() != AutomationDynamicNode.STATUS.RUNNING) { 60 | // If this is a dynamic node and its not in the running state, we should not be calculating its resources as available 61 | nodeMarkedForTermination = true; 62 | } 63 | } 64 | } 65 | if(instanceId != null) { 66 | log.info(String.format("Analyzing node %s...",instanceId)); 67 | } else { 68 | log.info("Analyzing node..."); 69 | } 70 | for (TestSlot testSlot : proxy.getTestSlots()) { 71 | //TODO Do selenium flavor of browsers to match here from RMN 72 | //TODO Better property matching 73 | TestSession session = testSlot.getSession(); 74 | Map testSlotCapabilities = testSlot.getCapabilities(); 75 | if(session != null) { 76 | Map sessionCapabilities = session.getRequestedCapabilities(); 77 | Object uuid = sessionCapabilities.get(AutomationConstants.UUID); 78 | // If the session has a UUID, go ahead and remove it from our runs that we're going to subtract from our available 79 | // node count as this means the run is under way 80 | if(uuid != null && currentRuns.containsKey(uuid)) { 81 | int previousCount = currentRuns.get(uuid); 82 | currentRuns.put((String)uuid,previousCount + 1); 83 | } 84 | if(runRequest.matchesCapabilities(testSlotCapabilities)) { 85 | matchingRunningSessions++; 86 | } 87 | runningSessions++; 88 | } 89 | if(runRequest.matchesCapabilities(testSlotCapabilities)) { 90 | matchingCapableSlots++; 91 | } 92 | } 93 | log.info(String.format("Node had %d matching running sessions and %d matching capable slots",matchingRunningSessions,matchingCapableSlots)); 94 | int nodeFreeSlots; 95 | // If the node is marked for termination, we need to subtract matching running sessions from our free count, and make sure to not add 96 | // any capable slots, as they're really not even 'capable' since the node will be shutdown 97 | if(nodeMarkedForTermination) { 98 | log.info(String.format("Node marked for termination. Subtracting %d sessions from %d total free slots",matchingRunningSessions,totalFreeSlots)); 99 | totalFreeSlots -= matchingRunningSessions; 100 | } else { 101 | // Decrement the running sessions only if running + free is more than the total threads the node can handle. This will handle 102 | // load from a capacity standpoint of a node 103 | if((runningSessions + matchingCapableSlots) > maxNodeThreadsAvailable ) { 104 | if(matchingCapableSlots < maxNodeThreadsAvailable) { 105 | log.debug(String.format("Subtracting %d running sessions from %d capable sessions",runningSessions,matchingCapableSlots)); 106 | nodeFreeSlots = matchingCapableSlots - runningSessions; 107 | } else { 108 | log.debug(String.format("Subtracting %d running sessions from %d maximum node thread limit",matchingCapableSlots,maxNodeThreadsAvailable)); 109 | nodeFreeSlots = maxNodeThreadsAvailable - runningSessions; 110 | } 111 | } else { 112 | log.debug(String.format("%d free node slots derived from matching capable slots",matchingCapableSlots)); 113 | nodeFreeSlots = matchingCapableSlots; 114 | // If there were any running sessions that match this browser, we need to subtract them from the capable sessions 115 | if(matchingRunningSessions != 0) { 116 | log.debug(String.format("%d matching running sessions will be subtracted from %d node free slots",matchingRunningSessions,nodeFreeSlots)); 117 | nodeFreeSlots -= matchingRunningSessions; 118 | } 119 | } 120 | // If nodeFreeSlots is negative, go ahead and subtract the capable sessions instead 121 | if(nodeFreeSlots < 0) { 122 | log.warn("The number of free node slots was less than 0. Resetting to 0."); 123 | nodeFreeSlots = 0; 124 | } 125 | totalFreeSlots += nodeFreeSlots; 126 | } 127 | } 128 | // Any runs still in this set means that run has not started yet, so we should consider this in our math 129 | for(String uuid : currentRuns.keySet()) { 130 | AutomationRunRequest request = AutomationContext.getContext().getRunRequest(uuid); 131 | // If we're not dealing with an old run request that just never started, go ahead and decrement 132 | // the value from available nodes on this hub 133 | if(request != null && !AutomationUtils.isCurrentTimeAfterDate(request.getCreatedDate(), 90, Calendar.SECOND) ) { 134 | if(!runRequest.matchesCapabilities(request)) { 135 | log.warn(String.format("Requested run %s did not match pending run %s so count will not be included",runRequest,request)); 136 | continue; 137 | } 138 | int currentlyRunningTestsForRun = currentRuns.get(uuid); 139 | // If some of the tests are underway, subtract the currently running tests from the number of total tests, and then subtract that 140 | // from the number of free slots. This way we're including tests that may not have started yet in our free resource check 141 | if(currentlyRunningTestsForRun < request.getThreadCount()) { 142 | int countToSubtract = request.getThreadCount() - currentlyRunningTestsForRun; 143 | log.debug(String.format("In progress run has %d threads that will be subtracted from our total free count %d.",countToSubtract,totalFreeSlots)); 144 | totalFreeSlots -= countToSubtract; 145 | } 146 | } 147 | } 148 | // Make sure we don't return a negative number to the caller 149 | if(totalFreeSlots < 0) { 150 | log.info("The number of total free node slots was less than 0. Resetting to 0."); 151 | totalFreeSlots = 0; 152 | } 153 | log.info(String.format("Returning %s free slots for request %s",totalFreeSlots,runRequest)); 154 | return totalFreeSlots; 155 | } 156 | 157 | /** 158 | * {@inheritDoc} 159 | */ 160 | @Override 161 | public int getNumInProgressTests(ProxySet proxySet, AutomationRunRequest runRequest) { 162 | int inProgressTests = 0; 163 | for(RemoteProxy proxy : proxySet) { 164 | for(TestSlot testSlot : proxy.getTestSlots() ) { 165 | TestSession session = testSlot.getSession(); 166 | if(session != null) { 167 | if(runRequest.matchesCapabilities(session.getRequestedCapabilities())) { 168 | inProgressTests++; 169 | } 170 | } 171 | } 172 | } 173 | return inProgressTests; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationRunRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | import java.util.Map; 18 | 19 | import org.apache.commons.lang3.StringUtils; 20 | import org.openqa.selenium.Platform; 21 | import org.openqa.selenium.remote.CapabilityType; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import com.google.common.annotations.VisibleForTesting; 26 | import com.rmn.qa.task.AbstractAutomationCleanupTask; 27 | 28 | /** 29 | * Represents a run request which will typically be sent in by a test run requesting resources. Used 30 | * to encapsulate various run parameters 31 | * @author mhardin 32 | */ 33 | public final class AutomationRunRequest { 34 | 35 | private static final Logger log = LoggerFactory.getLogger(AbstractAutomationCleanupTask.class); 36 | 37 | private final String uuid; 38 | private final Integer threadCount; 39 | private final String browser; 40 | private final String browserVersion; 41 | private final Platform platform; 42 | private final Date createdDate; 43 | 44 | // Require callers to have required variables through constructor below 45 | private AutomationRunRequest() { 46 | this(null); 47 | } 48 | 49 | /** 50 | * Constructs a run request instance for the specified browser 51 | * @param browser Browser for the requesting test run 52 | */ 53 | public AutomationRunRequest(String browser) { 54 | this(null,null,browser); 55 | } 56 | 57 | /** 58 | * Constructs a run request object 59 | * @param uuid UUID to represent the requesting test run 60 | * @param threadCount Number of threads for the requesting test run 61 | * @param browser Browser for the requesting test run 62 | */ 63 | public AutomationRunRequest(String uuid, Integer threadCount,String browser) { 64 | this(uuid,threadCount,browser,null,null); 65 | } 66 | 67 | /** 68 | * Constructs a run request object 69 | * @param runUuid UUID to represent the requesting test run 70 | * @param threadCount Number of threads for the requesting test run 71 | * @param browser Browser for the requesting test run 72 | * @param browserVersion Browser version for the requesting test run 73 | * @param platform Platform for the requesting test run 74 | */ 75 | public AutomationRunRequest(String runUuid, Integer threadCount, String browser, String browserVersion, Platform platform) { 76 | this(runUuid, threadCount, browser, browserVersion, platform, new Date()); 77 | } 78 | 79 | /** 80 | * Constructs a run request object 81 | * @param runUuid UUID to represent the requesting test run 82 | * @param threadCount Number of threads for the requesting test run 83 | * @param browser Browser for the requesting test run 84 | * @param browserVersion Browser version for the requesting test run 85 | * @param platform Platform for the requesting test run 86 | * @param createdDate Date that the test run request was received 87 | */ 88 | @VisibleForTesting 89 | public AutomationRunRequest(String runUuid, Integer threadCount, String browser,String browserVersion, Platform platform, Date createdDate) { 90 | this.uuid = runUuid; 91 | this.threadCount = threadCount; 92 | this.browser = browser; 93 | this.browserVersion = browserVersion; 94 | this.platform = platform; 95 | this.createdDate = createdDate; 96 | } 97 | 98 | /** 99 | * Generates a AutomationRunRequest object from the capabilities passed in 100 | * @param capabilities 101 | * @return 102 | */ 103 | public static AutomationRunRequest requestFromCapabilities(Map capabilities) { 104 | String capabilityBrowser = (String)capabilities.get(CapabilityType.BROWSER_NAME); 105 | String capabilityBrowserVersion = null; 106 | if(capabilities.containsKey(CapabilityType.VERSION)) { 107 | capabilityBrowserVersion = (String)capabilities.get(CapabilityType.VERSION); 108 | } 109 | Object platform = capabilities.get(CapabilityType.PLATFORM); 110 | Platform capabilityPlatform = AutomationUtils.getPlatformFromObject(platform); 111 | return new AutomationRunRequest(null,null,capabilityBrowser,capabilityBrowserVersion,capabilityPlatform); 112 | } 113 | 114 | /** 115 | * Returns the UUID for this run request 116 | * @return 117 | */ 118 | public String getUuid() { 119 | return uuid; 120 | } 121 | 122 | /** 123 | * Returns the thread count requested by this run 124 | * @return 125 | */ 126 | public int getThreadCount() { 127 | return threadCount; 128 | } 129 | 130 | /** 131 | * Returns the browser (e.g. 'chrome', 'firefox', etc) for this run request 132 | * @return 133 | */ 134 | public String getBrowser() { 135 | return browser; 136 | } 137 | 138 | /** 139 | * Returns the version of the browser (e.g. '27' for Firefox) 140 | * @return 141 | */ 142 | public String getBrowserVersion() { return browserVersion; } 143 | 144 | /** 145 | * Returns the Platform (e.g. 'Platform.WINDOWS') 146 | * @return 147 | */ 148 | public Platform getPlatform() { return platform; } 149 | /** 150 | * Returns the created date for this run request 151 | * @return 152 | */ 153 | public Date getCreatedDate() { 154 | return createdDate; 155 | } 156 | 157 | /** 158 | * Returns true if this run request is less than 2 minutes old, false otherwise 159 | * @return 160 | */ 161 | public boolean isNewRun() { 162 | Calendar c = Calendar.getInstance(); 163 | c.setTime(createdDate); 164 | c.add(Calendar.MINUTE,2); 165 | return new Date().before(c.getTime()); 166 | } 167 | 168 | @Override 169 | public String toString() { 170 | StringBuilder builder = new StringBuilder(); 171 | builder.append("Run Request"); 172 | if(!StringUtils.isEmpty(uuid)) { 173 | builder.append(" - UUID: ").append(uuid); 174 | } 175 | if(threadCount != null) { 176 | builder.append(" - Thread count: ").append(threadCount); 177 | } 178 | if(!StringUtils.isEmpty(browser)) { 179 | builder.append(" - Browser: ").append(browser); 180 | } 181 | if(platform != null) { 182 | builder.append(" - Platform: ").append(platform); 183 | } 184 | return builder.toString(); 185 | } 186 | 187 | /** 188 | * Returns true if this run request matches the capabilities passed in. Includes browser, browser version, and OS 189 | * @param capabilities 190 | * @return 191 | */ 192 | public boolean matchesCapabilities(Map capabilities) { 193 | String capabilityBrowser = (String)capabilities.get(CapabilityType.BROWSER_NAME); 194 | String capabilityBrowserVersion = (String)capabilities.get(CapabilityType.VERSION); 195 | Object capabilityPlatformObject = capabilities.get(CapabilityType.PLATFORM); 196 | Platform capabilityPlatform = AutomationUtils.getPlatformFromObject(capabilityPlatformObject); 197 | if(!AutomationUtils.lowerCaseMatch(browser, capabilityBrowser)) { 198 | return false; 199 | } 200 | if(browserVersion != null && !AutomationUtils.lowerCaseMatch(browserVersion,capabilityBrowserVersion)) { 201 | return false; 202 | } 203 | if(platform != null && !AutomationUtils.firstPlatformCanBeFulfilledBySecondPlatform(platform, capabilityPlatform)) { 204 | return false; 205 | } 206 | return true; 207 | } 208 | 209 | /** 210 | * Returns true if this run request matches the run request passed in. Includes browser, browser version, and OS 211 | * @param otherRequest 212 | * @return 213 | */ 214 | public boolean matchesCapabilities(AutomationRunRequest otherRequest) { 215 | if(!AutomationUtils.lowerCaseMatch(browser, otherRequest.getBrowser())) { 216 | return false; 217 | } 218 | if(browserVersion != null && browserVersion != otherRequest.getBrowserVersion()) { 219 | return false; 220 | } 221 | if(platform != null && !AutomationUtils.firstPlatformCanBeFulfilledBySecondPlatform(platform, otherRequest.getPlatform())) { 222 | return false; 223 | } 224 | return true; 225 | } 226 | 227 | @Override 228 | public boolean equals(Object o) { 229 | if (this == o) return true; 230 | if (o == null || getClass() != o.getClass()) return false; 231 | 232 | AutomationRunRequest that = (AutomationRunRequest) o; 233 | 234 | if (!browser.equals(that.browser)) return false; 235 | if (browserVersion != null ? !browserVersion.equals(that.browserVersion) : that.browserVersion != null) 236 | return false; 237 | if (platform != null ? !platform.equals(that.platform) : that.platform != null) return false; 238 | 239 | return true; 240 | } 241 | 242 | @Override 243 | public int hashCode() { 244 | int result = browser.hashCode(); 245 | result = 31 * result + (browserVersion != null ? browserVersion.hashCode() : 0); 246 | result = 31 * result + (platform != null ? platform.hashCode() : 0); 247 | return result; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/AutomationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | 18 | import org.apache.commons.lang3.StringUtils; 19 | import org.openqa.selenium.Platform; 20 | import org.openqa.selenium.WebDriverException; 21 | import org.openqa.selenium.remote.BrowserType; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | /** 26 | * Util methods 27 | * @author mhardin 28 | */ 29 | public final class AutomationUtils { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(AutomationUtils.class); 32 | /** 33 | * Modifies the specified date 34 | * @param dateToModify Date to modify 35 | * @param unitsToModify Number of units to modify (e.g. 6 for 6 seconds) 36 | * @param unitType Measurement type (e.g. Calendar.SECONDS) 37 | * @return Modified date 38 | */ 39 | public static Date modifyDate(Date dateToModify,int unitsToModify,int unitType) { 40 | Calendar c = Calendar.getInstance(); 41 | c.setTime(dateToModify); 42 | // Add 60 seconds so we're as close to the hour as we can be instead of adding 55 again 43 | c.add(unitType,unitsToModify); 44 | return c.getTime(); 45 | } 46 | 47 | /** 48 | * Returns true if the current time is after the specified date, false otherwise 49 | * @param dateToCheck Date to check against the current time 50 | * @param unitsToCheckWith Number of units to add/subtract from dateToCheck 51 | * @param unitType Unit type (e.g. Calendar.MINUTES) 52 | * @return 53 | */ 54 | public static boolean isCurrentTimeAfterDate(Date dateToCheck,int unitsToCheckWith,int unitType) { 55 | Date targetDate = AutomationUtils.modifyDate(dateToCheck, unitsToCheckWith, unitType); 56 | return new Date().after(targetDate); 57 | } 58 | 59 | /** 60 | * Returns true if the strings are lower case equal 61 | * @param string1 First string to compare 62 | * @param string2 Second string to compare 63 | * @return 64 | */ 65 | public static boolean lowerCaseMatch(String string1, String string2) { 66 | string2 = string2.toLowerCase().replace(" ",""); 67 | return string2.equals(string1.toLowerCase().replace(" ", "")); 68 | } 69 | 70 | /** 71 | * Returns the Selenium platform object from an unknown object, which could be a string or an actual object. 72 | * If the platform can not be determined, null will be returned 73 | * @param platformObj 74 | * @return 75 | */ 76 | public static Platform getPlatformFromObject(Object platformObj) { 77 | if (platformObj == null) { 78 | return null; 79 | } 80 | Platform parsedPlatform = null; 81 | if (platformObj instanceof Platform) { 82 | parsedPlatform = ((Platform) platformObj); 83 | } else if (platformObj instanceof String) { 84 | String platformString = (String) platformObj; 85 | if (StringUtils.isEmpty(platformString)) { 86 | return null; 87 | } 88 | try { 89 | parsedPlatform = Platform.fromString(platformString); 90 | } catch (WebDriverException e) { 91 | log.error("Error parsing out platform for string: " + platformObj, e); 92 | } 93 | } else { 94 | // Do nothing and return null for unexpected type 95 | log.warn("Platform was not an expected type: " + platformObj); 96 | } 97 | return parsedPlatform; 98 | } 99 | 100 | /** 101 | * Returns true if the requested browser and platform can be used within AMIs, and false otherwise 102 | * @param browserPair 103 | * @return 104 | */ 105 | public static boolean browserAndPlatformSupported(BrowserPlatformPair browserPair) { 106 | // If no platform is defined or the user specifies linux, perform the browser check. 107 | if(AutomationUtils.isPlatformUnix(browserPair.getPlatform())){ 108 | return lowerCaseMatch(BrowserType.CHROME,browserPair.getBrowser()) 109 | || lowerCaseMatch(BrowserType.FIREFOX,browserPair.getBrowser()); 110 | } else if (browserPair.getPlatform() == Platform.ANY || AutomationUtils.isPlatformWindows(browserPair.getPlatform())) { 111 | return lowerCaseMatch(BrowserType.CHROME, browserPair.getBrowser()) 112 | || lowerCaseMatch(BrowserType.FIREFOX, browserPair.getBrowser()) 113 | || lowerCaseMatch("internetexplorer", browserPair.getBrowser()); 114 | } else { 115 | return false; 116 | } 117 | } 118 | 119 | /** 120 | * Returns true if the platform is from the Windows family 121 | * @param platform 122 | * @return 123 | */ 124 | public static boolean isPlatformWindows(Platform platform) { 125 | return getUnderlyingFamily(platform) == Platform.WINDOWS; 126 | } 127 | 128 | /** 129 | * Returns true if the platform is from the Unix family 130 | * @param platform 131 | * @return 132 | */ 133 | public static boolean isPlatformUnix(Platform platform) { 134 | return getUnderlyingFamily(platform) == Platform.UNIX; 135 | } 136 | 137 | /** 138 | * Returns true if the platforms match. If either platform is Platform.ANY, this will return true. 139 | * @param platformOne 140 | * @param platformTwo 141 | * @return 142 | */ 143 | // Even though we're handling null in here, this shouldn't really happen unless someone has a mis-configured test request or 144 | // manually connects a node to this hub with a mis-configured JSON file. Unfortunately, there is not a lot we can do to prevent 145 | // this besides handling it here 146 | public static boolean firstPlatformCanBeFulfilledBySecondPlatform(Platform platformOne, Platform platformTwo) { 147 | // If either platform is ANY, this means this request should match 148 | if (platformOne == Platform.ANY || platformTwo == Platform.ANY) { 149 | return true; 150 | } else if (platformOne != null && platformTwo == null) { 151 | // If the first platform is requesting a specific platform, and the 2nd platform is null, 152 | // return false for a match as we can't determine if they do in fact match 153 | return false; 154 | } else if (platformOne == null) { 155 | return true; 156 | } else { 157 | // At the end of the day, we only support basic platforms (Windows or Linux), so group 158 | // each platform into the family and compare 159 | Platform platformOneFamily = getUnderlyingFamily(platformOne); 160 | Platform platformTwoFamily = getUnderlyingFamily(platformTwo); 161 | return platformOneFamily == platformTwoFamily; 162 | } 163 | } 164 | 165 | /** 166 | * Returns the underlying 'family' of the specified platform 167 | * @param platform 168 | * @return 169 | */ 170 | public static Platform getUnderlyingFamily(Platform platform) { 171 | if (platform == null) { 172 | return null; 173 | } 174 | if (platform == Platform.UNIX || platform == Platform.WINDOWS || platform == Platform.MAC || platform == Platform.ANY) { 175 | return platform; 176 | } else { 177 | return getUnderlyingFamily(platform.family()); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/BrowserPlatformPair.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa; 2 | 3 | import org.openqa.selenium.Platform; 4 | 5 | /** 6 | * Created by jchan on 5/16/16. 7 | */ 8 | public class BrowserPlatformPair { 9 | 10 | private String browser; 11 | private Platform platform; 12 | 13 | public BrowserPlatformPair(String browser, Platform platform){ 14 | this.browser = browser; 15 | this.platform = platform; 16 | } 17 | public void setBrowser(String browser){ 18 | this.browser = browser; 19 | } 20 | public void setPlatform(Platform platform){ 21 | this.platform = platform; 22 | } 23 | public String getBrowser(){ 24 | return this.browser; 25 | } 26 | public Platform getPlatform(){ 27 | return this.platform; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) { 33 | return true; 34 | } 35 | if (o == null || getClass() != o.getClass()) { 36 | return false; 37 | } 38 | 39 | BrowserPlatformPair that = (BrowserPlatformPair) o; 40 | 41 | if (browser != null ? !browser.equals(that.browser) : that.browser != null) { 42 | return false; 43 | } 44 | return platform == that.platform; 45 | 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | int result = browser != null ? browser.hashCode() : 0; 51 | result = 31 * result + (platform != null ? platform.hashCode() : 0); 52 | return result; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "BrowserPlatformPair{" + 58 | "browser='" + browser + '\'' + 59 | ", platform=" + platform + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/NodesCouldNotBeStartedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | public class NodesCouldNotBeStartedException extends Exception { 16 | public NodesCouldNotBeStartedException(String msg) { 17 | super(msg); 18 | } 19 | 20 | public NodesCouldNotBeStartedException(String msg, Throwable t) { 21 | super(msg,t); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/RegistryRetriever.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import org.openqa.grid.internal.Registry; 16 | 17 | /** 18 | * Wrapper interface used to retrieve a Selenium Registry object 19 | * @author mhardin 20 | */ 21 | public interface RegistryRetriever { 22 | 23 | /** 24 | * Retrieves a Selenium Registry 25 | * @return 26 | */ 27 | Registry retrieveRegistry(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/RequestMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import org.openqa.grid.internal.ProxySet; 16 | 17 | /** 18 | * Interface used to determine current load 19 | */ 20 | public interface RequestMatcher { 21 | 22 | /** 23 | * Returns the number of free threads which satisfy the conditions of the parameters 24 | * @param proxySet Set of current registered proxy objects 25 | * @param runRequest Request used to match against 26 | * @return 27 | */ 28 | int getNumFreeThreadsForParameters(ProxySet proxySet, AutomationRunRequest runRequest); 29 | 30 | /** 31 | * Returns the number of in progress tests which match the request passed in 32 | * @param proxySet Set of current registered proxy objects 33 | * @param runRequest Request used to match against 34 | * @return 35 | */ 36 | int getNumInProgressTests(ProxySet proxySet, AutomationRunRequest runRequest); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/aws/AwsTagReporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.aws; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.Collection; 17 | import java.util.List; 18 | import java.util.Properties; 19 | import java.util.Set; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.amazonaws.AmazonServiceException; 25 | import com.amazonaws.services.ec2.AmazonEC2Client; 26 | import com.amazonaws.services.ec2.model.CreateTagsRequest; 27 | import com.amazonaws.services.ec2.model.DescribeInstancesRequest; 28 | import com.amazonaws.services.ec2.model.DescribeInstancesResult; 29 | import com.amazonaws.services.ec2.model.Instance; 30 | import com.amazonaws.services.ec2.model.Tag; 31 | import com.google.common.annotations.VisibleForTesting; 32 | 33 | public class AwsTagReporter extends Thread { 34 | 35 | private static final Logger log = LoggerFactory.getLogger(AwsTagReporter.class); 36 | static int TIMEOUT_IN_SECONDS = 10 * 1000; 37 | 38 | private AmazonEC2Client ec2Client; 39 | private Collection instances; 40 | private Properties awsProperties; 41 | 42 | public AwsTagReporter(String testRunUuid, AmazonEC2Client ec2Client, Collection instances, Properties awsProperties) { 43 | this.ec2Client = ec2Client; 44 | this.instances = instances; 45 | this.awsProperties = awsProperties; 46 | this.setName("TagReporter-" + testRunUuid); 47 | } 48 | 49 | @Override 50 | public void run() { 51 | log.info("AwsTagReporter thread initialized"); 52 | DescribeInstancesRequest request = new DescribeInstancesRequest(); 53 | Collection instanceIds = new ArrayList<>(); 54 | for(Instance instance : instances) { 55 | instanceIds.add(instance.getInstanceId()); 56 | } 57 | request.withInstanceIds(instanceIds); 58 | long startTime = System.currentTimeMillis(); 59 | boolean instancesFound = false; 60 | do{ 61 | // Wait up to 10 seconds for the instances to exist with AWS 62 | if(System.currentTimeMillis() > startTime + AwsTagReporter.TIMEOUT_IN_SECONDS) { 63 | throw new RuntimeException("Error waiting for instances to exist to add tags"); 64 | } 65 | try{ 66 | DescribeInstancesResult existingInstances = ec2Client.describeInstances(request); 67 | if(existingInstances.getReservations().get(0).getInstances().size() == instances.size()) { 68 | log.info("Correct instances were found to add tags to!"); 69 | instancesFound = true; 70 | } 71 | } catch(Throwable t) { 72 | log.warn("Error finding instances. Sleeping for 500ms."); 73 | try { 74 | sleep(); 75 | } catch (InterruptedException e) { 76 | log.error("Error sleeping for adding tags", e); 77 | } 78 | } 79 | } while(!instancesFound); 80 | associateTags(instances); 81 | log.info("AwsTagReporter thread completed successfully"); 82 | } 83 | 84 | @VisibleForTesting 85 | void sleep() throws InterruptedException{ 86 | Thread.sleep(500); 87 | } 88 | 89 | /** 90 | * Associates the correct tags for each instance passed in 91 | * @param instances 92 | */ 93 | private void associateTags(Collection instances) { 94 | try{ 95 | for(Instance instance : instances) { 96 | log.info("Associating tags to instance: " + instance.getInstanceId()); 97 | String instanceId = instance.getInstanceId(); 98 | setTagsForInstance(instanceId); 99 | } 100 | log.info("Tags added!"); 101 | } catch(IndexOutOfBoundsException | ClassCastException | AmazonServiceException e) { 102 | log.error("Error adding tags. Please make sure your tag syntax is correct (refer to the readme)",e); 103 | } 104 | } 105 | 106 | /** 107 | * Sets tags for the specified instance 108 | * @param instanceId 109 | * @return 110 | */ 111 | private void setTagsForInstance(String instanceId) { 112 | Set keys = awsProperties.keySet(); 113 | List tags = new ArrayList<>(); 114 | for(Object o : keys) { 115 | if(o instanceof String && ((String)o).startsWith("tag")) { 116 | String values = (String)awsProperties.get(o); 117 | String[] splitValues = values.split(","); 118 | String key = splitValues[0]; 119 | String value = splitValues[1]; 120 | Tag tagToAdd = new Tag(key,value); 121 | log.info("Adding tag: " + tagToAdd); 122 | tags.add(tagToAdd); 123 | } 124 | } 125 | // Including a hard coded tag here so we can track which resources originate from this plugin 126 | Tag nodeTag = new Tag("LaunchSource","SeleniumGridScalerPlugin"); 127 | log.info("Adding hard-coded tag: " + nodeTag); 128 | tags.add(nodeTag); 129 | CreateTagsRequest ctr = new CreateTagsRequest(Arrays.asList(instanceId),tags); 130 | ec2Client.createTags(ctr); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/aws/VmManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.aws; 13 | 14 | import java.util.List; 15 | 16 | import org.openqa.selenium.Platform; 17 | 18 | import com.amazonaws.services.ec2.model.DescribeInstancesRequest; 19 | import com.amazonaws.services.ec2.model.Instance; 20 | import com.amazonaws.services.ec2.model.Reservation; 21 | import com.rmn.qa.NodesCouldNotBeStartedException; 22 | 23 | public interface VmManager { 24 | 25 | /** 26 | * Launches the specified instances 27 | * @param uuid UUID of the requesting test run 28 | * @param platform Platform of the requesting test run 29 | * @param browser Browser of the requesting test run 30 | * @param hubHostName Hub host name for the nodes to register with 31 | * @param nodeCount Number of nodes to be started 32 | * @param maxSessions Number of max sessions per node 33 | * @return 34 | */ 35 | // TODO Refactor into AutomationRunRequest 36 | List launchNodes(String uuid, Platform platform, String browser, String hubHostName, int nodeCount, int maxSessions) throws NodesCouldNotBeStartedException; 37 | 38 | /** 39 | * Terminates the specified instance 40 | * @param instanceId 41 | */ 42 | // TODO Rename to be node or instance in the name 43 | boolean terminateInstance(String instanceId); 44 | 45 | /** 46 | * Returns a list of reservations as defined in the {@link com.amazonaws.services.ec2.model.DescribeInstancesRequest DescribeInstancesRequest} 47 | * @param describeInstancesRequest 48 | * @return 49 | */ 50 | List describeInstances(DescribeInstancesRequest describeInstancesRequest); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/servlet/AutomationStatusServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.servlet; 13 | 14 | import java.io.ByteArrayInputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | 18 | import javax.servlet.ServletException; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | 22 | import org.openqa.grid.internal.Registry; 23 | import org.openqa.grid.web.servlet.RegistryBasedServlet; 24 | import org.openqa.selenium.Platform; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import com.google.common.io.ByteStreams; 29 | import com.rmn.qa.AutomationContext; 30 | import com.rmn.qa.AutomationRequestMatcher; 31 | import com.rmn.qa.AutomationRunContext; 32 | import com.rmn.qa.AutomationRunRequest; 33 | import com.rmn.qa.AutomationUtils; 34 | import com.rmn.qa.RequestMatcher; 35 | 36 | /** 37 | * Legacy API to pull free threads for a given browser 38 | */ 39 | public class AutomationStatusServlet extends RegistryBasedServlet { 40 | 41 | private static final long serialVersionUID = 8484071790930378855L; 42 | private static final Logger log = LoggerFactory.getLogger(AutomationStatusServlet.class); 43 | private RequestMatcher requestMatcher; 44 | 45 | /** 46 | * Constructs a default status servlet 47 | */ 48 | public AutomationStatusServlet() { 49 | this(null); 50 | } 51 | 52 | /** 53 | * Constructs a default status servlet with the specified {@link org.openqa.grid.internal.Registry register} 54 | * @param registry 55 | */ 56 | public AutomationStatusServlet(Registry registry) { 57 | super(registry); 58 | this.requestMatcher = new AutomationRequestMatcher(); 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | @Override 65 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 66 | response.setContentType("text/html"); 67 | response.setCharacterEncoding("UTF-8"); 68 | 69 | String browserRequested = request.getParameter("browser"); 70 | 71 | if (browserRequested == null) { 72 | response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Parameter 'browser' must be passed in as a query string parameter"); 73 | return; 74 | } 75 | // OS is optional 76 | String os = request.getParameter("os"); 77 | Platform platformRequested = AutomationUtils.getPlatformFromObject(os); 78 | AutomationRunRequest runRequest = new AutomationRunRequest(AutomationStatusServlet.class.getSimpleName(),null,browserRequested,null,platformRequested); 79 | log.info(String.format("Legacy server request received. Browser [%s]", browserRequested)); 80 | AutomationRunContext context = AutomationContext.getContext(); 81 | // If a run is already going on with this browser, return an error code 82 | if(context.hasRun(browserRequested)) { 83 | response.setStatus(400); 84 | return; 85 | } 86 | // Synchronize this block until we've added the run to our context for other potential threads to see 87 | int availableNodes = requestMatcher.getNumFreeThreadsForParameters(getRegistry().getAllProxies(),runRequest); 88 | response.setStatus(HttpServletResponse.SC_OK); 89 | // Add the browser so we know the nodes are occupied 90 | context.addRun(new AutomationRunRequest(browserRequested,availableNodes,browserRequested,null,platformRequested)); 91 | try (InputStream in = new ByteArrayInputStream(String.valueOf(availableNodes).getBytes("UTF-8"))){ 92 | ByteStreams.copy(in, response.getOutputStream()); 93 | } finally { 94 | response.flushBuffer(); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AbstractAutomationCleanupTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.task; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import com.rmn.qa.RegistryRetriever; 18 | 19 | /** 20 | * Base task class that has exception/running handling for other tasks to extend with their 21 | * own implementations 22 | * @author mhardin 23 | */ 24 | public abstract class AbstractAutomationCleanupTask extends Thread { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(AbstractAutomationCleanupTask.class); 27 | 28 | protected RegistryRetriever registryRetriever; 29 | private Throwable t; 30 | 31 | public AbstractAutomationCleanupTask(RegistryRetriever registryRetriever) { 32 | this.registryRetriever = registryRetriever; 33 | } 34 | 35 | @Override 36 | public void run() { 37 | try{ 38 | this.doWork(); 39 | }catch(Throwable t) { 40 | this.t = t; 41 | log.error(String.format("Error executing cleanup thread [%s]: %s", getDescription(), t), t); 42 | } 43 | } 44 | 45 | /** 46 | * Performs the work of the task 47 | */ 48 | public abstract void doWork(); 49 | 50 | /** 51 | * Returns the description of the task 52 | * @return 53 | */ 54 | public abstract String getDescription(); 55 | 56 | /** 57 | * Returns the exception of this task if any was encountered 58 | * @return 59 | */ 60 | public Throwable getThrowable() { 61 | return t; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AutomationHubCleanupTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.task; 13 | 14 | import java.text.ParseException; 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | import java.util.Map; 18 | 19 | import org.openqa.grid.internal.ProxySet; 20 | import org.openqa.grid.internal.utils.configuration.GridHubConfiguration; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | import com.google.common.annotations.VisibleForTesting; 24 | import com.rmn.qa.AutomationConstants; 25 | import com.rmn.qa.AutomationUtils; 26 | import com.rmn.qa.RegistryRetriever; 27 | import com.rmn.qa.aws.AwsVmManager; 28 | import com.rmn.qa.aws.VmManager; 29 | 30 | /** 31 | * Task to shut down the hub if it was dynamically started. This task should only be started if this hub is a 32 | * should be terminated via {@link com.rmn.qa.aws.VmManager EC2} 33 | * @author mhardin 34 | */ 35 | public class AutomationHubCleanupTask extends AbstractAutomationCleanupTask { 36 | 37 | private static final Logger log = LoggerFactory.getLogger(AutomationHubCleanupTask.class); 38 | 39 | private VmManager ec2; 40 | private final String instanceId; 41 | @VisibleForTesting 42 | static final String NAME = "Hub Cleanup Task"; 43 | protected static Date createdDate = null; 44 | protected static Date endDate = null; 45 | static boolean errorEncountered = false; 46 | 47 | /** 48 | * Constructs a hub cleanup task with the specified parameters 49 | * @param registryRetriever Context retrieval mechanism to use 50 | * @param ec2 EC2 implementation to use for interaction with the hub 51 | * @param instanceId Instance ID of the hub to cleanup 52 | */ 53 | public AutomationHubCleanupTask(RegistryRetriever registryRetriever, VmManager ec2,String instanceId) { 54 | super(registryRetriever); 55 | this.ec2 = ec2; 56 | this.instanceId = instanceId; 57 | } 58 | 59 | /** 60 | * Returns the ProxySet to be used for cleanup purposes. 61 | * @return 62 | */ 63 | @VisibleForTesting 64 | protected ProxySet getProxySet() { 65 | return registryRetriever.retrieveRegistry().getAllProxies(); 66 | } 67 | 68 | @Override 69 | public String getDescription() { 70 | return AutomationHubCleanupTask.NAME; 71 | } 72 | 73 | /** 74 | * Returns the created date which is pulled from the grid configuration 75 | * @return 76 | */ 77 | protected Object getCreatedDate() { 78 | GridHubConfiguration config = registryRetriever.retrieveRegistry().getConfiguration(); 79 | Object createdDate = getDate(config.custom); 80 | return createdDate; 81 | } 82 | 83 | /** 84 | + * Attempts to parse the created date of the node from the custom parameter 85 | + * @param customConfig 86 | + * @return 87 | + */ 88 | private Date getDate(Map customConfig) { 89 | String stringDate = (String)customConfig.get(AutomationConstants.CONFIG_CREATED_DATE); 90 | Date returnDate = null; 91 | try{ 92 | returnDate = AwsVmManager.NODE_DATE_FORMAT.parse(stringDate); 93 | } catch (ParseException pe) { 94 | log.error(String.format("Error trying to parse created date [%s]", stringDate), pe); 95 | } 96 | return returnDate; 97 | } 98 | 99 | // We're going to continuously monitor this hub to see if we can shut it down. If were approaching the next 100 | // billing cycle and the hub has no nodes, we will terminate the hub. Also, if there is an error parsing the created date, 101 | // we will terminate it at our earliest convenience (no nodes) without regard to the creation time 102 | @Override 103 | public void doWork() { 104 | log.info("Performing cleanup on hub."); 105 | synchronized (AutomationHubCleanupTask.class) { 106 | if(createdDate == null) { 107 | Object createdDate = getCreatedDate(); 108 | try{ 109 | AutomationHubCleanupTask.createdDate = AwsVmManager.NODE_DATE_FORMAT.parse((String)createdDate); 110 | } catch(ParseException pe) { 111 | String message = "Error parsing created date for hub: " + pe; 112 | log.error(message); 113 | errorEncountered = true; 114 | } 115 | if(!errorEncountered) { 116 | // Set the end date to be 55 minutes + creation date 117 | AutomationHubCleanupTask.endDate = AutomationUtils.modifyDate(AutomationHubCleanupTask.createdDate, 55, Calendar.MINUTE); 118 | } 119 | } 120 | } 121 | Date currentTime = new Date(); 122 | if(errorEncountered || currentTime.after(AutomationHubCleanupTask.endDate)) { 123 | // If we're into the next billing cycle, don't shut the hub down 124 | if(errorEncountered && getProxySet().isEmpty()) { 125 | log.info("Error was encountered parsing the created date, so the hub will be shutdown as it is empty."); 126 | ec2.terminateInstance(instanceId); 127 | } else if(AutomationUtils.isCurrentTimeAfterDate(AutomationHubCleanupTask.endDate, 6, Calendar.MINUTE)) { 128 | log.info("Current date: " + new Date()); 129 | log.info("Current end date: " + AutomationHubCleanupTask.endDate); 130 | log.info(String.format("Hub [%s] ran into the next billing cycle. Increasing end date.", instanceId)); 131 | AutomationHubCleanupTask.endDate = AutomationUtils.modifyDate(AutomationHubCleanupTask.endDate,60,Calendar.MINUTE); 132 | return; 133 | }else if(getProxySet().isEmpty()){ 134 | log.warn("No running nodes found after hub expiration time -- terminating hub: " + instanceId); 135 | ec2.terminateInstance(instanceId); 136 | } else { 137 | // This means tests are still running and we cannot terminate yet 138 | log.info("Hub could not be shutdown yet."); 139 | return; 140 | } 141 | } 142 | 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.task; 13 | 14 | import java.text.ParseException; 15 | import java.util.Date; 16 | import java.util.Map; 17 | 18 | import org.openqa.grid.internal.ProxySet; 19 | import org.openqa.grid.internal.RemoteProxy; 20 | import org.openqa.selenium.Platform; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.google.common.annotations.VisibleForTesting; 25 | import com.rmn.qa.AutomationConstants; 26 | import com.rmn.qa.AutomationContext; 27 | import com.rmn.qa.AutomationDynamicNode; 28 | import com.rmn.qa.AutomationRunContext; 29 | import com.rmn.qa.AutomationUtils; 30 | import com.rmn.qa.RegistryRetriever; 31 | import com.rmn.qa.aws.AwsVmManager; 32 | 33 | /** 34 | * Registry task which registers orphaned dynamic {@link com.rmn.qa.AutomationDynamicNode nodes}. This can happen if the hub process restarts for whatever reason 35 | * and loses track of previously registered nodes 36 | * @author mhardin 37 | */ 38 | public class AutomationOrphanedNodeRegistryTask extends AbstractAutomationCleanupTask { 39 | 40 | private static final Logger log = LoggerFactory.getLogger(AutomationOrphanedNodeRegistryTask.class); 41 | @VisibleForTesting 42 | static final String NAME = "Orphaned Node Registry Task"; 43 | 44 | /** 45 | * Constructs a registry task with the specified context retrieval mechanism 46 | * @param registryRetriever Represents the retrieval mechanism you wish to use 47 | */ 48 | public AutomationOrphanedNodeRegistryTask(RegistryRetriever registryRetriever) { 49 | super(registryRetriever); 50 | } 51 | 52 | /** 53 | * Returns the ProxySet to be used for cleanup purposes. 54 | * @return 55 | */ 56 | protected ProxySet getProxySet() { 57 | return registryRetriever.retrieveRegistry().getAllProxies(); 58 | } 59 | 60 | @Override 61 | public String getDescription() { 62 | return AutomationOrphanedNodeRegistryTask.NAME; 63 | } 64 | 65 | // We're going to continuously iterate over registered nodes with the hub. If they're expired, we're going to mark them for removal. 66 | // 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 67 | // into the running queue 68 | @Override 69 | public void doWork() { 70 | ProxySet proxySet = getProxySet(); 71 | if(proxySet != null && proxySet.size() > 0) { 72 | for(RemoteProxy proxy : proxySet) { 73 | Map customConfig = proxy.getConfig().custom; 74 | // If the config has an instanceId in it, this means this node was dynamically started and we should 75 | // track it if we are not already 76 | if(customConfig.containsKey(AutomationConstants.INSTANCE_ID)) { 77 | String instanceId = (String)customConfig.get(AutomationConstants.INSTANCE_ID); 78 | AutomationRunContext context = AutomationContext.getContext(); 79 | // If this node is already in our context, that means we are already tracking this node to terminate 80 | if(!context.nodeExists(instanceId)) { 81 | Date createdDate = getDate(customConfig); 82 | // If we couldn't parse the date out, we are sort of out of luck 83 | if(createdDate == null) { 84 | break; 85 | } 86 | proxy.getConfig(); 87 | String uuid = (String)customConfig.get(AutomationConstants.UUID); 88 | int threadCount = proxy.getConfig().maxSession; 89 | String browser = (String)customConfig.get(AutomationConstants.CONFIG_BROWSER); 90 | String os = (String)customConfig.get(AutomationConstants.CONFIG_OS); 91 | Platform platform = AutomationUtils.getPlatformFromObject(os); 92 | AutomationDynamicNode node = new AutomationDynamicNode(uuid, instanceId, browser, platform, createdDate, threadCount); 93 | log.info("Unregistered dynamic node found: " + node); 94 | context.addNode(node); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * Attempts to parse the created date of the node from the custom parameters 103 | * @param customConfig 104 | * @return 105 | */ 106 | private Date getDate(Map customConfig) { 107 | String stringDate = (String)customConfig.get(AutomationConstants.CONFIG_CREATED_DATE); 108 | Date returnDate = null; 109 | try{ 110 | returnDate = AwsVmManager.NODE_DATE_FORMAT.parse(stringDate); 111 | } catch (ParseException pe) { 112 | log.error(String.format("Error trying to parse created date [%s]: %s", stringDate, pe)); 113 | } 114 | return returnDate; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AutomationPendingNodeRegistryTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.task; 13 | 14 | import java.util.Map; 15 | 16 | import org.openqa.grid.internal.ProxySet; 17 | import org.openqa.grid.internal.RemoteProxy; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import org.openqa.grid.internal.utils.configuration.GridNodeConfiguration; 22 | import com.google.common.annotations.VisibleForTesting; 23 | import com.rmn.qa.AutomationConstants; 24 | import com.rmn.qa.AutomationContext; 25 | import com.rmn.qa.AutomationDynamicNode; 26 | import com.rmn.qa.AutomationRunContext; 27 | import com.rmn.qa.RegistryRetriever; 28 | import com.rmn.qa.aws.VmManager; 29 | 30 | /** 31 | * Registry task which registers dynamic {@link AutomationDynamicNode nodes} as they come online 32 | * 33 | * @author mhardin 34 | */ 35 | public class AutomationPendingNodeRegistryTask extends AbstractAutomationCleanupTask { 36 | 37 | private static final Logger log = LoggerFactory.getLogger(AutomationPendingNodeRegistryTask.class); 38 | @VisibleForTesting 39 | static final String NAME = "Pending Node Registry Task"; 40 | private VmManager vmManager; 41 | 42 | /** 43 | * Constructs a registry task with the specified context retrieval mechanism 44 | * 45 | * @param registryRetriever Represents the retrieval mechanism you wish to use 46 | */ 47 | public AutomationPendingNodeRegistryTask(RegistryRetriever registryRetriever, VmManager vmManager) { 48 | super(registryRetriever); 49 | this.vmManager = vmManager; 50 | } 51 | 52 | /** 53 | * Returns the ProxySet to be used for cleanup purposes. 54 | * 55 | * @return 56 | */ 57 | protected ProxySet getProxySet() { 58 | return registryRetriever.retrieveRegistry().getAllProxies(); 59 | } 60 | 61 | @Override 62 | public String getDescription() { 63 | return AutomationPendingNodeRegistryTask.NAME; 64 | } 65 | 66 | // We're going to continuously iterate over registered nodes with the hub. If they're expired, we're going to mark them for removal. 67 | // 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 68 | // into the running queue 69 | @Override 70 | public void doWork() { 71 | ProxySet proxySet = getProxySet(); 72 | if (proxySet != null && !proxySet.isEmpty()) { 73 | for (RemoteProxy proxy : proxySet) { 74 | Map customConfig = proxy.getConfig().custom; 75 | // If the config has an instanceId in it, this means this node was dynamically started and we should 76 | // track it if we are not already 77 | if (customConfig.containsKey(AutomationConstants.INSTANCE_ID)) { 78 | String instanceId = (String) customConfig.get(AutomationConstants.INSTANCE_ID); 79 | AutomationRunContext context = AutomationContext.getContext(); 80 | // If this node is already in our context, that means we are already tracking this node to terminate 81 | if (context.pendingNodeExists(instanceId)) { 82 | log.info(String.format("Pending node %s found in the running state. Removing from pending set", instanceId)); 83 | context.removePendingNode(instanceId); 84 | } 85 | } 86 | } 87 | } 88 | // Remove any pending nodes that haven't come online after a configured amount of time 89 | AutomationContext.getContext().removeExpiredPendingNodes(vmManager); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AutomationReaperTask.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.amazonaws.services.ec2.model.DescribeInstancesRequest; 11 | import com.amazonaws.services.ec2.model.Filter; 12 | import com.amazonaws.services.ec2.model.Instance; 13 | import com.amazonaws.services.ec2.model.Reservation; 14 | import com.google.common.annotations.VisibleForTesting; 15 | import com.rmn.qa.AutomationContext; 16 | import com.rmn.qa.AutomationUtils; 17 | import com.rmn.qa.RegistryRetriever; 18 | import com.rmn.qa.aws.VmManager; 19 | 20 | public class AutomationReaperTask extends AbstractAutomationCleanupTask { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(AutomationReaperTask.class); 23 | @VisibleForTesting static final String NAME = "VM Reaper Task"; 24 | 25 | private VmManager ec2; 26 | 27 | /** 28 | * Constructs a registry task with the specified context retrieval mechanism 29 | * @param registryRetriever Represents the retrieval mechanism you wish to use 30 | */ 31 | public AutomationReaperTask(RegistryRetriever registryRetriever,VmManager ec2) { 32 | super(registryRetriever); 33 | this.ec2 = ec2; 34 | } 35 | @Override 36 | public void doWork() { 37 | log.info("Running " + AutomationReaperTask.NAME); 38 | DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest(); 39 | Filter filter = new Filter("tag:LaunchSource"); 40 | filter.withValues("SeleniumGridScalerPlugin"); 41 | describeInstancesRequest.withFilters(filter); 42 | List reservations = ec2.describeInstances(describeInstancesRequest); 43 | for(Reservation reservation : reservations) { 44 | for(Instance instance : reservation.getInstances()) { 45 | // Look for orphaned nodes 46 | Date threshold = AutomationUtils.modifyDate(new Date(),-90, Calendar.MINUTE); 47 | String instanceId = instance.getInstanceId(); 48 | // 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 49 | if(threshold.after(instance.getLaunchTime()) && !AutomationContext.getContext().nodeExists(instanceId) && instance.getState().getCode() != 48) { // 48 == terminated 50 | log.info("Terminating orphaned node: " + instanceId); 51 | ec2.terminateInstance(instanceId); 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public String getDescription() { 59 | return AutomationReaperTask.NAME; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AutomationRunCleanupTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.task; 13 | 14 | import com.google.common.annotations.VisibleForTesting; 15 | import com.rmn.qa.AutomationContext; 16 | import com.rmn.qa.AutomationRunContext; 17 | import com.rmn.qa.RegistryRetriever; 18 | import org.openqa.grid.internal.ProxySet; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | /** 23 | * Task to cleanup any registered {@link com.rmn.qa.AutomationRunRequest runs} that no longer have any running tests. 24 | * This will happen after the configured amount of time has passed since the run has started 25 | * @author mhardin 26 | */ 27 | public class AutomationRunCleanupTask extends AbstractAutomationCleanupTask { 28 | 29 | private static final Logger log = LoggerFactory.getLogger(AutomationRunCleanupTask.class); 30 | @VisibleForTesting 31 | static final String NAME = "Run Cleanup Task"; 32 | 33 | /** 34 | * Constructs a run cleanup task with the specified context retrieval mechanism 35 | * @param registryRetriever 36 | */ 37 | public AutomationRunCleanupTask(RegistryRetriever registryRetriever) { 38 | super(registryRetriever); 39 | } 40 | 41 | /** 42 | * {@inheritDoc} 43 | */ 44 | @Override 45 | public String getDescription() { 46 | return AutomationRunCleanupTask.NAME; 47 | } 48 | 49 | // Basically we want to continuously monitor all runs that are registered and ensure that at least 50 | // one test is being run that belongs to that test run. If there are any orphaned runs, we will 51 | // go ahead and remove it from AutomationRunContext providing they are old enough 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | @Override 56 | public void doWork() { 57 | log.info("Performing cleanup on runs."); 58 | AutomationRunContext context = AutomationContext.getContext(); 59 | context.cleanUpRunRequests(getProxySet()); 60 | } 61 | 62 | /** 63 | * Returns the {@link org.openqa.grid.internal.ProxySet ProxySet} to be used for cleanup purposes. 64 | * @return 65 | */ 66 | @VisibleForTesting 67 | protected ProxySet getProxySet() { 68 | return registryRetriever.retrieveRegistry().getAllProxies(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/AutomationScaleNodeTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | package com.rmn.qa.task; 13 | 14 | import java.util.Date; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | import org.openqa.grid.internal.ProxySet; 20 | import org.openqa.selenium.Platform; 21 | import org.openqa.selenium.remote.CapabilityType; 22 | import org.openqa.selenium.remote.DesiredCapabilities; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import com.google.common.annotations.VisibleForTesting; 27 | import com.google.common.collect.Maps; 28 | import com.rmn.qa.AutomationDynamicNode; 29 | import com.rmn.qa.AutomationUtils; 30 | import com.rmn.qa.BrowserPlatformPair; 31 | import com.rmn.qa.NodesCouldNotBeStartedException; 32 | import com.rmn.qa.RegistryRetriever; 33 | import com.rmn.qa.aws.VmManager; 34 | import com.rmn.qa.servlet.AutomationTestRunServlet; 35 | 36 | /** 37 | * Registry task which registers unregistered dynamic {@link AutomationDynamicNode nodes}. This can happen if the hub process restarts for whatever reason 38 | * and loses track of previously registered nodes 39 | * @author mhardin 40 | */ 41 | public class AutomationScaleNodeTask extends AbstractAutomationCleanupTask { 42 | 43 | private static final Logger log = LoggerFactory.getLogger(AutomationScaleNodeTask.class); 44 | private static final int QUEUED_REQUEST_THRESHOLD_IN_MS = 5000; 45 | private Map queuedBrowsersPlatforms = Maps.newHashMap(); 46 | private Map pendingStartupCapacity = Maps.newHashMap(); 47 | private VmManager vmManager; 48 | @VisibleForTesting 49 | static final String NAME = "Automation Scale Node Task"; 50 | 51 | /** 52 | * Constructs a registry task with the specified context retrieval mechanism 53 | * @param registryRetriever Represents the retrieval mechanism you wish to use 54 | */ 55 | public AutomationScaleNodeTask(RegistryRetriever registryRetriever, VmManager vmManager) { 56 | super(registryRetriever); 57 | this.vmManager = vmManager; 58 | } 59 | 60 | /** 61 | * Returns the ProxySet to be used for cleanup purposes. 62 | * @return 63 | */ 64 | protected ProxySet getProxySet() { 65 | return registryRetriever.retrieveRegistry().getAllProxies(); 66 | } 67 | 68 | @Override 69 | public String getDescription() { 70 | return AutomationScaleNodeTask.NAME; 71 | } 72 | 73 | @VisibleForTesting 74 | Iterable getDesiredCapabilities() { 75 | return registryRetriever.retrieveRegistry().getDesiredCapabilities(); 76 | } 77 | 78 | @VisibleForTesting 79 | List startNodes(VmManager vmManager, int browsersToStart, String browser, Platform platform) throws NodesCouldNotBeStartedException { 80 | return AutomationTestRunServlet.startNodes(vmManager, "AD-HOC", browsersToStart, browser, platform); 81 | } 82 | 83 | /** 84 | * Returns true if nodePendingDate is more than 5 seconds older than the current time 85 | */ 86 | @VisibleForTesting 87 | boolean haveTestRequestsBeenQueuedForLongEnough(Date nowDate, Date nodePendingDate) { 88 | return nowDate.getTime() - nodePendingDate.getTime() > QUEUED_REQUEST_THRESHOLD_IN_MS; 89 | } 90 | 91 | /** 92 | * Returns true if the platform is not specified, is all platforms, or is linux 93 | * @param platformPair 94 | * @return 95 | */ 96 | private boolean isEligibleToScale(BrowserPlatformPair platformPair) { 97 | return AutomationUtils.browserAndPlatformSupported(platformPair); 98 | } 99 | 100 | // 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 101 | // over time to see if we need to spin up new instances ad-hoc 102 | @Override 103 | public void doWork() { 104 | log.warn("Doing node scale work"); 105 | // Iterate over all queued requests and track browser/platform combinations that are eligible to scale capacity for 106 | Iterator pendingCapabilities = getDesiredCapabilities().iterator(); 107 | if (pendingCapabilities.hasNext()) { 108 | log.info("Analyzing currently queued requests"); 109 | while (pendingCapabilities.hasNext()) { 110 | DesiredCapabilities capabilities = pendingCapabilities.next(); 111 | String browser = (String) capabilities.getCapability(CapabilityType.BROWSER_NAME); 112 | Object platformObject = capabilities.getCapability(CapabilityType.PLATFORM); 113 | Platform platform = AutomationUtils.getPlatformFromObject(platformObject); 114 | // If a valid platform wasn't able to be parsed from the queued test request, go ahead and default to Platform.ANY, 115 | // as Platform is not required for this plugin 116 | if (platform == null) { 117 | // Default to ANY here and let AwsVmManager dictate what ANY translates to 118 | platform = Platform.ANY; 119 | } 120 | // Group all platforms by their underlying family 121 | platform = AutomationUtils.getUnderlyingFamily(platform); 122 | BrowserPlatformPair desiredPair = new BrowserPlatformPair(browser, platform); 123 | 124 | // Don't attempt to calculate load for browsers & platform families we cannot start 125 | if (!isEligibleToScale(desiredPair)) { 126 | log.warn("Unsupported browser and platform pair, browser: " + browser + " platform family: " + platform.family()); 127 | continue; 128 | } 129 | 130 | // Handle requests for specific browser platforms. 131 | queuedBrowsersPlatforms.computeIfAbsent(new BrowserPlatformPair(browser, platform), s -> new Date()); 132 | } 133 | } 134 | // Now, iterate over eligible browser/platform combinations that we're tracking and attempt to scale up 135 | Iterator queuedBrowsersIterator = queuedBrowsersPlatforms.keySet().iterator(); 136 | while(queuedBrowsersIterator.hasNext()) { 137 | BrowserPlatformPair originalBrowserPlatformRequest = queuedBrowsersIterator.next(); 138 | Date currentTime = new Date(); 139 | Date timeBrowserPlatformQueued = queuedBrowsersPlatforms.get(originalBrowserPlatformRequest); 140 | if (haveTestRequestsBeenQueuedForLongEnough(currentTime, timeBrowserPlatformQueued)) { // If we've had pending queued requests for this browser for at least 5 seconds 141 | pendingCapabilities = getDesiredCapabilities().iterator(); 142 | if (pendingCapabilities.hasNext()) { 143 | int browsersToStart = 0; 144 | while (pendingCapabilities.hasNext()) { 145 | DesiredCapabilities currentlyQueuedCapabilities = pendingCapabilities.next(); 146 | String currentlyQueuedBrowser = (String) currentlyQueuedCapabilities.getCapability(CapabilityType.BROWSER_NAME); 147 | Object platformObject = currentlyQueuedCapabilities.getCapability(CapabilityType.PLATFORM); 148 | Platform currentlyQueuedPlatform = AutomationUtils.getPlatformFromObject(platformObject); 149 | // If a valid platform wasn't able to be parsed from the queued test request, go ahead and default to Platform.ANY, 150 | // as Platform is not required for this plugin 151 | if (currentlyQueuedPlatform == null) { 152 | currentlyQueuedPlatform = Platform.ANY; 153 | } 154 | // Group all platforms by their underlying family 155 | currentlyQueuedPlatform = AutomationUtils.getUnderlyingFamily(currentlyQueuedPlatform); 156 | if (originalBrowserPlatformRequest.equals(new BrowserPlatformPair(currentlyQueuedBrowser, currentlyQueuedPlatform))) { 157 | browsersToStart++; 158 | } 159 | } 160 | this.startNodesForBrowserPlatform(originalBrowserPlatformRequest, browsersToStart); 161 | } 162 | // Regardless of if we spun up browsers or not, clear this count out 163 | queuedBrowsersIterator.remove(); 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * Starts up the specified number of browsers for the specified browser/platform pair. Takes into account nodes that are pending startup 170 | * @param browserPlatform 171 | * @param browsersToStart 172 | */ 173 | private void startNodesForBrowserPlatform(BrowserPlatformPair browserPlatform, int browsersToStart) { 174 | // If there are queued up browser requests, go ahead and subtract nodes pending startup from the count to account for the pending capacity 175 | if (browsersToStart > 0 && pendingStartupCapacity.containsKey(browserPlatform)) { 176 | ScaleCapacityContext pendingCapacityContext = pendingStartupCapacity.get(browserPlatform); 177 | // Go ahead and clear out any nodes that have started up 178 | pendingCapacityContext.clearPendingNodes(); 179 | // This represents capacity that is pending startup and we need to subtract it from the total amount of nodes that we want to start 180 | // so we do not get an excess of capacity 181 | int pendingCapacity = pendingCapacityContext.getTotalCapacityCount(); 182 | if (pendingCapacity > 0) { 183 | log.warn(String.format("Subtracting %d capacity from queued load %s for browser/platform %s", pendingCapacity, browsersToStart, browserPlatform)); 184 | } 185 | browsersToStart = browsersToStart - pendingCapacity; 186 | } 187 | if (browsersToStart > 0) { 188 | log.info(String.format("Spinning up %d threads for browser/platform %s based on current test load", browsersToStart, browserPlatform)); 189 | try { 190 | List createdNodes = this.startNodes(vmManager, browsersToStart, browserPlatform.getBrowser(), browserPlatform.getPlatform()); 191 | // Grab the scale context object for this browser/platform pair 192 | ScaleCapacityContext contextForBrowserPair = pendingStartupCapacity.computeIfAbsent(browserPlatform, browserPlatformPair -> new ScaleCapacityContext()); 193 | // Add all the created nodes to the context object so we can compute load programmatically for pending browsers 194 | contextForBrowserPair.addAll(createdNodes); 195 | } catch (NodesCouldNotBeStartedException e) { 196 | throw new RuntimeException("Error scaling up nodes", e); 197 | } 198 | } 199 | 200 | } 201 | } -------------------------------------------------------------------------------- /src/main/java/com/rmn/qa/task/ScaleCapacityContext.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | 7 | import com.beust.jcommander.internal.Lists; 8 | import com.google.common.annotations.VisibleForTesting; 9 | import com.rmn.qa.AutomationContext; 10 | import com.rmn.qa.AutomationDynamicNode; 11 | 12 | /** 13 | * Created by matthew on 6/23/16. 14 | */ 15 | public class ScaleCapacityContext { 16 | 17 | @VisibleForTesting 18 | List nodesPendingStartup = Lists.newArrayList(); 19 | 20 | public int getTotalCapacityCount() { 21 | return nodesPendingStartup.stream().mapToInt(AutomationDynamicNode::getNodeCapacity).sum(); 22 | } 23 | 24 | public void addAll(Collection nodes) { 25 | nodesPendingStartup.addAll(nodes); 26 | } 27 | 28 | /** 29 | * Clears out any nodes that are no longer in the 'pending' state 30 | */ 31 | public void clearPendingNodes() { 32 | Iterator iterator = nodesPendingStartup.iterator(); 33 | while(iterator.hasNext()) { 34 | AutomationDynamicNode pendingNode = iterator.next(); 35 | if (!AutomationContext.getContext().pendingNodeExists(pendingNode.getInstanceId())) { 36 | iterator.remove(); 37 | } 38 | } 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "ScaleCapacityContext{" + 44 | "nodesPendingStartup=" + nodesPendingStartup + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/resources/.s3cfg: -------------------------------------------------------------------------------- 1 | [default] 2 | access_key = 3 | secret_key = -------------------------------------------------------------------------------- /src/main/resources/aws.properties.default: -------------------------------------------------------------------------------- 1 | ########### Rename this file to 'aws.properties' to override default values 2 | 3 | # Default region 4 | region=us-east-1 5 | 6 | # Endpoints 7 | us-east-1_endpoint=https://us-east-1.ec2.amazonaws.com 8 | us-west-1_endpoint=https://us-west-1.ec2.amazonaws.com 9 | us-west-2_endpoint=https://ec2.us-west-2.amazonaws.com 10 | eu-west-1_endpoint=https://ec2.eu-west-1.amazonaws.com 11 | eu-central-1_endpoint=https://ec2.eu-central-1.amazonaws.com 12 | ap-southeast-2_endpoint=https://ec2.ap-southeast-2.amazonaws.com 13 | ap-northeast-1_endpoint=https://ec2.ap-northeast-1.amazonaws.com 14 | sa-east-1_endpoint=https://ec2.sa-east-1.amazonaws.com 15 | 16 | # Node AMI Info 17 | us-east-1_linux_node_ami=ami-d216a3ba 18 | node_instance_type_chrome=c4.large 19 | # Firefox and IE will only run in 1 browser per VM so we'll use micros for them 20 | node_instance_type_firefox=t2.small 21 | node_instance_type_internetexplorer=t2.small 22 | us-east-1_windows_node_ami=ami-dcd869b4 23 | 24 | 25 | # Security Group Info 26 | # us-east-1_security_group=sg-00000000 27 | # VPC Info 28 | # us-east-1_subnet_id=subnet-00000000 29 | # Fallback VPCs 30 | # us-east-1_subnet_fallback_id_1=subnet-11111111 31 | # us-east-1_subnet_fallback_id_2=subnet-22222222 32 | # us-east-1_subnet_fallback_id_3=subnet-33333333 33 | # Key 34 | # us-east-1_key_name=keyNameHere 35 | # Tags to be used 36 | # tagProduct=Department,QA 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/hub.static.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": null, 3 | "port": 4444, 4 | "newSessionWaitTimeout": -1, 5 | "prioritizer": null, 6 | "throwOnCapabilityNotPresent": false, 7 | "nodePolling": 5000, 8 | "unregisterIfStillDownAfter": 5000, 9 | "nodeStatusCheckTimeout": 3000, 10 | "cleanUpCycle": 5000, 11 | "timeout": 80000, 12 | "servlets": ["com.rmn.qa.servlet.AutomationTestRunServlet","com.rmn.qa.servlet.StatusServlet"], 13 | "capabilityMatcher": "com.rmn.qa.AutomationCapabilityMatcher", 14 | "browserTimeout": 70000, 15 | "jettyMaxThreads":1024 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | %date{"MMM dd, yyyy HH:mm:ss:SSS aaa"} - %-5level [%thread] [%logger{0}] %msg%n 18 | 19 | 20 | 21 | 23 | 24 | ${logLocation:-log/automation.log} 25 | 26 | %date{"MMM dd, yyyy HH:mm:ss:SSS aaa"} - %-5level [%thread] [%logger{0}] %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/resources/node.linux.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": 3 | [ 4 | { 5 | "browserName": "firefox", 6 | "maxInstances": , 7 | "instanceId": , 8 | "seleniumProtocol": "WebDriver" 9 | }, 10 | { 11 | "browserName": "chrome", 12 | "maxInstances": , 13 | "instanceId": , 14 | "seleniumProtocol": "WebDriver" 15 | } 16 | ], 17 | "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy", 18 | "maxSession": , 19 | "register": true, 20 | "registerCycle": 5000, 21 | "hub": "", 22 | "custom": { 23 | "uuid":"", 24 | "instanceId": "", 25 | "createdDate":"", 26 | "createdBrowser":"", 27 | "createdOs":"" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/node.windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": 3 | [ 4 | { 5 | "browserName": "internet explorer", 6 | "maxInstances": , 7 | "instanceId": , 8 | "seleniumProtocol": "WebDriver" 9 | }, 10 | { 11 | "browserName": "chrome", 12 | "maxInstances": , 13 | "instanceId": , 14 | "seleniumProtocol": "WebDriver" 15 | }, 16 | { 17 | "browserName": "firefox", 18 | "maxInstances": , 19 | "instanceId"=, 20 | "seleniumProtocol": "WebDriver" 21 | } 22 | ], 23 | "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy", 24 | "maxSession": , 25 | "register": true, 26 | "registerCycle": 5000, 27 | "hub": "", 28 | "custom": { 29 | "uuid":"", 30 | "instanceId": "", 31 | "createdDate":"", 32 | "createdBrowser":"", 33 | "createdOs":"" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/AbstractAutomationCleanupTaskTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import org.junit.Test; 16 | 17 | import com.rmn.qa.task.AbstractAutomationCleanupTask; 18 | 19 | import junit.framework.Assert; 20 | 21 | /** 22 | * Created by mhardin on 4/25/14. 23 | */ 24 | public class AbstractAutomationCleanupTaskTest extends BaseTest { 25 | 26 | @Test 27 | public void testExceptionHandled() { 28 | TestTask task = new TestTask(null); 29 | task.run(); 30 | Assert.assertEquals("Correct exception should have been thrown",task.getThrowable(),task.re); 31 | } 32 | 33 | private static class TestTask extends AbstractAutomationCleanupTask { 34 | 35 | private RuntimeException re = new RuntimeException("test exception"); 36 | 37 | public TestTask(RegistryRetriever retriever) { 38 | super(retriever); 39 | } 40 | 41 | @Override 42 | public void doWork() { 43 | throw re; 44 | } 45 | 46 | @Override 47 | public String getDescription() { 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/AutomationCapabilityMatcherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.util.Date; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | import org.junit.After; 20 | import org.junit.Test; 21 | import org.openqa.selenium.Platform; 22 | import org.openqa.selenium.remote.CapabilityType; 23 | 24 | import com.google.common.collect.ImmutableMap; 25 | 26 | import junit.framework.Assert; 27 | 28 | /** 29 | * Created by mhardin on 4/25/14. 30 | */ 31 | public class AutomationCapabilityMatcherTest extends BaseTest { 32 | 33 | @Test 34 | public void testMatches() { 35 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 36 | Map nodeCapability = new HashMap(); 37 | nodeCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 38 | Map testCapability = new HashMap(); 39 | testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 40 | Assert.assertTrue("Capabilities should match as node is not dynamic",matcher.matches(nodeCapability,testCapability)); 41 | } 42 | 43 | @Test 44 | public void testNodeNotInContext() { 45 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 46 | Map nodeCapability = new HashMap(); 47 | nodeCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 48 | nodeCapability.put(AutomationConstants.INSTANCE_ID,"foobar"); 49 | Map testCapability = new HashMap(); 50 | testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 51 | AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser", Platform.LINUX, new Date(),10); 52 | node.updateStatus(AutomationDynamicNode.STATUS.EXPIRED); 53 | Assert.assertTrue("Capabilities should match as node is not in context",matcher.matches(nodeCapability,testCapability)); 54 | } 55 | 56 | @Test 57 | public void testExpiredNode() { 58 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 59 | Map nodeCapability = new HashMap(); 60 | nodeCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 61 | nodeCapability.put(AutomationConstants.INSTANCE_ID,"foo"); 62 | Map testCapability = new HashMap(); 63 | testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 64 | AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser",Platform.LINUX, new Date(),10); 65 | AutomationContext.getContext().addNode(node); 66 | node.updateStatus(AutomationDynamicNode.STATUS.EXPIRED); 67 | Assert.assertFalse("Capabilities should match as node is not in context", matcher.matches(nodeCapability, testCapability)); 68 | } 69 | 70 | @Test 71 | public void testTerminatedNode() { 72 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 73 | Map nodeCapability = new HashMap(); 74 | nodeCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 75 | nodeCapability.put(AutomationConstants.INSTANCE_ID,"foo"); 76 | Map testCapability = new HashMap(); 77 | testCapability.put(CapabilityType.BROWSER_NAME,"firefox"); 78 | AutomationDynamicNode node = new AutomationDynamicNode("uuid","foo","browser",Platform.LINUX, new Date(),10); 79 | AutomationContext.getContext().addNode(node); 80 | node.updateStatus(AutomationDynamicNode.STATUS.TERMINATED); 81 | Assert.assertFalse("Capabilities should match as node is not in context",matcher.matches(nodeCapability,testCapability)); 82 | } 83 | 84 | @Test 85 | public void testSystemPropertyParsed() { 86 | String soleProperty = "foo"; 87 | System.setProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME,soleProperty); 88 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 89 | Assert.assertEquals("Only one property should have been added", 1, matcher.additionalConsiderations.size()); 90 | Assert.assertTrue("Contained property should be correct", matcher.additionalConsiderations.contains(soleProperty)); 91 | } 92 | 93 | @Test 94 | public void testMultipleSystemPropertiesParsed() { 95 | String firstProperty = "foo", secondProperty = "bar"; 96 | System.setProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME,firstProperty + "," + secondProperty); 97 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 98 | Assert.assertEquals("Only one property should have been added",2,matcher.additionalConsiderations.size()); 99 | Assert.assertTrue("Contained property should be correct", matcher.additionalConsiderations.contains(firstProperty)); 100 | Assert.assertTrue("Contained property should be correct", matcher.additionalConsiderations.contains(secondProperty)); 101 | } 102 | 103 | @Test 104 | public void testPropertyDoesntMatch() { 105 | String soleProperty = "foo"; 106 | System.setProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME,soleProperty); 107 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 108 | Map nodeCapability = ImmutableMap.of("foo", (Object)"bar"); 109 | Map requestedCapability = ImmutableMap.of("foo", (Object)"doesntMatch"); 110 | Assert.assertFalse("Capabilities should not match due to override", matcher.matches(nodeCapability, requestedCapability)); 111 | } 112 | 113 | @Test 114 | public void testPropertyNotPresentInCapabilities() { 115 | String soleProperty = "foo"; 116 | System.setProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME,soleProperty); 117 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 118 | Map nodeCapability = ImmutableMap.of("browser", (Object)"bar"); 119 | Map requestedCapability = ImmutableMap.of("browser", (Object)"bar"); 120 | Assert.assertTrue("Capabilities should not match due to override", matcher.matches(nodeCapability, requestedCapability)); 121 | } 122 | 123 | @Test 124 | public void testPropertyDoesMatch() { 125 | String soleProperty = "foo"; 126 | System.setProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME,soleProperty); 127 | AutomationCapabilityMatcher matcher = new AutomationCapabilityMatcher(); 128 | Map nodeCapability = ImmutableMap.of("foo", (Object)"bar"); 129 | Map requestedCapability = ImmutableMap.of("foo", (Object)"bar"); 130 | Assert.assertTrue("Capabilities should not match due to override",matcher.matches(nodeCapability,requestedCapability)); 131 | } 132 | 133 | @After 134 | public void clearSystemProperty() { 135 | System.clearProperty(AutomationConstants.EXTRA_CAPABILITIES_PROPERTY_NAME); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/AutomationDynamicNodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import org.junit.Test; 16 | 17 | import nl.jqno.equalsverifier.EqualsVerifier; 18 | import nl.jqno.equalsverifier.Warning; 19 | 20 | /** 21 | * Created by mhardin on 5/1/14. 22 | */ 23 | public class AutomationDynamicNodeTest extends BaseTest { 24 | 25 | @Test 26 | public void equalsContract() { 27 | EqualsVerifier.forClass(AutomationDynamicNode.class).suppress(Warning.NULL_FIELDS,Warning.NONFINAL_FIELDS).verify(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/AutomationUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | 18 | import org.junit.Test; 19 | 20 | import junit.framework.Assert; 21 | 22 | public class AutomationUtilsTest extends BaseTest { 23 | 24 | @Test 25 | public void testDifferentCasedEquals() { 26 | String case1 = "CASE"; 27 | String case2 = case1.toLowerCase(); 28 | Assert.assertTrue("Match should not be case sensitive", AutomationUtils.lowerCaseMatch(case1, case2)); 29 | } 30 | 31 | @Test 32 | public void testDifferentCasedReversedEquals() { 33 | String case1 = "CASE"; 34 | String case2 = case1.toLowerCase(); 35 | Assert.assertTrue("Match should not be case sensitive", AutomationUtils.lowerCaseMatch(case2, case1)); 36 | } 37 | 38 | @Test 39 | public void testWhiteSpaceSensitiveEquals() { 40 | String case1 = " foo fighters "; 41 | String case2 = "foofighters"; 42 | Assert.assertTrue("Whitespace should be ignored", AutomationUtils.lowerCaseMatch(case1, case2)); 43 | } 44 | 45 | @Test 46 | public void testDateIsAfter() { 47 | Date baseDate = new Date(); 48 | Calendar c = Calendar.getInstance(); 49 | c.setTime(baseDate); 50 | c.add(Calendar.SECOND,-10); 51 | Date afterDate = c.getTime(); 52 | Assert.assertTrue("Date should be considered after", AutomationUtils.isCurrentTimeAfterDate(afterDate,9,Calendar.SECOND)); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa; 2 | 3 | import org.junit.After; 4 | 5 | /** 6 | * Created by matthew on 5/26/16. 7 | */ 8 | public abstract class BaseTest { 9 | 10 | @After 11 | // Since AutomationContext is a shared singleton, make sure and clear it after every test 12 | public void afterTest() { 13 | AutomationContext.refreshContext(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/MockHttpServletRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.io.BufferedReader; 16 | import java.io.IOException; 17 | import java.io.UnsupportedEncodingException; 18 | import java.security.Principal; 19 | import java.util.Collection; 20 | import java.util.Enumeration; 21 | import java.util.HashMap; 22 | import java.util.Locale; 23 | import java.util.Map; 24 | 25 | import javax.servlet.AsyncContext; 26 | import javax.servlet.DispatcherType; 27 | import javax.servlet.RequestDispatcher; 28 | import javax.servlet.ServletContext; 29 | import javax.servlet.ServletException; 30 | import javax.servlet.ServletInputStream; 31 | import javax.servlet.ServletRequest; 32 | import javax.servlet.ServletResponse; 33 | import javax.servlet.http.Cookie; 34 | import javax.servlet.http.HttpServletRequest; 35 | import javax.servlet.http.HttpServletResponse; 36 | import javax.servlet.http.HttpSession; 37 | import javax.servlet.http.HttpUpgradeHandler; 38 | import javax.servlet.http.Part; 39 | 40 | /** 41 | * Created by mhardin on 2/6/14. 42 | */ 43 | public class MockHttpServletRequest implements HttpServletRequest { 44 | 45 | private Map parameters = new HashMap(); 46 | 47 | @Override 48 | public String getAuthType() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public String changeSessionId() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public DispatcherType getDispatcherType() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public AsyncContext startAsync() throws IllegalStateException { 64 | return null; 65 | } 66 | 67 | @Override 68 | public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { 69 | return null; 70 | } 71 | 72 | @Override 73 | public Part getPart(String name) throws IOException, ServletException { 74 | return null; 75 | } 76 | 77 | @Override 78 | public T upgrade(Class handlerClass) throws IOException, ServletException { 79 | return null; 80 | } 81 | 82 | @Override 83 | public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { 84 | return false; 85 | } 86 | 87 | @Override 88 | public void login(String username, String password) throws ServletException { 89 | 90 | } 91 | 92 | @Override 93 | public void logout() throws ServletException { 94 | 95 | } 96 | 97 | @Override 98 | public Collection getParts() throws IOException, ServletException { 99 | return null; 100 | } 101 | 102 | @Override 103 | public boolean isAsyncStarted() { 104 | return false; 105 | } 106 | 107 | @Override 108 | public boolean isAsyncSupported() { 109 | return false; 110 | } 111 | 112 | @Override 113 | public AsyncContext getAsyncContext() { 114 | return null; 115 | } 116 | 117 | @Override 118 | public long getContentLengthLong() { 119 | return 0; 120 | } 121 | 122 | @Override 123 | public Cookie[] getCookies() { 124 | return new Cookie[0]; 125 | } 126 | 127 | @Override 128 | public long getDateHeader(String name) { 129 | return 0; 130 | } 131 | 132 | @Override 133 | public String getHeader(String name) { 134 | return null; 135 | } 136 | 137 | @Override 138 | public Enumeration getHeaders(String name) { 139 | return null; 140 | } 141 | 142 | @Override 143 | public Enumeration getHeaderNames() { 144 | return null; 145 | } 146 | 147 | @Override 148 | public int getIntHeader(String name) { 149 | return 0; 150 | } 151 | 152 | @Override 153 | public String getMethod() { 154 | return null; 155 | } 156 | 157 | @Override 158 | public String getPathInfo() { 159 | return null; 160 | } 161 | 162 | @Override 163 | public String getPathTranslated() { 164 | return null; 165 | } 166 | 167 | @Override 168 | public String getContextPath() { 169 | return null; 170 | } 171 | 172 | @Override 173 | public String getQueryString() { 174 | return null; 175 | } 176 | 177 | @Override 178 | public String getRemoteUser() { 179 | return null; 180 | } 181 | 182 | @Override 183 | public boolean isUserInRole(String role) { 184 | return false; 185 | } 186 | 187 | @Override 188 | public Principal getUserPrincipal() { 189 | return null; 190 | } 191 | 192 | @Override 193 | public String getRequestedSessionId() { 194 | return null; 195 | } 196 | 197 | @Override 198 | public String getRequestURI() { 199 | return null; 200 | } 201 | 202 | @Override 203 | public StringBuffer getRequestURL() { 204 | return null; 205 | } 206 | 207 | @Override 208 | public String getServletPath() { 209 | return null; 210 | } 211 | 212 | @Override 213 | public HttpSession getSession(boolean create) { 214 | return null; 215 | } 216 | 217 | @Override 218 | public HttpSession getSession() { 219 | return null; 220 | } 221 | 222 | @Override 223 | public boolean isRequestedSessionIdValid() { 224 | return false; 225 | } 226 | 227 | @Override 228 | public boolean isRequestedSessionIdFromCookie() { 229 | return false; 230 | } 231 | 232 | @Override 233 | public boolean isRequestedSessionIdFromURL() { 234 | return false; 235 | } 236 | 237 | @Override 238 | public boolean isRequestedSessionIdFromUrl() { 239 | return false; 240 | } 241 | 242 | @Override 243 | public Object getAttribute(String name) { 244 | return null; 245 | } 246 | 247 | @Override 248 | public Enumeration getAttributeNames() { 249 | return null; 250 | } 251 | 252 | @Override 253 | public String getCharacterEncoding() { 254 | return null; 255 | } 256 | 257 | @Override 258 | public void setCharacterEncoding(String env) throws UnsupportedEncodingException { 259 | 260 | } 261 | 262 | @Override 263 | public int getContentLength() { 264 | return 0; 265 | } 266 | 267 | @Override 268 | public String getContentType() { 269 | return null; 270 | } 271 | 272 | @Override 273 | public ServletInputStream getInputStream() throws IOException { 274 | return null; 275 | } 276 | 277 | @Override 278 | public String getParameter(String name) { 279 | return parameters.get(name); 280 | } 281 | 282 | public void setParameter(String key, String value) { 283 | parameters.put(key,value); 284 | } 285 | 286 | @Override 287 | public Enumeration getParameterNames() { 288 | return null; 289 | } 290 | 291 | @Override 292 | public String[] getParameterValues(String name) { 293 | return new String[0]; 294 | } 295 | 296 | @Override 297 | public Map getParameterMap() { 298 | return null; 299 | } 300 | 301 | @Override 302 | public String getProtocol() { 303 | return null; 304 | } 305 | 306 | @Override 307 | public String getScheme() { 308 | return null; 309 | } 310 | 311 | @Override 312 | public String getServerName() { 313 | return null; 314 | } 315 | 316 | @Override 317 | public int getServerPort() { 318 | return 0; 319 | } 320 | 321 | @Override 322 | public BufferedReader getReader() throws IOException { 323 | return null; 324 | } 325 | 326 | @Override 327 | public String getRemoteAddr() { 328 | return null; 329 | } 330 | 331 | @Override 332 | public String getRemoteHost() { 333 | return null; 334 | } 335 | 336 | @Override 337 | public void setAttribute(String name, Object o) { 338 | 339 | } 340 | 341 | @Override 342 | public void removeAttribute(String name) { 343 | 344 | } 345 | 346 | @Override 347 | public Locale getLocale() { 348 | return null; 349 | } 350 | 351 | @Override 352 | public Enumeration getLocales() { 353 | return null; 354 | } 355 | 356 | @Override 357 | public boolean isSecure() { 358 | return false; 359 | } 360 | 361 | @Override 362 | public RequestDispatcher getRequestDispatcher(String path) { 363 | return null; 364 | } 365 | 366 | @Override 367 | public String getRealPath(String path) { 368 | return null; 369 | } 370 | 371 | @Override 372 | public int getRemotePort() { 373 | return 0; 374 | } 375 | 376 | @Override 377 | public String getLocalName() { 378 | return null; 379 | } 380 | 381 | @Override 382 | public String getLocalAddr() { 383 | return null; 384 | } 385 | 386 | @Override 387 | public int getLocalPort() { 388 | return 0; 389 | } 390 | 391 | @Override 392 | public ServletContext getServletContext() { 393 | return null; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/MockHttpServletResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.io.IOException; 16 | import java.io.PrintWriter; 17 | import java.util.Collection; 18 | import java.util.Locale; 19 | 20 | import javax.servlet.ServletOutputStream; 21 | import javax.servlet.http.Cookie; 22 | import javax.servlet.http.HttpServletResponse; 23 | 24 | /** 25 | * Created by mhardin on 2/6/14. 26 | */ 27 | public class MockHttpServletResponse implements HttpServletResponse { 28 | 29 | private int errorCode; 30 | private String errorMessage; 31 | private int statusCode; 32 | 33 | @Override 34 | public void addCookie(Cookie cookie) { 35 | 36 | } 37 | 38 | @Override 39 | public int getStatus() { 40 | return 0; 41 | } 42 | 43 | @Override 44 | public Collection getHeaderNames() { 45 | return null; 46 | } 47 | 48 | @Override 49 | public void setContentLengthLong(long len) { 50 | 51 | } 52 | 53 | @Override 54 | public Collection getHeaders(String name) { 55 | return null; 56 | } 57 | 58 | @Override 59 | public String getHeader(String name) { 60 | return null; 61 | } 62 | 63 | @Override 64 | public boolean containsHeader(String name) { 65 | return false; 66 | } 67 | 68 | @Override 69 | public String encodeURL(String url) { 70 | return null; 71 | } 72 | 73 | @Override 74 | public String encodeRedirectURL(String url) { 75 | return null; 76 | } 77 | 78 | @Override 79 | public String encodeUrl(String url) { 80 | return null; 81 | } 82 | 83 | @Override 84 | public String encodeRedirectUrl(String url) { 85 | return null; 86 | } 87 | 88 | @Override 89 | public void sendError(int sc, String msg) throws IOException { 90 | this.errorCode = sc; 91 | this.errorMessage = msg; 92 | } 93 | 94 | @Override 95 | public void sendError(int sc) throws IOException { 96 | this.errorCode = sc; 97 | } 98 | 99 | public int getErrorCode() { 100 | return errorCode; 101 | } 102 | 103 | public String getErrorMessage() { 104 | return errorMessage; 105 | } 106 | 107 | @Override 108 | public void sendRedirect(String location) throws IOException { 109 | 110 | } 111 | 112 | @Override 113 | public void setDateHeader(String name, long date) { 114 | 115 | } 116 | 117 | @Override 118 | public void addDateHeader(String name, long date) { 119 | 120 | } 121 | 122 | @Override 123 | public void setHeader(String name, String value) { 124 | 125 | } 126 | 127 | @Override 128 | public void addHeader(String name, String value) { 129 | 130 | } 131 | 132 | @Override 133 | public void setIntHeader(String name, int value) { 134 | 135 | } 136 | 137 | @Override 138 | public void addIntHeader(String name, int value) { 139 | 140 | } 141 | 142 | @Override 143 | public void setStatus(int sc) { 144 | this.statusCode = sc; 145 | } 146 | 147 | public int getStatusCode() { 148 | return this.statusCode; 149 | } 150 | 151 | @Override 152 | public void setStatus(int sc, String sm) { 153 | 154 | } 155 | 156 | @Override 157 | public String getCharacterEncoding() { 158 | return null; 159 | } 160 | 161 | @Override 162 | public String getContentType() { 163 | return null; 164 | } 165 | 166 | @Override 167 | public ServletOutputStream getOutputStream() throws IOException { 168 | return null; 169 | } 170 | 171 | @Override 172 | public PrintWriter getWriter() throws IOException { 173 | return null; 174 | } 175 | 176 | @Override 177 | public void setCharacterEncoding(String charset) { 178 | 179 | } 180 | 181 | @Override 182 | public void setContentLength(int len) { 183 | 184 | } 185 | 186 | @Override 187 | public void setContentType(String type) { 188 | 189 | } 190 | 191 | @Override 192 | public void setBufferSize(int size) { 193 | 194 | } 195 | 196 | @Override 197 | public int getBufferSize() { 198 | return 0; 199 | } 200 | 201 | @Override 202 | public void flushBuffer() throws IOException { 203 | 204 | } 205 | 206 | @Override 207 | public void resetBuffer() { 208 | 209 | } 210 | 211 | @Override 212 | public boolean isCommitted() { 213 | return false; 214 | } 215 | 216 | @Override 217 | public void reset() { 218 | 219 | } 220 | 221 | @Override 222 | public void setLocale(Locale loc) { 223 | 224 | } 225 | 226 | @Override 227 | public Locale getLocale() { 228 | return null; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/MockRemoteProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.net.URL; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | import org.openqa.grid.common.RegistrationRequest; 21 | import org.openqa.grid.common.exception.GridException; 22 | import org.openqa.grid.internal.Registry; 23 | import org.openqa.grid.internal.RemoteProxy; 24 | import org.openqa.grid.internal.TestSession; 25 | import org.openqa.grid.internal.TestSlot; 26 | import org.openqa.grid.internal.utils.CapabilityMatcher; 27 | import org.openqa.grid.internal.utils.HtmlRenderer; 28 | import org.openqa.grid.internal.utils.configuration.GridNodeConfiguration; 29 | import org.openqa.selenium.remote.internal.HttpClientFactory; 30 | 31 | import com.google.gson.JsonObject; 32 | 33 | /** 34 | * Created by mhardin on 2/6/14. 35 | */ 36 | public class MockRemoteProxy implements RemoteProxy { 37 | 38 | private List testSlots = new ArrayList();; 39 | private GridNodeConfiguration config = new GridNodeConfiguration(); 40 | private CapabilityMatcher matcher; 41 | private int maxSessions; 42 | 43 | @Override 44 | public List getTestSlots() { 45 | return testSlots; 46 | } 47 | 48 | public void setTestSlots(List testSlots) { 49 | this.testSlots = testSlots; 50 | } 51 | 52 | public void setMultipleTestSlots(TestSlot testSlot, int count) { 53 | for(int i=0;i config) { 93 | this.config.custom = config; 94 | } 95 | 96 | @Override 97 | public RegistrationRequest getOriginalRegistrationRequest() { 98 | return null; 99 | } 100 | 101 | @Override 102 | public int getMaxNumberOfConcurrentTestSessions() { 103 | return maxSessions; 104 | } 105 | 106 | public void setMaxNumberOfConcurrentTestSessions(int sessions) { 107 | this.maxSessions = sessions; 108 | } 109 | 110 | @Override 111 | public URL getRemoteHost() { 112 | return null; 113 | } 114 | 115 | @Override 116 | public TestSession getNewSession(Map requestedCapability) { 117 | return null; 118 | } 119 | 120 | @Override 121 | public int getTotalUsed() { 122 | return 0; 123 | } 124 | 125 | @Override 126 | public HtmlRenderer getHtmlRender() { 127 | return null; 128 | } 129 | 130 | @Override 131 | public int getTimeOut() { 132 | return 0; 133 | } 134 | 135 | @Override 136 | public HttpClientFactory getHttpClientFactory() { 137 | return null; 138 | } 139 | 140 | @Override 141 | public JsonObject getStatus() throws GridException { 142 | return null; 143 | } 144 | 145 | @Override 146 | public boolean hasCapability(Map requestedCapability) { 147 | return false; 148 | } 149 | 150 | @Override 151 | public boolean isBusy() { 152 | return false; 153 | } 154 | 155 | @Override 156 | public float getResourceUsageInPercent() { 157 | return 0; 158 | } 159 | 160 | @Override 161 | public int compareTo(RemoteProxy remoteProxy) { 162 | return 0; 163 | } 164 | 165 | @Override 166 | public long getLastSessionStart() { 167 | return 0; 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/MockRequestMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import org.openqa.grid.internal.ProxySet; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class MockRequestMatcher implements RequestMatcher { 21 | 22 | private int threadsToReturn; 23 | private Map inProgressTests = new HashMap(); 24 | 25 | @Override 26 | public int getNumFreeThreadsForParameters(ProxySet proxySet, AutomationRunRequest request) { 27 | return threadsToReturn; 28 | } 29 | 30 | public void setThreadsToReturn(int threadsToReturn) { 31 | this.threadsToReturn = threadsToReturn; 32 | } 33 | 34 | @Override 35 | public int getNumInProgressTests(ProxySet proxySet, AutomationRunRequest request) { 36 | Integer returnValue = inProgressTests.get(request.getBrowser()); 37 | return (returnValue == null) ? 0 : returnValue; 38 | } 39 | 40 | public void setInProgressTests(String browser, int inProgressTests) { 41 | this.inProgressTests.put(browser,inProgressTests); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/MockVmManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | import org.openqa.selenium.Platform; 19 | 20 | import com.amazonaws.services.ec2.model.DescribeInstancesRequest; 21 | import com.amazonaws.services.ec2.model.Instance; 22 | import com.amazonaws.services.ec2.model.Reservation; 23 | import com.rmn.qa.aws.VmManager; 24 | 25 | public class MockVmManager implements VmManager { 26 | 27 | private boolean nodesLaunched = false; 28 | private int numberLaunched; 29 | private String browser; 30 | private boolean throwException = false; 31 | private boolean terminated = false; 32 | private List reservations; 33 | 34 | 35 | @Override 36 | public List launchNodes(String uuid, Platform platform, String browser, String hubHostName, int nodeCount, int maxSessions) { 37 | if(throwException) { 38 | throw new RuntimeException("Can't start nodes"); 39 | } 40 | this.nodesLaunched = true; 41 | this.numberLaunched = nodeCount; 42 | this.browser = browser; 43 | Instance instance = new Instance(); 44 | instance.setInstanceId("instanceId"); 45 | return Arrays.asList(instance); 46 | } 47 | 48 | @Override 49 | public boolean terminateInstance(String instanceId) { 50 | terminated = true; 51 | return true; 52 | } 53 | 54 | @Override 55 | public List describeInstances(DescribeInstancesRequest describeInstancesRequest) { 56 | return reservations; 57 | } 58 | 59 | public boolean isNodesLaunched() { 60 | return nodesLaunched; 61 | } 62 | 63 | public String getBrowser() { 64 | return browser; 65 | } 66 | 67 | public int getNumberLaunched() { 68 | return numberLaunched; 69 | } 70 | 71 | public void setThrowException() { 72 | throwException = true; 73 | } 74 | 75 | public boolean isTerminated() { 76 | return terminated; 77 | } 78 | 79 | public void setReservations(List reservations) { 80 | this.reservations = reservations; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/TestBrowserPlatformPair.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa; 2 | 3 | import org.junit.Test; 4 | import org.openqa.selenium.Platform; 5 | 6 | import junit.framework.Assert; 7 | 8 | /** 9 | * Created by matthew on 5/26/16. 10 | */ 11 | public class TestBrowserPlatformPair extends BaseTest { 12 | 13 | @Test 14 | public void getGet() { 15 | BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); 16 | Assert.assertEquals("Browsers should be equal", "chrome", one.getBrowser()); 17 | Assert.assertEquals("Platforms should be equal", Platform.LINUX, one.getPlatform()); 18 | } 19 | 20 | @Test 21 | public void testEquals() { 22 | BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); 23 | BrowserPlatformPair two = new BrowserPlatformPair("chrome", Platform.LINUX); 24 | Assert.assertTrue("Objects should be equal", one.equals(two)); 25 | } 26 | 27 | @Test 28 | public void testNotEqualsPlatform() { 29 | BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); 30 | BrowserPlatformPair two = new BrowserPlatformPair("chrome", Platform.UNIX); 31 | Assert.assertFalse("Objects should not be equal due to non-matching platform", one.equals(two)); 32 | } 33 | 34 | @Test 35 | public void testNotEqualsPlatformDifferentFamily() { 36 | BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.UNIX); 37 | BrowserPlatformPair two = new BrowserPlatformPair("chrome", Platform.WINDOWS); 38 | Assert.assertFalse("Objects should not be equal due to non-matching platform", one.equals(two)); 39 | } 40 | 41 | @Test 42 | public void testNotEqualsBrowser() { 43 | BrowserPlatformPair one = new BrowserPlatformPair("chrome", Platform.LINUX); 44 | BrowserPlatformPair two = new BrowserPlatformPair("firefox", Platform.LINUX); 45 | Assert.assertFalse("Objects should not be equal due to non-matching platform", one.equals(two)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/aws/AwsTagReporterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.aws; 14 | 15 | import java.util.Arrays; 16 | import java.util.Collection; 17 | import java.util.Properties; 18 | 19 | import org.junit.Test; 20 | 21 | import com.amazonaws.services.ec2.model.DescribeInstancesResult; 22 | import com.amazonaws.services.ec2.model.Instance; 23 | import com.amazonaws.services.ec2.model.Reservation; 24 | import com.rmn.qa.BaseTest; 25 | 26 | import junit.framework.Assert; 27 | 28 | public class AwsTagReporterTest extends BaseTest { 29 | 30 | @Test 31 | public void testTagsAssociated() { 32 | MockAmazonEc2Client client = new MockAmazonEc2Client(null); 33 | Collection instances = Arrays.asList(new Instance()); 34 | DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult(); 35 | Reservation reservation = new Reservation(); 36 | describeInstancesResult.setReservations(Arrays.asList(reservation)); 37 | reservation.setInstances(instances); 38 | client.setDescribeInstances(describeInstancesResult); 39 | Properties properties = new Properties(); 40 | properties.setProperty("tagAccounting","key,value"); 41 | properties.setProperty("function_tag","foo2"); 42 | properties.setProperty("product_tag","foo3"); 43 | AwsTagReporter reporter = new AwsTagReporter("testUuid",client,instances,properties); 44 | reporter.run(); 45 | } 46 | 47 | @Test 48 | public void testExceptionCaught() { 49 | MockAmazonEc2Client client = new MockAmazonEc2Client(null); 50 | Collection instances = Arrays.asList(new Instance()); 51 | DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult(); 52 | Reservation reservation = new Reservation(); 53 | describeInstancesResult.setReservations(Arrays.asList(reservation)); 54 | reservation.setInstances(instances); 55 | client.setDescribeInstances(describeInstancesResult); 56 | Properties properties = new Properties(); 57 | properties.setProperty("tagAccounting","key"); 58 | properties.setProperty("function_tag","foo2"); 59 | properties.setProperty("product_tag","foo3"); 60 | AwsTagReporter reporter = new AwsTagReporter("testUuid",client,instances,properties); 61 | reporter.run(); 62 | } 63 | 64 | @Test 65 | public void testClientThrowsErrors() { 66 | MockAmazonEc2Client client = new MockAmazonEc2Client(null); 67 | client.setDescribeInstancesToThrowError(); 68 | Collection instances = Arrays.asList(new Instance()); 69 | DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult(); 70 | Reservation reservation = new Reservation(); 71 | describeInstancesResult.setReservations(Arrays.asList(reservation)); 72 | reservation.setInstances(instances); 73 | client.setDescribeInstances(describeInstancesResult); 74 | Properties properties = new Properties(); 75 | properties.setProperty("accounting_tag","foo"); 76 | properties.setProperty("function_tag","foo2"); 77 | properties.setProperty("product_tag","foo3"); 78 | AwsTagReporter reporter = new AwsTagReporter("testUuid",client,instances,properties) { 79 | @Override 80 | void sleep() throws InterruptedException { 81 | // do nothing 82 | } 83 | }; 84 | reporter.run(); 85 | } 86 | 87 | @Test 88 | public void testSleepThrowsErrors() { 89 | MockAmazonEc2Client client = new MockAmazonEc2Client(null); 90 | client.setDescribeInstancesToThrowError(); 91 | Collection instances = Arrays.asList(new Instance()); 92 | DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult(); 93 | Reservation reservation = new Reservation(); 94 | describeInstancesResult.setReservations(Arrays.asList(reservation)); 95 | reservation.setInstances(instances); 96 | client.setDescribeInstances(describeInstancesResult); 97 | Properties properties = new Properties(); 98 | properties.setProperty("accounting_tag","foo"); 99 | properties.setProperty("function_tag","foo2"); 100 | properties.setProperty("product_tag","foo3"); 101 | AwsTagReporter reporter = new AwsTagReporter("testUuid",client,instances,properties) { 102 | @Override 103 | void sleep() throws InterruptedException { 104 | throw new InterruptedException(); 105 | } 106 | }; 107 | reporter.run(); 108 | } 109 | 110 | @Test() 111 | public void testThreadTimesOut() { 112 | MockAmazonEc2Client client = new MockAmazonEc2Client(null); 113 | Collection instances = Arrays.asList(new Instance()); 114 | DescribeInstancesResult describeInstancesResult = new DescribeInstancesResult(); 115 | Reservation reservation = new Reservation(); 116 | describeInstancesResult.setReservations(Arrays.asList(reservation)); 117 | // Make count mismatch 118 | reservation.setInstances(Arrays.asList(new Instance(),new Instance())); 119 | client.setDescribeInstances(describeInstancesResult); 120 | Properties properties = new Properties(); 121 | properties.setProperty("accounting_tag","foo"); 122 | properties.setProperty("function_tag","foo2"); 123 | properties.setProperty("product_tag","foo3"); 124 | AwsTagReporter reporter = new AwsTagReporter("testUuid",client,instances,properties); 125 | AwsTagReporter.TIMEOUT_IN_SECONDS = 1; 126 | try{ 127 | reporter.run(); 128 | } catch(RuntimeException e) { 129 | Assert.assertEquals("Error waiting for instances to exist to add tags",e.getMessage()); 130 | return; 131 | } 132 | Assert.fail("Exception should have been thrown since tags were never filed"); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/aws/MockAmazonEc2Client.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.aws; 14 | 15 | import com.amazonaws.AmazonClientException; 16 | import com.amazonaws.AmazonServiceException; 17 | import com.amazonaws.auth.AWSCredentials; 18 | import com.amazonaws.services.ec2.AmazonEC2Client; 19 | import com.amazonaws.services.ec2.model.CreateTagsRequest; 20 | import com.amazonaws.services.ec2.model.DescribeInstancesRequest; 21 | import com.amazonaws.services.ec2.model.DescribeInstancesResult; 22 | import com.amazonaws.services.ec2.model.RunInstancesRequest; 23 | import com.amazonaws.services.ec2.model.RunInstancesResult; 24 | import com.amazonaws.services.ec2.model.TerminateInstancesRequest; 25 | import com.amazonaws.services.ec2.model.TerminateInstancesResult; 26 | 27 | public class MockAmazonEc2Client extends AmazonEC2Client { 28 | 29 | private DescribeInstancesResult describeInstancesResult; 30 | private RunInstancesResult runInstancesResult; 31 | private RunInstancesRequest runInstancesRequest; 32 | private boolean throwDescribeInstancesError = false; 33 | private AmazonClientException throwRunInstancesError; 34 | private boolean throwExceptionsInRunInstancesIndefinitely = false; 35 | private CreateTagsRequest tagsCreated; 36 | private TerminateInstancesResult terminateInstancesResult; 37 | private TerminateInstancesRequest terminateInstancesRequest; 38 | 39 | public MockAmazonEc2Client(AWSCredentials credentials) { 40 | super(credentials); 41 | } 42 | 43 | 44 | @Override 45 | public DescribeInstancesResult describeInstances(DescribeInstancesRequest describeInstancesRequest) throws AmazonClientException { 46 | if(throwDescribeInstancesError) { 47 | throwDescribeInstancesError = false; 48 | throw new AmazonClientException("testError"); 49 | } 50 | return describeInstancesResult; 51 | } 52 | 53 | public void setDescribeInstancesToThrowError() { 54 | throwDescribeInstancesError = true; 55 | } 56 | 57 | 58 | 59 | public void setDescribeInstances(DescribeInstancesResult resultToReturn) { 60 | this.describeInstancesResult = resultToReturn; 61 | } 62 | 63 | @Override 64 | public void createTags(CreateTagsRequest createTagsRequest) throws AmazonClientException { 65 | this.tagsCreated = createTagsRequest; 66 | } 67 | 68 | @Override 69 | public RunInstancesResult runInstances(RunInstancesRequest runInstancesRequest) throws AmazonServiceException, AmazonClientException { 70 | if(throwRunInstancesError != null) { 71 | AmazonClientException exceptionToThrow = throwRunInstancesError; 72 | if(!throwExceptionsInRunInstancesIndefinitely) { 73 | throwRunInstancesError = null; 74 | } 75 | throw exceptionToThrow; 76 | } 77 | this.runInstancesRequest = runInstancesRequest; 78 | return runInstancesResult; 79 | } 80 | 81 | public void setRunInstances(RunInstancesResult runInstancesResult) { 82 | this.runInstancesResult = runInstancesResult; 83 | } 84 | 85 | public RunInstancesRequest getRunInstancesRequest() { 86 | return runInstancesRequest; 87 | } 88 | 89 | @Override 90 | public TerminateInstancesResult terminateInstances(TerminateInstancesRequest terminateInstancesRequest) throws AmazonServiceException, AmazonClientException { 91 | this.terminateInstancesRequest = terminateInstancesRequest; 92 | return terminateInstancesResult; 93 | } 94 | 95 | public void setTerminateInstancesResult(TerminateInstancesResult terminateInstancesResult) { 96 | this.terminateInstancesResult = terminateInstancesResult; 97 | } 98 | 99 | public TerminateInstancesRequest getTerminateInstancesRequest() { 100 | return terminateInstancesRequest; 101 | } 102 | 103 | public void setThrowDescribeInstancesError(AmazonClientException t) { 104 | this.throwRunInstancesError = t; 105 | } 106 | 107 | public void setThrowExceptionsInRunInstancesIndefinitely() { 108 | throwExceptionsInRunInstancesIndefinitely = true; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/aws/MockManageVm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.aws; 14 | 15 | import java.util.Properties; 16 | 17 | import org.openqa.selenium.Platform; 18 | 19 | import com.amazonaws.auth.AWSCredentials; 20 | import com.amazonaws.auth.BasicAWSCredentials; 21 | import com.amazonaws.services.ec2.AmazonEC2Client; 22 | 23 | public class MockManageVm extends AwsVmManager { 24 | 25 | private String userData; 26 | private AWSCredentials awsCredentials; 27 | 28 | public MockManageVm() { 29 | super(); 30 | } 31 | 32 | public MockManageVm(AmazonEC2Client client, Properties properties, String region) { 33 | super(client,properties,region); 34 | } 35 | 36 | public MockManageVm(AmazonEC2Client client, Properties properties, String region, BasicAWSCredentials credentials) { 37 | this(client, properties, region); 38 | this.credentials = credentials; 39 | } 40 | 41 | @Override 42 | String getUserData(String uuid, String hubHostName, String browser, Platform platform, int maxSessions) { 43 | return userData; 44 | } 45 | 46 | public void setUserData(String userData) { 47 | this.userData = userData; 48 | } 49 | 50 | public void setCredentials(AWSCredentials basicAWSCredentials) { 51 | this.credentials = basicAWSCredentials; 52 | } 53 | 54 | public void setAwsCredentials(AWSCredentials awsCredentials) { 55 | this.awsCredentials = awsCredentials; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/servlet/MockAutomationTestRunServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.servlet; 14 | 15 | import com.rmn.qa.RequestMatcher; 16 | import com.rmn.qa.aws.VmManager; 17 | import org.openqa.grid.internal.ProxySet; 18 | import org.openqa.grid.internal.Registry; 19 | 20 | public class MockAutomationTestRunServlet extends AutomationTestRunServlet { 21 | 22 | private ProxySet proxySet; 23 | 24 | public MockAutomationTestRunServlet(Registry registry, boolean initThreads, VmManager ec2,RequestMatcher requestMatcher){ 25 | super(registry,initThreads,ec2,requestMatcher); 26 | } 27 | 28 | @Override 29 | protected ProxySet getProxySet() { 30 | return proxySet; 31 | } 32 | 33 | public void setProxySet(ProxySet proxySet) { 34 | this.proxySet = proxySet; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/AutomationHubCleanupTaskTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | 18 | import org.junit.After; 19 | import org.junit.Test; 20 | import org.openqa.grid.internal.ProxySet; 21 | 22 | import com.rmn.qa.AutomationUtils; 23 | import com.rmn.qa.BaseTest; 24 | import com.rmn.qa.MockRemoteProxy; 25 | import com.rmn.qa.MockVmManager; 26 | import com.rmn.qa.aws.AwsVmManager; 27 | 28 | import junit.framework.Assert; 29 | 30 | public class AutomationHubCleanupTaskTest extends BaseTest { 31 | 32 | @After 33 | public void afterTest() { 34 | // Make sure we clear the statically set start and end dates for the hub 35 | MockAutomationHubCleanupTask.clearDates(); 36 | AutomationHubCleanupTask.errorEncountered = false; 37 | } 38 | 39 | @Test 40 | // Tests that the hardcoded name of the task is correct 41 | public void testTaskName() { 42 | AutomationHubCleanupTask task = new AutomationHubCleanupTask(null,null,null); 43 | Assert.assertEquals("Name should be the same",AutomationHubCleanupTask.NAME, task.getDescription() ); 44 | } 45 | 46 | @Test 47 | // Tests that the hub terminates after the correct amount of time 48 | public void testHubTerminated() { 49 | MockVmManager ec2 = new MockVmManager(); 50 | MockAutomationHubCleanupTask task = new MockAutomationHubCleanupTask(null,ec2,"dummyId"); 51 | ProxySet proxySet = new ProxySet(false); 52 | task.setProxySet(proxySet); 53 | String createdDate = AwsVmManager.NODE_DATE_FORMAT.format(AutomationUtils.modifyDate(new Date(), -56, Calendar.MINUTE)); 54 | task.setCreatedDate(createdDate); 55 | 56 | task.run(); 57 | Assert.assertTrue("Hub should be terminated as it was empty",ec2.isTerminated()); 58 | } 59 | 60 | @Test 61 | // Tests that the cleanup task can handle an incorrectly formatted date (should shut down) 62 | public void testHubTerminatedBadCreatedDate() { 63 | MockVmManager ec2 = new MockVmManager(); 64 | MockAutomationHubCleanupTask task = new MockAutomationHubCleanupTask(null,ec2,"dummyId"); 65 | ProxySet proxySet = new ProxySet(false); 66 | task.setProxySet(proxySet); 67 | // Set the date to a format that will not be parseable 68 | String createdDate = "Mon Feb 17 20:56:48 UTC 2014"; 69 | task.setCreatedDate(createdDate); 70 | 71 | task.run(); 72 | Assert.assertTrue("Error should have been encountered",AutomationHubCleanupTask.errorEncountered); 73 | Assert.assertTrue("Hub should be terminated even though the date format parsing failed",ec2.isTerminated()); 74 | } 75 | 76 | @Test 77 | // Tests that the error field defaults to false 78 | public void testHubErrorDefaultsToFalse() { 79 | Assert.assertFalse("Error not have been configured by default",AutomationHubCleanupTask.errorEncountered); 80 | } 81 | 82 | 83 | @Test 84 | // Tests that if the hub still has nodes registered to it, it will not terminate 85 | public void testHubNotTerminatedExistingNodes() { 86 | MockVmManager ec2 = new MockVmManager(); 87 | MockAutomationHubCleanupTask task = new MockAutomationHubCleanupTask(null,ec2,"dummyId"); 88 | ProxySet proxySet = new ProxySet(false); 89 | proxySet.add(new MockRemoteProxy()); 90 | task.setProxySet(proxySet); 91 | String createdDate = AwsVmManager.NODE_DATE_FORMAT.format(AutomationUtils.modifyDate(new Date(),-56, Calendar.MINUTE)); 92 | task.setCreatedDate(createdDate); 93 | 94 | task.run(); 95 | Assert.assertFalse("Hub should not be terminated as nodes are still registered", ec2.isTerminated()); 96 | } 97 | 98 | @Test 99 | // Tests that if the hub is in the next billing cycle, its end date will reset 100 | // instead of terminating 101 | public void testHubNotTerminatedNextBillingCycle() { 102 | MockVmManager ec2 = new MockVmManager(); 103 | MockAutomationHubCleanupTask task = new MockAutomationHubCleanupTask(null,ec2,"dummyId"); 104 | ProxySet proxySet = new ProxySet(false); 105 | proxySet.add(new MockRemoteProxy()); 106 | task.setProxySet(proxySet); 107 | Date newStartDate = AutomationUtils.modifyDate(new Date(),-61, Calendar.MINUTE); 108 | String createdDate = AwsVmManager.NODE_DATE_FORMAT.format(newStartDate); 109 | task.setCreatedDate(createdDate); 110 | 111 | task.run(); 112 | Assert.assertFalse("Hub should not be terminated as its in the next billing cycle",ec2.isTerminated()); 113 | Assert.assertTrue("End date should have been increased", AutomationUtils.modifyDate(newStartDate, 116, Calendar.MINUTE).after(task.getEndDate())); // 60 minutes + 55 114 | Assert.assertTrue("End date should have been increased",AutomationUtils.modifyDate(newStartDate,114,Calendar.MINUTE).before(task.getEndDate())); // 60 minutes + 55 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/AutomationOrphanedNodeRegistryTaskTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import java.text.DateFormat; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import org.junit.Test; 22 | import org.openqa.grid.internal.ProxySet; 23 | import org.openqa.selenium.Platform; 24 | 25 | import com.rmn.qa.AutomationCapabilityMatcher; 26 | import com.rmn.qa.AutomationConstants; 27 | import com.rmn.qa.AutomationContext; 28 | import com.rmn.qa.AutomationDynamicNode; 29 | import com.rmn.qa.BaseTest; 30 | import com.rmn.qa.MockRemoteProxy; 31 | import com.rmn.qa.aws.AwsVmManager; 32 | 33 | import junit.framework.Assert; 34 | 35 | /** 36 | * Created by mhardin on 4/24/14. 37 | */ 38 | public class AutomationOrphanedNodeRegistryTaskTest extends BaseTest { 39 | 40 | @Test 41 | // Tests that the hardcoded name of the task is correct 42 | public void testTaskName() { 43 | AutomationOrphanedNodeRegistryTask task = new AutomationOrphanedNodeRegistryTask(null); 44 | Assert.assertEquals("Name should be the same", AutomationOrphanedNodeRegistryTask.NAME, task.getDescription()); 45 | } 46 | 47 | @Test 48 | // Test that a node not in the context gets picked up and registered by the task 49 | public void testRegisterNewNode() { 50 | MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); 51 | ProxySet proxySet = new ProxySet(false); 52 | MockRemoteProxy proxy = new MockRemoteProxy(); 53 | proxySet.add(proxy); 54 | Map config = new HashMap<>(); 55 | String instanceId = "instanceId"; 56 | String uuid="testUuid"; 57 | int threadCount = 10; 58 | String browser = "firefox"; 59 | Platform os = Platform.LINUX; 60 | config.put(AutomationConstants.INSTANCE_ID,instanceId); 61 | config.put(AutomationConstants.UUID,uuid); 62 | //config.put(AutomationConstants.CONFIG_MAX_SESSION, threadCount); 63 | proxy.getConfig().maxSession = threadCount; 64 | config.put(AutomationConstants.CONFIG_BROWSER, browser); 65 | config.put(AutomationConstants.CONFIG_OS, os.toString()); 66 | config.put(AutomationConstants.CONFIG_CREATED_DATE, AwsVmManager.NODE_DATE_FORMAT.format(new Date())); 67 | proxy.setConfig(config); 68 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 69 | task.setProxySet(proxySet); 70 | Assert.assertNull("Node should not be registered before the task runs",AutomationContext.getContext().getNode(instanceId)); 71 | task.run(); 72 | AutomationDynamicNode existingNode = AutomationContext.getContext().getNode(instanceId); 73 | Assert.assertNotNull("Node should exist after the task has run",existingNode); 74 | Assert.assertEquals("UUID should match", uuid,existingNode.getUuid()); 75 | Assert.assertEquals("Browser should match", browser,existingNode.getBrowser()); 76 | Assert.assertEquals("Thread count should match", threadCount,existingNode.getNodeCapacity()); 77 | Assert.assertEquals("OS should match", os, existingNode.getPlatform()); 78 | } 79 | 80 | @Test 81 | // Test if a node already exists in the context that the task does not override that node 82 | public void testNodeAlreadyExists() { 83 | MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); 84 | ProxySet proxySet = new ProxySet(false); 85 | MockRemoteProxy proxy = new MockRemoteProxy(); 86 | proxySet.add(proxy); 87 | Map config = new HashMap<>(); 88 | String instanceId = "instanceId"; 89 | String uuid="testUuid"; 90 | int threadCount = 10; 91 | String browser = "firefox"; 92 | Platform os = Platform.LINUX; 93 | config.put(AutomationConstants.INSTANCE_ID,instanceId); 94 | config.put(AutomationConstants.UUID,"fake"); 95 | //config.put(AutomationConstants.CONFIG_MAX_SESSION, 1); 96 | proxy.getConfig().maxSession = 1; 97 | config.put(AutomationConstants.CONFIG_BROWSER, "fake"); 98 | config.put(AutomationConstants.CONFIG_OS, "fake"); 99 | config.put(AutomationConstants.CONFIG_CREATED_DATE, AwsVmManager.NODE_DATE_FORMAT.format(new Date())); 100 | proxy.setConfig(config); 101 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 102 | task.setProxySet(proxySet); 103 | AutomationDynamicNode existingNode = new AutomationDynamicNode(uuid,instanceId,browser,os,new Date(),threadCount); 104 | AutomationContext.getContext().addNode(existingNode); 105 | Assert.assertNotNull("Node should be registered before the task runs",AutomationContext.getContext().getNode(instanceId)); 106 | task.run(); 107 | AutomationDynamicNode newNode = AutomationContext.getContext().getNode(instanceId); 108 | Assert.assertNotNull("Node should still exist after the task has run",newNode); 109 | Assert.assertEquals("UUID should match the previously existing node", uuid, newNode.getUuid()); 110 | Assert.assertEquals("Browser should match the previously existing node", browser,newNode.getBrowser()); 111 | Assert.assertEquals("Thread count should match the previously existing node", threadCount, newNode.getNodeCapacity()); 112 | Assert.assertEquals("OS should match the previously existing node", os, newNode.getPlatform()); 113 | } 114 | 115 | @Test 116 | // Test that a node without an instance id does not get registered 117 | public void testRegisterNodeWithoutInstanceId() { 118 | MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); 119 | ProxySet proxySet = new ProxySet(false); 120 | MockRemoteProxy proxy = new MockRemoteProxy(); 121 | proxySet.add(proxy); 122 | Map config = new HashMap<>(); 123 | String uuid="testUuid"; 124 | int threadCount = 10; 125 | String browser = "firefox"; 126 | String os = "linux"; 127 | config.put(AutomationConstants.UUID,uuid); 128 | //config.put(AutomationConstants.CONFIG_MAX_SESSION, threadCount); 129 | proxy.getConfig().maxSession = threadCount; 130 | config.put(AutomationConstants.CONFIG_BROWSER, browser); 131 | config.put(AutomationConstants.CONFIG_OS, os); 132 | config.put(AutomationConstants.CONFIG_CREATED_DATE, AwsVmManager.NODE_DATE_FORMAT.format(new Date())); 133 | proxy.setConfig(config); 134 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 135 | task.setProxySet(proxySet); 136 | Assert.assertEquals("Node should not be registered before the task runs",0,AutomationContext.getContext().getNodes().size()); 137 | task.run(); 138 | Assert.assertEquals("Node should still not be registered after the task runs",0,AutomationContext.getContext().getNodes().size()); 139 | } 140 | 141 | @Test 142 | // Test that an empty proxy set does not result in any added nodes after the register task runs 143 | public void testEmptyProxySet() { 144 | MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); 145 | ProxySet proxySet = new ProxySet(false); 146 | task.setProxySet(proxySet); 147 | Assert.assertEquals("Node should not be registered before the task runs",0,AutomationContext.getContext().getNodes().size()); 148 | task.run(); 149 | Assert.assertEquals("Node should still not be registered after the task runs",0,AutomationContext.getContext().getNodes().size()); 150 | } 151 | 152 | @Test 153 | // Test that a null proxy set does not result in any added nodes 154 | public void testNullEmptyProxySet() { 155 | MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); 156 | ProxySet proxySet = new ProxySet(false); 157 | task.setProxySet(proxySet); 158 | Assert.assertEquals("Node should not be registered before the task runs",0,AutomationContext.getContext().getNodes().size()); 159 | task.run(); 160 | Assert.assertEquals("Node should still not be registered after the task runs",0,AutomationContext.getContext().getNodes().size()); 161 | } 162 | 163 | @Test 164 | // Test that a node with a bad date does not register successfully after the task runs 165 | public void testBadDateFormat() { 166 | MockAutomationOrphanedNodeRegistryTask task = new MockAutomationOrphanedNodeRegistryTask(null); 167 | ProxySet proxySet = new ProxySet(false); 168 | MockRemoteProxy proxy = new MockRemoteProxy(); 169 | proxySet.add(proxy); 170 | Map config = new HashMap<>(); 171 | String instanceId = "instanceId"; 172 | String uuid="testUuid"; 173 | int threadCount = 10; 174 | String browser = "firefox"; 175 | String os = "linux"; 176 | config.put(AutomationConstants.INSTANCE_ID,instanceId); 177 | config.put(AutomationConstants.UUID,uuid); 178 | //config.put(AutomationConstants.CONFIG_MAX_SESSION, threadCount); 179 | proxy.getConfig().maxSession = threadCount; 180 | config.put(AutomationConstants.CONFIG_BROWSER, browser); 181 | config.put(AutomationConstants.CONFIG_OS, os); 182 | 183 | DateFormat badDateFormat = new SimpleDateFormat("MM HH:mm:ss"); 184 | config.put(AutomationConstants.CONFIG_CREATED_DATE, badDateFormat.format(new Date())); 185 | proxy.setConfig(config); 186 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 187 | task.setProxySet(proxySet); 188 | Assert.assertNull("Node should not be registered before the task runs",AutomationContext.getContext().getNode(instanceId)); 189 | task.run(); 190 | Assert.assertNull("Node should still not be registered after the task runs",AutomationContext.getContext().getNode(instanceId)); 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/AutomationPendingNodeRegistryTaskTest.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import org.junit.Test; 9 | import org.openqa.grid.internal.ProxySet; 10 | 11 | import com.rmn.qa.AutomationCapabilityMatcher; 12 | import com.rmn.qa.AutomationConstants; 13 | import com.rmn.qa.AutomationContext; 14 | import com.rmn.qa.AutomationDynamicNode; 15 | import com.rmn.qa.AutomationRunContext; 16 | import com.rmn.qa.AutomationUtils; 17 | import com.rmn.qa.BaseTest; 18 | import com.rmn.qa.MockRemoteProxy; 19 | import com.rmn.qa.MockVmManager; 20 | 21 | import junit.framework.Assert; 22 | 23 | /** 24 | * Created by matthew on 3/18/16. 25 | */ 26 | public class AutomationPendingNodeRegistryTaskTest extends BaseTest { 27 | 28 | @Test 29 | public void testPendingNodeRemoved() { 30 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); 31 | ProxySet proxySet = new ProxySet(false); 32 | MockRemoteProxy proxy = new MockRemoteProxy(); 33 | proxySet.add(proxy); 34 | Map config = new HashMap<>(); 35 | String instanceId = "instanceId"; 36 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); 37 | AutomationContext.getContext().addPendingNode(node); 38 | config.put(AutomationConstants.INSTANCE_ID, instanceId); 39 | proxy.setConfig(config); 40 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 41 | task.setProxySet(proxySet); 42 | 43 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); 44 | task.doWork(); 45 | Assert.assertFalse("Instance should not show as pending after task has run", AutomationContext.getContext().pendingNodeExists(instanceId)); 46 | } 47 | 48 | @Test 49 | public void testPendingNodeNotRemovedMismatchInstanceId() { 50 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); 51 | ProxySet proxySet = new ProxySet(false); 52 | MockRemoteProxy proxy = new MockRemoteProxy(); 53 | proxySet.add(proxy); 54 | Map config = new HashMap<>(); 55 | String instanceId = "instanceId"; 56 | String instanceIdDifferent = "differentInstanceId"; 57 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceIdDifferent, null, null, null, new Date(), 1); 58 | AutomationContext.getContext().addPendingNode(node); 59 | config.put(AutomationConstants.INSTANCE_ID, instanceId); 60 | proxy.setConfig(config); 61 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 62 | task.setProxySet(proxySet); 63 | 64 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceIdDifferent)); 65 | task.doWork(); 66 | Assert.assertTrue("Instance should still be pending as it hasn't come online", AutomationContext.getContext().pendingNodeExists(instanceIdDifferent)); 67 | Assert.assertFalse("Instance should not be pending as it never was pending to begin with", AutomationContext.getContext().pendingNodeExists(instanceId)); 68 | 69 | } 70 | 71 | @Test 72 | public void testPendingNodeNotRemovedNoConfigId() { 73 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); 74 | ProxySet proxySet = new ProxySet(false); 75 | MockRemoteProxy proxy = new MockRemoteProxy(); 76 | proxySet.add(proxy); 77 | Map config = new HashMap<>(); 78 | String instanceId = "instanceId"; 79 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); 80 | AutomationContext.getContext().addPendingNode(node); 81 | proxy.setConfig(config); 82 | proxy.setCapabilityMatcher(new AutomationCapabilityMatcher()); 83 | task.setProxySet(proxySet); 84 | 85 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); 86 | task.doWork(); 87 | Assert.assertTrue("Node should still be pending after task runs as there was no instance id in the config", AutomationContext.getContext().pendingNodeExists(instanceId)); 88 | } 89 | 90 | @Test 91 | public void testPendingNodeNotRemovedNullProxySet() { 92 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); 93 | ProxySet proxySet = new ProxySet(false); 94 | String instanceId = "instanceId"; 95 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); 96 | AutomationContext.getContext().addPendingNode(node); 97 | task.setProxySet(proxySet); 98 | 99 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); 100 | task.doWork(); 101 | Assert.assertTrue("Node should still be pending after task runs as there was no instance id in the config", AutomationContext.getContext().pendingNodeExists(instanceId)); 102 | } 103 | 104 | @Test 105 | public void testPendingNodeNotRemovedEmptyProxySet() { 106 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null); 107 | ProxySet proxySet = new ProxySet(false); 108 | String instanceId = "instanceId"; 109 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, new Date(), 1); 110 | AutomationContext.getContext().addPendingNode(node); 111 | task.setProxySet(proxySet); 112 | 113 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); 114 | task.doWork(); 115 | Assert.assertTrue("Node should still be pending after task runs as there was no instance id in the config", AutomationContext.getContext().pendingNodeExists(instanceId)); 116 | } 117 | 118 | @Test 119 | // Make sure the pending node gets removed after its been pending for too long 120 | public void testPendingNodeRemovedAfterTimeout() { 121 | MockVmManager vmManager = new MockVmManager(); 122 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null, vmManager); 123 | String instanceId = "instanceId"; 124 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, AutomationUtils.modifyDate(new Date(), - (AutomationRunContext.PENDING_NODE_EXPIRATION_TIME_IN_MINUTES + 1), Calendar.MINUTE), 1); 125 | AutomationContext.getContext().addPendingNode(node); 126 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); 127 | task.doWork(); 128 | Assert.assertTrue("Instance should be terminated", vmManager.isTerminated()); 129 | Assert.assertFalse("Instance should not show as pending after task has run", AutomationContext.getContext().pendingNodeExists(instanceId)); 130 | Assert.assertEquals("Instance should have a terminated status", AutomationDynamicNode.STATUS.TERMINATED, node.getStatus()); 131 | } 132 | 133 | @Test 134 | // Make sure the pending node gets removed after its been pending for too long 135 | public void testPendingNodeNotRemovedBeforeTimeout() { 136 | MockVmManager vmManager = new MockVmManager(); 137 | MockAutomationPendingNodeRegistryTask task = new MockAutomationPendingNodeRegistryTask(null, vmManager); 138 | String instanceId = "instanceId"; 139 | AutomationDynamicNode node = new AutomationDynamicNode(null, instanceId, null, null, null, AutomationUtils.modifyDate(new Date(), - (AutomationRunContext.PENDING_NODE_EXPIRATION_TIME_IN_MINUTES - 1), Calendar.MINUTE), 1); 140 | AutomationContext.getContext().addPendingNode(node); 141 | Assert.assertTrue("Node should be pending before task runs", AutomationContext.getContext().pendingNodeExists(instanceId)); 142 | task.doWork(); 143 | Assert.assertFalse("Instance should not be terminated as timeout wasn't hit", vmManager.isTerminated()); 144 | Assert.assertTrue("Node should still be in the pending set as it wasn't removed", AutomationContext.getContext().pendingNodeExists(instanceId)); 145 | Assert.assertEquals("Instance should have a terminated status", AutomationDynamicNode.STATUS.RUNNING, node.getStatus()); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/AutomationReaperTaskTest.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import java.util.Arrays; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | 7 | import org.junit.Test; 8 | 9 | import com.amazonaws.services.ec2.model.Instance; 10 | import com.amazonaws.services.ec2.model.InstanceState; 11 | import com.amazonaws.services.ec2.model.Reservation; 12 | import com.rmn.qa.AutomationContext; 13 | import com.rmn.qa.AutomationDynamicNode; 14 | import com.rmn.qa.AutomationUtils; 15 | import com.rmn.qa.BaseTest; 16 | import com.rmn.qa.MockVmManager; 17 | 18 | import junit.framework.Assert; 19 | 20 | public class AutomationReaperTaskTest extends BaseTest { 21 | 22 | @Test 23 | public void testShutdown() { 24 | MockVmManager ec2 = new MockVmManager(); 25 | Reservation reservation = new Reservation(); 26 | String instanceId = "foobar"; 27 | Instance instance = new Instance() 28 | .withState(new InstanceState().withCode(45)) 29 | .withInstanceId(instanceId) 30 | .withLaunchTime(AutomationUtils.modifyDate(new Date(),-5,Calendar.HOUR)); 31 | reservation.setInstances(Arrays.asList(instance)); 32 | ec2.setReservations(Arrays.asList(reservation)); 33 | AutomationReaperTask task = new AutomationReaperTask(null,ec2); 34 | task.run(); 35 | Assert.assertTrue("Node should be terminated as it was empty", ec2.isTerminated()); 36 | } 37 | 38 | @Test 39 | // Tests that a node that is not old enough is not terminated 40 | public void testNoShutdownTooRecent() { 41 | MockVmManager ec2 = new MockVmManager(); 42 | Reservation reservation = new Reservation(); 43 | Instance instance = new Instance(); 44 | String instanceId = "foo"; 45 | instance.setInstanceId(instanceId); 46 | instance.setLaunchTime(AutomationUtils.modifyDate(new Date(),-15,Calendar.MINUTE)); 47 | reservation.setInstances(Arrays.asList(instance)); 48 | ec2.setReservations(Arrays.asList(reservation)); 49 | AutomationReaperTask task = new AutomationReaperTask(null,ec2); 50 | task.run(); 51 | Assert.assertFalse("Node should NOT be terminated as it was not old", ec2.isTerminated()); 52 | } 53 | 54 | @Test 55 | // Tests that a node that is being tracked internally is not shut down 56 | public void testNoShutdownNodeTracked() { 57 | MockVmManager ec2 = new MockVmManager(); 58 | Reservation reservation = new Reservation(); 59 | Instance instance = new Instance(); 60 | String instanceId = "foo"; 61 | AutomationContext.getContext().addNode(new AutomationDynamicNode("faky",instanceId,null,null,new Date(),1)); 62 | instance.setInstanceId(instanceId); 63 | instance.setLaunchTime(AutomationUtils.modifyDate(new Date(),-5,Calendar.HOUR)); 64 | reservation.setInstances(Arrays.asList(instance)); 65 | ec2.setReservations(Arrays.asList(reservation)); 66 | AutomationReaperTask task = new AutomationReaperTask(null,ec2); 67 | task.run(); 68 | Assert.assertFalse("Node should NOT be terminated as it was tracked internally", ec2.isTerminated()); 69 | } 70 | 71 | @Test 72 | // Tests that the hardcoded name of the task is correct 73 | public void testTaskName() { 74 | AutomationReaperTask task = new AutomationReaperTask(null,null); 75 | Assert.assertEquals("Name should be the same",AutomationReaperTask.NAME, task.getDescription() ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/AutomationRunCleanupTaskTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | 18 | import org.junit.Test; 19 | import org.openqa.grid.internal.ProxySet; 20 | import org.openqa.selenium.Platform; 21 | 22 | import com.rmn.qa.AutomationContext; 23 | import com.rmn.qa.AutomationRunContext; 24 | import com.rmn.qa.AutomationRunRequest; 25 | import com.rmn.qa.AutomationUtils; 26 | import com.rmn.qa.BaseTest; 27 | import com.rmn.qa.MockRemoteProxy; 28 | 29 | import junit.framework.Assert; 30 | 31 | /** 32 | * Created by mhardin on 5/1/14. 33 | */ 34 | public class AutomationRunCleanupTaskTest extends BaseTest { 35 | 36 | @Test 37 | // Tests that an old run not in progress is no longer registered 38 | public void testCleanup() { 39 | AutomationRunRequest oldRequest = new AutomationRunRequest("uuid",10,"firefox","10", Platform.LINUX, AutomationUtils.modifyDate(new Date(), -1, Calendar.HOUR)); 40 | AutomationRunContext context = AutomationContext.getContext(); 41 | context.addRun(oldRequest); 42 | 43 | Assert.assertTrue("Run should exist", context.hasRun(oldRequest.getUuid())); 44 | ProxySet proxySet = new ProxySet(false); 45 | proxySet.add(new MockRemoteProxy()); 46 | context.cleanUpRunRequests(proxySet); 47 | MockAutomationRunCleanupTask task = new MockAutomationRunCleanupTask(null); 48 | task.setProxySet(proxySet); 49 | task.run(); 50 | Assert.assertFalse("Run request should no longer exist as it should have been removed",context.hasRun(oldRequest.getUuid())); 51 | } 52 | 53 | @Test 54 | // Tests that the task hard coded name matches the task name 55 | public void testTaskName() { 56 | AutomationRunCleanupTask task = new AutomationRunCleanupTask(null); 57 | Assert.assertEquals("Name should be the same",AutomationRunCleanupTask.NAME, task.getDescription() ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/MockAutomationHubCleanupTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import com.rmn.qa.RegistryRetriever; 16 | import com.rmn.qa.aws.VmManager; 17 | import org.openqa.grid.internal.ProxySet; 18 | 19 | import java.util.Date; 20 | 21 | // We're extending the task so we can override the created date behavior 22 | public class MockAutomationHubCleanupTask extends AutomationHubCleanupTask { 23 | 24 | private Object createdDate; 25 | private ProxySet proxySet; 26 | 27 | public MockAutomationHubCleanupTask(RegistryRetriever retrieveContext, VmManager ec2, String instanceId) { 28 | super(retrieveContext, ec2, instanceId); 29 | } 30 | 31 | @Override 32 | protected Object getCreatedDate() { 33 | return createdDate; //To change body of overridden methods use File | Settings | File Templates. 34 | } 35 | 36 | @Override 37 | protected ProxySet getProxySet() { 38 | return proxySet; 39 | } 40 | 41 | static Date getEndDate() { 42 | return AutomationHubCleanupTask.endDate; 43 | } 44 | 45 | /** 46 | * Clears the start/end date 47 | */ 48 | static void clearDates() { 49 | AutomationHubCleanupTask.createdDate = null; 50 | AutomationHubCleanupTask.endDate = null; 51 | } 52 | 53 | public void setCreatedDate(Object createdDate) { 54 | this.createdDate = createdDate; 55 | } 56 | 57 | public void setProxySet(ProxySet proxySet) { 58 | this.proxySet = proxySet; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/MockAutomationNodeCleanupTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import org.openqa.grid.internal.ProxySet; 16 | import org.openqa.grid.internal.RemoteProxy; 17 | 18 | import com.rmn.qa.RegistryRetriever; 19 | import com.rmn.qa.RequestMatcher; 20 | import com.rmn.qa.aws.VmManager; 21 | 22 | public class MockAutomationNodeCleanupTask extends AutomationNodeCleanupTask { 23 | 24 | private ProxySet proxySet; 25 | private boolean proxyRemoved = false; 26 | 27 | public MockAutomationNodeCleanupTask(RegistryRetriever retrieveContext, VmManager ec2, RequestMatcher requestMatcher) { 28 | super(retrieveContext, ec2, requestMatcher); 29 | } 30 | 31 | @Override 32 | protected ProxySet getProxySet() { 33 | return proxySet; 34 | } 35 | 36 | public void setProxySet(ProxySet proxySet) { 37 | this.proxySet = proxySet; 38 | } 39 | 40 | @Override 41 | protected void removeProxy(RemoteProxy proxy) { 42 | proxyRemoved = true; 43 | } 44 | 45 | public boolean isProxyRemoved() { 46 | return proxyRemoved; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/MockAutomationOrphanedNodeRegistryTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import org.openqa.grid.internal.ProxySet; 16 | 17 | import com.rmn.qa.RegistryRetriever; 18 | 19 | /** 20 | * Created by mhardin on 4/24/14. 21 | */ 22 | public class MockAutomationOrphanedNodeRegistryTask extends AutomationOrphanedNodeRegistryTask { 23 | 24 | private ProxySet proxySet; 25 | 26 | public MockAutomationOrphanedNodeRegistryTask(RegistryRetriever retrieveContext) { 27 | super(retrieveContext); 28 | } 29 | 30 | public void setProxySet(ProxySet proxySet) { 31 | this.proxySet = proxySet; 32 | } 33 | 34 | @Override 35 | public ProxySet getProxySet() { 36 | return proxySet; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/MockAutomationPendingNodeRegistryTask.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import org.openqa.grid.internal.ProxySet; 4 | 5 | import com.rmn.qa.RegistryRetriever; 6 | import com.rmn.qa.aws.MockManageVm; 7 | import com.rmn.qa.aws.VmManager; 8 | 9 | /** 10 | * Created by matthew on 3/18/16. 11 | */ 12 | public class MockAutomationPendingNodeRegistryTask extends AutomationPendingNodeRegistryTask { 13 | 14 | private ProxySet proxySet; 15 | 16 | public MockAutomationPendingNodeRegistryTask(RegistryRetriever registryRetriever) { 17 | super(registryRetriever, new MockManageVm()); 18 | } 19 | 20 | public MockAutomationPendingNodeRegistryTask(RegistryRetriever registryRetriever, VmManager vmManager) { 21 | super(registryRetriever, vmManager); 22 | } 23 | 24 | @Override 25 | protected ProxySet getProxySet() { 26 | return proxySet; 27 | } 28 | 29 | public void setProxySet(ProxySet proxySet) { 30 | this.proxySet = proxySet; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/MockAutomationRunCleanupTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 RetailMeNot, Inc. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | */ 12 | 13 | package com.rmn.qa.task; 14 | 15 | import com.rmn.qa.RegistryRetriever; 16 | import org.openqa.grid.internal.ProxySet; 17 | 18 | /** 19 | * Created by mhardin on 5/1/14. 20 | */ 21 | public class MockAutomationRunCleanupTask extends AutomationRunCleanupTask { 22 | 23 | private ProxySet proxySet; 24 | 25 | public MockAutomationRunCleanupTask(RegistryRetriever retrieveContext) { 26 | super(retrieveContext); 27 | } 28 | 29 | public ProxySet getProxySet() { 30 | return proxySet; 31 | } 32 | 33 | public void setProxySet(ProxySet proxySet) { 34 | this.proxySet = proxySet; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/MockAutomationScaleNodeTask.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.openqa.grid.internal.ProxySet; 7 | import org.openqa.selenium.Platform; 8 | import org.openqa.selenium.remote.DesiredCapabilities; 9 | 10 | import com.beust.jcommander.internal.Lists; 11 | import com.rmn.qa.AutomationContext; 12 | import com.rmn.qa.AutomationDynamicNode; 13 | import com.rmn.qa.MockVmManager; 14 | import com.rmn.qa.NodesCouldNotBeStartedException; 15 | import com.rmn.qa.RegistryRetriever; 16 | import com.rmn.qa.aws.VmManager; 17 | 18 | /** 19 | * Created by matthew on 3/18/16. 20 | */ 21 | public class MockAutomationScaleNodeTask extends AutomationScaleNodeTask { 22 | 23 | private ProxySet proxySet; 24 | private Iterable desiredCapabilities; 25 | private List nodesStarted = Lists.newArrayList(); 26 | private List browserStarted = Lists.newArrayList(); 27 | private List platformStarted = Lists.newArrayList(); 28 | private List numThreadsStarted = Lists.newArrayList(); 29 | private boolean nodeOldEnoughToStart = true; 30 | 31 | public MockAutomationScaleNodeTask(RegistryRetriever retriever, MockVmManager vmManager) { 32 | super(retriever, vmManager); 33 | } 34 | 35 | @Override 36 | public ProxySet getProxySet() { 37 | return proxySet; 38 | } 39 | 40 | public void setProxySet(ProxySet proxySet) { 41 | this.proxySet = proxySet; 42 | } 43 | 44 | @Override 45 | Iterable getDesiredCapabilities() { 46 | return desiredCapabilities; 47 | } 48 | 49 | public void setDesiredCapabilities(Iterable desiredCapabilities) { 50 | this.desiredCapabilities = desiredCapabilities; 51 | } 52 | 53 | @Override 54 | List startNodes(VmManager vmManager, int browsersToStart, String browser, Platform platform) throws NodesCouldNotBeStartedException { 55 | List createdNodes = Lists.newArrayList(); 56 | numThreadsStarted.add(browsersToStart); 57 | browserStarted.add(browser); 58 | platformStarted.add(platform); 59 | for (int i=0;i getNodesStarted() { 79 | return nodesStarted; 80 | } 81 | 82 | public List getBrowserStarted() { 83 | return browserStarted; 84 | } 85 | 86 | public List getPlatformStarted() { 87 | return platformStarted; 88 | } 89 | 90 | public List getNumThreadsStarted() { 91 | return numThreadsStarted; 92 | } 93 | 94 | public void clear() { 95 | nodesStarted = Lists.newArrayList(); 96 | browserStarted = Lists.newArrayList(); 97 | platformStarted = Lists.newArrayList(); 98 | numThreadsStarted = Lists.newArrayList(); 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/test/java/com/rmn/qa/task/ScaleCapacityContextTest.java: -------------------------------------------------------------------------------- 1 | package com.rmn.qa.task; 2 | 3 | import java.util.Date; 4 | 5 | import org.junit.Test; 6 | 7 | import com.beust.jcommander.internal.Lists; 8 | import com.rmn.qa.AutomationContext; 9 | import com.rmn.qa.AutomationDynamicNode; 10 | import com.rmn.qa.BaseTest; 11 | 12 | import junit.framework.Assert; 13 | 14 | /** 15 | * Created by matthew on 3/18/16. 16 | */ 17 | public class ScaleCapacityContextTest extends BaseTest { 18 | 19 | @Test 20 | public void testCapacityCount() { 21 | ScaleCapacityContext context = new ScaleCapacityContext(); 22 | AutomationDynamicNode firstNode = new AutomationDynamicNode(null, "foo", null, null, new Date(), 1); 23 | AutomationDynamicNode secondNode = new AutomationDynamicNode(null, "bar", null, null, new Date(), 3); 24 | int expectedCount = firstNode.getNodeCapacity() + secondNode.getNodeCapacity(); 25 | context.addAll(Lists.newArrayList(firstNode, secondNode)); 26 | Assert.assertEquals("Total capacity count should be correct", expectedCount, context.getTotalCapacityCount()); 27 | } 28 | 29 | @Test 30 | public void clearPendingNodes() { 31 | ScaleCapacityContext context = new ScaleCapacityContext(); 32 | AutomationDynamicNode firstNode = new AutomationDynamicNode(null, "foo", null, null, new Date(), 1); 33 | AutomationDynamicNode secondNode = new AutomationDynamicNode(null, "bar", null, null, new Date(), 1); 34 | context.addAll(Lists.newArrayList(firstNode, secondNode)); 35 | Assert.assertEquals("Size should be correct as nodes should exist in context", 2, context.nodesPendingStartup.size()); 36 | context.clearPendingNodes(); 37 | Assert.assertEquals("Size should be empty as nodes should be removed", 0, context.nodesPendingStartup.size()); 38 | } 39 | 40 | @Test 41 | public void nodesDontClear() { 42 | ScaleCapacityContext context = new ScaleCapacityContext(); 43 | AutomationDynamicNode firstNode = new AutomationDynamicNode(null, "foo", null, null, new Date(), 1); 44 | AutomationDynamicNode secondNode = new AutomationDynamicNode(null, "bar", null, null, new Date(), 1); 45 | context.addAll(Lists.newArrayList(firstNode, secondNode)); 46 | Assert.assertEquals("Size should be correct as nodes should exist in context", 2, context.nodesPendingStartup.size()); 47 | AutomationContext.getContext().addPendingNode(firstNode); 48 | AutomationContext.getContext().addPendingNode(secondNode); 49 | context.clearPendingNodes(); 50 | Assert.assertEquals("Size should be the same as nodes should exist in context", 2, context.nodesPendingStartup.size()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /startGrid: -------------------------------------------------------------------------------- 1 | # check required params 2 | [ -z ${AWS_ACCESS_KEY} ] && echo "AWS_ACCESS_KEY MUST BE SET!" && exit 1 3 | [ -z ${AWS_SECRET_KEY} ] && echo "AWS_SECRET_KEY MUST BE SET!" && exit 1 4 | 5 | # set defaults 6 | [ -z ${IP_ADDRESS} ] && IP_ADDRESS=10.10.10.10 7 | 8 | java -DawsAccessKey="${AWS_ACCESS_KEY}" \ 9 | -DawsSecretKey="${AWS_SECRET_KEY}" \ 10 | -DipAddress="${IP_ADDRESS}" \ 11 | -cp /target/automation-grid.jar org.openqa.grid.selenium.GridLauncherV3 \ 12 | -role hub \ 13 | -servlets "com.rmn.qa.servlet.AutomationTestRunServlet","com.rmn.qa.servlet.StatusServlet" 14 | 15 | 16 | 17 | #-DPOOL_MAX=1024 18 | #-DawsAccessKey=foo 19 | #-DawsSecretKey=bar 20 | #-DtotalNodeCount=300 21 | #-DipAddress="10.10.10.10" 22 | #-hubConfig src/main/resources/hub.static.json 23 | #-log grid.log 24 | #-jettyMaxThreads 1024 25 | 26 | 27 | --------------------------------------------------------------------------------