Selenium Grid啓動過程——版本3.141.59

版本

3.141.59

入口類

org.openqa.grid.selenium.GridLauncherV3

相關啓動代碼:

public static void main(String[] args) {
   new GridLauncherV3().launch(args);
}

public Stoppable launch(String[] args) {
    return Optional.ofNullable(buildLauncher(args))
        .map(l -> l.launch(args))
        .orElse(()->{});
}

可見,實際上的啓動過程分爲兩步:1.用main函數的外部參數創建啓動器;2.用創建好的啓動器運行launch方法進行啓動。

創建啓動器

 /**
   * From the {@code args}, builds a new {@link GridItemLauncher} and populates it properly.
   *
   * @return null if no role is found, or a properly populated {@link GridItemLauncher}.
   */
  private GridItemLauncher buildLauncher(String[] args) {
    if (Arrays.asList(args).contains("-htmlSuite")) {
      out.println(Joiner.on("\n").join(
          "Download the Selenium HTML Runner from http://www.seleniumhq.org/download/ and",
          "use that to run your HTML suite."));
      return null;
    }

    String role = "standalone";

    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("-role=")) {
        role = args[i].substring("-role=".length());
      } else if (args[i].equals("-role")) {
        i++;  // Increment, because we're going to need this.
        if (i < args.length) {
          role = args[i];
        } else {
          role = null;  // Will cause us to print the usage information.
        }
      }
    }

    GridRole gridRole = GridRole.get(role);
    if (gridRole == null || LAUNCHERS.get(gridRole) == null) {
      printInfoAboutRoles(role);
      return null;
    }

    return LAUNCHERS.get(gridRole);
  }

這裏解釋下這段代碼。
首先檢查下-htmlSuite參數,如果有,那麼會提示你去下載Selenium HTML Runner,然後退出啓動過程。
如果沒有-htmlSuite參數,接着就獲取-role參數,通過判斷字符串來轉爲枚舉中的一種,默認判斷爲null,會退出啓動過程。簡單看下能實例化成哪幾種類型:

public enum GridRole {
  NOT_GRID, HUB, NODE;

這三種分別代表了三種模式,即註冊中心和節點合一模式、註冊中心模式、節點模式。註冊中心需要配合節點使用。
在返回時,返回了LAUNCHERS.get(gridRole)LAUNCHERS是由org.openqa.grid.selenium.GridLauncherV3#buildLaunchers初始化的,是一個ImmutableMap類型的Map。Size爲3,key是GridRole的三個枚舉成員,value是每個枚舉對應的初始化方法。

啓動

.put(GridRole.HUB, (args) -> {
          GridHubCliOptions options = new GridHubCliOptions();
          if (!parse(args, options, options.getCommonGridOptions().getCommonOptions())) {
            return ()->{};
          }

          GridHubConfiguration configuration = new GridHubConfiguration(options);
          configuration.setRawArgs(args); // for grid console

          log.info(String.format(
              "Launching Selenium Grid hub on port %s", configuration.port));
          Hub hub = new Hub(configuration);
          hub.start();
          return hub;
        })

這裏介紹Hub的啓動,第一個if檢查命令中是否有version或者help,有的話就執行相應的打印提示操作後退出啓動過程,否則繼續執行。
繼續執行的是初始化配置。這裏涉及到一個配置類GridHubConfiguration。這個類會默認利用內部的DEFAULT_CONFIG_FROM_JSON靜態成員變量初始化自己其他的成員變量。但如果在啓動時通過參數-hubConfig傳入配置文件,則利用傳入的配置文件路徑初始化某些成員變量。從該類中可知,默認配置文件在爲org/openqa/grid/common/defaults/DefaultHub.json,在jar包的相關位置可以找到。自定義配置文件可以參考文件裏的寫法。
接着就通過給Hub傳入配置參數,來進行啓動工作:

 Hub hub = new Hub(configuration);
 hub.start();

new Hub

 public Hub(GridHubConfiguration gridHubConfiguration) {
    config = gridHubConfiguration == null ? new GridHubConfiguration() : gridHubConfiguration;

    try {
      registry = (GridRegistry) Class.forName(config.registry).newInstance();
      registry.setHub(this);
      registry.setThrowOnCapabilityNotPresent(config.throwOnCapabilityNotPresent);
    } catch (Throwable e) {
      throw new GridConfigurationException("Error creating class with " + config.registry +
                                           " : " + e.getMessage(), e);
    }

    if (config.host == null) {
      config.host = "0.0.0.0"; //default to all adapters
    }

    if (config.port == null) {
      config.port = 4444;
    }

    if (config.servlets != null) {
      for (String s : config.servlets) {
        Class<? extends Servlet> servletClass = ExtraServletUtil.createServlet(s);
        if (servletClass != null) {
          String path = "/grid/admin/" + servletClass.getSimpleName() + "/*";
          log.info("binding " + servletClass.getCanonicalName() + " to " + path);
          addServlet(path, servletClass);
        }
      }
    }

    // start the registry, now that 'config' is all setup
    registry.start();

    new JMXHelper().register(this);
  }

這裏用配置中的registry反射實例化一個GridRegistry實例,並簡單設置下必備參數。值得注意的是config.servlets的相關操作,這裏相當於提示我們,如果還想給Hub節點拓展更多的Api接口,是如何進行初始化相關Servlet。注意,Selenium實際上是一個由內嵌Jetty管理的Server。

hub.start()

