All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.master.webapp.MetaBrowser Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.master.webapp;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.StreamSupport;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.AdvancedScanResultConsumer;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.AsyncTable;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.master.RegionState;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.yetus.audience.InterfaceAudience;

import org.apache.hbase.thirdparty.com.google.common.collect.Iterators;
import org.apache.hbase.thirdparty.io.netty.handler.codec.http.QueryStringEncoder;

/**
 * 

* Support class for the "Meta Entries" section in {@code resources/hbase-webapps/master/table.jsp}. *

*

* Interface. This class's intended consumer is {@code table.jsp}. As such, it's primary * interface is the active {@link HttpServletRequest}, from which it uses the {@code scan_*} request * parameters. This class supports paging through an optionally filtered view of the contents of * {@code hbase:meta}. Those filters and the pagination offset are specified via these request * parameters. It provides helper methods for constructing pagination links. *

    *
  • {@value #NAME_PARAM} - the name of the table requested. The only table of our concern here is * {@code hbase:meta}; any other value is effectively ignored by the giant conditional in the * jsp.
  • *
  • {@value #SCAN_LIMIT_PARAM} - specifies a limit on the number of region (replicas) rendered on * the by the table in a single request -- a limit on page size. This corresponds to the number of * {@link RegionReplicaInfo} objects produced by {@link Results#iterator()}. When a value for * {@code scan_limit} is invalid or not specified, the default value of {@value #SCAN_LIMIT_DEFAULT} * is used. In order to avoid excessive resource consumption, a maximum value of * {@value #SCAN_LIMIT_MAX} is enforced.
  • *
  • {@value #SCAN_REGION_STATE_PARAM} - an optional filter on {@link RegionState}.
  • *
  • {@value #SCAN_START_PARAM} - specifies the rowkey at which a scan should start. For usage * details, see the below section on Pagination.
  • *
  • {@value #SCAN_TABLE_PARAM} - specifies a filter on the values returned, limiting them to * regions from a specified table. This parameter is implemented as a prefix filter on the * {@link Scan}, so in effect it can be used for simple namespace and multi-table matches.
  • *
*

*

* Pagination. A single page of results are made available via {@link #getResults()} / an * instance of {@link Results}. Callers use its {@link Iterator} consume the page of * {@link RegionReplicaInfo} instances, each of which represents a region or region replica. Helper * methods are provided for building page navigation controls preserving the user's selected filter * set: {@link #buildFirstPageUrl()}, {@link #buildNextPageUrl(byte[])}. Pagination is implemented * using a simple offset + limit system. Offset is provided by the {@value #SCAN_START_PARAM}, limit * via {@value #SCAN_LIMIT_PARAM}. Under the hood, the {@link Scan} is constructed with * {@link Scan#setMaxResultSize(long)} set to ({@value SCAN_LIMIT_PARAM} +1), while the * {@link Results} {@link Iterator} honors {@value #SCAN_LIMIT_PARAM}. The +1 allows the caller to * know if a "next page" is available via {@link Results#hasMoreResults()}. Note that this * pagination strategy is incomplete when it comes to region replicas and can potentially omit * rendering replicas that fall between the last rowkey offset and {@code replicaCount % page size}. *

*

* Error Messages. Any time there's an error parsing user input, a message will be populated * in {@link #getErrorMessages()}. Any fields which produce an error will have their filter values * set to the default, except for a value of {@value #SCAN_LIMIT_PARAM} that exceeds * {@value #SCAN_LIMIT_MAX}, in which case {@value #SCAN_LIMIT_MAX} is used. *

*/ @InterfaceAudience.Private public class MetaBrowser { public static final String NAME_PARAM = "name"; public static final String SCAN_LIMIT_PARAM = "scan_limit"; public static final String SCAN_REGION_STATE_PARAM = "scan_region_state"; public static final String SCAN_START_PARAM = "scan_start"; public static final String SCAN_TABLE_PARAM = "scan_table"; public static final int SCAN_LIMIT_DEFAULT = 10; public static final int SCAN_LIMIT_MAX = 10_000; private final AsyncConnection connection; private final HttpServletRequest request; private final List errorMessages; private final String name; private final Integer scanLimit; private final RegionState.State scanRegionState; private final byte[] scanStart; private final TableName scanTable; public MetaBrowser(final AsyncConnection connection, final HttpServletRequest request) { this.connection = connection; this.request = request; this.errorMessages = new LinkedList<>(); this.name = resolveName(request); this.scanLimit = resolveScanLimit(request); this.scanRegionState = resolveScanRegionState(request); this.scanStart = resolveScanStart(request); this.scanTable = resolveScanTable(request); } public List getErrorMessages() { return errorMessages; } public String getName() { return name; } public Integer getScanLimit() { return scanLimit; } public byte[] getScanStart() { return scanStart; } public RegionState.State getScanRegionState() { return scanRegionState; } public TableName getScanTable() { return scanTable; } public Results getResults() { final AsyncTable asyncTable = connection.getTable(TableName.META_TABLE_NAME); return new Results(asyncTable.getScanner(buildScan())); } @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("scanStart", scanStart).append("scanLimit", scanLimit).append("scanTable", scanTable) .append("scanRegionState", scanRegionState).toString(); } private static String resolveName(final HttpServletRequest request) { return resolveRequestParameter(request, NAME_PARAM); } private Integer resolveScanLimit(final HttpServletRequest request) { final String requestValueStr = resolveRequestParameter(request, SCAN_LIMIT_PARAM); if (StringUtils.isBlank(requestValueStr)) { return null; } final Integer requestValue = tryParseInt(requestValueStr); if (requestValue == null) { errorMessages.add(buildScanLimitMalformedErrorMessage(requestValueStr)); return null; } if (requestValue <= 0) { errorMessages.add(buildScanLimitLTEQZero(requestValue)); return SCAN_LIMIT_DEFAULT; } final int truncatedValue = Math.min(requestValue, SCAN_LIMIT_MAX); if (requestValue != truncatedValue) { errorMessages.add(buildScanLimitExceededErrorMessage(requestValue)); } return truncatedValue; } private RegionState.State resolveScanRegionState(final HttpServletRequest request) { final String requestValueStr = resolveRequestParameter(request, SCAN_REGION_STATE_PARAM); if (requestValueStr == null) { return null; } final RegionState.State requestValue = tryValueOf(RegionState.State.class, requestValueStr); if (requestValue == null) { errorMessages.add(buildScanRegionStateMalformedErrorMessage(requestValueStr)); return null; } return requestValue; } private static byte[] resolveScanStart(final HttpServletRequest request) { // TODO: handle replicas that fall between the last rowkey and pagination limit. final String requestValue = resolveRequestParameter(request, SCAN_START_PARAM); if (requestValue == null) { return null; } return Bytes.toBytesBinary(requestValue); } private static TableName resolveScanTable(final HttpServletRequest request) { final String requestValue = resolveRequestParameter(request, SCAN_TABLE_PARAM); if (requestValue == null) { return null; } return TableName.valueOf(requestValue); } private static String resolveRequestParameter(final HttpServletRequest request, final String param) { if (request == null) { return null; } final String requestValueStrEnc = request.getParameter(param); if (StringUtils.isBlank(requestValueStrEnc)) { return null; } return urlDecode(requestValueStrEnc); } private static Filter buildTableFilter(final TableName tableName) { return new PrefixFilter(tableName.toBytes()); } private static Filter buildScanRegionStateFilter(final RegionState.State state) { return new SingleColumnValueFilter(HConstants.CATALOG_FAMILY, HConstants.STATE_QUALIFIER, CompareOperator.EQUAL, // use the same serialization strategy as found in MetaTableAccessor#addRegionStateToPut Bytes.toBytes(state.name())); } private Filter buildScanFilter() { if (scanTable == null && scanRegionState == null) { return null; } final List filters = new ArrayList<>(2); if (scanTable != null) { filters.add(buildTableFilter(scanTable)); } if (scanRegionState != null) { filters.add(buildScanRegionStateFilter(scanRegionState)); } if (filters.size() == 1) { return filters.get(0); } return new FilterList(FilterList.Operator.MUST_PASS_ALL, filters); } private Scan buildScan() { final Scan metaScan = new Scan().addFamily(HConstants.CATALOG_FAMILY).readVersions(1) .setLimit((scanLimit != null ? scanLimit : SCAN_LIMIT_DEFAULT) + 1); if (scanStart != null) { metaScan.withStartRow(scanStart, false); } final Filter filter = buildScanFilter(); if (filter != null) { metaScan.setFilter(filter); } return metaScan; } /** * Adds {@code value} to {@code encoder} under {@code paramName} when {@code value} is non-null. */ private void addParam(final QueryStringEncoder encoder, final String paramName, final Object value) { if (value != null) { encoder.addParam(paramName, value.toString()); } } private QueryStringEncoder buildFirstPageEncoder() { final QueryStringEncoder encoder = new QueryStringEncoder(request.getRequestURI()); addParam(encoder, NAME_PARAM, name); addParam(encoder, SCAN_LIMIT_PARAM, scanLimit); addParam(encoder, SCAN_REGION_STATE_PARAM, scanRegionState); addParam(encoder, SCAN_TABLE_PARAM, scanTable); return encoder; } public String buildFirstPageUrl() { return buildFirstPageEncoder().toString(); } static String buildStartParamFrom(final byte[] lastRow) { if (lastRow == null) { return null; } return urlEncode(Bytes.toStringBinary(lastRow)); } public String buildNextPageUrl(final byte[] lastRow) { final QueryStringEncoder encoder = buildFirstPageEncoder(); final String startRow = buildStartParamFrom(lastRow); addParam(encoder, SCAN_START_PARAM, startRow); return encoder.toString(); } private static String urlEncode(final String val) { if (StringUtils.isEmpty(val)) { return null; } try { return URLEncoder.encode(val, StandardCharsets.UTF_8.toString()); } catch (UnsupportedEncodingException e) { return null; } } private static String urlDecode(final String val) { if (StringUtils.isEmpty(val)) { return null; } try { return URLDecoder.decode(val, StandardCharsets.UTF_8.toString()); } catch (UnsupportedEncodingException e) { return null; } } private static Integer tryParseInt(final String val) { if (StringUtils.isEmpty(val)) { return null; } try { return Integer.parseInt(val); } catch (NumberFormatException e) { return null; } } private static > T tryValueOf(final Class clazz, final String value) { if (clazz == null || value == null) { return null; } try { return Enum.valueOf(clazz, value); } catch (IllegalArgumentException e) { return null; } } private static String buildScanLimitExceededErrorMessage(final int requestValue) { return String.format("Requested SCAN_LIMIT value %d exceeds maximum value %d.", requestValue, SCAN_LIMIT_MAX); } private static String buildScanLimitMalformedErrorMessage(final String requestValue) { return String.format("Requested SCAN_LIMIT value '%s' cannot be parsed as an integer.", requestValue); } private static String buildScanLimitLTEQZero(final int requestValue) { return String.format("Requested SCAN_LIMIT value %d is <= 0.", requestValue); } private static String buildScanRegionStateMalformedErrorMessage(final String requestValue) { return String.format( "Requested SCAN_REGION_STATE value '%s' cannot be parsed as a RegionState.", requestValue); } /** * Encapsulates the results produced by this {@link MetaBrowser} instance. */ public final class Results implements AutoCloseable, Iterable { private final ResultScanner resultScanner; private final Iterator sourceIterator; private Results(final ResultScanner resultScanner) { this.resultScanner = resultScanner; this.sourceIterator = StreamSupport.stream(resultScanner.spliterator(), false) .map(RegionReplicaInfo::from).flatMap(Collection::stream).iterator(); } /** * @return {@code true} when the underlying {@link ResultScanner} is not yet exhausted, * {@code false} otherwise. */ public boolean hasMoreResults() { return sourceIterator.hasNext(); } @Override public void close() { if (resultScanner != null) { resultScanner.close(); } } @Override public Iterator iterator() { return Iterators.limit(sourceIterator, scanLimit != null ? scanLimit : SCAN_LIMIT_DEFAULT); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy