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.zookeeper;
019
020import com.google.errorprone.annotations.RestrictedApi;
021import org.apache.hadoop.hbase.HConstants;
022import org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException;
023import org.apache.hadoop.hbase.ServerName;
024import org.apache.hadoop.hbase.client.RegionInfo;
025import org.apache.hadoop.hbase.exceptions.DeserializationException;
026import org.apache.hadoop.hbase.master.RegionState;
027import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.apache.zookeeper.KeeperException;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
034import org.apache.hadoop.hbase.shaded.protobuf.generated.ZooKeeperProtos.MetaRegionServer;
035
036/**
037 * Utility class to perform operation (get/wait for/verify/set/delete) on znode in ZooKeeper which
038 * keeps hbase:meta region server location.
039 * <p/>
040 * Stateless class with a bunch of static methods. Doesn't manage resources passed in (e.g.
041 * Connection, ZKWatcher etc).
042 * <p/>
043 * Meta region location is set by <code>RegionServerServices</code>. This class doesn't use ZK
044 * watchers, rather accesses ZK directly.
045 * <p/>
046 * TODO: rewrite using RPC calls to master to find out about hbase:meta.
047 */
048@InterfaceAudience.Private
049public final class MetaTableLocator {
050  private static final Logger LOG = LoggerFactory.getLogger(MetaTableLocator.class);
051
052  private MetaTableLocator() {
053  }
054
055  /**
056   * Gets the meta region location, if available. Does not block.
057   * @param zkw zookeeper connection to use
058   * @return server name or null if we failed to get the data.
059   */
060  @RestrictedApi(explanation = "Should only be called in tests or ZKUtil", link = "",
061      allowedOnPath = ".*/src/test/.*|.*/ZKDump\\.java")
062  public static ServerName getMetaRegionLocation(final ZKWatcher zkw) {
063    try {
064      RegionState state = getMetaRegionState(zkw);
065      return state.isOpened() ? state.getServerName() : null;
066    } catch (KeeperException ke) {
067      return null;
068    }
069  }
070
071  /**
072   * Gets the meta region location, if available. Does not block.
073   * @param zkw       reference to the {@link ZKWatcher} which also contains configuration and
074   *                  operation
075   * @param replicaId the ID of the replica
076   * @return server name
077   */
078  @RestrictedApi(explanation = "Should only be called in self or ZKUtil", link = "",
079      allowedOnPath = ".*(MetaTableLocator|ZKDump)\\.java")
080  public static ServerName getMetaRegionLocation(final ZKWatcher zkw, int replicaId) {
081    try {
082      RegionState state = getMetaRegionState(zkw, replicaId);
083      return state.isOpened() ? state.getServerName() : null;
084    } catch (KeeperException ke) {
085      return null;
086    }
087  }
088
089  /**
090   * Gets the meta region location, if available, and waits for up to the specified timeout if not
091   * immediately available. Given the zookeeper notification could be delayed, we will try to get
092   * the latest data.
093   * @param zkw     reference to the {@link ZKWatcher} which also contains configuration and
094   *                operation
095   * @param timeout maximum time to wait, in millis
096   * @return server name for server hosting meta region formatted as per {@link ServerName}, or null
097   *         if none available
098   * @throws InterruptedException             if interrupted while waiting
099   * @throws NotAllMetaRegionsOnlineException if a meta or root region is not online
100   */
101  @RestrictedApi(explanation = "Should only be called in tests", link = "",
102      allowedOnPath = ".*/src/test/.*")
103  public static ServerName waitMetaRegionLocation(ZKWatcher zkw, long timeout)
104    throws InterruptedException, NotAllMetaRegionsOnlineException {
105    return waitMetaRegionLocation(zkw, RegionInfo.DEFAULT_REPLICA_ID, timeout);
106  }
107
108  /**
109   * Gets the meta region location, if available, and waits for up to the specified timeout if not
110   * immediately available. Given the zookeeper notification could be delayed, we will try to get
111   * the latest data.
112   * @param zkw       reference to the {@link ZKWatcher} which also contains configuration and
113   *                  operation
114   * @param replicaId the ID of the replica
115   * @param timeout   maximum time to wait, in millis
116   * @return server name for server hosting meta region formatted as per {@link ServerName}, or null
117   *         if none available
118   * @throws InterruptedException             if waiting for the socket operation fails
119   * @throws NotAllMetaRegionsOnlineException if a meta or root region is not online
120   */
121  private static ServerName waitMetaRegionLocation(ZKWatcher zkw, int replicaId, long timeout)
122    throws InterruptedException, NotAllMetaRegionsOnlineException {
123    try {
124      if (ZKUtil.checkExists(zkw, zkw.getZNodePaths().baseZNode) == -1) {
125        String errorMsg = "Check the value configured in 'zookeeper.znode.parent'. "
126          + "There could be a mismatch with the one configured in the master.";
127        LOG.error(errorMsg);
128        throw new IllegalArgumentException(errorMsg);
129      }
130    } catch (KeeperException e) {
131      throw new IllegalStateException("KeeperException while trying to check baseZNode:", e);
132    }
133    ServerName sn = blockUntilAvailable(zkw, replicaId, timeout);
134
135    if (sn == null) {
136      throw new NotAllMetaRegionsOnlineException("Timed out; " + timeout + "ms");
137    }
138
139    return sn;
140  }
141
142  /**
143   * Sets the location of <code>hbase:meta</code> in ZooKeeper to the specified server address.
144   * @param zookeeper  zookeeper reference
145   * @param serverName The server hosting <code>hbase:meta</code>
146   * @param state      The region transition state
147   * @throws KeeperException unexpected zookeeper exception
148   */
149  @RestrictedApi(explanation = "Should only be called in tests", link = "",
150      allowedOnPath = ".*/src/test/.*")
151  public static void setMetaLocation(ZKWatcher zookeeper, ServerName serverName,
152    RegionState.State state) throws KeeperException {
153    setMetaLocation(zookeeper, serverName, RegionInfo.DEFAULT_REPLICA_ID, state);
154  }
155
156  /**
157   * Sets the location of <code>hbase:meta</code> in ZooKeeper to the specified server address.
158   * @param zookeeper  reference to the {@link ZKWatcher} which also contains configuration and
159   *                   operation
160   * @param serverName the name of the server
161   * @param replicaId  the ID of the replica
162   * @param state      the state of the region
163   * @throws KeeperException if a ZooKeeper operation fails
164   */
165  public static void setMetaLocation(ZKWatcher zookeeper, ServerName serverName, int replicaId,
166    RegionState.State state) throws KeeperException {
167    if (serverName == null) {
168      LOG.warn("Tried to set null ServerName in hbase:meta; skipping -- ServerName required");
169      return;
170    }
171    LOG.info("Setting hbase:meta replicaId={} location in ZooKeeper as {}, state={}", replicaId,
172      serverName, state);
173    // Make the MetaRegionServer pb and then get its bytes and save this as
174    // the znode content.
175    MetaRegionServer pbrsr =
176      MetaRegionServer.newBuilder().setServer(ProtobufUtil.toServerName(serverName))
177        .setRpcVersion(HConstants.RPC_CURRENT_VERSION).setState(state.convert()).build();
178    byte[] data = ProtobufUtil.prependPBMagic(pbrsr.toByteArray());
179    try {
180      ZKUtil.setData(zookeeper, zookeeper.getZNodePaths().getZNodeForReplica(replicaId), data);
181    } catch (KeeperException.NoNodeException nne) {
182      if (replicaId == RegionInfo.DEFAULT_REPLICA_ID) {
183        LOG.debug("hbase:meta region location doesn't exist, create it");
184      } else {
185        LOG.debug(
186          "hbase:meta region location doesn't exist for replicaId=" + replicaId + ", create it");
187      }
188      ZKUtil.createAndWatch(zookeeper, zookeeper.getZNodePaths().getZNodeForReplica(replicaId),
189        data);
190    }
191  }
192
193  /**
194   * Load the meta region state from the meta server ZNode.
195   */
196  @RestrictedApi(explanation = "Should only be called in self or tests", link = "",
197      allowedOnPath = ".*/src/test/.*|.*/MetaTableLocator\\.java")
198  public static RegionState getMetaRegionState(ZKWatcher zkw) throws KeeperException {
199    return getMetaRegionState(zkw, RegionInfo.DEFAULT_REPLICA_ID);
200  }
201
202  /**
203   * Load the meta region state from the meta region server ZNode.
204   * @param zkw       reference to the {@link ZKWatcher} which also contains configuration and
205   *                  operation
206   * @param replicaId the ID of the replica
207   * @throws KeeperException if a ZooKeeper operation fails
208   */
209  public static RegionState getMetaRegionState(ZKWatcher zkw, int replicaId)
210    throws KeeperException {
211    RegionState regionState = null;
212    try {
213      byte[] data = ZKUtil.getData(zkw, zkw.getZNodePaths().getZNodeForReplica(replicaId));
214      regionState = ProtobufUtil.parseMetaRegionStateFrom(data, replicaId);
215    } catch (DeserializationException e) {
216      throw ZKUtil.convert(e);
217    } catch (InterruptedException e) {
218      Thread.currentThread().interrupt();
219    }
220    return regionState;
221  }
222
223  /**
224   * Deletes the location of <code>hbase:meta</code> in ZooKeeper.
225   * @param zookeeper zookeeper reference
226   * @throws KeeperException unexpected zookeeper exception
227   */
228  @RestrictedApi(explanation = "Should only be called in tests", link = "",
229      allowedOnPath = ".*/src/test/.*")
230  public static void deleteMetaLocation(ZKWatcher zookeeper) throws KeeperException {
231    deleteMetaLocation(zookeeper, RegionInfo.DEFAULT_REPLICA_ID);
232  }
233
234  public static void deleteMetaLocation(ZKWatcher zookeeper, int replicaId) throws KeeperException {
235    if (replicaId == RegionInfo.DEFAULT_REPLICA_ID) {
236      LOG.info("Deleting hbase:meta region location in ZooKeeper");
237    } else {
238      LOG.info("Deleting hbase:meta for {} region location in ZooKeeper", replicaId);
239    }
240    try {
241      // Just delete the node. Don't need any watches.
242      ZKUtil.deleteNode(zookeeper, zookeeper.getZNodePaths().getZNodeForReplica(replicaId));
243    } catch (KeeperException.NoNodeException nne) {
244      // Has already been deleted
245    }
246  }
247
248  /**
249   * Wait until the meta region is available and is not in transition.
250   * @param zkw       reference to the {@link ZKWatcher} which also contains configuration and
251   *                  constants
252   * @param replicaId the ID of the replica
253   * @param timeout   maximum time to wait in millis
254   * @return ServerName or null if we timed out.
255   * @throws InterruptedException if waiting for the socket operation fails
256   */
257  private static ServerName blockUntilAvailable(final ZKWatcher zkw, int replicaId,
258    final long timeout) throws InterruptedException {
259    if (timeout < 0) {
260      throw new IllegalArgumentException();
261    }
262
263    if (zkw == null) {
264      throw new IllegalArgumentException();
265    }
266
267    long startTime = EnvironmentEdgeManager.currentTime();
268    ServerName sn = null;
269    while (true) {
270      sn = getMetaRegionLocation(zkw, replicaId);
271      if (
272        sn != null || (EnvironmentEdgeManager.currentTime() - startTime)
273            > timeout - HConstants.SOCKET_RETRY_WAIT_MS
274      ) {
275        break;
276      }
277      Thread.sleep(HConstants.SOCKET_RETRY_WAIT_MS);
278    }
279    return sn;
280  }
281}