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 LockAndQueue metaLock;
060
061  public SchemaLocking(Function<Long, Procedure<?>> procedureRetriever) {
062    this.procedureRetriever = procedureRetriever;
063    this.metaLock = new LockAndQueue(procedureRetriever);
064  }
065
066  private <T> LockAndQueue getLock(Map<T, LockAndQueue> map, T key) {
067    LockAndQueue lock = map.get(key);
068    if (lock == null) {
069      lock = new LockAndQueue(procedureRetriever);
070      map.put(key, lock);
071    }
072    return lock;
073  }
074
075  LockAndQueue getTableLock(TableName tableName) {
076    return getLock(tableLocks, tableName);
077  }
078
079  LockAndQueue removeTableLock(TableName tableName) {
080    return tableLocks.remove(tableName);
081  }
082
083  LockAndQueue getNamespaceLock(String namespace) {
084    return getLock(namespaceLocks, namespace);
085  }
086
087  LockAndQueue getRegionLock(String encodedRegionName) {
088    return getLock(regionLocks, encodedRegionName);
089  }
090
091  /**
092   * @deprecated only used for {@link RecoverMetaProcedure}. Should be removed along with
093   *             {@link RecoverMetaProcedure}.
094   */
095  @Deprecated
096  LockAndQueue getMetaLock() {
097    return metaLock;
098  }
099
100  LockAndQueue removeRegionLock(String encodedRegionName) {
101    return regionLocks.remove(encodedRegionName);
102  }
103
104  LockAndQueue getServerLock(ServerName serverName) {
105    return getLock(serverLocks, serverName);
106  }
107
108  LockAndQueue removeServerLock(ServerName serverName) {
109    return serverLocks.remove(serverName);
110  }
111
112  LockAndQueue getPeerLock(String peerId) {
113    return getLock(peerLocks, peerId);
114  }
115
116  LockAndQueue removePeerLock(String peerId) {
117    return peerLocks.remove(peerId);
118  }
119
120  private LockedResource createLockedResource(LockedResourceType resourceType, String resourceName,
121    LockAndQueue queue) {
122    LockType lockType;
123    Procedure<?> exclusiveLockOwnerProcedure;
124    int sharedLockCount;
125
126    if (queue.hasExclusiveLock()) {
127      lockType = LockType.EXCLUSIVE;
128      exclusiveLockOwnerProcedure = queue.getExclusiveLockOwnerProcedure();
129      sharedLockCount = 0;
130    } else {
131      lockType = LockType.SHARED;
132      exclusiveLockOwnerProcedure = null;
133      sharedLockCount = queue.getSharedLockCount();
134    }
135
136    List<Procedure<?>> waitingProcedures = new ArrayList<>();
137
138    queue.filterWaitingQueue(p -> p instanceof LockProcedure)
139      .forEachOrdered(waitingProcedures::add);
140
141    return new LockedResource(resourceType, resourceName, lockType, exclusiveLockOwnerProcedure,
142      sharedLockCount, waitingProcedures);
143  }
144
145  private <T> void addToLockedResources(List<LockedResource> lockedResources,
146    Map<T, LockAndQueue> locks, Function<T, String> keyTransformer,
147    LockedResourceType resourcesType) {
148    locks.entrySet().stream().filter(e -> e.getValue().isLocked())
149      .map(e -> createLockedResource(resourcesType, keyTransformer.apply(e.getKey()), e.getValue()))
150      .forEachOrdered(lockedResources::add);
151  }
152
153  /**
154   * List lock queues.
155   * @return the locks
156   */
157  List<LockedResource> getLocks() {
158    List<LockedResource> lockedResources = new ArrayList<>();
159    addToLockedResources(lockedResources, serverLocks, sn -> sn.getServerName(),
160      LockedResourceType.SERVER);
161    addToLockedResources(lockedResources, namespaceLocks, Function.identity(),
162      LockedResourceType.NAMESPACE);
163    addToLockedResources(lockedResources, tableLocks, tn -> tn.getNameAsString(),
164      LockedResourceType.TABLE);
165    addToLockedResources(lockedResources, regionLocks, Function.identity(),
166      LockedResourceType.REGION);
167    addToLockedResources(lockedResources, peerLocks, Function.identity(), LockedResourceType.PEER);
168    addToLockedResources(lockedResources, ImmutableMap.of(TableName.META_TABLE_NAME, metaLock),
169      tn -> tn.getNameAsString(), LockedResourceType.META);
170    return lockedResources;
171  }
172
173  /**
174   * @return {@link LockedResource} for resource of specified type & name. null if resource is not
175   *         locked.
176   */
177  LockedResource getLockResource(LockedResourceType resourceType, String resourceName) {
178    LockAndQueue queue;
179    switch (resourceType) {
180      case SERVER:
181        queue = serverLocks.get(ServerName.valueOf(resourceName));
182        break;
183      case NAMESPACE:
184        queue = namespaceLocks.get(resourceName);
185        break;
186      case TABLE:
187        queue = tableLocks.get(TableName.valueOf(resourceName));
188        break;
189      case REGION:
190        queue = regionLocks.get(resourceName);
191        break;
192      case PEER:
193        queue = peerLocks.get(resourceName);
194        break;
195      case META:
196        queue = metaLock;
197      default:
198        queue = null;
199    }
200    return queue != null ? createLockedResource(resourceType, resourceName, queue) : null;
201  }
202
203  /**
204   * Removes all locks by clearing the maps. Used when procedure executor is stopped for failure and
205   * recovery testing.
206   */
207  void clear() {
208    serverLocks.clear();
209    namespaceLocks.clear();
210    tableLocks.clear();
211    regionLocks.clear();
212    peerLocks.clear();
213  }
214
215  @Override
216  public String toString() {
217    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
218      .append("serverLocks", filterUnlocked(serverLocks))
219      .append("namespaceLocks", filterUnlocked(namespaceLocks))
220      .append("tableLocks", filterUnlocked(tableLocks))
221      .append("regionLocks", filterUnlocked(regionLocks))
222      .append("peerLocks", filterUnlocked(peerLocks))
223      .append("metaLocks", filterUnlocked(ImmutableMap.of(TableName.META_TABLE_NAME, metaLock)))
224      .build();
225
226  }
227
228  private String filterUnlocked(Map<?, LockAndQueue> locks) {
229    return locks.entrySet().stream().filter(val -> !val.getValue().isLocked())
230      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
231  }
232}