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}