/*
* 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.hive.hcatalog.streaming.mutate.client.lock;
import static org.apache.hadoop.hive.metastore.api.LockState.ABORT;
import static org.apache.hadoop.hive.metastore.api.LockState.ACQUIRED;
import static org.apache.hadoop.hive.metastore.api.LockState.NOT_ACQUIRED;
import static org.apache.hadoop.hive.metastore.api.LockState.WAITING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.metastore.IMetaStoreClient;
import org.apache.hadoop.hive.metastore.api.DataOperationType;
import org.apache.hadoop.hive.metastore.api.LockComponent;
import org.apache.hadoop.hive.metastore.api.LockLevel;
import org.apache.hadoop.hive.metastore.api.LockRequest;
import org.apache.hadoop.hive.metastore.api.LockResponse;
import org.apache.hadoop.hive.metastore.api.LockType;
import org.apache.hadoop.hive.metastore.api.NoSuchLockException;
import org.apache.hadoop.hive.metastore.api.NoSuchTxnException;
import org.apache.hadoop.hive.metastore.api.Table;
import org.apache.hadoop.hive.metastore.api.TxnAbortedException;
import org.apache.thrift.TException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import com.google.common.collect.ImmutableSet;
@RunWith(MockitoJUnitRunner.class)
public class TestLock {
private static final Table SOURCE_TABLE_1 = createTable("DB", "SOURCE_1");
private static final Table SOURCE_TABLE_2 = createTable("DB", "SOURCE_2");
private static final Table SINK_TABLE = createTable("DB", "SINK");
private static final Set<Table> SOURCES = ImmutableSet.of(SOURCE_TABLE_1, SOURCE_TABLE_2);
private static final Set<Table> SINKS = ImmutableSet.of(SINK_TABLE);
private static final Set<Table> TABLES = ImmutableSet.of(SOURCE_TABLE_1, SOURCE_TABLE_2, SINK_TABLE);
private static final long LOCK_ID = 42;
private static final long TRANSACTION_ID = 109;
private static final String USER = "ewest";
@Mock
private IMetaStoreClient mockMetaStoreClient;
@Mock
private LockFailureListener mockListener;
@Mock
private LockResponse mockLockResponse;
@Mock
private HeartbeatFactory mockHeartbeatFactory;
@Mock
private Timer mockHeartbeat;
@Captor
private ArgumentCaptor<LockRequest> requestCaptor;
private Lock readLock;
private Lock writeLock;
private HiveConf configuration = new HiveConf();
@Before
public void injectMocks() throws Exception {
when(mockMetaStoreClient.lock(any(LockRequest.class))).thenReturn(mockLockResponse);
when(mockLockResponse.getLockid()).thenReturn(LOCK_ID);
when(mockLockResponse.getState()).thenReturn(ACQUIRED);
when(
mockHeartbeatFactory.newInstance(any(IMetaStoreClient.class), any(LockFailureListener.class), any(Long.class),
any(Collection.class), anyLong(), anyInt())).thenReturn(mockHeartbeat);
readLock = new Lock(mockMetaStoreClient, mockHeartbeatFactory, configuration, mockListener, USER, SOURCES,
Collections.<Table> emptySet(), 3, 0);
writeLock = new Lock(mockMetaStoreClient, mockHeartbeatFactory, configuration, mockListener, USER, SOURCES, SINKS,
3, 0);
}
@Test
public void testAcquireReadLockWithNoIssues() throws Exception {
readLock.acquire();
assertEquals(Long.valueOf(LOCK_ID), readLock.getLockId());
assertNull(readLock.getTransactionId());
}
@Test(expected = IllegalArgumentException.class)
public void testAcquireWriteLockWithoutTxn() throws Exception {
writeLock.acquire();
}
@Test(expected = IllegalArgumentException.class)
public void testAcquireWriteLockWithInvalidTxn() throws Exception {
writeLock.acquire(0);
}
@Test
public void testAcquireTxnLockWithNoIssues() throws Exception {
writeLock.acquire(TRANSACTION_ID);
assertEquals(Long.valueOf(LOCK_ID), writeLock.getLockId());
assertEquals(Long.valueOf(TRANSACTION_ID), writeLock.getTransactionId());
}
@Test
public void testAcquireReadLockCheckHeartbeatCreated() throws Exception {
configuration.set("hive.txn.timeout", "100s");
readLock.acquire();
verify(mockHeartbeatFactory).newInstance(eq(mockMetaStoreClient), eq(mockListener), any(Long.class), eq(SOURCES),
eq(LOCK_ID), eq(75));
}
@Test
public void testAcquireTxnLockCheckHeartbeatCreated() throws Exception {
configuration.set("hive.txn.timeout", "100s");
writeLock.acquire(TRANSACTION_ID);
verify(mockHeartbeatFactory).newInstance(eq(mockMetaStoreClient), eq(mockListener), eq(TRANSACTION_ID),
eq(TABLES), eq(LOCK_ID), eq(75));
}
@Test
public void testAcquireLockCheckUser() throws Exception {
readLock.acquire();
verify(mockMetaStoreClient).lock(requestCaptor.capture());
LockRequest actualRequest = requestCaptor.getValue();
assertEquals(USER, actualRequest.getUser());
}
@Test
public void testAcquireReadLockCheckLocks() throws Exception {
readLock.acquire();
verify(mockMetaStoreClient).lock(requestCaptor.capture());
LockRequest request = requestCaptor.getValue();
assertEquals(0, request.getTxnid());
assertEquals(USER, request.getUser());
assertEquals(InetAddress.getLocalHost().getHostName(), request.getHostname());
List<LockComponent> components = request.getComponent();
assertEquals(2, components.size());
LockComponent expected1 = new LockComponent(LockType.SHARED_READ, LockLevel.TABLE, "DB");
expected1.setTablename("SOURCE_1");
expected1.setOperationType(DataOperationType.INSERT);
expected1.setIsAcid(true);
assertTrue(components.contains(expected1));
LockComponent expected2 = new LockComponent(LockType.SHARED_READ, LockLevel.TABLE, "DB");
expected2.setTablename("SOURCE_2");
expected2.setOperationType(DataOperationType.INSERT);
expected2.setIsAcid(true);
assertTrue(components.contains(expected2));
}
@Test
public void testAcquireTxnLockCheckLocks() throws Exception {
writeLock.acquire(TRANSACTION_ID);
verify(mockMetaStoreClient).lock(requestCaptor.capture());
LockRequest request = requestCaptor.getValue();
assertEquals(TRANSACTION_ID, request.getTxnid());
assertEquals(USER, request.getUser());
assertEquals(InetAddress.getLocalHost().getHostName(), request.getHostname());
List<LockComponent> components = request.getComponent();
assertEquals(3, components.size());
LockComponent expected1 = new LockComponent(LockType.SHARED_READ, LockLevel.TABLE, "DB");
expected1.setTablename("SOURCE_1");
expected1.setOperationType(DataOperationType.INSERT);
expected1.setIsAcid(true);
assertTrue(components.contains(expected1));
LockComponent expected2 = new LockComponent(LockType.SHARED_READ, LockLevel.TABLE, "DB");
expected2.setTablename("SOURCE_2");
expected2.setOperationType(DataOperationType.INSERT);
expected2.setIsAcid(true);
assertTrue(components.contains(expected2));
LockComponent expected3 = new LockComponent(LockType.SHARED_WRITE, LockLevel.TABLE, "DB");
expected3.setTablename("SINK");
expected3.setOperationType(DataOperationType.UPDATE);
expected3.setIsAcid(true);
assertTrue(components.contains(expected3));
}
@Test(expected = LockException.class)
public void testAcquireLockNotAcquired() throws Exception {
when(mockLockResponse.getState()).thenReturn(NOT_ACQUIRED);
readLock.acquire();
}
@Test(expected = LockException.class)
public void testAcquireLockAborted() throws Exception {
when(mockLockResponse.getState()).thenReturn(ABORT);
readLock.acquire();
}
@Test(expected = LockException.class)
public void testAcquireLockWithWaitRetriesExceeded() throws Exception {
when(mockLockResponse.getState()).thenReturn(WAITING, WAITING, WAITING);
readLock.acquire();
}
@Test
public void testAcquireLockWithWaitRetries() throws Exception {
when(mockLockResponse.getState()).thenReturn(WAITING, WAITING, ACQUIRED);
readLock.acquire();
assertEquals(Long.valueOf(LOCK_ID), readLock.getLockId());
}
@Test
public void testReleaseLock() throws Exception {
readLock.acquire();
readLock.release();
verify(mockMetaStoreClient).unlock(LOCK_ID);
}
@Test
public void testReleaseLockNoLock() throws Exception {
readLock.release();
verifyNoMoreInteractions(mockMetaStoreClient);
}
@Test
public void testReleaseLockCancelsHeartbeat() throws Exception {
readLock.acquire();
readLock.release();
verify(mockHeartbeat).cancel();
}
@Test
public void testReadHeartbeat() throws Exception {
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, null, SOURCES, LOCK_ID);
task.run();
verify(mockMetaStoreClient).heartbeat(0, LOCK_ID);
}
@Test
public void testTxnHeartbeat() throws Exception {
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, TRANSACTION_ID, SOURCES,
LOCK_ID);
task.run();
verify(mockMetaStoreClient).heartbeat(TRANSACTION_ID, LOCK_ID);
}
@Test
public void testReadHeartbeatFailsNoSuchLockException() throws Exception {
Throwable t = new NoSuchLockException();
doThrow(t).when(mockMetaStoreClient).heartbeat(0, LOCK_ID);
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, null, SOURCES, LOCK_ID);
task.run();
verify(mockListener).lockFailed(LOCK_ID, null, Lock.asStrings(SOURCES), t);
}
@Test
public void testTxnHeartbeatFailsNoSuchLockException() throws Exception {
Throwable t = new NoSuchLockException();
doThrow(t).when(mockMetaStoreClient).heartbeat(TRANSACTION_ID, LOCK_ID);
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, TRANSACTION_ID, SOURCES,
LOCK_ID);
task.run();
verify(mockListener).lockFailed(LOCK_ID, TRANSACTION_ID, Lock.asStrings(SOURCES), t);
}
@Test
public void testHeartbeatFailsNoSuchTxnException() throws Exception {
Throwable t = new NoSuchTxnException();
doThrow(t).when(mockMetaStoreClient).heartbeat(TRANSACTION_ID, LOCK_ID);
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, TRANSACTION_ID, SOURCES,
LOCK_ID);
task.run();
verify(mockListener).lockFailed(LOCK_ID, TRANSACTION_ID, Lock.asStrings(SOURCES), t);
}
@Test
public void testHeartbeatFailsTxnAbortedException() throws Exception {
Throwable t = new TxnAbortedException();
doThrow(t).when(mockMetaStoreClient).heartbeat(TRANSACTION_ID, LOCK_ID);
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, TRANSACTION_ID, SOURCES,
LOCK_ID);
task.run();
verify(mockListener).lockFailed(LOCK_ID, TRANSACTION_ID, Lock.asStrings(SOURCES), t);
}
@Test
public void testHeartbeatContinuesTException() throws Exception {
Throwable t = new TException();
doThrow(t).when(mockMetaStoreClient).heartbeat(0, LOCK_ID);
HeartbeatTimerTask task = new HeartbeatTimerTask(mockMetaStoreClient, mockListener, TRANSACTION_ID, SOURCES,
LOCK_ID);
task.run();
verifyZeroInteractions(mockListener);
}
private static Table createTable(String databaseName, String tableName) {
Table table = new Table();
table.setDbName(databaseName);
table.setTableName(tableName);
return table;
}
}