Nagios與Selenium結合能夠執行更復雜的頁面檢查,檢測網站運行狀態、任務運行狀況等。html
Nagios集成Selenium步驟:java
說明:Java Main函數使用了https://github.com/czunker/check_selenium 的CallSeleniumTest類,略做修改。
完整源碼可從GitHub下載。linux
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.itrunner</groupId> <artifactId>nagios-selenium</artifactId> <version>1.0</version> <packaging>jar</packaging> <properties> <project.encoding>UTF-8</project.encoding> <jdk.version>1.8</jdk.version> <selenium.version>3.141.5</selenium.version> </properties> <build> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>${jdk.version}</source> <target>${jdk.version}</target> <encoding>${project.encoding}</encoding> </configuration> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.1.0</version> <configuration> <encoding>${project.encoding}</encoding> </configuration> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <manifest> <mainClass>org.itrunner.tests.CallSeleniumTest</mainClass> <addClasspath>true</addClasspath> </manifest> </archive> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.1.0</version> <configuration> <descriptors> <descriptor>src/assembly/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>3.5.0.1254</version> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-chrome-driver</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-support</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>com.codeborne</groupId> <artifactId>phantomjsdriver</artifactId> <version>1.4.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>commons-cli</groupId> <artifactId>commons-cli</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies> </project>
調用測試的入口類。
CallSeleniumTestios
package org.itrunner.tests; import org.apache.commons.cli.*; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.firefox.NotConnectedException; public class CallSeleniumTest { private static int timeout = 30; private static final int NAGIOS_OK = 0; private static final int NAGIOS_WARNING = 1; private static final int NAGIOS_CRITICAL = 2; private static final int NAGIOS_UNKNOWN = 3; private static final String NAGIOS_TEXT_OK = "OK"; private static final String NAGIOS_TEXT_WARNING = "WARNING"; private static final String NAGIOS_TEXT_CRITICAL = "CRITICAL"; private static final String NAGIOS_TEXT_UNKNOWN = "UNKNOWN"; private Options options = null; private TestResult runTest(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<TestBase> seleniumTestClass = (Class<TestBase>) Class.forName(className); return seleniumTestClass.newInstance().run(); } public static void main(String[] args) { CallSeleniumTest seTest = new CallSeleniumTest(); Option optionClass = new Option("c", "class", true, "full classname of test case (required) e.g. \"org.itrunner.tests.hosts.Baidu\""); Option optionTimeout = new Option("t", "timeout", true, "timeout, default is 30"); Option optionVerbose = new Option("v", "verbose", false, "show a lot of information (useful in case of problems)"); Option optionHelp = new Option("h", "help", false, "show this help screen"); seTest.options = new Options(); seTest.options.addOption(optionClass); seTest.options.addOption(optionTimeout); seTest.options.addOption(optionVerbose); seTest.options.addOption(optionHelp); CommandLineParser parser = new DefaultParser(); CommandLine cmd = null; String output = NAGIOS_TEXT_UNKNOWN + " - Upps"; int nagios = NAGIOS_UNKNOWN; try { cmd = parser.parse(seTest.options, args); // has to be checked manually, otherwise you can't access the help message without specifying correct parameters if (cmd.hasOption("h") || !cmd.hasOption("c")) { usage(seTest.options); System.exit(nagios); //NOSONAR } if (cmd.hasOption("t")) { timeout = Integer.parseInt(cmd.getOptionValue("t")); } TestResult result = seTest.runTest(cmd.getOptionValue("c")); output = NAGIOS_TEXT_OK + " - " + cmd.getOptionValue("c") + " Tests passed - " + result.toString(); nagios = NAGIOS_OK; } catch (ParseException e) { output = NAGIOS_TEXT_UNKNOWN + " - Parameter problems: " + e.getMessage(); nagios = NAGIOS_UNKNOWN; usage(seTest.options); } catch (ClassNotFoundException e) { output = NAGIOS_TEXT_UNKNOWN + " - Test case class: " + e.getMessage() + " not found!"; nagios = NAGIOS_UNKNOWN; } catch (TimeoutException | CriticalException e) { output = NAGIOS_TEXT_CRITICAL + " - Test Failures: " + e.getMessage(); nagios = NAGIOS_CRITICAL; } catch (Exception e) { output = NAGIOS_TEXT_WARNING + " - Test Failures: " + processException(cmd, e); nagios = NAGIOS_WARNING; } finally { println(output); System.exit(nagios); //NOSONAR } } private static String processException(CommandLine cmd, Exception e) { if (cmd != null && cmd.hasOption("v")) { e.printStackTrace(); //NOSONAR } if (isCausedBy(e, NotConnectedException.class)) { return "Failed to connect to binary FirefoxBinary"; } return e.getMessage(); } private static void usage(Options options) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("check_selenium", options); println("This version of check_selenium was tested with:"); println(" - selenium 3.14.0"); println(" - selenium ide 3.3.1"); println(" - test case exported as Java / JUnit 4 / WebDriver"); println("Some example calls:"); println(" ./check_selenium.sh -c \"org.itrunner.tests.hosts.Baidu\""); println(" ./check_selenium.sh --class \"org.itrunner.tests.hosts.Baidu\""); } public static int getTimeout() { return timeout; } private static void println(String x) { System.out.println(x); //NOSONAR } private static <T extends Throwable> boolean isCausedBy(final Throwable exception, Class<T> clazz) { Throwable cause = exception; while (cause != null) { if (clazz.isInstance(cause)) { return true; } cause = cause.getCause(); } return false; } }
check_selenium.shgit
#!/bin/bash JAVA_HOME=/opt/java/jdk1.8.0_191 $JAVA_HOME/bin/java -Djava.util.logging.config.file=logging.properties -jar $(dirname $0)/lib/nagios-selenium-1.0.jar $@
config.inigithub
# drive type - chrome, firefox or phantomjs driver.type=chrome log.file=chrome.log log.level=FATAL chrome.driver=/home/nagios/chrome/chromedriver gecko.driver=/home/nagios/gecko/geckodriver phantomjs.binary.path=/home/nagios/phantomjs/bin/phantomjs phantomjs.ghostdriver.path=/home/nagios/ghostdriver-master/src/main.js proxy.host= baidu.url=https://www.baidu.com baidu.username=xxxxxx@163.com baidu.password=xxxxxx
Configweb
import java.io.FileReader; import java.io.IOException; import java.util.Properties; public enum Config { CONFIG; private static final String FILE_NAME = "config.ini"; private Properties properties; Config() { properties = new Properties(); try { properties.load(new FileReader(CommonUtil.getJarPath() + FILE_NAME)); } catch (IOException e) { //NOSONAR e.printStackTrace(); //NOSONAR } } public String getDriverType() { return getProperty("driver.type"); } public String getLogFile() { return getProperty("log.file"); } public String getLogLevel() { return getProperty("log.level"); } public String getChromeDriver() { return getProperty("chrome.driver"); } public String getGeckoDriver() { return getProperty("gecko.driver"); } public String getPhantomJsBinaryPath() { return getProperty("phantomjs.binary.path"); } public String getPhantomJsGhostDriverPath() { return getProperty("phantomjs.ghostdriver.path"); } public String getProxyHost() { return getProperty("proxy.host"); } public String getBaiduUrl() { return getProperty("baidu.url"); } public String getBaiduUsername() { return getProperty("baidu.username"); } public String getBaiduPassword() { return getProperty("baidu.password"); } public String getProperty(String key) { return properties.getProperty(key); } }
在nagios中運行Selenium測試時,爲避免浪費資源,提升性能,通常不使用圖形界面。
Selenium支持的Headless Web Driver:chrome
輕量級的Web Driver實現,基於HtmlUnit,純Java、支持JavScript。HtmlUnit能夠模擬Chrome、Firefox或IE瀏覽器,默認爲Chrome。HtmlUnit使用了JavaScript引擎Rhino,主流瀏覽器沒有采用Rhino的,測試時可能會有JavaScript兼容性問題。shell
<dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>htmlunit-driver</artifactId> <version>2.33.1</version> </dependency>
public static WebDriver createHtmlUnitDriver() { HtmlUnitDriver htmlUnitDriver = new HtmlUnitDriver(true); if (hasProxy()) { htmlUnitDriver.setProxySettings(getProxy()); } return htmlUnitDriver; }
基於Headless Browser PhantomJS,對JavaScript的支持好,經常使用於網絡監控,但PhantomJS項目現已暫停,selenium 3.8.1之後再也不支持。
使用PhantomJSDriver時,需下載PhantomJS執行文件和源碼,配置「phantomjs.binary.path」和「phantomjs.ghostdriver.path」。apache
啓用Headless模式:
FirefoxOptions options = new FirefoxOptions(); options.setHeadless(true);
Selenium 3 FirefoxDriver提供兩種DriverService:GeckoDriverService和XpiDriverService,默認使用GeckoDriverService,需安裝firefox,下載geckodriver,配置GeckoDriver路徑。
FirefoxDriver與Nagios集成,若Driver沒有正常執行quit(),會形成大量進程不能終止,臨時目錄堆積大量profile文件。
啓用Headless模式:
ChromeOptions options = new ChromeOptions(); options.setHeadless(true);
使用ChromeDriver,需安裝Chrome,下載ChromeDriver,配置ChromeDriver路徑。
DriverFactory
建立ChromeDriver、FirefoxDriver或PhantomJSDriver。
package org.itrunner.tests; import org.openqa.selenium.Proxy; import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.firefox.FirefoxDriverLogLevel; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.firefox.GeckoDriverService; import org.openqa.selenium.phantomjs.PhantomJSDriver; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.service.DriverService; import java.io.File; import java.util.ArrayList; import java.util.List; import static org.itrunner.tests.utils.Config.CONFIG; import static org.openqa.selenium.phantomjs.PhantomJSDriverService.*; public class DriverFactory { private static DriverService service; private static RemoteWebDriver driver; private DriverFactory() { } public static RemoteWebDriver createDriver() { String driverType = CONFIG.getDriverType(); if (driverType.equals("phantomjs")) { return createPhantomJSDriver(); } if (driverType.equals("firefox")) { return createFirefoxDriver(); } return createChromeDriver(); } public static RemoteWebDriver createChromeDriver() { try { service = new ChromeDriverService.Builder() .usingDriverExecutable(new File(CONFIG.getChromeDriver())) .usingAnyFreePort() .withSilent(true) .withVerbose(false) .withLogFile(new File(CONFIG.getLogFile())) .build(); service.start(); ChromeOptions options = new ChromeOptions(); options.setHeadless(true); if (hasProxy()) { options.setProxy(getProxy()); } driver = new RemoteWebDriver(service.getUrl(), options); } catch (Exception e) { // do nothing } return driver; } public static RemoteWebDriver createFirefoxDriver() { try { service = new GeckoDriverService.Builder() .usingDriverExecutable(new File(CONFIG.getGeckoDriver())) .usingAnyFreePort() .withLogFile(new File(CONFIG.getLogFile())) .build(); service.start(); FirefoxOptions options = new FirefoxOptions(); options.setHeadless(true); options.setLogLevel(FirefoxDriverLogLevel.fromString(CONFIG.getLogLevel())); if (hasProxy()) { options.setProxy(getProxy()); } driver = new RemoteWebDriver(service.getUrl(), options); } catch (Exception e) { // do nothing } return driver; } public static RemoteWebDriver createPhantomJSDriver() { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setJavascriptEnabled(true); capabilities.setCapability(PHANTOMJS_EXECUTABLE_PATH_PROPERTY, CONFIG.getPhantomJsBinaryPath()); capabilities.setCapability(PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY, CONFIG.getPhantomJsGhostDriverPath()); List<String> cliArgs = new ArrayList<>(); cliArgs.add("--web-security=false"); cliArgs.add("--ssl-protocol=any"); cliArgs.add("--ignore-ssl-errors=true"); capabilities.setCapability(PHANTOMJS_CLI_ARGS, cliArgs); // Control LogLevel for GhostDriver, via CLI arguments String[] ghostDriverCliArgs = {"--logFile=" + CONFIG.getLogFile(), "--logLevel=" + CONFIG.getLogLevel()}; capabilities.setCapability(PHANTOMJS_GHOSTDRIVER_CLI_ARGS, ghostDriverCliArgs); if (hasProxy()) { capabilities.setCapability("proxy", getProxy()); } driver = new PhantomJSDriver(capabilities); return driver; } public static void quit() { if (driver != null) { driver.quit(); } if (service != null) { service.stop(); } driver = null; service = null; } private static Proxy getProxy() { Proxy proxy = new Proxy(); proxy.setHttpProxy(CONFIG.getProxyHost()); return proxy; } private static boolean hasProxy() { return CONFIG.getProxyHost() != null && !CONFIG.getProxyHost().equals(""); } }
說明:
TestBase
測試基類
package org.itrunner.tests; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import static java.lang.System.currentTimeMillis; import static org.openqa.selenium.OutputType.BYTES; import static org.openqa.selenium.support.ui.ExpectedConditions.titleIs; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated; public abstract class TestBase { protected RemoteWebDriver driver; private long startTime; private TestResult result = new TestResult(); @Before public void setup() { startTime = currentTimeMillis(); nextStep(this::createDriver, "init"); setTimeout(); addShutdownHook(); } TestResult run() { setup(); test(); tearDown(); setTotalTime(); return result; } @After public void tearDown() { nextStep(() -> DriverFactory.quit(), "destroy"); } private void createDriver() { driver = DriverFactory.createDriver(); } public abstract void test(); protected void savePageSource(String host) { try { File sourceFile = new File("reports/" + host + ".html"); FileUtils.writeStringToFile(sourceFile, driver.getPageSource(), Charset.defaultCharset()); } catch (IOException e) { //NOSONAR // do nothing } } protected void takeScreenshot(String host) { File imageFile = new File("reports/" + host + ".png"); try { FileUtils.writeByteArrayToFile(imageFile, driver.getScreenshotAs(BYTES)); } catch (IOException e) { //NOSONAR // do nothing } } protected void nextStep(TestStep step, String stepName) { long beginTime = currentTimeMillis(); try { step.run(); } catch (TimeoutException | NoSuchElementException e) { throw new CriticalException(stepName + " failed: " + e.getMessage(), e); } result.putStepTime(stepName, currentTimeMillis() - beginTime); } public boolean isElementPresent(By by) { try { return waitForElementPresent(by); } catch (TimeoutException e) { //NOSONAR return false; } } protected void open(final String url) { nextStep(() -> driver.get(url), "loading page"); } public void click(final By by, String stepName) { nextStep(() -> driver.findElement(by).click(), stepName); } protected void waitForTitlePresent(final String title) { nextStep(() -> waitForCondition(titleIs(title)), "finding title"); } private boolean waitForElementPresent(By by) { return waitForCondition(visibilityOfElementLocated(by)).isDisplayed(); } private <T> T waitForCondition(ExpectedCondition<T> condition) { return (new WebDriverWait(driver, CallSeleniumTest.getTimeout())).until(condition); } private void setTimeout() { driver.manage().timeouts().implicitlyWait(CallSeleniumTest.getTimeout(), TimeUnit.SECONDS); } private void setTotalTime() { result.setTotalTime(currentTimeMillis() - startTime); } private void addShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread("Selenium Quit Hook") { @Override public void run() { DriverFactory.quit(); } }); } }
示例代碼記錄了每步執行的時間。
網站測試
package org.itrunner.tests.hosts; import org.itrunner.tests.TestBase; import org.junit.Test; import org.openqa.selenium.By; import static org.itrunner.tests.utils.Config.CONFIG; public class Baidu extends TestBase { @Override @Test public void test() { open(CONFIG.getBaiduUrl()); waitForTitlePresent("百度一下,你就知道"); nextStep(() -> { driver.findElement(By.linkText("登陸")).click(); driver.findElement(By.id("TANGRAM__PSP_10__footerULoginBtn")).click(); driver.findElement(By.id("TANGRAM__PSP_10__userName")).sendKeys(CONFIG.getBaiduUsername()); driver.findElement(By.id("TANGRAM__PSP_10__password")).sendKeys(CONFIG.getBaiduPassword()); driver.findElement(By.id("TANGRAM__PSP_10__submit")).click(); }, "login"); } }
在nagios中運行是不須要selenium日誌的,將其關閉。
logging.properties
org.openqa.selenium.level=OFF
使用maven assembly打包,爲便於隨時調整配置,將配置文件放在Jar外。
<assembly> <id>bin</id> <formats> <!--<format>zip</format>--> <format>dir</format> </formats> <baseDirectory>libexec</baseDirectory> <dependencySets> <dependencySet> <useProjectArtifact>true</useProjectArtifact> <outputDirectory>lib</outputDirectory> </dependencySet> </dependencySets> <fileSets> <fileSet> <directory>src/main/resources</directory> <outputDirectory>.</outputDirectory> <includes> <include>check_selenium.sh</include> </includes> </fileSet> <fileSet> <directory>target/classes</directory> <outputDirectory>lib</outputDirectory> <includes> <include>config.ini</include> </includes> </fileSet> </fileSets> </assembly>
打包後的結構以下:
部署時將libexec目錄下的內容拷貝到nagios/libexec目錄便可。
define command { command_name check_selenium command_line $USER1$/check_selenium.sh -c $ARG1$ }
define service { service_description selenium_baidu use service-check-05min host_name www.baidu.com check_command check_selenium!org.itrunner.tests.hosts.Baidu }
Error: Package: perl-Net-SNMP-6.0.1-7.el7.noarch (epel) Requires: perl(Crypt::DES)
在RHEL 7安裝perl-Net-SNMP時,報這個錯誤,請先到rpmfind下載安裝perl-Crypt-DES-2.05-20.el7.x86_64.rpm
CHECK_NRPE: Received 0 bytes from daemon. Check the remote server logs for error messages
Install nrpe:
./configure --enable-command-args
NRPE installation error: configure: error: Cannot find ssl headers
yum install openssl-devel
建立文件/etc/yum.repos.d/google-chrome.repo,內容以下:
[google-chrome] name=google-chrome baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
$ wget https://dl.google.com/linux/linux_signing_key.pub $ sudo rpm --import linux_signing_key.pub
$ sudo yum install google-chrome-stable
Nagios - The Industry Standard In IT Infrastructure Monitoring
Nagios Core
Nagios Core Quickstart Installation Guides
Nagios NRPE Documentation
Zabbix - The Enterprise-Class Open Source Network Monitoring Solution
SeleniumHQ
HtmlUnit
geckodriver
PhantomJS