blob: ab351913d7a017c44f9a909c0e8618173e2d4d5d [file] [log] [blame]
/**
* 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);
}
}