|
@@ -0,0 +1,348 @@
|
|
|
+/**
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
+ * contributor license agreements. See the NOTICE file distributed with this
|
|
|
+ * work for additional information regarding copyright ownership. The ASF
|
|
|
+ * licenses this file to you under the Apache License, Version 2.0 (the
|
|
|
+ * "License"); you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ * <p>
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ * <p>
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
+ * License for the specific language governing permissions and limitations under
|
|
|
+ * the License.
|
|
|
+ */
|
|
|
+
|
|
|
+package org.apache.hadoop.fs.ozone;
|
|
|
+
|
|
|
+import org.apache.commons.lang3.RandomStringUtils;
|
|
|
+import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
|
|
|
+import org.apache.hadoop.fs.FileSystem;
|
|
|
+import org.apache.hadoop.fs.FsShell;
|
|
|
+import org.apache.hadoop.fs.Path;
|
|
|
+import org.apache.hadoop.hdds.HddsConfigKeys;
|
|
|
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
|
|
|
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
|
|
|
+import org.apache.hadoop.ozone.MiniOzoneCluster;
|
|
|
+import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
|
|
|
+import org.apache.hadoop.ozone.OmUtils;
|
|
|
+import org.apache.hadoop.ozone.OzoneConfigKeys;
|
|
|
+import org.apache.hadoop.ozone.OzoneConsts;
|
|
|
+import org.apache.hadoop.ozone.client.ObjectStore;
|
|
|
+import org.apache.hadoop.ozone.client.OzoneClientFactory;
|
|
|
+import org.apache.hadoop.ozone.client.OzoneVolume;
|
|
|
+import org.apache.hadoop.ozone.om.OMConfigKeys;
|
|
|
+import org.apache.hadoop.ozone.om.OMStorage;
|
|
|
+import org.apache.hadoop.ozone.om.OzoneManager;
|
|
|
+import org.apache.hadoop.test.GenericTestUtils;
|
|
|
+import org.apache.hadoop.util.ToolRunner;
|
|
|
+import org.apache.ratis.util.LifeCycle;
|
|
|
+import org.hamcrest.core.StringContains;
|
|
|
+import org.junit.After;
|
|
|
+import org.junit.Assert;
|
|
|
+import org.junit.Before;
|
|
|
+import org.junit.Test;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Optional;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import static org.apache.hadoop.hdds.HddsUtils.getHostName;
|
|
|
+import static org.apache.hadoop.hdds.HddsUtils.getHostPort;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Test client-side URI handling with Ozone Manager HA.
|
|
|
+ */
|
|
|
+public class TestOzoneFsHAURLs {
|
|
|
+ public static final Logger LOG = LoggerFactory.getLogger(
|
|
|
+ TestOzoneFsHAURLs.class);
|
|
|
+
|
|
|
+ private OzoneConfiguration conf;
|
|
|
+ private MiniOzoneCluster cluster;
|
|
|
+ private String omId;
|
|
|
+ private String omServiceId;
|
|
|
+ private String clusterId;
|
|
|
+ private String scmId;
|
|
|
+ private OzoneManager om;
|
|
|
+ private int numOfOMs;
|
|
|
+
|
|
|
+ private String volumeName;
|
|
|
+ private String bucketName;
|
|
|
+ private String rootPath;
|
|
|
+
|
|
|
+ private final String o3fsImplKey =
|
|
|
+ "fs." + OzoneConsts.OZONE_URI_SCHEME + ".impl";
|
|
|
+ private final String o3fsImplValue =
|
|
|
+ "org.apache.hadoop.fs.ozone.OzoneFileSystem";
|
|
|
+
|
|
|
+ private static final long LEADER_ELECTION_TIMEOUT = 500L;
|
|
|
+
|
|
|
+ @Before
|
|
|
+ public void init() throws Exception {
|
|
|
+ conf = new OzoneConfiguration();
|
|
|
+ omId = UUID.randomUUID().toString();
|
|
|
+ omServiceId = "om-service-test1";
|
|
|
+ numOfOMs = 3;
|
|
|
+ clusterId = UUID.randomUUID().toString();
|
|
|
+ scmId = UUID.randomUUID().toString();
|
|
|
+ final String path = GenericTestUtils.getTempPath(omId);
|
|
|
+ java.nio.file.Path metaDirPath = java.nio.file.Paths.get(path, "om-meta");
|
|
|
+ conf.setBoolean(OzoneConfigKeys.OZONE_ENABLED, true);
|
|
|
+ conf.set(HddsConfigKeys.OZONE_METADATA_DIRS, metaDirPath.toString());
|
|
|
+ conf.set(ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY, "127.0.0.1:0");
|
|
|
+ conf.setBoolean(OMConfigKeys.OZONE_OM_RATIS_ENABLE_KEY, true);
|
|
|
+ conf.setTimeDuration(
|
|
|
+ OMConfigKeys.OZONE_OM_LEADER_ELECTION_MINIMUM_TIMEOUT_DURATION_KEY,
|
|
|
+ LEADER_ELECTION_TIMEOUT, TimeUnit.MILLISECONDS);
|
|
|
+
|
|
|
+ OMStorage omStore = new OMStorage(conf);
|
|
|
+ omStore.setClusterId(clusterId);
|
|
|
+ omStore.setScmId(scmId);
|
|
|
+ // writes the version file properties
|
|
|
+ omStore.initialize();
|
|
|
+
|
|
|
+ // Start the cluster
|
|
|
+ cluster = MiniOzoneCluster.newHABuilder(conf)
|
|
|
+ .setClusterId(clusterId)
|
|
|
+ .setScmId(scmId)
|
|
|
+ .setOMServiceId(omServiceId)
|
|
|
+ .setNumOfOzoneManagers(numOfOMs)
|
|
|
+ .build();
|
|
|
+ cluster.waitForClusterToBeReady();
|
|
|
+
|
|
|
+ om = cluster.getOzoneManager();
|
|
|
+ Assert.assertEquals(LifeCycle.State.RUNNING, om.getOmRatisServerState());
|
|
|
+
|
|
|
+ volumeName = "volume" + RandomStringUtils.randomNumeric(5);
|
|
|
+ ObjectStore objectStore =
|
|
|
+ OzoneClientFactory.getRpcClient(omServiceId, conf).getObjectStore();
|
|
|
+ objectStore.createVolume(volumeName);
|
|
|
+
|
|
|
+ OzoneVolume retVolumeinfo = objectStore.getVolume(volumeName);
|
|
|
+ bucketName = "bucket" + RandomStringUtils.randomNumeric(5);
|
|
|
+ retVolumeinfo.createBucket(bucketName);
|
|
|
+
|
|
|
+ rootPath = String.format("%s://%s.%s.%s/", OzoneConsts.OZONE_URI_SCHEME,
|
|
|
+ bucketName, volumeName, omServiceId);
|
|
|
+ // Set fs.defaultFS
|
|
|
+ conf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, rootPath);
|
|
|
+ FileSystem fs = FileSystem.get(conf);
|
|
|
+ // Create some dirs
|
|
|
+ Path root = new Path("/");
|
|
|
+ Path dir1 = new Path(root, "dir1");
|
|
|
+ Path dir12 = new Path(dir1, "dir12");
|
|
|
+ Path dir2 = new Path(root, "dir2");
|
|
|
+ fs.mkdirs(dir12);
|
|
|
+ fs.mkdirs(dir2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @After
|
|
|
+ public void shutdown() {
|
|
|
+ if (cluster != null) {
|
|
|
+ cluster.shutdown();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return the leader OM's RPC address in the MiniOzoneHACluster
|
|
|
+ */
|
|
|
+ private String getLeaderOMNodeAddr() {
|
|
|
+ String leaderOMNodeAddr = null;
|
|
|
+ Collection<String> omNodeIds = OmUtils.getOMNodeIds(conf, omServiceId);
|
|
|
+ assert(omNodeIds.size() == numOfOMs);
|
|
|
+ MiniOzoneHAClusterImpl haCluster = (MiniOzoneHAClusterImpl) cluster;
|
|
|
+ // Note: this loop may be implemented inside MiniOzoneHAClusterImpl
|
|
|
+ for (String omNodeId : omNodeIds) {
|
|
|
+ // Find the leader OM
|
|
|
+ if (!haCluster.getOzoneManager(omNodeId).isLeader()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // ozone.om.address.omServiceId.omNode
|
|
|
+ String leaderOMNodeAddrKey = OmUtils.addKeySuffixes(
|
|
|
+ OMConfigKeys.OZONE_OM_ADDRESS_KEY, omServiceId, omNodeId);
|
|
|
+ leaderOMNodeAddr = conf.get(leaderOMNodeAddrKey);
|
|
|
+ LOG.info("Found leader OM: nodeId=" + omNodeId + ", " +
|
|
|
+ leaderOMNodeAddrKey + "=" + leaderOMNodeAddr);
|
|
|
+ // Leader found, no need to continue loop
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // There has to be a leader
|
|
|
+ assert(leaderOMNodeAddr != null);
|
|
|
+ return leaderOMNodeAddr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get host name from an address. This uses getHostName() internally.
|
|
|
+ * @param addr Address with port number
|
|
|
+ * @return Host name
|
|
|
+ */
|
|
|
+ private String getHostFromAddress(String addr) {
|
|
|
+ Optional<String> hostOptional = getHostName(addr);
|
|
|
+ assert(hostOptional.isPresent());
|
|
|
+ return hostOptional.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get port number from an address. This uses getHostPort() internally.
|
|
|
+ * @param addr Address with port
|
|
|
+ * @return Port number
|
|
|
+ */
|
|
|
+ private int getPortFromAddress(String addr) {
|
|
|
+ Optional<Integer> portOptional = getHostPort(addr);
|
|
|
+ assert(portOptional.isPresent());
|
|
|
+ return portOptional.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test OM HA URLs with qualified fs.defaultFS.
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testWithQualifiedDefaultFS() throws Exception {
|
|
|
+ OzoneConfiguration clientConf = new OzoneConfiguration(conf);
|
|
|
+ clientConf.setQuietMode(false);
|
|
|
+ clientConf.set(o3fsImplKey, o3fsImplValue);
|
|
|
+ // fs.defaultFS = o3fs://bucketName.volumeName.omServiceId/
|
|
|
+ clientConf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, rootPath);
|
|
|
+
|
|
|
+ // Pick leader OM's RPC address and assign it to ozone.om.address for
|
|
|
+ // the test case: ozone fs -ls o3fs://bucket.volume.om1/
|
|
|
+ String leaderOMNodeAddr = getLeaderOMNodeAddr();
|
|
|
+ // ozone.om.address was set to service id in MiniOzoneHAClusterImpl
|
|
|
+ clientConf.set(OMConfigKeys.OZONE_OM_ADDRESS_KEY, leaderOMNodeAddr);
|
|
|
+
|
|
|
+ FsShell shell = new FsShell(clientConf);
|
|
|
+ int res;
|
|
|
+ try {
|
|
|
+ // Test case 1: ozone fs -ls /
|
|
|
+ // Expectation: Success.
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", "/"});
|
|
|
+ // Check return value, should be 0 (success)
|
|
|
+ Assert.assertEquals(res, 0);
|
|
|
+
|
|
|
+ // Test case 2: ozone fs -ls o3fs:///
|
|
|
+ // Expectation: Success. fs.defaultFS is a fully qualified path.
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", "o3fs:///"});
|
|
|
+ Assert.assertEquals(res, 0);
|
|
|
+
|
|
|
+ // Test case 3: ozone fs -ls o3fs://bucket.volume/
|
|
|
+ // Expectation: Fail. Must have service id or host name when HA is enabled
|
|
|
+ String unqualifiedPath1 = String.format("%s://%s.%s/",
|
|
|
+ OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName);
|
|
|
+ try (GenericTestUtils.SystemErrCapturer capture =
|
|
|
+ new GenericTestUtils.SystemErrCapturer()) {
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", unqualifiedPath1});
|
|
|
+ // Check stderr, inspired by testDFSWithInvalidCommmand
|
|
|
+ Assert.assertThat("Command did not print the error message " +
|
|
|
+ "correctly for test case: ozone fs -ls o3fs://bucket.volume/",
|
|
|
+ capture.getOutput(), StringContains.containsString(
|
|
|
+ "-ls: Service ID or host name must not"
|
|
|
+ + " be omitted when ozone.om.service.ids is defined."));
|
|
|
+ }
|
|
|
+ // Check return value, should be -1 (failure)
|
|
|
+ Assert.assertEquals(res, -1);
|
|
|
+
|
|
|
+ // Test case 4: ozone fs -ls o3fs://bucket.volume.om1/
|
|
|
+ // Expectation: Success. The client should use the port number
|
|
|
+ // set in ozone.om.address.
|
|
|
+ String qualifiedPath1 = String.format("%s://%s.%s.%s/",
|
|
|
+ OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName,
|
|
|
+ getHostFromAddress(leaderOMNodeAddr));
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", qualifiedPath1});
|
|
|
+ // Note: this test case will fail if the port is not from the leader node
|
|
|
+ Assert.assertEquals(res, 0);
|
|
|
+
|
|
|
+ // Test case 5: ozone fs -ls o3fs://bucket.volume.om1:port/
|
|
|
+ // Expectation: Success.
|
|
|
+ String qualifiedPath2 = String.format("%s://%s.%s.%s/",
|
|
|
+ OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName,
|
|
|
+ leaderOMNodeAddr);
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", qualifiedPath2});
|
|
|
+ Assert.assertEquals(res, 0);
|
|
|
+
|
|
|
+ // Test case 6: ozone fs -ls o3fs://bucket.volume.id1/
|
|
|
+ // Expectation: Success.
|
|
|
+ String qualifiedPath3 = String.format("%s://%s.%s.%s/",
|
|
|
+ OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName, omServiceId);
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", qualifiedPath3});
|
|
|
+ Assert.assertEquals(res, 0);
|
|
|
+
|
|
|
+ // Test case 7: ozone fs -ls o3fs://bucket.volume.id1:port/
|
|
|
+ // Expectation: Fail. Service ID does not use port information.
|
|
|
+ // Use the port number from leader OM (doesn't really matter)
|
|
|
+ String unqualifiedPath2 = String.format("%s://%s.%s.%s:%d/",
|
|
|
+ OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName,
|
|
|
+ omServiceId, getPortFromAddress(leaderOMNodeAddr));
|
|
|
+ try (GenericTestUtils.SystemErrCapturer capture =
|
|
|
+ new GenericTestUtils.SystemErrCapturer()) {
|
|
|
+ res = ToolRunner.run(shell, new String[] {"-ls", unqualifiedPath2});
|
|
|
+ // Check stderr
|
|
|
+ Assert.assertThat("Command did not print the error message " +
|
|
|
+ "correctly for test case: "
|
|
|
+ + "ozone fs -ls o3fs://bucket.volume.id1:port/",
|
|
|
+ capture.getOutput(), StringContains.containsString(
|
|
|
+ "does not use port information"));
|
|
|
+ }
|
|
|
+ // Check return value, should be -1 (failure)
|
|
|
+ Assert.assertEquals(res, -1);
|
|
|
+ } finally {
|
|
|
+ shell.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper function for testOtherDefaultFS(),
|
|
|
+ * run fs -ls o3fs:/// against different fs.defaultFS input.
|
|
|
+ *
|
|
|
+ * @param defaultFS Desired fs.defaultFS to be used in the test
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ private void testWithDefaultFS(String defaultFS) throws Exception {
|
|
|
+ OzoneConfiguration clientConf = new OzoneConfiguration(conf);
|
|
|
+ clientConf.setQuietMode(false);
|
|
|
+ clientConf.set(o3fsImplKey, o3fsImplValue);
|
|
|
+ // fs.defaultFS = file:///
|
|
|
+ clientConf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY,
|
|
|
+ defaultFS);
|
|
|
+
|
|
|
+ FsShell shell = new FsShell(clientConf);
|
|
|
+ try {
|
|
|
+ // Test case: ozone fs -ls o3fs:///
|
|
|
+ // Expectation: Fail. fs.defaultFS is not a qualified o3fs URI.
|
|
|
+ int res = ToolRunner.run(shell, new String[] {"-ls", "o3fs:///"});
|
|
|
+ Assert.assertEquals(res, -1);
|
|
|
+ } finally {
|
|
|
+ shell.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test OM HA URLs with some unqualified fs.defaultFS.
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testOtherDefaultFS() throws Exception {
|
|
|
+ // Test scenarios where fs.defaultFS isn't a fully qualified o3fs
|
|
|
+
|
|
|
+ // fs.defaultFS = file:///
|
|
|
+ testWithDefaultFS(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_DEFAULT);
|
|
|
+
|
|
|
+ // fs.defaultFS = hdfs://ns1/
|
|
|
+ testWithDefaultFS("hdfs://ns1/");
|
|
|
+
|
|
|
+ // fs.defaultFS = o3fs:///
|
|
|
+ String unqualifiedFs1 = String.format(
|
|
|
+ "%s:///", OzoneConsts.OZONE_URI_SCHEME);
|
|
|
+ testWithDefaultFS(unqualifiedFs1);
|
|
|
+
|
|
|
+ // fs.defaultFS = o3fs://bucketName.volumeName/
|
|
|
+ String unqualifiedFs2 = String.format("%s://%s.%s/",
|
|
|
+ OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName);
|
|
|
+ testWithDefaultFS(unqualifiedFs2);
|
|
|
+ }
|
|
|
+}
|