/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.ignite.yarn;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import junit.framework.TestCase;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.yarn.api.protocolrecords.RegisterApplicationMasterResponse;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.Container;
import org.apache.hadoop.yarn.api.records.ContainerId;
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
import org.apache.hadoop.yarn.api.records.ContainerStatus;
import org.apache.hadoop.yarn.api.records.FinalApplicationStatus;
import org.apache.hadoop.yarn.api.records.NodeId;
import org.apache.hadoop.yarn.api.records.Priority;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.client.api.AMRMClient;
import org.apache.hadoop.yarn.client.api.NMClient;
import org.apache.hadoop.yarn.client.api.async.AMRMClientAsync;
import org.apache.hadoop.yarn.exceptions.YarnException;
/**
* Application master tests.
*/
public class IgniteApplicationMasterSelfTest extends TestCase {
/** */
private ApplicationMaster appMaster;
/** */
private ClusterProperties props;
/** */
private RMMock rmMock = new RMMock();
/**
* @throws Exception If failed.
*/
@Override protected void setUp() throws Exception {
super.setUp();
props = new ClusterProperties();
appMaster = new ApplicationMaster("test", props);
appMaster.setSchedulerTimeout(10000);
rmMock.clear();
}
/**
* @throws Exception If failed.
*/
public void testContainerAllocate() throws Exception {
appMaster.setRmClient(rmMock);
appMaster.setNmClient(new NMMock());
props.cpusPerNode(2);
props.memoryPerNode(1024);
props.instances(3);
Thread thread = runAppMaster(appMaster);
List<AMRMClient.ContainerRequest> contRequests = collectRequests(rmMock, 2, 1000);
interruptedThread(thread);
assertEquals(3, contRequests.size());
for (AMRMClient.ContainerRequest req : contRequests) {
assertEquals(2, req.getCapability().getVirtualCores());
assertEquals(1024, req.getCapability().getMemory());
}
}
/**
* Tests whether memory overhead is allocated within container memory.
*
* @throws Exception If failed.
*/
public void testMemoryOverHeadAllocation() throws Exception {
appMaster.setRmClient(rmMock);
appMaster.setNmClient(new NMMock());
props.cpusPerNode(2);
props.memoryPerNode(1024);
props.memoryOverHeadPerNode(512);
props.instances(3);
Thread thread = runAppMaster(appMaster);
List<AMRMClient.ContainerRequest> contRequests = collectRequests(rmMock, 1, 1000);
interruptedThread(thread);
assertEquals(3, contRequests.size());
for (AMRMClient.ContainerRequest req : contRequests) {
assertEquals(2, req.getCapability().getVirtualCores());
assertEquals(1024 + 512, req.getCapability().getMemory());
}
}
/**
* Tests whether memory overhead prevents from allocating container.
*
* @throws Exception If failed.
*/
public void testMemoryOverHeadPreventAllocation() throws Exception {
rmMock.availableRes(new MockResource(1024, 2));
appMaster.setRmClient(rmMock);
appMaster.setNmClient(new NMMock());
props.cpusPerNode(2);
props.memoryPerNode(1024);
props.memoryOverHeadPerNode(512);
props.instances(3);
Thread thread = runAppMaster(appMaster);
List<AMRMClient.ContainerRequest> contRequests = collectRequests(rmMock, 1, 1000);
interruptedThread(thread);
assertEquals(0, contRequests.size());
}
/**
* @throws Exception If failed.
*/
public void testClusterResource() throws Exception {
rmMock.availableRes(new MockResource(1024, 2));
appMaster.setRmClient(rmMock);
appMaster.setNmClient(new NMMock());
props.cpusPerNode(8);
props.memoryPerNode(10240);
props.instances(3);
Thread thread = runAppMaster(appMaster);
List<AMRMClient.ContainerRequest> contRequests = collectRequests(rmMock, 1, 1000);
interruptedThread(thread);
assertEquals(0, contRequests.size());
}
/**
* @throws Exception If failed.
*/
public void testClusterAllocatedResource() throws Exception {
rmMock.availableRes(new MockResource(1024, 2));
appMaster.setRmClient(rmMock);
appMaster.setNmClient(new NMMock());
appMaster.setFs(new MockFileSystem());
props.cpusPerNode(8);
props.memoryPerNode(5000);
props.instances(3);
// Check that container resources
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 5, 2000)));
assertEquals(0, appMaster.getContainers().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 10, 2000)));
assertEquals(0, appMaster.getContainers().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 1, 7000)));
assertEquals(0, appMaster.getContainers().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 8, 5000)));
assertEquals(1, appMaster.getContainers().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 10, 7000)));
assertEquals(2, appMaster.getContainers().size());
}
/**
* @throws Exception If failed.
*/
public void testStartReleaseContainer() throws Exception {
rmMock.availableRes(new MockResource(1024, 2));
NMMock nmClient = new NMMock();
appMaster.setRmClient(rmMock);
appMaster.setNmClient(nmClient);
appMaster.setFs(new MockFileSystem());
props.cpusPerNode(8);
props.memoryPerNode(5000);
props.instances(3);
// Check that container resources
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 5, 2000)));
assertEquals(1, rmMock.releasedResources().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 5, 7000)));
assertEquals(2, rmMock.releasedResources().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 9, 2000)));
assertEquals(3, rmMock.releasedResources().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 8, 5000)));
assertEquals(3, rmMock.releasedResources().size());
assertEquals(1, nmClient.startedContainer().size());
}
/**
* @throws Exception If failed.
*/
public void testHostnameConstraint() throws Exception {
rmMock.availableRes(new MockResource(1024, 2));
NMMock nmClient = new NMMock();
appMaster.setRmClient(rmMock);
appMaster.setNmClient(nmClient);
appMaster.setFs(new MockFileSystem());
props.cpusPerNode(8);
props.memoryPerNode(5000);
props.instances(3);
props.hostnameConstraint(Pattern.compile("ignoreHost"));
// Check that container resources
appMaster.onContainersAllocated(Collections.singletonList(createContainer("simple", 8, 5000)));
assertEquals(0, rmMock.releasedResources().size());
assertEquals(1, nmClient.startedContainer().size());
appMaster.onContainersAllocated(Collections.singletonList(createContainer("ignoreHost", 8, 5000)));
assertEquals(1, rmMock.releasedResources().size());
assertEquals(1, nmClient.startedContainer().size());
}
/**
* @throws Exception If failed.
*/
public void testContainerEnvironment() throws Exception {
props.memoryPerNode(1001);
props.memoryOverHeadPerNode(2002);
// Properties are used to initialize AM container environment
Map<String, String> result = props.toEnvs();
assertEquals(1001, (int) Double.parseDouble(result.get(ClusterProperties.IGNITE_MEMORY_PER_NODE)));
assertEquals(2002, (int) Double.parseDouble(result.get(ClusterProperties.IGNITE_MEMORY_OVERHEAD_PER_NODE)));
}
/**
* @param host Host.
* @param cpu Cpu count.
* @param mem Memory.
* @return Container.
*/
private Container createContainer(String host, int cpu, int mem) {
return Container.newInstance(
ContainerId.newContainerId(ApplicationAttemptId.newInstance(ApplicationId.newInstance(0l, 0), 0),
ThreadLocalRandom.current().nextLong()),
NodeId.newInstance(host, 0),
"example.com",
new MockResource(mem, cpu),
Priority.newInstance(0),
null
);
}
/**
* @param rmMock RM mock.
* @param expectedCnt Expected cnt.
* @param timeOut Timeout.
* @return Requests.
*/
private List<AMRMClient.ContainerRequest> collectRequests(RMMock rmMock, int expectedCnt, int timeOut) {
long startTime = System.currentTimeMillis();
List<AMRMClient.ContainerRequest> requests = rmMock.requests();
while (requests.size() < expectedCnt
&& (System.currentTimeMillis() - startTime) < timeOut)
requests = rmMock.requests();
return requests;
}
/**
* Runs appMaster other thread.
*
* @param appMaster Application master.
* @return Thread.
*/
private static Thread runAppMaster(final ApplicationMaster appMaster) {
Thread thread = new Thread(new Runnable() {
@Override public void run() {
try {
appMaster.run();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
});
thread.start();
return thread;
}
/**
* Interrupt thread and join.
*
* @param thread Thread.
*/
private static void interruptedThread(Thread thread) throws InterruptedException {
thread.interrupt();
thread.join();
}
/**
* Resource manager mock.
*/
private static class RMMock extends AMRMClientAsync {
/** */
private List<AMRMClient.ContainerRequest> contRequests = new ArrayList<>();
/** */
private List<ContainerId> releasedConts = new ArrayList<>();
/** */
private Resource availableRes;
/** */
public RMMock() {
super(0, null);
}
/**
* @return Requests.
*/
public List<AMRMClient.ContainerRequest> requests() {
return contRequests;
}
/**
* @return Released resources.
*/
public List<ContainerId> releasedResources() {
return releasedConts;
}
/**
* Sets resource.
*
* @param availableRes Available resource.
*/
public void availableRes(Resource availableRes) {
this.availableRes = availableRes;
}
/**
* Clear internal state.
*/
public void clear() {
contRequests.clear();
releasedConts.clear();
availableRes = null;
}
/** {@inheritDoc} */
@Override public List<? extends Collection> getMatchingRequests(Priority priority, String resourceName,
Resource capability) {
return null;
}
/** {@inheritDoc} */
@Override public RegisterApplicationMasterResponse registerApplicationMaster(String appHostName,
int appHostPort, String appTrackingUrl) throws YarnException, IOException {
return null;
}
/** {@inheritDoc} */
@Override public void unregisterApplicationMaster(FinalApplicationStatus appStatus, String appMessage,
String appTrackingUrl) throws YarnException, IOException {
// No-op.
}
/** {@inheritDoc} */
@Override public void addContainerRequest(AMRMClient.ContainerRequest req) {
contRequests.add(req);
}
/** {@inheritDoc} */
@Override public void removeContainerRequest(AMRMClient.ContainerRequest req) {
// No-op.
}
/** {@inheritDoc} */
@Override public void releaseAssignedContainer(ContainerId containerId) {
releasedConts.add(containerId);
}
/** {@inheritDoc} */
@Override public Resource getAvailableResources() {
return availableRes;
}
/** {@inheritDoc} */
@Override public int getClusterNodeCount() {
return 0;
}
/**
* Update application's blacklist with addition or removal resources.
*
* @param blacklistAdditions list of resources which should be added to the
* application blacklist
* @param blacklistRemovals list of resources which should be removed from the
* application blacklist
*/
public void updateBlacklist(List blacklistAdditions, List blacklistRemovals) {
// No-op.
}
}
/**
* Network manager mock.
*/
public static class NMMock extends NMClient {
/** */
private List<ContainerLaunchContext> startedContainer = new ArrayList<>();
/** */
public NMMock() {
super("name");
}
/**
* @return Started containers.
*/
public List<ContainerLaunchContext> startedContainer() {
return startedContainer;
}
/** {@inheritDoc} */
@Override public Map<String, ByteBuffer> startContainer(Container container,
ContainerLaunchContext containerLaunchContext) throws YarnException, IOException {
startedContainer.add(containerLaunchContext);
return null;
}
/** {@inheritDoc} */
@Override public void stopContainer(ContainerId containerId, NodeId nodeId) throws YarnException, IOException {
// No-op.
}
/** {@inheritDoc} */
@Override public ContainerStatus getContainerStatus(ContainerId containerId, NodeId nodeId)
throws YarnException, IOException {
return null;
}
/** {@inheritDoc} */
@Override public void cleanupRunningContainersOnStop(boolean enabled) {
// No-op.
}
}
/**
* Resource.
*/
public static class MockResource extends Resource {
/** Memory. */
private int mem;
/** CPU. */
private int cpu;
/**
* @param mem Memory.
* @param cpu CPU.
*/
public MockResource(int mem, int cpu) {
this.mem = mem;
this.cpu = cpu;
}
/** {@inheritDoc} */
@Override public int getMemory() {
return mem;
}
/** {@inheritDoc} */
@Override public void setMemory(int memory) {
this.mem = memory;
}
/** {@inheritDoc} */
@Override public int getVirtualCores() {
return cpu;
}
/** {@inheritDoc} */
@Override public void setVirtualCores(int vCores) {
this.cpu = vCores;
}
/** {@inheritDoc} */
@Override public int compareTo(Resource resource) {
return 0;
}
}
/**
* Mock file system.
*/
public static class MockFileSystem extends FileSystem {
/** */
public MockFileSystem() {
}
/** {@inheritDoc} */
@Override public Path makeQualified(Path path) {
return new Path("/test/path");
}
/** {@inheritDoc} */
@Override public FileStatus getFileStatus(Path f) throws IOException {
return new FileStatus();
}
/** {@inheritDoc} */
@Override public boolean mkdirs(Path f, FsPermission permission) throws IOException {
return false;
}
/** {@inheritDoc} */
@Override public Path getWorkingDirectory() {
return null;
}
/** {@inheritDoc} */
@Override public void setWorkingDirectory(Path new_dir) {
// No-op.
}
/** {@inheritDoc} */
@Override public FileStatus[] listStatus(Path f) throws FileNotFoundException, IOException {
return new FileStatus[0];
}
/** {@inheritDoc} */
@Override public boolean delete(Path f, boolean recursive) throws IOException {
return false;
}
/** {@inheritDoc} */
@Override public boolean rename(Path src, Path dst) throws IOException {
return false;
}
/** {@inheritDoc} */
@Override public FSDataOutputStream append(Path f, int bufferSize, Progressable progress) throws IOException {
return null;
}
/** {@inheritDoc} */
@Override public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize,
short replication, long blockSize, Progressable progress) throws IOException {
return null;
}
/** {@inheritDoc} */
@Override public FSDataInputStream open(Path f, int bufferSize) throws IOException {
return null;
}
/** {@inheritDoc} */
@Override public URI getUri() {
return null;
}
}
}