001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.io.File;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.PrintWriter;
028import java.util.UUID;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.hbase.HealthChecker.HealthCheckerExitStatus;
033import org.apache.hadoop.hbase.testclassification.MiscTests;
034import org.apache.hadoop.hbase.testclassification.SmallTests;
035import org.apache.hadoop.util.Shell;
036import org.junit.After;
037import org.junit.ClassRule;
038import org.junit.Test;
039import org.junit.experimental.categories.Category;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043@Category({MiscTests.class, SmallTests.class})
044public class TestNodeHealthCheckChore {
045
046  @ClassRule
047  public static final HBaseClassTestRule CLASS_RULE =
048      HBaseClassTestRule.forClass(TestNodeHealthCheckChore.class);
049
050  private static final Logger LOG = LoggerFactory.getLogger(TestNodeHealthCheckChore.class);
051  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
052  private static final int SCRIPT_TIMEOUT = 5000;
053  private File healthScriptFile;
054  private String eol = System.getProperty("line.separator");
055
056  @After
057  public void cleanUp() throws IOException {
058    // delete and recreate the test directory, ensuring a clean test dir between tests
059    Path testDir = UTIL.getDataTestDir();
060    FileSystem fs = UTIL.getTestFileSystem();
061    fs.delete(testDir, true);
062    if (!fs.mkdirs(testDir)) throw new IOException("Failed mkdir " + testDir);
063  }
064
065  @Test
066  public void testHealthCheckerSuccess() throws Exception {
067    String normalScript = "echo \"I am all fine\"";
068    healthCheckerTest(normalScript, HealthCheckerExitStatus.SUCCESS);
069  }
070
071  @Test
072  public void testHealthCheckerFail() throws Exception {
073    String errorScript = "echo ERROR" + eol + "echo \"Node not healthy\"";
074    healthCheckerTest(errorScript, HealthCheckerExitStatus.FAILED);
075  }
076
077  @Test
078  public void testHealthCheckerTimeout() throws Exception {
079    String timeOutScript = "sleep 10" + eol + "echo \"I am fine\"";
080    healthCheckerTest(timeOutScript, HealthCheckerExitStatus.TIMED_OUT);
081  }
082
083  public void healthCheckerTest(String script, HealthCheckerExitStatus expectedStatus)
084      throws Exception {
085    Configuration config = getConfForNodeHealthScript();
086    config.addResource(healthScriptFile.getName());
087    String location = healthScriptFile.getAbsolutePath();
088    long timeout = config.getLong(HConstants.HEALTH_SCRIPT_TIMEOUT, SCRIPT_TIMEOUT);
089
090    HealthChecker checker = new HealthChecker();
091    checker.init(location, timeout);
092
093    createScript(script, true);
094    HealthReport report = checker.checkHealth();
095    assertEquals(expectedStatus, report.getStatus());
096
097    LOG.info("Health Status:" + report.getHealthReport());
098
099    this.healthScriptFile.delete();
100  }
101
102  @Test
103  public void testRSHealthChore() throws Exception{
104    Stoppable stop = new StoppableImplementation();
105    Configuration conf = getConfForNodeHealthScript();
106    String errorScript = "echo ERROR" + eol + " echo \"Server not healthy\"";
107    createScript(errorScript, true);
108    HealthCheckChore rsChore = new HealthCheckChore(100, stop, conf);
109    try {
110      //Default threshold is three.
111      rsChore.chore();
112      rsChore.chore();
113      assertFalse("Stoppable must not be stopped.", stop.isStopped());
114      rsChore.chore();
115      assertTrue("Stoppable must have been stopped.", stop.isStopped());
116    } finally {
117      stop.stop("Finished w/ test");
118    }
119  }
120
121  private void createScript(String scriptStr, boolean setExecutable)
122      throws Exception {
123    if (!this.healthScriptFile.exists()) {
124      if (!healthScriptFile.createNewFile()) {
125        throw new IOException("Failed create of " + this.healthScriptFile);
126      }
127    }
128    PrintWriter pw = new PrintWriter(new FileOutputStream(healthScriptFile));
129    try {
130      pw.println(scriptStr);
131      pw.flush();
132    } finally {
133      pw.close();
134    }
135    healthScriptFile.setExecutable(setExecutable);
136    LOG.info("Created " + this.healthScriptFile + ", executable=" + setExecutable);
137  }
138
139  private Configuration getConfForNodeHealthScript() throws IOException {
140    Configuration conf = UTIL.getConfiguration();
141    File tempDir = new File(UTIL.getDataTestDir().toString());
142    if (!tempDir.exists()) {
143      if (!tempDir.mkdirs()) {
144        throw new IOException("Failed mkdirs " + tempDir);
145      }
146    }
147    String scriptName = "HealthScript" + UUID.randomUUID().toString()
148        + (Shell.WINDOWS ? ".cmd" : ".sh");
149    healthScriptFile = new File(tempDir.getAbsolutePath(), scriptName);
150    conf.set(HConstants.HEALTH_SCRIPT_LOC, healthScriptFile.getAbsolutePath());
151    conf.setLong(HConstants.HEALTH_FAILURE_THRESHOLD, 3);
152    conf.setLong(HConstants.HEALTH_SCRIPT_TIMEOUT, SCRIPT_TIMEOUT);
153    return conf;
154  }
155
156  /**
157   * Simple helper class that just keeps track of whether or not its stopped.
158   */
159  private static class StoppableImplementation implements Stoppable {
160    private volatile boolean stop = false;
161
162    @Override
163    public void stop(String why) {
164      this.stop = true;
165    }
166
167    @Override
168    public boolean isStopped() {
169      return this.stop;
170    }
171
172  }
173}