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}