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.client;
019
020import static org.apache.hadoop.hbase.TableName.META_TABLE_NAME;
021import static org.junit.jupiter.api.Assertions.assertInstanceOf;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.assertThrows;
024import static org.mockito.ArgumentMatchers.any;
025import static org.mockito.Mockito.doAnswer;
026import static org.mockito.Mockito.mock;
027import static org.mockito.Mockito.mockStatic;
028import static org.mockito.Mockito.when;
029
030import java.util.List;
031import java.util.concurrent.CompletableFuture;
032import java.util.concurrent.ExecutionException;
033import java.util.concurrent.TimeUnit;
034import java.util.concurrent.atomic.AtomicReference;
035import org.apache.hadoop.hbase.ClientMetaTableAccessor;
036import org.apache.hadoop.hbase.HRegionLocation;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.TableNotFoundException;
039import org.apache.hadoop.hbase.testclassification.ClientTests;
040import org.apache.hadoop.hbase.testclassification.MediumTests;
041import org.apache.hadoop.hbase.util.FutureUtils;
042import org.junit.jupiter.api.AfterEach;
043import org.junit.jupiter.api.BeforeEach;
044import org.junit.jupiter.api.Tag;
045import org.junit.jupiter.api.Test;
046import org.mockito.MockedStatic;
047
048import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer;
049
050@Tag(MediumTests.TAG)
051@Tag(ClientTests.TAG)
052public class TestCompactFromClient {
053
054  private org.apache.logging.log4j.core.Appender mockAppender;
055
056  @BeforeEach
057  public void setUp() {
058    mockAppender = mock(org.apache.logging.log4j.core.Appender.class);
059    when(mockAppender.getName()).thenReturn("mockAppender");
060    when(mockAppender.isStarted()).thenReturn(true);
061  }
062
063  @AfterEach
064  public void tearDown() {
065    ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager
066      .getLogger(FutureUtils.class)).removeAppender(mockAppender);
067  }
068
069  @Test
070  public void testCompactTableWithNullLocations() throws Exception {
071    AtomicReference<String> msg = new AtomicReference<>();
072    AtomicReference<Throwable> throwable = new AtomicReference<>();
073
074    ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager
075      .getLogger(FutureUtils.class)).addAppender(mockAppender);
076    doAnswer(invocation -> {
077      org.apache.logging.log4j.core.LogEvent logEvent =
078        invocation.getArgument(0, org.apache.logging.log4j.core.LogEvent.class);
079      org.apache.logging.log4j.Level originalLevel = logEvent.getLevel();
080      if (originalLevel.equals(org.apache.logging.log4j.Level.ERROR)) {
081        msg.set(logEvent.getMessage().getFormattedMessage());
082        throwable.set(logEvent.getThrown());
083      }
084      return null;
085    }).when(mockAppender).append(any(org.apache.logging.log4j.core.LogEvent.class));
086
087    TableName tableName = TableName.valueOf("testCompactNullLocations");
088    CompletableFuture<List<HRegionLocation>> nullLocationsFuture =
089      CompletableFuture.completedFuture(null);
090
091    try (
092      MockedStatic<ClientMetaTableAccessor> mockedMeta = mockStatic(ClientMetaTableAccessor.class);
093      AsyncConnectionImpl connection = mock(AsyncConnectionImpl.class)) {
094      mockedMeta.when(() -> ClientMetaTableAccessor.getTableHRegionLocations(any(AsyncTable.class),
095        any(TableName.class))).thenReturn(nullLocationsFuture);
096      AsyncTable<AdvancedScanResultConsumer> metaTable = mock(AsyncTable.class);
097      when(connection.getTable(META_TABLE_NAME)).thenReturn(metaTable);
098
099      HashedWheelTimer hashedWheelTimer = mock(HashedWheelTimer.class);
100      AsyncAdminBuilderBase asyncAdminBuilderBase = mock(AsyncAdminBuilderBase.class);
101      RawAsyncHBaseAdmin admin =
102        new RawAsyncHBaseAdmin(connection, hashedWheelTimer, asyncAdminBuilderBase);
103
104      CompletableFuture<Void> future = admin.compact(tableName, CompactType.NORMAL);
105      // future.get() throws ExecutionException, and the cause is TableNotFoundException
106      ExecutionException ex =
107        assertThrows(ExecutionException.class, () -> future.get(1, TimeUnit.SECONDS));
108      assertInstanceOf(TableNotFoundException.class, ex.getCause(),
109        "Expected TableNotFoundException as the cause of ExecutionException");
110
111      assertNull(msg.get(), "No error message should be logged");
112      assertNull(throwable.get(), "No Exception should be logged");
113    }
114  }
115}