├── src └── main │ ├── java │ └── org │ │ └── apache │ │ └── mesos │ │ └── selenium │ │ ├── model │ │ ├── SeleniumGridResource.java │ │ ├── SeleniumNode.java │ │ ├── SeleniumGrid.java │ │ └── SeleniumHub.java │ │ ├── FrameworkConfiguration.java │ │ ├── SeleniumDockerCommand.java │ │ ├── SeleniumFramework.java │ │ ├── State.java │ │ └── SeleniumScheduler.java │ └── resources │ └── config.json ├── README.md └── pom.xml /src/main/java/org/apache/mesos/selenium/model/SeleniumGridResource.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium.model; 2 | 3 | /** 4 | * Created by waseemh on 15/09/2015. 5 | */ 6 | public abstract class SeleniumGridResource { 7 | 8 | private int cpus; 9 | 10 | private int mem; 11 | 12 | private int disk; 13 | 14 | public int getCpus() { 15 | return cpus; 16 | } 17 | 18 | public void setCpus(int cpus) { 19 | this.cpus = cpus; 20 | } 21 | 22 | public int getMem() { 23 | return mem; 24 | } 25 | 26 | public void setMem(int mem) { 27 | this.mem = mem; 28 | } 29 | 30 | public int getDisk() { 31 | return disk; 32 | } 33 | 34 | public void setDisk(int disk) { 35 | this.disk = disk; 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/model/SeleniumNode.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium.model; 2 | 3 | /** 4 | * Created by waseemh on 14/09/2015. 5 | */ 6 | public class SeleniumNode extends SeleniumGridResource { 7 | 8 | private String browser; 9 | 10 | private int instances; 11 | 12 | public String getBrowser() { 13 | return browser; 14 | } 15 | 16 | public void setBrowser(String browser) { 17 | this.browser = browser; 18 | } 19 | 20 | public int getInstances() { 21 | return instances; 22 | } 23 | 24 | public void setInstances(int instances) { 25 | this.instances = instances; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object object) { 30 | if(!(object instanceof SeleniumNode)) { 31 | return false; 32 | } 33 | else { 34 | return ((SeleniumNode) object).getBrowser().equals(this.getBrowser()); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "zooKeeper": "zk://172.31.0.11:2181/mesos", 3 | "grid": { 4 | "hub": { 5 | "newSessionWaitTimeout": -1, 6 | "jettyMaxThreads": -1, 7 | "nodePolling": 5000, 8 | "cleanUpCycle": 5000, 9 | "timeout": 30000, 10 | "browserTimeout": 0, 11 | "maxSession": 5, 12 | "unregisterIfStillDownAfter": 3000, 13 | "mem": 512, 14 | "cpus": 0.5, 15 | "disk": 512 16 | }, 17 | "nodes": [ 18 | { 19 | "browser": "firefox", 20 | "mem": 256, 21 | "cpus": 0.5, 22 | "disk": 512, 23 | "instances": 1 24 | }, 25 | { 26 | "browser": "chrome", 27 | "mem": 256, 28 | "cpus": 0.5, 29 | "disk": 256, 30 | "instances": 1 31 | } 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/model/SeleniumGrid.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by waseemh on 07/09/2015. 8 | */ 9 | public class SeleniumGrid { 10 | 11 | private SeleniumHub hub; 12 | 13 | private List nodes; 14 | 15 | public SeleniumHub getHub() { 16 | return hub; 17 | } 18 | 19 | public void setHub(SeleniumHub hub) { 20 | this.hub = hub; 21 | } 22 | 23 | public List getNodes() { 24 | return nodes; 25 | } 26 | 27 | public List getNodesAsResources() { 28 | 29 | final List gridNodes = new ArrayList(); 30 | //adding nodes according to requested instances. 31 | //for each instance of same node, we add it multiple times as a grid resource. 32 | for(SeleniumNode node : nodes) { 33 | for(int i=0; i < node.getInstances(); i++) { 34 | gridNodes.add(node); 35 | } 36 | } 37 | return gridNodes; 38 | } 39 | 40 | public void setNodes(List nodes) { 41 | this.nodes = nodes; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/FrameworkConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import org.apache.mesos.selenium.model.SeleniumGrid; 5 | 6 | /** 7 | * Created by waseemh on 14/09/2015. 8 | */ 9 | public class FrameworkConfiguration { 10 | 11 | private SeleniumGrid grid; 12 | 13 | private String zooKeeper; 14 | 15 | private static String frameworkName = "selenium-grid"; 16 | 17 | @JsonIgnore 18 | private State state; 19 | 20 | public static String getFrameworkName() { 21 | return frameworkName; 22 | } 23 | 24 | public static void setFrameworkName(String frameworkName) { 25 | FrameworkConfiguration.frameworkName = frameworkName; 26 | } 27 | 28 | public SeleniumGrid getGrid() { 29 | return grid; 30 | } 31 | 32 | public void setGrid(SeleniumGrid grid) { 33 | this.grid = grid; 34 | } 35 | 36 | public String getZooKeeper() { 37 | return zooKeeper; 38 | } 39 | 40 | public void setZooKeeper(String zooKeeper) { 41 | this.zooKeeper = zooKeeper; 42 | } 43 | 44 | public State getState() { 45 | return state; 46 | } 47 | 48 | public void setState(State state) { 49 | this.state = state; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/SeleniumDockerCommand.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium; 2 | 3 | import org.apache.mesos.Protos; 4 | import org.apache.mesos.selenium.model.SeleniumGridResource; 5 | import org.apache.mesos.selenium.model.SeleniumHub; 6 | import org.apache.mesos.selenium.model.SeleniumNode; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by waseemh on 14/09/2015. 13 | */ 14 | public class SeleniumDockerCommand { 15 | 16 | private final static String SELENIUM_HUB_IMAGE = "selenium/hub:2.47.1"; 17 | private final static String SELENIUM_NODE_FIREFOX_IMAGE = "selenium/node-firefox:2.47.1"; 18 | private final static String SELENIUM_NODE_CHROME_IMAGE = "selenium/node-chrome:2.47.1"; 19 | 20 | public static String getImageName(SeleniumGridResource gridResource) { 21 | if(gridResource instanceof SeleniumNode) { 22 | if(((SeleniumNode) gridResource).getBrowser().equals("firefox")) { 23 | return SELENIUM_NODE_FIREFOX_IMAGE; 24 | } 25 | else if (((SeleniumNode) gridResource).getBrowser().equals("chrome")) { 26 | return SELENIUM_NODE_CHROME_IMAGE; 27 | } 28 | else return null; 29 | } 30 | else if(gridResource instanceof SeleniumHub) { 31 | return SELENIUM_HUB_IMAGE; 32 | } 33 | else return null; 34 | } 35 | 36 | public static List getCommandParameters(String hubContainerName, SeleniumGridResource gridResource) { 37 | List parameters = new ArrayList(); 38 | if(gridResource instanceof SeleniumNode) { 39 | parameters.add(org.apache.mesos.Protos.Parameter.newBuilder().setKey("link").setValue(hubContainerName+":hub").build()); 40 | } 41 | if(gridResource instanceof SeleniumHub) { 42 | parameters.add(org.apache.mesos.Protos.Parameter.newBuilder().setKey("publish").setValue("4444:4444").build()); 43 | } 44 | return parameters; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/model/SeleniumHub.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium.model; 2 | 3 | /** 4 | * Created by waseemh on 15/09/2015. 5 | */ 6 | public class SeleniumHub extends SeleniumGridResource { 7 | 8 | private int newSessionWaitTimeout; 9 | 10 | private int jettyMaxThreads; 11 | 12 | private int nodePolling; 13 | 14 | private int cleanUpCycle; 15 | 16 | private int timeout; 17 | 18 | private int browserTimeout; 19 | 20 | private int maxSession; 21 | 22 | private int unregisterIfStillDownAfter; 23 | 24 | public int getNewSessionWaitTimeout() { 25 | return newSessionWaitTimeout; 26 | } 27 | 28 | public void setNewSessionWaitTimeout(int newSessionWaitTimeout) { 29 | this.newSessionWaitTimeout = newSessionWaitTimeout; 30 | } 31 | 32 | public int getJettyMaxThreads() { 33 | return jettyMaxThreads; 34 | } 35 | 36 | public void setJettyMaxThreads(int jettyMaxThreads) { 37 | this.jettyMaxThreads = jettyMaxThreads; 38 | } 39 | 40 | public int getNodePolling() { 41 | return nodePolling; 42 | } 43 | 44 | public void setNodePolling(int nodePolling) { 45 | this.nodePolling = nodePolling; 46 | } 47 | 48 | public int getCleanUpCycle() { 49 | return cleanUpCycle; 50 | } 51 | 52 | public void setCleanUpCycle(int cleanUpCycle) { 53 | this.cleanUpCycle = cleanUpCycle; 54 | } 55 | 56 | public int getTimeout() { 57 | return timeout; 58 | } 59 | 60 | public void setTimeout(int timeout) { 61 | this.timeout = timeout; 62 | } 63 | 64 | public int getBrowserTimeout() { 65 | return browserTimeout; 66 | } 67 | 68 | public void setBrowserTimeout(int browserTimeout) { 69 | this.browserTimeout = browserTimeout; 70 | } 71 | 72 | public int getMaxSession() { 73 | return maxSession; 74 | } 75 | 76 | public void setMaxSession(int maxSession) { 77 | this.maxSession = maxSession; 78 | } 79 | 80 | public int getUnregisterIfStillDownAfter() { 81 | return unregisterIfStillDownAfter; 82 | } 83 | 84 | public void setUnregisterIfStillDownAfter(int unregisterIfStillDownAfter) { 85 | this.unregisterIfStillDownAfter = unregisterIfStillDownAfter; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/SeleniumFramework.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.apache.mesos.MesosSchedulerDriver; 5 | import org.apache.mesos.Protos; 6 | import org.apache.mesos.Scheduler; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | /** 14 | * CLI Params 15 | * 16 | * Created by waseemh on 08/09/2015. 17 | */ 18 | public class SeleniumFramework { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(SeleniumFramework.class); 21 | 22 | public static void main(String[] args) { 23 | 24 | FrameworkConfiguration frameworkConfiguration = null; 25 | try { 26 | frameworkConfiguration = initConfiguration(args[0]); 27 | } catch (IOException e) { 28 | e.printStackTrace(); 29 | } 30 | 31 | LOGGER.info("Setting up the Framework."); 32 | Protos.FrameworkInfo.Builder framework = Protos.FrameworkInfo.newBuilder() 33 | .setName("Selenium Grid") 34 | .setUser("") 35 | .setCheckpoint(true) //DCOS-04 Scheduler MUST enable checkpointing. 36 | .setFailoverTimeout(86400D); //DCOS-01 Scheduler MUST register with a failover timeout. 37 | 38 | LOGGER.info("Setting up the State."); 39 | State state = new State(frameworkConfiguration.getZooKeeper()); 40 | state.removeFrameworkId(); 41 | frameworkConfiguration.setState(state); 42 | Protos.FrameworkID frameworkId = state.getFrameworkId(); 43 | if(frameworkId != null){ 44 | framework.setId(frameworkId); //DCOS-02 Scheduler MUST persist their FrameworkID for failover. 45 | } 46 | 47 | LOGGER.info("Setting up the Scheduler"); 48 | final Scheduler scheduler = new SeleniumScheduler(frameworkConfiguration); 49 | final MesosSchedulerDriver schedulerDriver = new MesosSchedulerDriver(scheduler, framework.build(), frameworkConfiguration.getZooKeeper()); 50 | 51 | int status = schedulerDriver.run() == Protos.Status.DRIVER_STOPPED ? 0 : 1; 52 | schedulerDriver.stop(); 53 | System.exit(status); 54 | 55 | } 56 | 57 | private static FrameworkConfiguration initConfiguration(String jsonFile) throws IOException { 58 | ObjectMapper mapper = new ObjectMapper(); 59 | return mapper.readValue(new File(jsonFile), FrameworkConfiguration.class); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | selenium-mesos 2 | ======== 3 | 4 | A Mesos framework for launching Selenium instances at scale using Docker containers. 5 | Currently, framework only supports setting up a single Selenium Grid with a single hub and multiple nodes. 6 | 7 | Roadmap 8 | ======= 9 | 10 | ### Features 11 | - Support complex setups with multiple grids and standalone instances. 12 | - Non-ZooKeeper architectures. 13 | 14 | Requirements 15 | ======== 16 | 17 | - Apache Mesos 0.23.0 and above 18 | - JDK 6+ and Maven (optional for building source) 19 | 20 | Configuration 21 | ======== 22 | 23 | Framework is configured using a JSON-format file. It includes ZooKeeper URL(s) and the requested Selenium instances with their resource requirements. 24 | 25 | An example of a Grid setup which includes one hub and three nodes (two firefox + one chrome): 26 | 27 | { 28 | "zooKeeper": "zk://172.31.0.11:2181/mesos", 29 | "grid": { 30 | "hub": { 31 | "mem": 256, 32 | "cpus": 0.5, 33 | "disk": 512 34 | }, 35 | "nodes": [ 36 | { 37 | "browser": "firefox", 38 | "mem": 512, 39 | "cpus": 0.5, 40 | "disk": 512, 41 | "instances": 2 42 | }, 43 | { 44 | "browser": "chrome", 45 | "mem": 512, 46 | "cpus": 0.5, 47 | "disk": 256, 48 | "instances": 1 49 | } 50 | ] 51 | } 52 | } 53 | 54 | Building from Sources 55 | ======== 56 | 57 | Maven is used as a build system. 58 | In order to produce a package, run maven command `mvn clean package -DskipTests`. 59 | Tests can be executed using command `mvn test`. 60 | 61 | Running the framework 62 | ======== 63 | 64 | In order to start the framework, both JAR file and configuration JSON file should be placed in Mesos master and launched using java command. 65 | 66 | java -jar /path/to/mesos-selenium.jar /path/to/config.json 67 | 68 | License 69 | ======== 70 | 71 | Copyright to Waseem Hamshawi 72 | 73 | Licensed under the Apache License, Version 2.0 (the "License"); 74 | you may not use this file except in compliance with the License. 75 | You may obtain a copy of the License at 76 | 77 | http://www.apache.org/licenses/LICENSE-2.0 78 | 79 | Unless required by applicable law or agreed to in writing, software 80 | distributed under the License is distributed on an "AS IS" BASIS, 81 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 82 | See the License for the specific language governing permissions and 83 | limitations under the License. 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.apache.mesos 8 | selenium-mesos 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.mesos 14 | mesos 15 | 0.23.0 16 | 17 | 18 | org.slf4j 19 | slf4j-jdk14 20 | 1.7.5 21 | 22 | 23 | com.fasterxml.jackson.core 24 | jackson-core 25 | 2.6.1 26 | 27 | 28 | com.fasterxml.jackson.core 29 | jackson-annotations 30 | 2.6.1 31 | 32 | 33 | com.fasterxml.jackson.core 34 | jackson-databind 35 | 2.6.1 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | maven-assembly-plugin 45 | 46 | 47 | 48 | org.apache.mesos.selenium.SeleniumFramework 49 | 50 | 51 | 52 | jar-with-dependencies 53 | 54 | 55 | 56 | 57 | 58 | make-assembly 59 | package 60 | 61 | single 62 | 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-compiler-plugin 69 | 70 | 1.7 71 | 1.7 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/State.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import org.apache.mesos.state.Variable; 5 | import org.apache.mesos.state.ZooKeeperState; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.apache.mesos.Protos.FrameworkID; 9 | 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.TimeUnit; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * Class in charge of reading/manipulating the Zookeeper state. 17 | * Used for keeping the Framework ID persistent 18 | */ 19 | public class State { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(State.class); 22 | 23 | //Regex used for parsing the zookeeper url 24 | private static final String userAndPass = "[^/@]+"; 25 | private static final String hostAndPort = "[A-z0-9-.]+(?::\\d+)?"; 26 | private static final String zookeeperNode = "[^/]+"; 27 | private static final String zookeeperUrlRegex = "^zk://((?:" + userAndPass + "@)?(?:" + hostAndPort + "(?:," + hostAndPort + ")*))(/" + zookeeperNode + "(?:/" + zookeeperNode + ")*)$"; 28 | private static final String validZookeeperUrl = "zk://host1:port1,host2:port2,.../path"; 29 | private static final Pattern zookeeperUrlPattern = Pattern.compile(zookeeperUrlRegex); 30 | 31 | private final ZooKeeperState state; // the zookeeper state 32 | 33 | /** 34 | * Constructor for State 35 | * 36 | * @param zookeeperUrl the URL of the Zookeeper 37 | */ 38 | public State(String zookeeperUrl) { 39 | Matcher matcher = validateZookeeperUrl(zookeeperUrl); 40 | state = new ZooKeeperState( 41 | matcher.group(1), 42 | 30000, 43 | TimeUnit.MILLISECONDS, 44 | "/" + FrameworkConfiguration.getFrameworkName() + matcher.group(2)); 45 | } 46 | 47 | /** 48 | * Determines whether the passed in string is a valid zookeeper url or not 49 | * 50 | * @param zookeeperUrl the zookeeper url string 51 | * @return the Matcher used for validating the string 52 | * @throws IllegalArgumentException when the String is an invalid zookeeper url. 53 | */ 54 | private Matcher validateZookeeperUrl(String zookeeperUrl) throws IllegalArgumentException { 55 | Matcher matcher = zookeeperUrlPattern.matcher(zookeeperUrl); 56 | if (!matcher.matches()) { 57 | throw new IllegalArgumentException(String.format("Invalid zk url format: '%s' expected '%s'", zookeeperUrl, validZookeeperUrl)); 58 | } 59 | return matcher; 60 | } 61 | 62 | /** 63 | * Returns the Framework ID stored in the Zookeeper state. 64 | * 65 | * @return the Framework ID stored in the Zookeeper state. Null if no Framework ID was found. 66 | */ 67 | public FrameworkID getFrameworkId() { 68 | try { 69 | byte[] existingFrameworkId = state.fetch("frameworkId").get().value(); 70 | if (existingFrameworkId.length > 0) { 71 | FrameworkID frameworkId = FrameworkID.parseFrom(existingFrameworkId); 72 | LOGGER.info("Found FrameworkID " + frameworkId.getValue()); 73 | return frameworkId; 74 | } else { 75 | LOGGER.info("No existing FrameworkID found"); 76 | return null; 77 | } 78 | } catch (InterruptedException | ExecutionException | InvalidProtocolBufferException e) { 79 | LOGGER.error("Failed to get framework ID from Zookeeper state", e); 80 | throw new RuntimeException("Failed to get framework ID from Zookeeper state", e); 81 | } 82 | } 83 | 84 | /** 85 | * Sets the Framework ID in the Zookeeper state 86 | * 87 | * @param frameworkId the Framework ID to set 88 | */ 89 | public void setFrameworkId(FrameworkID frameworkId) { 90 | Variable value; 91 | try { 92 | value = state.fetch("frameworkId").get(); 93 | value = value.mutate(frameworkId.toByteArray()); 94 | state.store(value).get(); 95 | LOGGER.info("Set framework ID in Zookeeper state to {}", frameworkId.getValue()); 96 | } catch (InterruptedException | ExecutionException e) { 97 | LOGGER.error("Failed to set framework ID in Zookeeper state", e); 98 | throw new RuntimeException("Failed to set framework ID in Zookeeper state", e); 99 | } 100 | } 101 | 102 | /** 103 | * Removes the Framework ID from the Zookeeper. 104 | */ 105 | public void removeFrameworkId() { 106 | //TODO: Call this when gracefully shutting down the scheduler. 107 | Variable value; 108 | try { 109 | value = state.fetch("frameworkId").get(); 110 | state.expunge(value); 111 | } catch (InterruptedException | ExecutionException e) { 112 | LOGGER.error("Failed to remove framework ID from Zookeeper state", e); 113 | throw new RuntimeException("Failed to remove framework ID from Zookeeper state", e); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/org/apache/mesos/selenium/SeleniumScheduler.java: -------------------------------------------------------------------------------- 1 | package org.apache.mesos.selenium; 2 | 3 | import com.fasterxml.jackson.core.JsonFactory; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.apache.mesos.Protos; 7 | import org.apache.mesos.Scheduler; 8 | import org.apache.mesos.SchedulerDriver; 9 | import org.apache.mesos.selenium.model.SeleniumGridResource; 10 | import org.apache.mesos.selenium.model.SeleniumHub; 11 | import org.apache.mesos.selenium.model.SeleniumNode; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.awt.*; 16 | import java.io.IOException; 17 | import java.util.*; 18 | import java.util.List; 19 | import java.util.concurrent.atomic.AtomicInteger; 20 | 21 | /** 22 | * Created by waseemh on 15/06/2015. 23 | */ 24 | public class SeleniumScheduler implements Scheduler { 25 | 26 | private final FrameworkConfiguration config; 27 | 28 | /** List of pending instances. */ 29 | private final Queue pendingNodes; 30 | 31 | private Map runningTasks; 32 | 33 | private final SeleniumHub hub; 34 | 35 | private boolean isHubLaunched; 36 | 37 | private boolean isHubRunning; 38 | 39 | private String hubContainerName; 40 | 41 | private static final Logger LOGGER = LoggerFactory.getLogger(SeleniumScheduler.class); 42 | 43 | private static final AtomicInteger TASK_ID_GENERATOR = new AtomicInteger(); // used to generate task numbers 44 | 45 | public SeleniumScheduler(FrameworkConfiguration frameworkConfiguration) { 46 | this.config = frameworkConfiguration; 47 | this.pendingNodes = new LinkedList(config.getGrid().getNodesAsResources()); 48 | this.runningTasks = new HashMap(); 49 | this.hub = config.getGrid().getHub(); 50 | this.isHubLaunched = false; 51 | this.isHubRunning = false; 52 | } 53 | 54 | @Override 55 | public void registered(SchedulerDriver schedulerDriver, Protos.FrameworkID frameworkID, Protos.MasterInfo masterInfo) { 56 | LOGGER.info("Framework registered! ID: " + frameworkID.getValue()); 57 | config.getState().setFrameworkId(frameworkID); 58 | } 59 | 60 | @Override 61 | public void reregistered(SchedulerDriver schedulerDriver, Protos.MasterInfo masterInfo) { 62 | config.getState().removeFrameworkId(); 63 | LOGGER.info("Re-registered"); 64 | } 65 | 66 | @Override 67 | public void resourceOffers(SchedulerDriver schedulerDriver, List offers) { 68 | 69 | boolean matched = false; 70 | 71 | for (Protos.Offer offer : offers) { 72 | if(!isHubLaunched) { 73 | if (matches(offer,hub)) { 74 | matched = true; 75 | Protos.TaskID taskID = launchTask(schedulerDriver,offer,hub); 76 | runningTasks.put(taskID, hub); 77 | isHubLaunched = true; 78 | } 79 | } 80 | else if(isHubRunning) { 81 | for (SeleniumNode node : pendingNodes) { 82 | if (matches(offer, node)) { 83 | matched = true; 84 | Protos.TaskID taskID = launchTask(schedulerDriver, offer, node); 85 | runningTasks.put(taskID, node); 86 | pendingNodes.remove(node); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | if (!matched) { 93 | schedulerDriver.declineOffer(offer.getId()); 94 | } 95 | } 96 | 97 | } 98 | 99 | private Protos.TaskID launchTask(SchedulerDriver schedulerDriver, Protos.Offer offer, SeleniumGridResource gridResource) { 100 | 101 | Protos.TaskID taskId = Protos.TaskID.newBuilder().setValue(FrameworkConfiguration.getFrameworkName()+"-"+TASK_ID_GENERATOR.getAndIncrement()).build(); 102 | 103 | // docker image info 104 | Protos.ContainerInfo.DockerInfo.Builder dockerInfoBuilder = Protos.ContainerInfo.DockerInfo.newBuilder(); 105 | dockerInfoBuilder.setNetwork(Protos.ContainerInfo.DockerInfo.Network.BRIDGE); 106 | dockerInfoBuilder.setImage(SeleniumDockerCommand.getImageName(gridResource)); 107 | dockerInfoBuilder.addAllParameters(SeleniumDockerCommand.getCommandParameters(hubContainerName,gridResource)); 108 | 109 | // container info 110 | Protos.ContainerInfo.Builder containerInfoBuilder = Protos.ContainerInfo.newBuilder(); 111 | containerInfoBuilder.setType(Protos.ContainerInfo.Type.DOCKER); 112 | containerInfoBuilder.setDocker(dockerInfoBuilder.build()); 113 | 114 | Protos.TaskInfo task = Protos.TaskInfo 115 | .newBuilder() 116 | .setName("task " + taskId.getValue()) 117 | .setTaskId(taskId) 118 | .setSlaveId(offer.getSlaveId()) 119 | .addResources( 120 | Protos.Resource 121 | .newBuilder() 122 | .setName("cpus") 123 | .setType(Protos.Value.Type.SCALAR) 124 | .setScalar( 125 | Protos.Value.Scalar.newBuilder() 126 | .setValue(gridResource.getCpus()). 127 | build()).build()) 128 | .addResources( 129 | Protos.Resource 130 | .newBuilder() 131 | .setName("mem") 132 | .setType(Protos.Value.Type.SCALAR) 133 | .setScalar( 134 | Protos.Value.Scalar 135 | .newBuilder() 136 | .setValue(gridResource.getMem()) 137 | .build()).build()) 138 | .setContainer(containerInfoBuilder) 139 | .setCommand(Protos.CommandInfo.newBuilder().setShell(false)).build(); 140 | 141 | schedulerDriver.launchTasks(Arrays.asList(offer.getId()), Arrays.asList(task)); 142 | 143 | return taskId; 144 | } 145 | 146 | private static boolean matches(Protos.Offer offer, SeleniumGridResource gridResource) { 147 | 148 | double cpus = -1; 149 | double mem = -1; 150 | 151 | for (Protos.Resource resource : offer.getResourcesList()) { 152 | if (resource.getName().equals("cpus")) { 153 | if (resource.getType().equals(Protos.Value.Type.SCALAR)) { 154 | cpus = resource.getScalar().getValue(); 155 | } else { 156 | LOGGER.error("Cpus resource was not a scalar: {0}", resource.getType().toString()); 157 | } 158 | } else if (resource.getName().equals("mem")) { 159 | if (resource.getType().equals(Protos.Value.Type.SCALAR)) { 160 | mem = resource.getScalar().getValue(); 161 | } else { 162 | LOGGER.error("Mem resource was not a scalar: {0}", resource.getType().toString()); 163 | } 164 | // } else if (resource.getName().equals("disk")) { 165 | // LOGGER.warn("Ignoring disk resources from offer"); 166 | // } else { 167 | // LOGGER.warn("Ignoring unknown resource type: {0}", resource.getName()); 168 | // } 169 | } 170 | } 171 | 172 | if (cpus < 0) { 173 | LOGGER.error("No cpus resource present"); 174 | } 175 | if (mem < 0) { 176 | LOGGER.error("No mem resource present"); 177 | } 178 | 179 | // Check for sufficient cpu and memory resources in the offer. 180 | double requestedCpus = gridResource.getCpus(); 181 | double requestedMem = gridResource.getMem(); 182 | 183 | if (requestedCpus <= cpus && requestedMem <= mem) { 184 | return true; 185 | } else { 186 | // LOGGER.info( 187 | // "Offer not sufficient for slave request:\n" 188 | // + offer.getResourcesList().toString() 189 | // + " cpus: " + requestedCpus + "\n" 190 | // + " mem: " + requestedMem); 191 | return false; 192 | } 193 | } 194 | 195 | @Override 196 | public void offerRescinded(SchedulerDriver schedulerDriver, Protos.OfferID offerID) { 197 | System.out.println("This offer's been rescinded. Tough luck, cowboy."); 198 | } 199 | 200 | @Override 201 | public void statusUpdate(SchedulerDriver schedulerDriver, Protos.TaskStatus taskStatus) { 202 | 203 | Protos.TaskID taskId = taskStatus.getTaskId(); 204 | 205 | LOGGER.info("Task {} is in state {}", taskId.getValue(), taskStatus.getState()); 206 | 207 | switch (taskStatus.getState()) { 208 | case TASK_FAILED: 209 | SeleniumGridResource gridResource = runningTasks.get(taskId); 210 | if(gridResource instanceof SeleniumNode) { 211 | pendingNodes.add((SeleniumNode) gridResource); 212 | } 213 | else if (gridResource instanceof SeleniumHub) { 214 | isHubLaunched = false; 215 | } 216 | break; 217 | case TASK_RUNNING: 218 | SeleniumGridResource resource = runningTasks.get(taskId); 219 | if(resource instanceof SeleniumHub) { 220 | String containerJsonData = new String(taskStatus.getData().toByteArray()); 221 | JsonFactory factory = new JsonFactory(); 222 | 223 | ObjectMapper mapper = new ObjectMapper(factory); 224 | JsonNode rootNode = null; 225 | try { 226 | rootNode = mapper.readTree(containerJsonData); 227 | } catch (IOException e) { 228 | e.printStackTrace(); 229 | } 230 | 231 | hubContainerName = rootNode.get(0).get("Name").asText().replace("/",""); 232 | LOGGER.info("Container Name:" + hubContainerName); 233 | isHubRunning = true; 234 | LOGGER.info("Hub is running..."); 235 | } 236 | break; 237 | case TASK_FINISHED: 238 | LOGGER.info("Unregistering task {} due to state: {}", taskId.getValue(), taskStatus.getState()); 239 | runningTasks.remove(taskId); 240 | break; 241 | } 242 | } 243 | 244 | @Override 245 | public void frameworkMessage(SchedulerDriver schedulerDriver, Protos.ExecutorID executorID, Protos.SlaveID slaveID, byte[] bytes) { 246 | LOGGER.info("Received message (scheduler): " + new String(bytes) 247 | + " from " + executorID.getValue()); 248 | } 249 | 250 | @Override 251 | public void disconnected(SchedulerDriver schedulerDriver) { 252 | LOGGER.info("We got disconnected yo"); 253 | } 254 | 255 | @Override 256 | public void slaveLost(SchedulerDriver schedulerDriver, Protos.SlaveID slaveID) { 257 | LOGGER.info("Lost slave: " + slaveID); 258 | } 259 | 260 | @Override 261 | public void executorLost(SchedulerDriver schedulerDriver, Protos.ExecutorID executorID, Protos.SlaveID slaveID, int i) { 262 | LOGGER.info("Lost executor on slave " + slaveID); 263 | } 264 | 265 | @Override 266 | public void error(SchedulerDriver schedulerDriver, String s) { 267 | LOGGER.info("We've got errors, man: " + s); 268 | } 269 | } --------------------------------------------------------------------------------