001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to you under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.hadoop.hbase.quotas;
018
019import java.io.IOException;
020import java.util.Map;
021import java.util.Map.Entry;
022import java.util.Objects;
023import java.util.concurrent.locks.ReentrantReadWriteLock;
024import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
025import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
026import java.util.stream.Collectors;
027import org.apache.hadoop.hbase.Cell;
028import org.apache.hadoop.hbase.CellScanner;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.Connection;
031import org.apache.hadoop.hbase.client.RegionInfo;
032import org.apache.hadoop.hbase.client.Result;
033import org.apache.hadoop.hbase.client.ResultScanner;
034import org.apache.hadoop.hbase.client.Scan;
035import org.apache.hadoop.hbase.client.Table;
036import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot.SpaceQuotaStatus;
037import org.apache.yetus.audience.InterfaceAudience;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
042
043import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
044import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
045import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota;
046
047/**
048 * {@link QuotaSnapshotStore} for tables.
049 */
050@InterfaceAudience.Private
051public class TableQuotaSnapshotStore implements QuotaSnapshotStore<TableName> {
052  private static final Logger LOG = LoggerFactory.getLogger(TableQuotaSnapshotStore.class);
053
054  private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
055  private final ReadLock rlock = lock.readLock();
056  private final WriteLock wlock = lock.writeLock();
057
058  private final Connection conn;
059  private final QuotaObserverChore chore;
060  private Map<RegionInfo,Long> regionUsage;
061
062  public TableQuotaSnapshotStore(Connection conn, QuotaObserverChore chore, Map<RegionInfo,Long> regionUsage) {
063    this.conn = Objects.requireNonNull(conn);
064    this.chore = Objects.requireNonNull(chore);
065    this.regionUsage = Objects.requireNonNull(regionUsage);
066  }
067
068  @Override
069  public SpaceQuota getSpaceQuota(TableName subject) throws IOException {
070    Quotas quotas = getQuotaForTable(subject);
071    if (quotas != null && quotas.hasSpace()) {
072      return quotas.getSpace();
073    }
074    return null;
075  }
076  /**
077   * Fetches the table quota. Visible for mocking/testing.
078   */
079  Quotas getQuotaForTable(TableName table) throws IOException {
080    return QuotaTableUtil.getTableQuota(conn, table);
081  }
082
083  @Override
084  public SpaceQuotaSnapshot getCurrentState(TableName table) {
085    // Defer the "current state" to the chore
086    return chore.getTableQuotaSnapshot(table);
087  }
088
089  @Override
090  public SpaceQuotaSnapshot getTargetState(
091      TableName table, SpaceQuota spaceQuota) throws IOException {
092    rlock.lock();
093    try {
094      final long sizeLimitInBytes = spaceQuota.getSoftLimit();
095      long sum = 0L;
096      for (Entry<RegionInfo,Long> entry : filterBySubject(table)) {
097        sum += entry.getValue();
098      }
099      // Add in the size for any snapshots against this table
100      sum += getSnapshotSizesForTable(table);
101      // Observance is defined as the size of the table being less than the limit
102      SpaceQuotaStatus status = sum <= sizeLimitInBytes ? SpaceQuotaStatus.notInViolation()
103          : new SpaceQuotaStatus(ProtobufUtil.toViolationPolicy(spaceQuota.getViolationPolicy()));
104      return new SpaceQuotaSnapshot(status, sum, sizeLimitInBytes);
105    } finally {
106      rlock.unlock();
107    }
108  }
109
110  /**
111   * Fetches any serialized snapshot sizes from the quota table for the {@code tn} provided. Any
112   * malformed records are skipped with a warning printed out.
113   */
114  long getSnapshotSizesForTable(TableName tn) throws IOException {
115    try (Table quotaTable = conn.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
116      Scan s = QuotaTableUtil.createScanForSpaceSnapshotSizes(tn);
117      ResultScanner rs = quotaTable.getScanner(s);
118      try {
119        long size = 0L;
120        // Should just be a single row (for our table)
121        for (Result result : rs) {
122          // May have multiple columns, one for each snapshot
123          CellScanner cs = result.cellScanner();
124          while (cs.advance()) {
125            Cell current = cs.current();
126            try {
127              long snapshotSize = QuotaTableUtil.parseSnapshotSize(current);
128              if (LOG.isTraceEnabled()) {
129                LOG.trace("Saw snapshot size of " + snapshotSize + " for " + current);
130              }
131              size += snapshotSize;
132            } catch (InvalidProtocolBufferException e) {
133              LOG.warn("Failed to parse snapshot size from cell: " + current);
134            }
135          }
136        }
137        return size;
138      } finally {
139        if (null != rs) {
140          rs.close();
141        }
142      }
143    }
144  }
145
146  @Override
147  public Iterable<Entry<RegionInfo, Long>> filterBySubject(TableName table) {
148    rlock.lock();
149    try {
150      return regionUsage.entrySet().stream()
151        .filter(entry -> table.equals(entry.getKey().getTable())).collect(Collectors.toList());
152    } finally {
153      rlock.unlock();
154    }
155  }
156
157  @Override
158  public void setCurrentState(TableName table, SpaceQuotaSnapshot snapshot) {
159    // Defer the "current state" to the chore
160    this.chore.setTableQuotaSnapshot(table, snapshot);
161  }
162
163  @Override
164  public void setRegionUsage(Map<RegionInfo,Long> regionUsage) {
165    wlock.lock();
166    try {
167      this.regionUsage = Objects.requireNonNull(regionUsage);
168    } finally {
169      wlock.unlock();
170    }
171  }
172}