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 */
018
019package org.apache.hadoop.hbase;
020
021import java.io.BufferedReader;
022import java.io.BufferedWriter;
023import java.io.File;
024import java.io.FileNotFoundException;
025import java.io.FileReader;
026import java.io.FileWriter;
027import java.io.IOException;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer;
030import org.apache.hadoop.hbase.zookeeper.MasterAddressTracker;
031import org.apache.hadoop.hbase.zookeeper.ZKUtil;
032import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
033import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
034import org.apache.yetus.audience.InterfaceAudience;
035import org.apache.zookeeper.KeeperException;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * <p>Contains a set of methods for the collaboration between the start/stop scripts and the
041 * servers. It allows to delete immediately the znode when the master or the regions server crashes.
042 * The region server / master writes a specific file when it starts / becomes main master. When they
043 * end properly, they delete the file.</p>
044 * <p>In the script, we check for the existence of these files when the program ends. If they still
045 * exist we conclude that the server crashed, likely without deleting their znode. To have a faster
046 * recovery we delete immediately the znode.</p>
047 * <p>The strategy depends on the server type. For a region server we store the znode path in the
048 * file, and use it to delete it. for a master, as the znode path constant whatever the server, we
049 * check its content to make sure that the backup server is not now in charge.</p>
050 */
051@InterfaceAudience.Private
052public final class ZNodeClearer {
053  private static final Logger LOG = LoggerFactory.getLogger(ZNodeClearer.class);
054
055  private ZNodeClearer() {}
056
057  /**
058   * Logs the errors without failing on exception.
059   */
060  public static void writeMyEphemeralNodeOnDisk(String fileContent) {
061    String fileName = ZNodeClearer.getMyEphemeralNodeFileName();
062    if (fileName == null) {
063      LOG.warn("Environment variable HBASE_ZNODE_FILE not set; znodes will not be cleared " +
064          "on crash by start scripts (Longer MTTR!)");
065      return;
066    }
067
068    FileWriter fstream;
069    try {
070      fstream = new FileWriter(fileName);
071    } catch (IOException e) {
072      LOG.warn("Can't write znode file "+fileName, e);
073      return;
074    }
075
076    BufferedWriter out = new BufferedWriter(fstream);
077
078    try {
079      try {
080        out.write(fileContent + "\n");
081      } finally {
082        try {
083          out.close();
084        } finally {
085          fstream.close();
086        }
087      }
088    } catch (IOException e) {
089      LOG.warn("Can't write znode file "+fileName, e);
090    }
091  }
092
093  /**
094   * read the content of znode file, expects a single line.
095   */
096  public static String readMyEphemeralNodeOnDisk() throws IOException {
097    String fileName = getMyEphemeralNodeFileName();
098    if (fileName == null){
099      throw new FileNotFoundException("No filename; set environment variable HBASE_ZNODE_FILE");
100    }
101    FileReader znodeFile = new FileReader(fileName);
102    BufferedReader br = null;
103    try {
104      br = new BufferedReader(znodeFile);
105      String file_content = br.readLine();
106      return file_content;
107    } finally {
108      if (br != null) br.close();
109    }
110  }
111
112  /**
113   * Get the name of the file used to store the znode contents
114   */
115  public static String getMyEphemeralNodeFileName() {
116    return System.getenv().get("HBASE_ZNODE_FILE");
117  }
118
119  /**
120   *  delete the znode file
121   */
122  public static void deleteMyEphemeralNodeOnDisk() {
123    String fileName = getMyEphemeralNodeFileName();
124
125    if (fileName != null) {
126      new File(fileName).delete();
127    }
128  }
129
130  /**
131   * See HBASE-14861. We are extracting master ServerName from rsZnodePath
132   * example: "/hbase/rs/server.example.com,16020,1448266496481"
133   * @param rsZnodePath from HBASE_ZNODE_FILE
134   * @return String representation of ServerName or null if fails
135   */
136
137  public static String parseMasterServerName(String rsZnodePath) {
138    String masterServerName = null;
139    try {
140      String[] rsZnodeParts = rsZnodePath.split("/");
141      masterServerName = rsZnodeParts[rsZnodeParts.length -1];
142    } catch (IndexOutOfBoundsException e) {
143      LOG.warn("String " + rsZnodePath + " has wrong format", e);
144    }
145    return masterServerName;
146  }
147
148  /**
149   *
150   * @return true if cluster is configured with master-rs collocation
151   */
152  private static boolean tablesOnMaster(Configuration conf) {
153    boolean tablesOnMaster = true;
154    String confValue = conf.get(BaseLoadBalancer.TABLES_ON_MASTER);
155    if (confValue != null && confValue.equalsIgnoreCase("none")) {
156      tablesOnMaster = false;
157    }
158    return tablesOnMaster;
159  }
160
161  /**
162   * Delete the master znode if its content (ServerName string) is the same
163   *  as the one in the znode file. (env: HBASE_ZNODE_FILE). I case of master-rs
164   *  colloaction we extract ServerName string from rsZnode path.(HBASE-14861)
165   * @return true on successful deletion, false otherwise.
166   */
167  public static boolean clear(Configuration conf) {
168    Configuration tempConf = new Configuration(conf);
169    tempConf.setInt("zookeeper.recovery.retry", 0);
170
171    ZKWatcher zkw;
172    try {
173      zkw = new ZKWatcher(tempConf, "clean znode for master",
174          new Abortable() {
175            @Override public void abort(String why, Throwable e) {}
176            @Override public boolean isAborted() { return false; }
177          });
178    } catch (IOException e) {
179      LOG.warn("Can't connect to zookeeper to read the master znode", e);
180      return false;
181    }
182
183    String znodeFileContent;
184    try {
185      znodeFileContent = ZNodeClearer.readMyEphemeralNodeOnDisk();
186      if (ZNodeClearer.tablesOnMaster(conf)) {
187        // In case of master crash also remove rsZnode since master is also regionserver
188        ZKUtil.deleteNodeFailSilent(zkw,
189          ZNodePaths.joinZNode(zkw.getZNodePaths().rsZNode, znodeFileContent));
190        return MasterAddressTracker.deleteIfEquals(zkw,
191          ZNodeClearer.parseMasterServerName(znodeFileContent));
192      } else {
193        return MasterAddressTracker.deleteIfEquals(zkw, znodeFileContent);
194      }
195    } catch (FileNotFoundException fnfe) {
196      // If no file, just keep going -- return success.
197      LOG.warn("Can't find the znode file; presume non-fatal", fnfe);
198      return true;
199    } catch (IOException e) {
200      LOG.warn("Can't read the content of the znode file", e);
201      return false;
202    } catch (KeeperException e) {
203      LOG.warn("ZooKeeper exception deleting znode", e);
204      return false;
205    } finally {
206      zkw.close();
207    }
208  }
209}