blob: d0d19c23eb95c9ffb94d5ad1e01cc65479ffc9d4 [file] [log] [blame]
/*
* 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.phoenix.query;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_FAMILY_NAME_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_TABLE_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_SCHEMA_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TTL_FOR_MUTEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.phoenix.SystemExitRule;
import org.apache.phoenix.exception.PhoenixIOException;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.monitoring.GlobalClientMetrics;
import org.apache.phoenix.util.ReadOnlyProps;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class ConnectionQueryServicesImplTest {
private static final PhoenixIOException PHOENIX_IO_EXCEPTION =
new PhoenixIOException(new Exception("Test exception"));
private TableDescriptor sysMutexTableDescCorrectTTL = TableDescriptorBuilder
.newBuilder(TableName.valueOf(SYSTEM_MUTEX_NAME))
.setColumnFamily(ColumnFamilyDescriptorBuilder
.newBuilder(SYSTEM_MUTEX_FAMILY_NAME_BYTES)
.setTimeToLive(TTL_FOR_MUTEX)
.build())
.build();
@ClassRule
public static final SystemExitRule SYSTEM_EXIT_RULE = new SystemExitRule();
@Mock
private ConnectionQueryServicesImpl mockCqs;
@Mock
private Admin mockAdmin;
@Mock
private ReadOnlyProps readOnlyProps;
@Mock
private Connection mockConn;
@Mock
private Table mockTable;
public static final TableDescriptorBuilder SYS_TASK_TDB = TableDescriptorBuilder
.newBuilder(TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_TASK_NAME));
public static final TableDescriptorBuilder SYS_TASK_TDB_SP = TableDescriptorBuilder
.newBuilder(TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_TASK_NAME))
.setRegionSplitPolicyClassName("abc");
@Before
public void setup() throws IOException, NoSuchFieldException,
IllegalAccessException, SQLException {
MockitoAnnotations.initMocks(this);
Field props = ConnectionQueryServicesImpl.class
.getDeclaredField("props");
props.setAccessible(true);
props.set(mockCqs, readOnlyProps);
props = ConnectionQueryServicesImpl.class.getDeclaredField("connection");
props.setAccessible(true);
props.set(mockCqs, mockConn);
when(mockCqs.checkIfSysMutexExistsAndModifyTTLIfRequired(mockAdmin))
.thenCallRealMethod();
when(mockCqs.updateAndConfirmSplitPolicyForTask(SYS_TASK_TDB))
.thenCallRealMethod();
when(mockCqs.updateAndConfirmSplitPolicyForTask(SYS_TASK_TDB_SP))
.thenCallRealMethod();
when(mockCqs.getSysMutexTable()).thenCallRealMethod();
when(mockCqs.getAdmin()).thenCallRealMethod();
when(mockCqs.getTable(Mockito.any())).thenCallRealMethod();
when(mockCqs.getTableIfExists(Mockito.any())).thenCallRealMethod();
}
@SuppressWarnings("unchecked")
@Test
public void testExceptionHandlingOnSystemNamespaceCreation() throws Exception {
// Invoke the real methods for these two calls
when(mockCqs.createSchema(any(List.class), anyString())).thenCallRealMethod();
doCallRealMethod().when(mockCqs).ensureSystemTablesMigratedToSystemNamespace();
// Do nothing for this method, just check that it was invoked later
doNothing().when(mockCqs).createSysMutexTableIfNotExists(any(Admin.class));
// Spoof out this call so that ensureSystemTablesUpgrade() will return-fast.
when(mockCqs.getSystemTableNamesInDefaultNamespace(any(Admin.class)))
.thenReturn(Collections.<TableName> emptyList());
// Throw a special exception to check on later
doThrow(PHOENIX_IO_EXCEPTION).when(mockCqs).ensureNamespaceCreated(anyString());
// Make sure that ensureSystemTablesMigratedToSystemNamespace will try to migrate
// the system tables.
Map<String,String> props = new HashMap<>();
props.put(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, "true");
when(mockCqs.getProps()).thenReturn(new ReadOnlyProps(props));
mockCqs.ensureSystemTablesMigratedToSystemNamespace();
// Should be called after upgradeSystemTables()
// Proves that execution proceeded
verify(mockCqs).getSystemTableNamesInDefaultNamespace(any());
try {
// Verifies that the exception is propagated back to the caller
mockCqs.createSchema(Collections.<Mutation> emptyList(), "");
} catch (PhoenixIOException e) {
assertEquals(PHOENIX_IO_EXCEPTION, e);
}
}
@Test
public void testGetNextRegionStartKey() {
RegionInfo mockHRegionInfo = org.mockito.Mockito.mock(RegionInfo.class);
RegionInfo mockPrevHRegionInfo = org.mockito.Mockito.mock(RegionInfo.class);
HRegionLocation mockRegionLocation = org.mockito.Mockito.mock(HRegionLocation.class);
HRegionLocation mockPrevRegionLocation = org.mockito.Mockito.mock(HRegionLocation.class);
ConnectionQueryServicesImpl mockCqsi =
org.mockito.Mockito.mock(ConnectionQueryServicesImpl.class,
org.mockito.Mockito.CALLS_REAL_METHODS);
byte[] corruptedStartAndEndKey = "0x3000".getBytes();
byte[] corruptedDecreasingKey = "0x2999".getBytes();
byte[] corruptedNewEndKey = "0x3001".getBytes();
byte[] notCorruptedStartKey = "0x2999".getBytes();
byte[] notCorruptedEndKey = "0x3000".getBytes();
byte[] notCorruptedNewKey = "0x3001".getBytes();
byte[] mockTableName = "dummyTable".getBytes();
when(mockRegionLocation.getRegion()).thenReturn(mockHRegionInfo);
when(mockHRegionInfo.getRegionName()).thenReturn(mockTableName);
when(mockPrevRegionLocation.getRegion()).thenReturn(mockPrevHRegionInfo);
when(mockPrevHRegionInfo.getRegionName()).thenReturn(mockTableName);
// comparing the current regionInfo endKey is equal to the previous endKey
// [0x3000, Ox3000) vs 0x3000
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(corruptedStartAndEndKey);
when(mockHRegionInfo.getEndKey()).thenReturn(corruptedStartAndEndKey);
when(mockPrevHRegionInfo.getEndKey()).thenReturn(corruptedStartAndEndKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedStartAndEndKey, true,
mockPrevRegionLocation);
// comparing the current regionInfo endKey is less than previous endKey
// [0x3000,0x2999) vs 0x3000
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(corruptedStartAndEndKey);
when(mockHRegionInfo.getEndKey()).thenReturn(corruptedDecreasingKey);
when(mockPrevHRegionInfo.getEndKey()).thenReturn(corruptedStartAndEndKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedStartAndEndKey, true,
mockPrevRegionLocation);
// comparing the current regionInfo endKey is greater than the previous endKey
// [0x2999,0x3001) vs 0x3000.
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(corruptedDecreasingKey);
when(mockHRegionInfo.getEndKey()).thenReturn(corruptedNewEndKey);
when(mockPrevHRegionInfo.getEndKey()).thenReturn(corruptedStartAndEndKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedStartAndEndKey, true,
mockPrevRegionLocation);
// comparing the current regionInfo startKey is greater than the previous endKey leading to a hole
// [0x3000,0x3001) vs 0x2999
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(corruptedStartAndEndKey);
when(mockHRegionInfo.getEndKey()).thenReturn(corruptedNewEndKey);
when(mockPrevHRegionInfo.getEndKey()).thenReturn(corruptedDecreasingKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedDecreasingKey, true,
mockPrevRegionLocation);
// comparing the current regionInfo startKey is less than the previous endKey leading to an overlap
// [0x2999,0x3001) vs 0x3000.
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(corruptedDecreasingKey);
when(mockHRegionInfo.getEndKey()).thenReturn(corruptedNewEndKey);
when(mockPrevHRegionInfo.getEndKey()).thenReturn(corruptedStartAndEndKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, corruptedStartAndEndKey, true,
mockPrevRegionLocation);
// comparing the current regionInfo startKey is equal to the previous endKey
// [0x3000,0x3001) vs 0x3000
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(corruptedStartAndEndKey);
when(mockHRegionInfo.getEndKey()).thenReturn(notCorruptedNewKey);
when(mockPrevHRegionInfo.getEndKey()).thenReturn(notCorruptedEndKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, notCorruptedEndKey, false,
mockPrevRegionLocation);
// test EMPTY_START_ROW
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(HConstants.EMPTY_START_ROW);
when(mockHRegionInfo.getEndKey()).thenReturn(notCorruptedEndKey);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, HConstants.EMPTY_START_ROW, false,
null);
//test EMPTY_END_ROW
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric().reset();
when(mockHRegionInfo.getStartKey()).thenReturn(notCorruptedStartKey);
when(mockHRegionInfo.getEndKey()).thenReturn(HConstants.EMPTY_END_ROW);
testGetNextRegionStartKey(mockCqsi, mockRegionLocation, notCorruptedStartKey, false, null);
}
private void testGetNextRegionStartKey(ConnectionQueryServicesImpl mockCqsi,
HRegionLocation mockRegionLocation, byte[] key, boolean isCorrupted,
HRegionLocation mockPrevRegionLocation) {
mockCqsi.getNextRegionStartKey(mockRegionLocation, key, mockPrevRegionLocation);
assertEquals(isCorrupted ? 1 : 0,
GlobalClientMetrics.GLOBAL_HBASE_COUNTER_METADATA_INCONSISTENCY.getMetric()
.getValue());
}
@Test
public void testSysMutexCheckReturnsFalseWhenTableAbsent() throws Exception {
// Override the getDescriptor() call to throw instead
doThrow(new TableNotFoundException())
.when(mockAdmin)
.getDescriptor(TableName.valueOf(SYSTEM_MUTEX_NAME));
doThrow(new TableNotFoundException())
.when(mockAdmin)
.getDescriptor(TableName.valueOf(SYSTEM_SCHEMA_NAME, SYSTEM_MUTEX_TABLE_NAME));
assertFalse(mockCqs.checkIfSysMutexExistsAndModifyTTLIfRequired(mockAdmin));
}
@Test
public void testSysMutexCheckModifiesTTLWhenWrong() throws Exception {
// Set the wrong TTL
TableDescriptor sysMutexTableDescWrongTTL = TableDescriptorBuilder
.newBuilder(TableName.valueOf(SYSTEM_MUTEX_NAME))
.setColumnFamily(ColumnFamilyDescriptorBuilder
.newBuilder(SYSTEM_MUTEX_FAMILY_NAME_BYTES)
.setTimeToLive(HConstants.FOREVER)
.build())
.build();
when(mockAdmin.getDescriptor(TableName.valueOf(SYSTEM_MUTEX_NAME)))
.thenReturn(sysMutexTableDescWrongTTL);
assertTrue(mockCqs.checkIfSysMutexExistsAndModifyTTLIfRequired(mockAdmin));
verify(mockAdmin, Mockito.times(1)).modifyTable(sysMutexTableDescCorrectTTL);
}
@Test
public void testSysMutexCheckDoesNotModifyTableDescWhenTTLCorrect() throws Exception {
when(mockAdmin.getDescriptor(TableName.valueOf(SYSTEM_MUTEX_NAME)))
.thenReturn(sysMutexTableDescCorrectTTL);
assertTrue(mockCqs.checkIfSysMutexExistsAndModifyTTLIfRequired(mockAdmin));
verify(mockAdmin, Mockito.times(0)).modifyTable(any(TableDescriptor.class));
}
@Test
public void testSysTaskSplitPolicy() throws Exception {
assertTrue(mockCqs.updateAndConfirmSplitPolicyForTask(SYS_TASK_TDB));
assertFalse(mockCqs.updateAndConfirmSplitPolicyForTask(SYS_TASK_TDB));
}
@Test
public void testSysTaskSplitPolicyWithError() {
try {
mockCqs.updateAndConfirmSplitPolicyForTask(SYS_TASK_TDB_SP);
fail("Split policy for SYSTEM.TASK cannot be updated");
} catch (SQLException e) {
assertEquals("ERROR 908 (43M19): REGION SPLIT POLICY is incorrect."
+ " Region split policy for table TASK is expected to be "
+ "among: [null, org.apache.phoenix.schema.SystemTaskSplitPolicy]"
+ " , actual split policy: abc tableName=SYSTEM.TASK",
e.getMessage());
}
}
@Test
public void testGetSysMutexTableWithName() throws Exception {
when(mockAdmin.tableExists(any())).thenReturn(true);
when(mockConn.getAdmin()).thenReturn(mockAdmin);
when(mockConn.getTable(TableName.valueOf("SYSTEM.MUTEX")))
.thenReturn(mockTable);
assertSame(mockCqs.getSysMutexTable(), mockTable);
verify(mockAdmin, Mockito.times(1)).tableExists(any());
verify(mockConn, Mockito.times(1)).getAdmin();
verify(mockConn, Mockito.times(1))
.getTable(TableName.valueOf("SYSTEM.MUTEX"));
}
@Test
public void testGetSysMutexTableWithNamespace() throws Exception {
when(mockAdmin.tableExists(any())).thenReturn(false);
when(mockConn.getAdmin()).thenReturn(mockAdmin);
when(mockConn.getTable(TableName.valueOf("SYSTEM:MUTEX")))
.thenReturn(mockTable);
assertSame(mockCqs.getSysMutexTable(), mockTable);
verify(mockAdmin, Mockito.times(1)).tableExists(any());
verify(mockConn, Mockito.times(1)).getAdmin();
verify(mockConn, Mockito.times(1))
.getTable(TableName.valueOf("SYSTEM:MUTEX"));
}
}