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.regionserver;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024import static org.junit.jupiter.api.Assertions.fail;
025
026import java.net.InetAddress;
027import java.net.NetworkInterface;
028import java.util.ArrayList;
029import java.util.Enumeration;
030import java.util.List;
031import java.util.Locale;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.hbase.HBaseConfiguration;
034import org.apache.hadoop.hbase.HBaseTestingUtil;
035import org.apache.hadoop.hbase.StartTestingClusterOption;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.testclassification.RegionServerTests;
038import org.apache.hadoop.hbase.util.DNS;
039import org.apache.hadoop.hbase.zookeeper.ZKUtil;
040import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
041import org.junit.jupiter.api.AfterEach;
042import org.junit.jupiter.api.BeforeEach;
043import org.junit.jupiter.api.Tag;
044import org.junit.jupiter.api.Test;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048/**
049 * Tests for the hostname specification by region server
050 */
051@Tag(RegionServerTests.TAG)
052@Tag(MediumTests.TAG)
053public class TestRegionServerHostname {
054
055  private static final Logger LOG = LoggerFactory.getLogger(TestRegionServerHostname.class);
056
057  private HBaseTestingUtil TEST_UTIL;
058
059  private static final int NUM_MASTERS = 1;
060  private static final int NUM_RS = 1;
061
062  @BeforeEach
063  public void setup() {
064    Configuration conf = HBaseConfiguration.create();
065    TEST_UTIL = new HBaseTestingUtil(conf);
066  }
067
068  @AfterEach
069  public void teardown() throws Exception {
070    TEST_UTIL.shutdownMiniCluster();
071  }
072
073  @Test
074  public void testInvalidRegionServerHostnameAbortsServer() throws Exception {
075    String invalidHostname = "hostAddr.invalid";
076    TEST_UTIL.getConfiguration().set(DNS.UNSAFE_RS_HOSTNAME_KEY, invalidHostname);
077    HRegionServer hrs = null;
078    try {
079      hrs = new HRegionServer(TEST_UTIL.getConfiguration());
080    } catch (IllegalArgumentException iae) {
081      assertTrue(iae.getMessage().contains("Failed resolve of " + invalidHostname)
082        || iae.getMessage().contains("Problem binding to " + invalidHostname), iae.getMessage());
083    }
084    assertNull(hrs, "Failed to validate against invalid hostname");
085  }
086
087  @Test
088  public void testRegionServerHostname() throws Exception {
089    for (NetworkInterface ni : getValidNetworkInterfaces()) {
090      Enumeration<InetAddress> addrList = ni.getInetAddresses();
091      // iterate through host addresses and use each as hostname
092      while (addrList.hasMoreElements()) {
093        InetAddress addr = addrList.nextElement();
094        if (
095          addr.isLoopbackAddress() || addr.isLinkLocalAddress() || addr.isMulticastAddress()
096            || !addr.isSiteLocalAddress()
097        ) {
098          continue;
099        }
100        String hostName = addr.getHostName();
101        LOG.info("Found " + hostName + " on " + ni + ", addr=" + addr);
102
103        TEST_UTIL.getConfiguration().set(DNS.MASTER_HOSTNAME_KEY, hostName);
104        TEST_UTIL.getConfiguration().set(DNS.UNSAFE_RS_HOSTNAME_KEY, hostName);
105        StartTestingClusterOption option = StartTestingClusterOption.builder()
106          .numMasters(NUM_MASTERS).numRegionServers(NUM_RS).numDataNodes(NUM_RS).build();
107        TEST_UTIL.startMiniCluster(option);
108        try {
109          ZKWatcher zkw = TEST_UTIL.getZooKeeperWatcher();
110          List<String> servers = ZKUtil.listChildrenNoWatch(zkw, zkw.getZNodePaths().rsZNode);
111          assertEquals(NUM_RS, servers.size());
112          for (String server : servers) {
113            assertTrue(server.startsWith(hostName.toLowerCase(Locale.ROOT) + ","),
114              "From zookeeper: " + server + " hostname: " + hostName);
115          }
116          zkw.close();
117        } finally {
118          TEST_UTIL.shutdownMiniCluster();
119        }
120      }
121    }
122  }
123
124  @Test
125  public void testDeprecatedConfigs() throws Exception {
126    Configuration conf = TEST_UTIL.getConfiguration();
127    new HRegionServer(conf);
128    conf.setBoolean(HRegionServer.RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, false);
129    assertFalse(
130      conf.getBoolean(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, true));
131    conf.setBoolean(HRegionServer.RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, true);
132    assertTrue(
133      conf.getBoolean(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, false));
134    conf.setBoolean(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, true);
135    assertTrue(conf.getBoolean(HRegionServer.RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, false));
136    conf.setBoolean(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, false);
137    assertFalse(conf.getBoolean(HRegionServer.RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, true));
138
139    conf.setBoolean(DNS.RS_HOSTNAME_KEY, false);
140    assertFalse(conf.getBoolean(DNS.UNSAFE_RS_HOSTNAME_KEY, true));
141    conf.setBoolean(DNS.RS_HOSTNAME_KEY, true);
142    assertTrue(conf.getBoolean(DNS.UNSAFE_RS_HOSTNAME_KEY, false));
143    conf.setBoolean(DNS.UNSAFE_RS_HOSTNAME_KEY, true);
144    assertTrue(conf.getBoolean(DNS.RS_HOSTNAME_KEY, false));
145    conf.setBoolean(DNS.UNSAFE_RS_HOSTNAME_KEY, false);
146    assertFalse(conf.getBoolean(DNS.RS_HOSTNAME_KEY, true));
147  }
148
149  @Test
150  public void testConflictRegionServerHostnameConfigurationsAbortServer() throws Exception {
151    Enumeration<NetworkInterface> netInterfaceList = NetworkInterface.getNetworkInterfaces();
152    while (netInterfaceList.hasMoreElements()) {
153      NetworkInterface ni = netInterfaceList.nextElement();
154      Enumeration<InetAddress> addrList = ni.getInetAddresses();
155      // iterate through host addresses and use each as hostname
156      while (addrList.hasMoreElements()) {
157        InetAddress addr = addrList.nextElement();
158        if (addr.isLoopbackAddress() || addr.isLinkLocalAddress() || addr.isMulticastAddress()) {
159          continue;
160        }
161        String hostName = addr.getHostName();
162        LOG.info("Found " + hostName + " on " + ni);
163
164        TEST_UTIL.getConfiguration().set(DNS.MASTER_HOSTNAME_KEY, hostName);
165        // "hbase.unsafe.regionserver.hostname" and
166        // "hbase.unsafe.regionserver.hostname.disable.master.reversedns"
167        // are mutually exclusive. Exception should be thrown if both are used.
168        TEST_UTIL.getConfiguration().set(DNS.UNSAFE_RS_HOSTNAME_KEY, hostName);
169        TEST_UTIL.getConfiguration()
170          .setBoolean(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, true);
171        try {
172          StartTestingClusterOption option = StartTestingClusterOption.builder()
173            .numMasters(NUM_MASTERS).numRegionServers(NUM_RS).numDataNodes(NUM_RS).build();
174          TEST_UTIL.startMiniCluster(option);
175        } catch (Exception e) {
176          Throwable t1 = e.getCause();
177          Throwable t2 = t1.getCause();
178          assertTrue(
179            t2.getMessage()
180              .contains(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY + " and "
181                + DNS.UNSAFE_RS_HOSTNAME_KEY + " are mutually exclusive"),
182            t1.getMessage() + " - " + t2.getMessage());
183          return;
184        } finally {
185          TEST_UTIL.shutdownMiniCluster();
186        }
187        fail("Failed to validate against conflict hostname configurations");
188      }
189    }
190  }
191
192  @Test
193  public void testRegionServerHostnameReportedToMaster() throws Exception {
194    TEST_UTIL.getConfiguration()
195      .setBoolean(HRegionServer.UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY, true);
196    StartTestingClusterOption option = StartTestingClusterOption.builder().numMasters(NUM_MASTERS)
197      .numRegionServers(NUM_RS).numDataNodes(NUM_RS).build();
198    TEST_UTIL.startMiniCluster(option);
199    int expectedRS = NUM_RS;
200    try (ZKWatcher zkw = TEST_UTIL.getZooKeeperWatcher()) {
201      List<String> servers = ZKUtil.listChildrenNoWatch(zkw, zkw.getZNodePaths().rsZNode);
202      assertEquals(expectedRS, servers.size());
203    }
204  }
205
206  private boolean ignoreNetworkInterface(NetworkInterface networkInterface) throws Exception {
207    return networkInterface == null || networkInterface.isLoopback() || networkInterface.isVirtual()
208      || !networkInterface.isUp();
209  }
210
211  private List<NetworkInterface> getValidNetworkInterfaces() throws Exception {
212    List<NetworkInterface> validNetworkInterfaces = new ArrayList<>();
213    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
214    while (interfaces.hasMoreElements()) {
215      NetworkInterface networkInterface = interfaces.nextElement();
216      if (ignoreNetworkInterface(networkInterface)) {
217        continue;
218      }
219      validNetworkInterfaces.add(networkInterface);
220    }
221    return validNetworkInterfaces;
222  }
223}