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