  public void start() {
    initServer();

    try {
      server.start();
    } catch (Exception e) {
      try {
        stop();
      } catch (Exception ignore) {
      }
      if (e instanceof BindException) {
        log.severe(String.format(
            "Port %s is busy, please choose a free port for the hub and specify it using -port option", config.port));
        return;
      } else {
        throw new RuntimeException(e);
      }
    }

    log.info("Selenium Grid hub is up and running");
    log.info(String.format("Nodes should register to %s", getRegistrationURL()));
    log.info(String.format("Clients should connect to %s", getWebDriverHubRequestURL()));
  }

其中org.openqa.grid.web.Hub#initServer中初始化一個QueuedThreadPool,這是Selenium自定義的一個用來管理任務線程的類。可以通過配置文件中的jettyMaxThreads參數來配置其線程池大小。利用線程池初始化一個org.seleniumhq.jetty9.server.Server實例。
這個過程中,有個比較重要的方法調用,org.openqa.grid.web.Hub#addDefaultServlets方法中將一些路徑關聯org.openqa.grid.web.servlet.RegistryBasedServlet的子類上。令Selenium Hub真正對外提供服務。

 private void addDefaultServlets(ServletContextHandler handler) {
    // add mandatory default servlets
    handler.addServlet(RegistrationServlet.class.getName(), "/grid/register/*");

    handler.addServlet(DriverServlet.class.getName(), "/wd/hub/*");
    handler.addServlet(DriverServlet.class.getName(), "/selenium-server/driver/*");

    handler.addServlet(ProxyStatusServlet.class.getName(), "/grid/api/proxy/*");
    handler.addServlet(NodeSessionsServlet.class.getName(), "/grid/api/sessions/*");

    handler.addServlet(HubStatusServlet.class.getName(), "/grid/api/hub/*");

    ServletHolder statusHolder = new ServletHolder(new HubW3CStatusServlet(getRegistry()));
    handler.addServlet(statusHolder, "/status");
    handler.addServlet(statusHolder, "/wd/hub/status");

    handler.addServlet(TestSessionStatusServlet.class.getName(), "/grid/api/testsession/*");

    // add optional default servlets
    if (!config.isWithOutServlet(ResourceServlet.class)) {
      handler.addServlet(ResourceServlet.class.getName(), "/grid/resources/*");
    }

    if (!config.isWithOutServlet(DisplayHelpServlet.class)) {
      handler.addServlet(DisplayHelpServlet.class.getName(), "/*");
      handler.setInitParameter(DisplayHelpServlet.HELPER_TYPE_PARAMETER, config.role);
    }

    if (!config.isWithOutServlet(ConsoleServlet.class)) {
      handler.addServlet(ConsoleServlet.class.getName(), "/grid/console/*");
      handler.setInitParameter(ConsoleServlet.CONSOLE_PATH_PARAMETER, "/grid/console");
    }

    if (!config.isWithOutServlet(LifecycleServlet.class)) {
      handler.addServlet(LifecycleServlet.class.getName(), "/lifecycle-manager/*");
    }

    if (!config.isWithOutServlet(Grid1HeartbeatServlet.class)) {
      handler.addServlet(Grid1HeartbeatServlet.class.getName(), "/heartbeat");
    }
  }

相關暴露的接口可以查詢官方文檔或者直接查看對應類中的處理方法。自定義接口可以參考它們的實現。

server.start()

 public final void start() throws Exception {
        synchronized(this._lock) {
            try {
                if (this._state == 2 || this._state == 1) {
                    return;
                }

                this.setStarting();
                this.doStart();
                this.setStarted();
            } catch (Throwable var4) {
                this.setFailed(var4);
                throw var4;
            }

        }
    }

這裏都是Jetty的啓動內容。

啓動完成

當控制檯看到“Selenium Grid hub is up and running”輸出時,整個Hub節點就啓動成功。

16:41:16.705 INFO [Hub.start] - Selenium Grid hub is up and running
16:41:16.740 INFO [Hub.start] - Nodes should register to http://******:5555/grid/register/
16:41:16.773 INFO [Hub.start] - Clients should connect to http://******:5555/wd/hub

Node應向第一條Url( http://******:5555/grid/register/)註冊,使用WebDriver的客戶端應向第二條Url(http://******:5555/wd/hub)連接。

總結

其他兩種——Standalone和Node啓動邏輯類似,跟着Hub的啓動邏輯也可以看到它們的啓動邏輯。
總之,Selenium Grid的啓動依靠傳入參數以及Json文件配置來完成啓動流程。並且有默認的Json配置文件。可以自定義Sevlet添加擴展接口。
這就是Selenium啓動過程能告訴我們的信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章