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;
019
020import static org.junit.jupiter.api.Assertions.assertNotNull;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022import static org.junit.jupiter.api.Assertions.fail;
023
024import java.io.IOException;
025import javax.management.remote.JMXConnector;
026import javax.management.remote.JMXConnectorFactory;
027import javax.naming.ServiceUnavailableException;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.client.Admin;
030import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
031import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
032import org.apache.hadoop.hbase.coprocessor.ObserverContext;
033import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
034import org.apache.hadoop.hbase.security.AccessDeniedException;
035import org.apache.hadoop.hbase.security.access.AccessController;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.testclassification.MiscTests;
038import org.junit.jupiter.api.AfterAll;
039import org.junit.jupiter.api.AfterEach;
040import org.junit.jupiter.api.BeforeAll;
041import org.junit.jupiter.api.BeforeEach;
042import org.junit.jupiter.api.Tag;
043import org.junit.jupiter.api.Test;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047/**
048 * Test case for JMX Connector Server.
049 */
050@Tag(MiscTests.TAG)
051@Tag(MediumTests.TAG)
052public class TestJMXConnectorServer {
053
054  private static final Logger LOG = LoggerFactory.getLogger(TestJMXConnectorServer.class);
055  private static HBaseTestingUtil UTIL = new HBaseTestingUtil();
056
057  private static Configuration conf = null;
058  private static Admin admin;
059  // RMI registry port
060  private static int rmiRegistryPort;
061  // Switch for customized Accesscontroller to throw ACD exception while executing test case
062  private volatile static boolean hasAccess;
063
064  @BeforeAll
065  public static void setUpBeforeClass() throws Exception {
066    conf = UTIL.getConfiguration();
067    String cps = JMXListener.class.getName() + "," + MyAccessController.class.getName();
068    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, cps);
069    conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, cps);
070    rmiRegistryPort = UTIL.randomFreePort();
071    conf.setInt("master.rmi.registry.port", rmiRegistryPort);
072    conf.setInt("regionserver.rmi.registry.port", rmiRegistryPort);
073    UTIL.startMiniCluster();
074    admin = UTIL.getConnection().getAdmin();
075  }
076
077  @AfterAll
078  public static void tearDownAfterClass() throws Exception {
079    admin.close();
080    UTIL.shutdownMiniCluster();
081  }
082
083  @BeforeEach
084  public void setUp() {
085    hasAccess = false;
086  }
087
088  @AfterEach
089  public void tearDown() {
090    hasAccess = true;
091  }
092
093  /**
094   * This tests to validate the HMaster's ConnectorServer after unauthorised stopMaster call.
095   */
096  @Test
097  public void testHMConnectorServerWhenStopMaster() throws Exception {
098    // try to stop master
099    boolean accessDenied = false;
100    try {
101      LOG.info("Stopping HMaster...");
102      admin.stopMaster();
103    } catch (AccessDeniedException e) {
104      LOG.info("Exception occurred while stopping HMaster. ", e);
105      accessDenied = true;
106    }
107    assertTrue(accessDenied);
108
109    checkConnector();
110  }
111
112  /**
113   * This tests to validate the RegionServer's ConnectorServer after unauthorised stopRegionServer
114   * call.
115   */
116  @Test
117  public void testRSConnectorServerWhenStopRegionServer() throws Exception {
118    ServerName serverName = UTIL.getHBaseCluster().getRegionServer(0).getServerName();
119    LOG.info("Stopping Region Server...");
120    admin.stopRegionServer(serverName.getHostname() + ":" + serverName.getPort());
121
122    checkConnector();
123  }
124
125  /**
126   * This tests to validate the HMaster's ConnectorServer after unauthorised shutdown call.
127   */
128  @Test
129  public void testHMConnectorServerWhenShutdownCluster() throws Exception {
130    boolean accessDenied = false;
131    try {
132      LOG.info("Stopping HMaster...");
133      admin.shutdown();
134    } catch (AccessDeniedException e) {
135      LOG.error("Exception occurred while stopping HMaster. ", e);
136      accessDenied = true;
137    }
138    assertTrue(accessDenied);
139
140    checkConnector();
141  }
142
143  private void checkConnector() throws Exception {
144    // Check whether HMaster JMX Connector server can be connected
145    JMXConnector connector = null;
146    try {
147      connector = JMXConnectorFactory
148        .connect(JMXListener.buildJMXServiceURL(rmiRegistryPort, rmiRegistryPort));
149    } catch (IOException e) {
150      if (e.getCause() instanceof ServiceUnavailableException) {
151        fail("Can't connect to ConnectorServer.");
152      }
153    }
154    assertNotNull(connector, "JMXConnector should not be null.");
155    connector.close();
156  }
157
158  /*
159   * Customized class for test case execution which will throw ACD exception while executing
160   * stopMaster/preStopRegionServer/preShutdown explicitly.
161   */
162  public static class MyAccessController extends AccessController {
163    @Override
164    public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) {
165      // Do nothing. In particular, stop the creation of the hbase:acl table. It makes the
166      // shutdown take time.
167    }
168
169    @Override
170    public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException {
171      if (!hasAccess) {
172        throw new AccessDeniedException("Insufficient permissions to stop master");
173      }
174    }
175
176    @Override
177    public void preStopRegionServer(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
178      throws IOException {
179      if (!hasAccess) {
180        throw new AccessDeniedException("Insufficient permissions to stop region server.");
181      }
182    }
183
184    @Override
185    public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c) throws IOException {
186      if (!hasAccess) {
187        throw new AccessDeniedException("Insufficient permissions to shut down cluster.");
188      }
189    }
190
191    @Override
192    public void preExecuteProcedures(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
193      throws IOException {
194      // FIXME: ignore the procedure permission check since in our UT framework master is neither
195      // the systemuser nor the superuser so we can not call executeProcedures...
196    }
197  }
198}