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.master.procedure;
019
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.function.Function;
025import java.util.stream.Collectors;
026import org.apache.commons.lang3.builder.ToStringBuilder;
027import org.apache.commons.lang3.builder.ToStringStyle;
028import org.apache.hadoop.hbase.ServerName;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.master.locking.LockProcedure;
031import org.apache.hadoop.hbase.procedure2.LockAndQueue;
032import org.apache.hadoop.hbase.procedure2.LockType;
033import org.apache.hadoop.hbase.procedure2.LockedResource;
034import org.apache.hadoop.hbase.procedure2.LockedResourceType;
035import org.apache.hadoop.hbase.procedure2.Procedure;
036import org.apache.yetus.audience.InterfaceAudience;
037
038import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
039
040/**
041 * <p>
042 * Locks on namespaces, tables, and regions.
043 * </p>
044 * <p>
045 * Since LockAndQueue implementation is NOT thread-safe, schedLock() guards all calls to these
046 * locks.
047 * </p>
048 */
049@InterfaceAudience.Private
050class SchemaLocking {
051
052  private final Function<Long, Procedure<?>> procedureRetriever;
053  private final Map<ServerName, LockAndQueue> serverLocks = new HashMap<>();
054  private final Map<String, LockAndQueue> namespaceLocks = new HashMap<>();
055  private final Map<TableName, LockAndQueue> tableLocks = new HashMap<>();
056  // Single map for all regions irrespective of tables. Key is encoded region name.
057  private final Map<String, LockAndQueue> regionLocks = new HashMap<>();
058  private final Map<String, LockAndQueue> peerLocks = new HashMap<>();
059  private final Map<String, LockAndQueue> globalLocks = new HashMap<>();
060  private final LockAndQueue metaLock;
061
062  public SchemaLocking(Function<Long, Procedure<?>> procedureRetriever) {
063    this.procedureRetriever = procedureRetriever;
064    this.metaLock = new LockAndQueue(procedureRetriever);
065  }
066
067  private <T> LockAndQueue getLock(Map<T, LockAndQueue> map, T key) {
068    LockAndQueue lock = map.get(key);
069    if (lock == null) {
070      lock = new LockAndQueue(procedureRetriever);
071      map.put(key, lock);
072    }
073    return lock;
074  }
075
076  LockAndQueue getTableLock(TableName tableName) {
077    return getLock(tableLocks, tableName);
078  }
079
080  LockAndQueue removeTableLock(TableName tableName) {
081    return tableLocks.remove(tableName);
082  }
083
084  LockAndQueue getNamespaceLock(String namespace) {
085    return getLock(namespaceLocks, namespace);
086  }
087
088  LockAndQueue getRegionLock(String encodedRegionName) {
089    return getLock(regionLocks, encodedRegionName);
090  }
091
092  /**
093   * @deprecated only used for {@link RecoverMetaProcedure}. Should be removed along with
094   *             {@link RecoverMetaProcedure}.
095   */
096  @Deprecated
097  LockAndQueue getMetaLock() {
098    return metaLock;
099  }
100
101  LockAndQueue getGlobalLock(String globalId) {
102    return getLock(globalLocks, globalId);
103  }
104
105  LockAndQueue removeRegionLock(String encodedRegionName) {
106    return regionLocks.remove(encodedRegionName);
107  }
108
109  LockAndQueue getServerLock(ServerName serverName) {
110    return getLock(serverLocks, serverName);
111  }
112
113  LockAndQueue removeServerLock(ServerName serverName) {
114    return serverLocks.remove(serverName);
115  }
116
117  LockAndQueue getPeerLock(String peerId) {
118    return getLock(peerLocks, peerId);
119  }
120
121  LockAndQueue removePeerLock(String peerId) {
122    return peerLocks.remove(peerId);
123  }
124
125  LockAndQueue removeGlobalLock(String globalId) {
126    return globalLocks.remove(globalId);
127  }
128
129  private LockedResource createLockedResource(LockedResourceType resourceType, String resourceName,
130    LockAndQueue queue) {
131    LockType lockType;
132    Procedure<?> exclusiveLockOwnerProcedure;
133    int sharedLockCount;
134
135    if (queue.hasExclusiveLock()) {
136      lockType = LockType.EXCLUSIVE;
137      exclusiveLockOwnerProcedure = queue.getExclusiveLockOwnerProcedure();
138      sharedLockCount = 0;
139    } else {
140      lockType = LockType.SHARED;
141      exclusiveLockOwnerProcedure = null;
142      sharedLockCount = queue.getSharedLockCount();
143    }
144
145    List<Procedure<?>> waitingProcedures = new ArrayList<>();
146
147    queue.filterWaitingQueue(p -> p instanceof LockProcedure)
148      .forEachOrdered(waitingProcedures::add);
149
150    return new LockedResource(resourceType, resourceName, lockType, exclusiveLockOwnerProcedure,
151      sharedLockCount, waitingProcedures);
152  }
153
154  private <T> void addToLockedResources(List<LockedResource> lockedResources,
155    Map<T, LockAndQueue> locks, Function<T, String> keyTransformer,
156    LockedResourceType resourcesType) {
157    locks.entrySet().stream().filter(e -> e.getValue().isLocked())
158      .map(e -> createLockedResource(resourcesType, keyTransformer.apply(e.getKey()), e.getValue()))
159      .forEachOrdered(lockedResources::add);
160  }
161
162  /**
163   * List lock queues.
164   * @return the locks
165   */
166  List<LockedResource> getLocks() {
167    List<LockedResource> lockedResources = new ArrayList<>();
168    addToLockedResources(lockedResources, serverLocks, sn -> sn.getServerName(),
169      LockedResourceType.SERVER);
170    addToLockedResources(lockedResources, namespaceLocks, Function.identity(),
171      LockedResourceType.NAMESPACE);
172    addToLockedResources(lockedResources, tableLocks, tn -> tn.getNameAsString(),
173      LockedResourceType.TABLE);
174    addToLockedResources(lockedResources, regionLocks, Function.identity(),
175      LockedResourceType.REGION);
176    addToLockedResources(lockedResources, peerLocks, Function.identity(), LockedResourceType.PEER);
177    addToLockedResources(lockedResources, ImmutableMap.of(TableName.META_TABLE_NAME, metaLock),
178      tn -> tn.getNameAsString(), LockedResourceType.META);
179    addToLockedResources(lockedResources, globalLocks, Function.identity(),
180      LockedResourceType.GLOBAL);
181    return lockedResources;
182  }
183
184  /**
185   * @return {@link LockedResource} for resource of specified type & name. null if resource is not
186   *         locked.
187   */
188  LockedResource getLockResource(LockedResourceType resourceType, String resourceName) {
189    LockAndQueue queue;
190    switch (resourceType) {
191      case SERVER:
192        queue = serverLocks.get(ServerName.valueOf(resourceName));
193        break;
194      case NAMESPACE:
195        queue = namespaceLocks.get(resourceName);
196        break;
197      case TABLE:
198        queue = tableLocks.get(TableName.valueOf(resourceName));
199        break;
200      case REGION:
201        queue = regionLocks.get(resourceName);
202        break;
203      case PEER:
204        queue = peerLocks.get(resourceName);
205        break;
206      case META:
207        queue = metaLock;
208        break;
209      case GLOBAL:
210        queue = globalLocks.get(resourceName);
211        break;
212      default:
213        queue = null;
214        break;
215    }
216    return queue != null ? createLockedResource(resourceType, resourceName, queue) : null;
217  }
218
219  /**
220   * Removes all locks by clearing the maps. Used when procedure executor is stopped for failure and
221   * recovery testing.
222   */
223  void clear() {
224    serverLocks.clear();
225    namespaceLocks.clear();
226    tableLocks.clear();
227    regionLocks.clear();
228    peerLocks.clear();
229  }
230
231  @Override
232  public String toString() {
233    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
234      .append("serverLocks", filterUnlocked(serverLocks))
235      .append("namespaceLocks", filterUnlocked(namespaceLocks))
236      .append("tableLocks", filterUnlocked(tableLocks))
237      .append("regionLocks", filterUnlocked(regionLocks))
238      .append("peerLocks", filterUnlocked(peerLocks))
239      .append("metaLocks", filterUnlocked(ImmutableMap.of(TableName.META_TABLE_NAME, metaLock)))
240      .append("globalLocks", filterUnlocked(globalLocks)).build();
241  }
242
243  private String filterUnlocked(Map<?, LockAndQueue> locks) {
244    return locks.entrySet().stream().filter(val -> !val.getValue().isLocked())
245      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
246  }
247}