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.ipc; 019 020import static org.junit.Assert.assertNotNull; 021import static org.junit.Assert.assertTrue; 022import static org.mockito.ArgumentMatchers.any; 023import static org.mockito.Mockito.doThrow; 024import static org.mockito.Mockito.mock; 025import static org.mockito.Mockito.when; 026 027import java.io.IOException; 028import java.net.InetAddress; 029import java.nio.ByteBuffer; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.ExtendedCellScanner; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseConfiguration; 034import org.apache.hadoop.hbase.codec.Codec; 035import org.apache.hadoop.hbase.io.ByteBuffAllocator; 036import org.apache.hadoop.hbase.testclassification.MediumTests; 037import org.apache.hadoop.hbase.testclassification.RPCTests; 038import org.junit.Before; 039import org.junit.ClassRule; 040import org.junit.Test; 041import org.junit.experimental.categories.Category; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045import org.apache.hbase.thirdparty.com.google.protobuf.BlockingService; 046import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors.MethodDescriptor; 047import org.apache.hbase.thirdparty.com.google.protobuf.Message; 048 049import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos; 050import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.RequestHeader; 051 052/** 053 * Test for ServerCall IOException handling in setResponse method. 054 */ 055@Category({ RPCTests.class, MediumTests.class }) 056public class TestServerCall { 057 058 @ClassRule 059 public static final HBaseClassTestRule CLASS_RULE = 060 HBaseClassTestRule.forClass(TestServerCall.class); 061 062 private static final Logger LOG = LoggerFactory.getLogger(TestServerCall.class); 063 064 private Configuration conf; 065 private NettyServerRpcConnection mockConnection; 066 private RequestHeader header; 067 private Message mockParam; 068 private ByteBuffAllocator mockAllocator; 069 private CellBlockBuilder mockCellBlockBuilder; 070 private InetAddress mockAddr; 071 private BlockingService mockService; 072 private MethodDescriptor mockMethodDescriptor; 073 074 @Before 075 public void setUp() throws Exception { 076 conf = HBaseConfiguration.create(); 077 mockConnection = mock(NettyServerRpcConnection.class); 078 header = RequestHeader.newBuilder().setCallId(1).setMethodName("testMethod") 079 .setRequestParam(true).build(); 080 mockParam = mock(Message.class); 081 mockAllocator = mock(ByteBuffAllocator.class); 082 mockCellBlockBuilder = mock(CellBlockBuilder.class); 083 mockAddr = mock(InetAddress.class); 084 085 mockMethodDescriptor = 086 org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService.getDescriptor() 087 .getMethods().get(0); 088 089 mockService = mock(BlockingService.class); 090 mockConnection.codec = mock(Codec.class); 091 092 when(mockAllocator.isReservoirEnabled()).thenReturn(false); 093 } 094 095 /** 096 * Test that when IOException occurs during response creation in setResponse, an error response is 097 * created and sent to the client instead of leaving the response as null. 098 */ 099 @Test 100 public void testSetResponseWithIOException() throws Exception { 101 // Create a CellBlockBuilder that throws IOException 102 CellBlockBuilder failingCellBlockBuilder = mock(CellBlockBuilder.class); 103 doThrow(new IOException("Test IOException during buildCellBlock")).when(failingCellBlockBuilder) 104 .buildCellBlock(any(), any(), any()); 105 106 // Create NettyServerCall instance 107 NettyServerCall call = new NettyServerCall(1, mockService, mockMethodDescriptor, header, 108 mockParam, null, mockConnection, 100, mockAddr, System.currentTimeMillis(), 60000, 109 mockAllocator, failingCellBlockBuilder, null); 110 111 // Set a successful response, but CellBlockBuilder will fail 112 Message mockResponse = mock(Message.class); 113 ExtendedCellScanner mockCellScanner = mock(ExtendedCellScanner.class); 114 115 LOG.info("Testing setResponse with IOException in buildCellBlock"); 116 call.setResponse(mockResponse, mockCellScanner, null, null); 117 118 // Verify that response is not null and contains error information 119 BufferChain response = call.getResponse(); 120 assertNotNull("Response should not be null even when IOException occurs", response); 121 assertTrue("Call should be marked as error", call.isError); 122 123 // Verify the response buffer is valid 124 ByteBuffer[] bufs = response.getBuffers(); 125 assertNotNull("Response buffers should not be null", bufs); 126 assertTrue("Response should have at least one buffer", bufs.length > 0); 127 } 128 129 /** 130 * Test the case where both normal response creation and error response creation fail with 131 * IOException. 132 */ 133 @Test 134 public void testSetResponseWithDoubleIOException() throws Exception { 135 136 CellBlockBuilder failingCellBlockBuilder = mock(CellBlockBuilder.class); 137 doThrow(new IOException("Test IOException")).when(failingCellBlockBuilder).buildCellBlock(any(), 138 any(), any()); 139 140 NettyServerCall call = new NettyServerCall(1, mockService, mockMethodDescriptor, header, 141 mockParam, null, mockConnection, 100, mockAddr, System.currentTimeMillis(), 60000, 142 mockAllocator, failingCellBlockBuilder, null); 143 144 Message mockResponse = mock(Message.class); 145 ExtendedCellScanner mockCellScanner = mock(ExtendedCellScanner.class); 146 147 // Even if error response creation might fail, the call should still be marked as error 148 call.setResponse(mockResponse, mockCellScanner, null, null); 149 assertTrue("Call should be marked as error", call.isError); 150 } 151 152 /** 153 * Test normal response creation to ensure our changes don't break the normal flow. 154 */ 155 @Test 156 public void testSetResponseNormalFlow() throws Exception { 157 CellBlockBuilder normalCellBlockBuilder = mock(CellBlockBuilder.class); 158 when(normalCellBlockBuilder.buildCellBlock(any(), any(), any())).thenReturn(null); 159 160 NettyServerCall call = new NettyServerCall(1, mockService, mockMethodDescriptor, header, 161 mockParam, null, mockConnection, 100, mockAddr, System.currentTimeMillis(), 60000, 162 mockAllocator, normalCellBlockBuilder, null); 163 164 RPCProtos.CellBlockMeta mockResponse = 165 RPCProtos.CellBlockMeta.newBuilder().setLength(0).build(); 166 167 LOG.info("Testing normal setResponse flow"); 168 call.setResponse(mockResponse, null, null, null); 169 170 BufferChain response = call.getResponse(); 171 assertNotNull("Response should not be null in normal flow", response); 172 assertTrue("Call should not be marked as error in normal flow", !call.isError); 173 } 174}