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