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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.io.File;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.PrintWriter;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.fs.FileSystem;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.hbase.HealthChecker.HealthCheckerExitStatus;
032import org.apache.hadoop.hbase.testclassification.MiscTests;
033import org.apache.hadoop.hbase.testclassification.SmallTests;
034import org.apache.hadoop.util.Shell;
035import org.junit.jupiter.api.AfterEach;
036import org.junit.jupiter.api.Tag;
037import org.junit.jupiter.api.Test;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041@Tag(MiscTests.TAG)
042@Tag(SmallTests.TAG)
043public class TestNodeHealthCheckChore {
044
045  private static final Logger LOG = LoggerFactory.getLogger(TestNodeHealthCheckChore.class);
046  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
047  private static final int SCRIPT_TIMEOUT = 5000;
048  private File healthScriptFile;
049  private String eol = System.getProperty("line.separator");
050
051  @AfterEach
052  public void cleanUp() throws IOException {
053    // delete and recreate the test directory, ensuring a clean test dir between tests
054    Path testDir = UTIL.getDataTestDir();
055    FileSystem fs = UTIL.getTestFileSystem();
056    fs.delete(testDir, true);
057    if (!fs.mkdirs(testDir)) throw new IOException("Failed mkdir " + testDir);
058  }
059
060  @Test
061  public void testHealthCheckerSuccess() throws Exception {
062    String normalScript = "echo \"I am all fine\"";
063    healthCheckerTest(normalScript, HealthCheckerExitStatus.SUCCESS);
064  }
065
066  @Test
067  public void testHealthCheckerFail() throws Exception {
068    String errorScript = "echo ERROR" + eol + "echo \"Node not healthy\"";
069    healthCheckerTest(errorScript, HealthCheckerExitStatus.FAILED);
070  }
071
072  @Test
073  public void testHealthCheckerTimeout() throws Exception {
074    String timeOutScript = "sleep 10" + eol + "echo \"I am fine\"";
075    healthCheckerTest(timeOutScript, HealthCheckerExitStatus.TIMED_OUT);
076  }
077
078  public void healthCheckerTest(String script, HealthCheckerExitStatus expectedStatus)
079    throws Exception {
080    Configuration config = getConfForNodeHealthScript();
081    config.addResource(healthScriptFile.getName());
082    String location = healthScriptFile.getAbsolutePath();
083    long timeout = config.getLong(HConstants.HEALTH_SCRIPT_TIMEOUT, SCRIPT_TIMEOUT);
084
085    HealthChecker checker = new HealthChecker();
086    checker.init(location, timeout);
087
088    createScript(script, true);
089    HealthReport report = checker.checkHealth();
090    assertEquals(expectedStatus, report.getStatus());
091
092    LOG.info("Health Status:" + report.getHealthReport());
093
094    this.healthScriptFile.delete();
095  }
096
097  @Test
098  public void testRSHealthChore() throws Exception {
099    Stoppable stop = new StoppableImplementation();
100    Configuration conf = getConfForNodeHealthScript();
101    String errorScript = "echo ERROR" + eol + " echo \"Server not healthy\"";
102    createScript(errorScript, true);
103    HealthCheckChore rsChore = new HealthCheckChore(100, stop, conf);
104    try {
105      // Default threshold is three.
106      rsChore.chore();
107      rsChore.chore();
108      assertFalse(stop.isStopped(), "Stoppable must not be stopped.");
109      rsChore.chore();
110      assertTrue(stop.isStopped(), "Stoppable must have been stopped.");
111    } finally {
112      stop.stop("Finished w/ test");
113    }
114  }
115
116  private void createScript(String scriptStr, boolean setExecutable) throws Exception {
117    if (!this.healthScriptFile.exists()) {
118      if (!healthScriptFile.createNewFile()) {
119        throw new IOException("Failed create of " + this.healthScriptFile);
120      }
121    }
122    PrintWriter pw = new PrintWriter(new FileOutputStream(healthScriptFile));
123    try {
124      pw.println(scriptStr);
125      pw.flush();
126    } finally {
127      pw.close();
128    }
129    healthScriptFile.setExecutable(setExecutable);
130    LOG.info("Created " + this.healthScriptFile + ", executable=" + setExecutable);
131  }
132
133  private Configuration getConfForNodeHealthScript() throws IOException {
134    Configuration conf = UTIL.getConfiguration();
135    File tempDir = new File(UTIL.getDataTestDir().toString());
136    if (!tempDir.exists()) {
137      if (!tempDir.mkdirs()) {
138        throw new IOException("Failed mkdirs " + tempDir);
139      }
140    }
141    String scriptName =
142      "HealthScript" + UTIL.getRandomUUID().toString() + (Shell.WINDOWS ? ".cmd" : ".sh");
143    healthScriptFile = new File(tempDir.getAbsolutePath(), scriptName);
144    conf.set(HConstants.HEALTH_SCRIPT_LOC, healthScriptFile.getAbsolutePath());
145    conf.setLong(HConstants.HEALTH_FAILURE_THRESHOLD, 3);
146    conf.setLong(HConstants.HEALTH_SCRIPT_TIMEOUT, SCRIPT_TIMEOUT);
147    return conf;
148  }
149
150  /**
151   * Simple helper class that just keeps track of whether or not its stopped.
152   */
153  private static class StoppableImplementation implements Stoppable {
154    private volatile boolean stop = false;
155
156    @Override
157    public void stop(String why) {
158      this.stop = true;
159    }
160
161    @Override
162    public boolean isStopped() {
163      return this.stop;
164    }
165
166  }
167}