/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.test; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.testng.Assert; /** * Repeatedly polls a given URL, to check if it is always available. * * @author Alex, Aled */ public class WebAppMonitor implements Runnable { final AtomicBoolean shouldBeActive = new AtomicBoolean(true); final AtomicBoolean isActive = new AtomicBoolean(false); final AtomicInteger successes = new AtomicInteger(0); final AtomicInteger failures = new AtomicInteger(0); final AtomicLong lastTime = new AtomicLong(-1); final AtomicReference<Object> lastStatus = new AtomicReference<Object>(null); final AtomicReference<Object> lastFailure = new AtomicReference<Object>(null); Logger log; Object problem = null; String url; long delayMillis = 500; int expectedResponseCode = 200; public WebAppMonitor(String url) { this.url = url; } public WebAppMonitor() { } public WebAppMonitor logFailures(Logger log) { this.log = log; return this; } public WebAppMonitor delayMillis(long val) { this.delayMillis = val; return this; } public WebAppMonitor expectedResponseCode(int val) { this.expectedResponseCode = val; return this; } public WebAppMonitor url(String val) { this.url = val; return this; } public void run() { synchronized (isActive) { if (isActive.getAndSet(true)) throw new IllegalStateException("already running"); } while (shouldBeActive.get()) { long startTime = System.currentTimeMillis(); try { if (preAttempt()) { int code = HttpTestUtils.getHttpStatusCode(url); lastTime.set(System.currentTimeMillis() - startTime); lastStatus.set(code); if (isResponseOkay(code)) { successes.incrementAndGet(); } else { lastFailure.set(code); failures.incrementAndGet(); onFailure("return code "+code); } } } catch (Exception e) { lastTime.set(System.currentTimeMillis()-startTime); lastStatus.set(e); lastFailure.set(e); failures.incrementAndGet(); onFailure(e); } try { if (delayMillis > 0) { Thread.sleep(delayMillis); } } catch (InterruptedException e) { onFailure(e); shouldBeActive.set(false); } } synchronized (isActive) { if (!isActive.getAndSet(false)) throw new IllegalStateException("shouldn't be possible!"); isActive.notifyAll(); } } public boolean isResponseOkay(Object code) { return code!=null && new Integer(expectedResponseCode).equals(code); } public void setDelayMillis(long delayMillis) { this.delayMillis = delayMillis; } public long getDelayMillis() { return delayMillis; } public void terminate() throws InterruptedException { shouldBeActive.set(false); synchronized (isActive) { while (isActive.get()) isActive.wait(); } } public int getFailures() { return failures.get(); } public int getSuccesses() { return successes.get(); } public void setUrl(String url) { this.url = url; } public String getUrl() { return url; } public Object getProblem() { return problem; } public int getAttempts() { return getFailures()+getSuccesses(); } public boolean getLastWasFailed() { return isResponseOkay(getLastStatus()); } public Object getLastStatus() { return lastStatus.get(); } public long getLastTime() { return lastTime.get(); } /** result code (int) or exception */ public Object getLastFailure() { return lastFailure.get(); } public void onFailure(Object problem) { if (log != null) { log.warn("Detected failure in monitor accessing "+getUrl()+": "+problem); } this.problem = problem; } /** return false to skip a run */ public boolean preAttempt() { return true; } public WebAppMonitor assertNoFailures(String message) { return assertSuccessFraction(message, 1.0); } public WebAppMonitor assertAttemptsMade(int minAttempts, String message) { if (getAttempts()<minAttempts) { Assert.fail(message+" -- webapp access failures! " + "(0 attempts made; probably blocked on server)"); } return this; } public WebAppMonitor waitForAtLeastOneAttempt() { return waitForAtLeastOneAttempt(Asserts.DEFAULT_LONG_TIMEOUT); } public WebAppMonitor waitForAtLeastOneAttempt(Duration timeout) { Asserts.succeedsEventually(MutableMap.of("timeout", timeout), new Runnable() { @Override public void run() { Assert.assertTrue(getAttempts() >= 1); }}); return this; } public WebAppMonitor assertSuccessFraction(String message, double fraction) { int failures = getFailures(); int attempts = getAttempts(); if ((failures > (1-fraction) * attempts + 0.0001) || attempts <= 0) { Assert.fail(message+" -- webapp access failures! " + "("+failures+" failed of "+attempts+" monitoring attempts) against "+getUrl()+"; " + "last was "+getLastStatus()+" taking "+getLastTime()+"ms" + (getLastFailure() != null ? "; last failure was "+getLastFailure() : "")); } return this; } public WebAppMonitor resetCounts() { failures.set(0); successes.set(0); return this; } }