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.util.Arrays;
022import java.util.Collection;
023import java.util.Iterator;
024
025import org.apache.hadoop.hbase.client.RegionInfo;
026import org.apache.hadoop.hbase.client.RegionReplicaUtil;
027import org.apache.hadoop.hbase.util.Bytes;
028import org.apache.yetus.audience.InterfaceAudience;
029
030/**
031 * Container for holding a list of {@link HRegionLocation}'s that correspond to the
032 * same range. The list is indexed by the replicaId. This is an immutable list,
033 * however mutation operations are provided which returns a new List via copy-on-write
034 * (assuming small number of locations)
035 */
036@InterfaceAudience.Private
037public class RegionLocations implements Iterable<HRegionLocation> {
038
039  private final int numNonNullElements;
040
041  // locations array contains the HRL objects for known region replicas indexes by the replicaId.
042  // elements can be null if the region replica is not known at all. A null value indicates
043  // that there is a region replica with the index as replicaId, but the location is not known
044  // in the cache.
045  private final HRegionLocation[] locations; // replicaId -> HRegionLocation.
046
047  /**
048   * Constructs the region location list. The locations array should
049   * contain all the locations for known replicas for the region, and should be
050   * sorted in replicaId ascending order, although it can contain nulls indicating replicaIds
051   * that the locations of which are not known.
052   * @param locations an array of HRegionLocations for the same region range
053   */
054  public RegionLocations(HRegionLocation... locations) {
055    int numNonNullElements = 0;
056    int maxReplicaId = -1;
057    int maxReplicaIdIndex = -1;
058    int index = 0;
059    for (HRegionLocation loc : locations) {
060      if (loc != null) {
061        if (loc.getRegion().getReplicaId() >= maxReplicaId) {
062          maxReplicaId = loc.getRegion().getReplicaId();
063          maxReplicaIdIndex = index;
064        }
065      }
066      index++;
067    }
068    // account for the null elements in the array after maxReplicaIdIndex
069    maxReplicaId = maxReplicaId + (locations.length - (maxReplicaIdIndex + 1) );
070
071    if (maxReplicaId + 1 == locations.length) {
072      this.locations = locations;
073    } else {
074      this.locations = new HRegionLocation[maxReplicaId + 1];
075      for (HRegionLocation loc : locations) {
076        if (loc != null) {
077          this.locations[loc.getRegion().getReplicaId()] = loc;
078        }
079      }
080    }
081    for (HRegionLocation loc : this.locations) {
082      if (loc != null && loc.getServerName() != null){
083        numNonNullElements++;
084      }
085    }
086    this.numNonNullElements = numNonNullElements;
087  }
088
089  public RegionLocations(Collection<HRegionLocation> locations) {
090    this(locations.toArray(new HRegionLocation[locations.size()]));
091  }
092
093  /**
094   * Returns the size of the list even if some of the elements
095   * might be null.
096   * @return the size of the list (corresponding to the max replicaId)
097   */
098  public int size() {
099    return locations.length;
100  }
101
102  /**
103   * Returns the size of not-null locations
104   * @return the size of not-null locations
105   */
106  public int numNonNullElements() {
107    return numNonNullElements;
108  }
109
110  /**
111   * Returns whether there are non-null elements in the list
112   * @return whether there are non-null elements in the list
113   */
114  public boolean isEmpty() {
115    return numNonNullElements == 0;
116  }
117
118  /**
119   * Returns a new RegionLocations with the locations removed (set to null)
120   * which have the destination server as given.
121   * @param serverName the serverName to remove locations of
122   * @return an RegionLocations object with removed locations or the same object
123   * if nothing is removed
124   */
125  public RegionLocations removeByServer(ServerName serverName) {
126    HRegionLocation[] newLocations = null;
127    for (int i = 0; i < locations.length; i++) {
128      // check whether something to remove
129      if (locations[i] != null && serverName.equals(locations[i].getServerName())) {
130        if (newLocations == null) { //first time
131          newLocations = new HRegionLocation[locations.length];
132          System.arraycopy(locations, 0, newLocations, 0, i);
133        }
134        newLocations[i] = null;
135      } else if (newLocations != null) {
136        newLocations[i] = locations[i];
137      }
138    }
139    return newLocations == null ? this : new RegionLocations(newLocations);
140  }
141
142  /**
143   * Removes the given location from the list
144   * @param location the location to remove
145   * @return an RegionLocations object with removed locations or the same object
146   * if nothing is removed
147   */
148  public RegionLocations remove(HRegionLocation location) {
149    if (location == null) return this;
150    if (location.getRegion() == null) return this;
151    int replicaId = location.getRegion().getReplicaId();
152    if (replicaId >= locations.length) return this;
153
154    // check whether something to remove. HRL.compareTo() compares ONLY the
155    // serverName. We want to compare the HRI's as well.
156    if (locations[replicaId] == null
157        || RegionInfo.COMPARATOR.compare(location.getRegion(), locations[replicaId].getRegion()) != 0
158        || !location.equals(locations[replicaId])) {
159      return this;
160    }
161
162    HRegionLocation[] newLocations = new HRegionLocation[locations.length];
163    System.arraycopy(locations, 0, newLocations, 0, locations.length);
164    newLocations[replicaId] = null;
165
166    return new RegionLocations(newLocations);
167  }
168
169  /**
170   * Removes location of the given replicaId from the list
171   * @param replicaId the replicaId of the location to remove
172   * @return an RegionLocations object with removed locations or the same object
173   * if nothing is removed
174   */
175  public RegionLocations remove(int replicaId) {
176    if (getRegionLocation(replicaId) == null) {
177      return this;
178    }
179
180    HRegionLocation[] newLocations = new HRegionLocation[locations.length];
181
182    System.arraycopy(locations, 0, newLocations, 0, locations.length);
183    if (replicaId < newLocations.length) {
184      newLocations[replicaId] = null;
185    }
186
187    return new RegionLocations(newLocations);
188  }
189
190  /**
191   * Set the element to null if its getServerName method returns null. Returns null if all the
192   * elements are removed.
193   */
194  public RegionLocations removeElementsWithNullLocation() {
195    HRegionLocation[] newLocations = new HRegionLocation[locations.length];
196    boolean hasNonNullElement = false;
197    for (int i = 0; i < locations.length; i++) {
198      if (locations[i] != null && locations[i].getServerName() != null) {
199        hasNonNullElement = true;
200        newLocations[i] = locations[i];
201      }
202    }
203    return hasNonNullElement ? new RegionLocations(newLocations) : null;
204  }
205
206  /**
207   * Merges this RegionLocations list with the given list assuming
208   * same range, and keeping the most up to date version of the
209   * HRegionLocation entries from either list according to seqNum. If seqNums
210   * are equal, the location from the argument (other) is taken.
211   * @param other the locations to merge with
212   * @return an RegionLocations object with merged locations or the same object
213   * if nothing is merged
214   */
215  public RegionLocations mergeLocations(RegionLocations other) {
216    assert other != null;
217
218    HRegionLocation[] newLocations = null;
219
220    // Use the length from other, since it is coming from meta. Otherwise,
221    // in case of region replication going down, we might have a leak here.
222    int max = other.locations.length;
223
224    RegionInfo regionInfo = null;
225    for (int i = 0; i < max; i++) {
226      HRegionLocation thisLoc = this.getRegionLocation(i);
227      HRegionLocation otherLoc = other.getRegionLocation(i);
228      if (regionInfo == null && otherLoc != null && otherLoc.getRegion() != null) {
229        // regionInfo is the first non-null HRI from other RegionLocations. We use it to ensure that
230        // all replica region infos belong to the same region with same region id.
231        regionInfo = otherLoc.getRegion();
232      }
233
234      HRegionLocation selectedLoc = selectRegionLocation(thisLoc,
235        otherLoc, true, false);
236
237      if (selectedLoc != thisLoc) {
238        if (newLocations == null) {
239          newLocations = new HRegionLocation[max];
240          System.arraycopy(locations, 0, newLocations, 0, i);
241        }
242      }
243      if (newLocations != null) {
244        newLocations[i] = selectedLoc;
245      }
246    }
247
248    // ensure that all replicas share the same start code. Otherwise delete them
249    if (newLocations != null && regionInfo != null) {
250      for (int i=0; i < newLocations.length; i++) {
251        if (newLocations[i] != null) {
252          if (!RegionReplicaUtil.isReplicasForSameRegion(regionInfo,
253            newLocations[i].getRegion())) {
254            newLocations[i] = null;
255          }
256        }
257      }
258    }
259
260    return newLocations == null ? this : new RegionLocations(newLocations);
261  }
262
263  private HRegionLocation selectRegionLocation(HRegionLocation oldLocation,
264      HRegionLocation location, boolean checkForEquals, boolean force) {
265    if (location == null) {
266      return oldLocation == null ? null : oldLocation;
267    }
268
269    if (oldLocation == null) {
270      return location;
271    }
272
273    if (force
274        || isGreaterThan(location.getSeqNum(), oldLocation.getSeqNum(), checkForEquals)) {
275      return location;
276    }
277    return oldLocation;
278  }
279
280  /**
281   * Updates the location with new only if the new location has a higher
282   * seqNum than the old one or force is true.
283   * @param location the location to add or update
284   * @param checkForEquals whether to update the location if seqNums for the
285   * HRegionLocations for the old and new location are the same
286   * @param force whether to force update
287   * @return an RegionLocations object with updated locations or the same object
288   * if nothing is updated
289   */
290  public RegionLocations updateLocation(HRegionLocation location,
291      boolean checkForEquals, boolean force) {
292    assert location != null;
293
294    int replicaId = location.getRegion().getReplicaId();
295
296    HRegionLocation oldLoc = getRegionLocation(location.getRegion().getReplicaId());
297    HRegionLocation selectedLoc = selectRegionLocation(oldLoc, location,
298      checkForEquals, force);
299
300    if (selectedLoc == oldLoc) {
301      return this;
302    }
303    HRegionLocation[] newLocations = new HRegionLocation[Math.max(locations.length, replicaId +1)];
304    System.arraycopy(locations, 0, newLocations, 0, locations.length);
305    newLocations[replicaId] = location;
306    // ensure that all replicas share the same start code. Otherwise delete them
307    for (int i=0; i < newLocations.length; i++) {
308      if (newLocations[i] != null) {
309        if (!RegionReplicaUtil.isReplicasForSameRegion(location.getRegion(),
310          newLocations[i].getRegion())) {
311          newLocations[i] = null;
312        }
313      }
314    }
315    return new RegionLocations(newLocations);
316  }
317
318  private boolean isGreaterThan(long a, long b, boolean checkForEquals) {
319    return a > b || (checkForEquals && (a == b));
320  }
321
322  public HRegionLocation getRegionLocation(int replicaId) {
323    if (replicaId >= locations.length) {
324      return null;
325    }
326    return locations[replicaId];
327  }
328
329  /**
330   * Returns the region location from the list for matching regionName, which can
331   * be regionName or encodedRegionName
332   * @param regionName regionName or encodedRegionName
333   * @return HRegionLocation found or null
334   */
335  public HRegionLocation getRegionLocationByRegionName(byte[] regionName) {
336    for (HRegionLocation loc : locations) {
337      if (loc != null) {
338        if (Bytes.equals(loc.getRegion().getRegionName(), regionName)
339            || Bytes.equals(loc.getRegion().getEncodedNameAsBytes(), regionName)) {
340          return loc;
341        }
342      }
343    }
344    return null;
345  }
346
347  public HRegionLocation[] getRegionLocations() {
348    return locations;
349  }
350
351  public HRegionLocation getDefaultRegionLocation() {
352    return locations[RegionInfo.DEFAULT_REPLICA_ID];
353  }
354
355  /**
356   * Returns the first not-null region location in the list
357   */
358  public HRegionLocation getRegionLocation() {
359    for (HRegionLocation loc : locations) {
360      if (loc != null) {
361        return loc;
362      }
363    }
364    return null;
365  }
366
367  @Override
368  public Iterator<HRegionLocation> iterator() {
369    return Arrays.asList(locations).iterator();
370  }
371
372  @Override
373  public String toString() {
374    StringBuilder builder = new StringBuilder("[");
375    for (HRegionLocation loc : locations) {
376      if (builder.length() > 1) {
377        builder.append(", ");
378      }
379      builder.append(loc == null ? "null" : loc);
380    }
381    builder.append("]");
382    return builder.toString();
383  }
384
385}