YARN-9476. [YARN-9473] Create unit tests for VE plugin. Contributed by Peter Bacsko.

This commit is contained in:
Zhankun Tang 2019-04-30 11:06:44 +08:00
parent 24f218aef8
commit 7fbaa7d66f
2 changed files with 419 additions and 2 deletions

View File

@ -74,7 +74,7 @@ public NECVEPlugin() throws ResourceHandlerException {
if (envScriptName != null) { if (envScriptName != null) {
binaryName = envScriptName; binaryName = envScriptName;
} }
LOG.info("Use {} as script name.", envScriptName); LOG.info("Use {} as script name.", binaryName);
// Try to find the script based on an environment variable, if set // Try to find the script based on an environment variable, if set
boolean found = false; boolean found = false;
@ -115,11 +115,13 @@ public NECVEPlugin() throws ResourceHandlerException {
} }
} }
@Override
public DeviceRegisterRequest getRegisterRequestInfo() { public DeviceRegisterRequest getRegisterRequestInfo() {
return DeviceRegisterRequest.Builder.newInstance() return DeviceRegisterRequest.Builder.newInstance()
.setResourceName("nec.com/ve").build(); .setResourceName("nec.com/ve").build();
} }
@Override
public Set<Device> getDevices() { public Set<Device> getDevices() {
Set<Device> devices = null; Set<Device> devices = null;
@ -135,6 +137,7 @@ public Set<Device> getDevices() {
return devices; return devices;
} }
@Override
public DeviceRuntimeSpec onDevicesAllocated(Set<Device> set, public DeviceRuntimeSpec onDevicesAllocated(Set<Device> set,
YarnRuntimeType yarnRuntimeType) { YarnRuntimeType yarnRuntimeType) {
return null; return null;
@ -151,6 +154,7 @@ private Set<Device> parseOutput(String output) {
LOG.info("Parsing output: {}", output); LOG.info("Parsing output: {}", output);
String[] lines = output.split("\n"); String[] lines = output.split("\n");
outer:
for (String line : lines) { for (String line : lines) {
Device.Builder builder = Device.Builder.newInstance(); Device.Builder builder = Device.Builder.newInstance();
@ -163,7 +167,7 @@ private Set<Device> parseOutput(String output) {
String[] tokens = keyValue.trim().split("="); String[] tokens = keyValue.trim().split("=");
if (tokens.length != 2) { if (tokens.length != 2) {
LOG.error("Unknown format of script output! Skipping this line"); LOG.error("Unknown format of script output! Skipping this line");
continue; continue outer;
} }
final String key = tokens[0]; final String key = tokens[0];

View File

@ -0,0 +1,413 @@
/**
* 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.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.com.nec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.apache.commons.io.FileUtils;
import org.apache.curator.shaded.com.google.common.collect.Lists;
import org.apache.hadoop.util.Shell.CommandExecutor;
import org.apache.hadoop.yarn.server.nodemanager.api.deviceplugin.Device;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.resources.ResourceHandlerException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Unit tests for NECVEPlugin class.
*
*/
@RunWith(MockitoJUnitRunner.class)
public class TestNECVEPlugin {
private static final String DEFAULT_SCRIPT_NAME = "nec-ve-get.py";
private static final String[] EMPTY_SEARCH_DIRS = new String[] {};
private static final Comparator<Device> DEVICE_COMPARATOR =
Comparator.comparingInt(Device::getId);
private Function<String, String> envProvider;
private Map<String, String> env;
private String[] defaultSearchDirs;
private Function<String[], CommandExecutor>
commandExecutorProvider;
private String testFolder;
@Mock
private CommandExecutor mockCommandExecutor;
private String defaultScriptOutput;
private NECVEPlugin plugin;
@Before
public void setup() throws IOException {
env = new HashMap<>();
envProvider = (String var) -> env.get(var);
commandExecutorProvider = (String[] cmd) -> mockCommandExecutor;
// default output of MockCommandExecutor - single device
defaultScriptOutput = getOutputForDevice(
0,
"/dev/ve0",
"ONLINE",
"0000:65:00.0",
243,
0);
}
@After
public void teardown() throws IOException {
if (testFolder != null) {
File f = new File(testFolder);
FileUtils.deleteDirectory(f);
}
}
@Test
public void testParseScriptOutput()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
plugin.setCommandExecutorProvider(commandExecutorProvider);
when(mockCommandExecutor.getOutput()).thenReturn(defaultScriptOutput);
Set<Device> devices = plugin.getDevices();
assertEquals("Number of devices", 1, devices.size());
Device device = devices.iterator().next();
assertEquals("Device id", 0, device.getId());
assertEquals("Device path", "/dev/ve0", device.getDevPath());
assertEquals("Bus Id", "0000:65:00.0", device.getBusID());
assertEquals("Status", "ONLINE", device.getStatus());
assertEquals("Major number", 243, device.getMajorNumber());
assertEquals("Minor number", 0, device.getMinorNumber());
}
@Test
public void testParseMultipleDevices()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
plugin.setCommandExecutorProvider(commandExecutorProvider);
defaultScriptOutput += "\n";
defaultScriptOutput += getOutputForDevice(1,
"/dev/ve1",
"ONLINE",
"0000:66:00.0",
244,
1);
defaultScriptOutput += "\n";
defaultScriptOutput += getOutputForDevice(2,
"/dev/ve2",
"ONLINE",
"0000:67:00.0",
245,
2);
when(mockCommandExecutor.getOutput()).thenReturn(defaultScriptOutput);
Set<Device> devices = plugin.getDevices();
assertEquals("Number of devices", 3, devices.size());
List<Device> devicesList = Lists.newArrayList(devices);
// Sort devices by id
Collections.sort(devicesList, DEVICE_COMPARATOR);
Device device0 = devicesList.get(0);
assertEquals("Device id", 0, device0.getId());
assertEquals("Device path", "/dev/ve0", device0.getDevPath());
assertEquals("Bus Id", "0000:65:00.0", device0.getBusID());
assertEquals("Status", "ONLINE", device0.getStatus());
assertEquals("Major number", 243, device0.getMajorNumber());
assertEquals("Minor number", 0, device0.getMinorNumber());
Device device1 = devicesList.get(1);
assertEquals("Device id", 1, device1.getId());
assertEquals("Device path", "/dev/ve1", device1.getDevPath());
assertEquals("Bus Id", "0000:66:00.0", device1.getBusID());
assertEquals("Status", "ONLINE", device1.getStatus());
assertEquals("Major number", 244, device1.getMajorNumber());
assertEquals("Minor number", 1, device1.getMinorNumber());
Device device2 = devicesList.get(2);
assertEquals("Device id", 2, device2.getId());
assertEquals("Device path", "/dev/ve2", device2.getDevPath());
assertEquals("Bus Id", "0000:67:00.0", device2.getBusID());
assertEquals("Status", "ONLINE", device2.getStatus());
assertEquals("Major number", 245, device2.getMajorNumber());
assertEquals("Minor number", 2, device2.getMinorNumber());
}
@Test
public void testOfflineDeviceIsSkipped()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
plugin.setCommandExecutorProvider(commandExecutorProvider);
defaultScriptOutput = getOutputForDevice(
0,
"/dev/ve0",
"OFFLINE",
"0000:65:00.0",
243,
0);
when(mockCommandExecutor.getOutput()).thenReturn(defaultScriptOutput);
Set<Device> devices = plugin.getDevices();
assertEquals("Number of devices", 0, devices.size());
}
@Test
public void testUnparseableLineSkipped()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
plugin.setCommandExecutorProvider(commandExecutorProvider);
defaultScriptOutput += "\n";
defaultScriptOutput += "cannot,be,parsed\n";
defaultScriptOutput += getOutputForDevice(1,
"/dev/ve1",
"ONLINE",
"0000:66:00.0",
244,
1);
when(mockCommandExecutor.getOutput()).thenReturn(defaultScriptOutput);
Set<Device> devices = plugin.getDevices();
assertEquals("Number of devices", 2, devices.size());
List<Device> devicesList = Lists.newArrayList(devices);
Collections.sort(devicesList, DEVICE_COMPARATOR);
Device device0 = devicesList.get(0);
assertEquals("Device id", 0, device0.getId());
assertEquals("Device path", "/dev/ve0", device0.getDevPath());
assertEquals("Bus Id", "0000:65:00.0", device0.getBusID());
assertEquals("Status", "ONLINE", device0.getStatus());
assertEquals("Major number", 243, device0.getMajorNumber());
assertEquals("Minor number", 0, device0.getMinorNumber());
Device device1 = devicesList.get(1);
assertEquals("Device id", 1, device1.getId());
assertEquals("Device path", "/dev/ve1", device1.getDevPath());
assertEquals("Bus Id", "0000:66:00.0", device1.getBusID());
assertEquals("Status", "ONLINE", device1.getStatus());
assertEquals("Major number", 244, device1.getMajorNumber());
assertEquals("Minor number", 1, device1.getMinorNumber());
}
@Test
public void testScriptFoundWithDifferentName()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
final String dummyScriptName = "dummy-script.py";
Path scriptPath = Paths.get(testFolder, dummyScriptName);
Files.createFile(scriptPath);
Files.delete(Paths.get(testFolder, DEFAULT_SCRIPT_NAME));
env.put("NEC_VE_GET_SCRIPT_NAME", dummyScriptName);
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
verifyBinaryPathSet(scriptPath);
}
@Test
public void testScriptFoundWithExplicitPath()
throws ResourceHandlerException, IOException {
setupTestDirectory("_temp_" + System.currentTimeMillis());
Path scriptPath = Paths.get(testFolder, DEFAULT_SCRIPT_NAME);
Files.createFile(scriptPath);
scriptPath.toFile().setExecutable(true);
assertTrue("Cannot set executable flag", scriptPath.toFile().canExecute());
env.put("NEC_VE_GET_SCRIPT_PATH",
testFolder + "/" + DEFAULT_SCRIPT_NAME);
plugin = new NECVEPlugin(envProvider, EMPTY_SEARCH_DIRS);
verifyBinaryPathSet(scriptPath);
}
@Test(expected = ResourceHandlerException.class)
public void testExplicitPathPointsToDirectory()
throws ResourceHandlerException, IOException {
setupTestDirectory("_temp_" + System.currentTimeMillis());
env.put("NEC_VE_GET_SCRIPT_PATH", testFolder);
plugin = new NECVEPlugin(envProvider, EMPTY_SEARCH_DIRS);
}
@Test(expected = ResourceHandlerException.class)
public void testExplicitPathIsNotExecutable()
throws ResourceHandlerException, IOException{
setupTestDirectory("_temp_" + System.currentTimeMillis());
Path scriptPath = Paths.get(testFolder, DEFAULT_SCRIPT_NAME);
Files.createFile(scriptPath);
scriptPath.toFile().setExecutable(false);
assertFalse("File is executable", scriptPath.toFile().canExecute());
env.put("NEC_VE_GET_SCRIPT_PATH",
testFolder + "/" + DEFAULT_SCRIPT_NAME);
plugin = new NECVEPlugin(envProvider, EMPTY_SEARCH_DIRS);
}
@Test
public void testScriptFoundUnderHadoopCommonPath()
throws ResourceHandlerException, IOException {
setupTestDirectory("_temp_" + System.currentTimeMillis());
Path p = Paths.get(testFolder, "/sbin/DevicePluginScript");
Files.createDirectories(p);
Path scriptPath = Paths.get(testFolder, "/sbin/DevicePluginScript",
DEFAULT_SCRIPT_NAME);
Files.createFile(scriptPath);
env.put("HADOOP_COMMON_HOME", testFolder);
plugin = new NECVEPlugin(envProvider, EMPTY_SEARCH_DIRS);
verifyBinaryPathSet(scriptPath);
}
@Test
public void testScriptFoundUnderBasicSearchDirs()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
Path scriptPath = Paths.get(testFolder, DEFAULT_SCRIPT_NAME);
verifyBinaryPathSet(scriptPath);
}
@Test
public void testAllocateSingleDevice()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
Set<Device> available = new HashSet<>();
Device device = getTestDevice(0);
available.add(device);
Set<Device> allocated = plugin.allocateDevices(available, 1, env);
assertEquals("No. of devices", 1, allocated.size());
Device allocatedDevice = allocated.iterator().next();
assertSame("Device", device, allocatedDevice);
}
@Test
public void testAllocateMultipleDevices()
throws ResourceHandlerException, IOException {
setupTestDirectoryWithScript();
plugin = new NECVEPlugin(envProvider, defaultSearchDirs);
Set<Device> available = new HashSet<>();
Device device0 = getTestDevice(0);
Device device1 = getTestDevice(1);
available.add(device0);
available.add(device1);
Set<Device> allocated = plugin.allocateDevices(available, 2, env);
assertEquals("No. of devices", 2, allocated.size());
assertTrue("Device missing", allocated.contains(device0));
assertTrue("Device missing", allocated.contains(device1));
}
private void setupTestDirectoryWithScript() throws IOException {
setupTestDirectory(null);
Files.createFile(Paths.get(testFolder, DEFAULT_SCRIPT_NAME));
}
private String getOutputForDevice(int id, String devPath, String state,
String busId, int major, int minor) {
return String.format(
"id=%d, dev=%s, state=%s, busId=%s, major=%d, minor=%d",
id, devPath, state, busId, major, minor);
}
private void setupTestDirectory(String postFix) throws IOException {
String path = "target/temp/" +
TestNECVEPlugin.class.getName() +
(postFix == null ? "" : postFix);
testFolder = new File(path).getAbsolutePath();
File f = new File(testFolder);
FileUtils.deleteDirectory(f);
if (!f.mkdirs()) {
throw new RuntimeException("Could not create directory: " +
f.getAbsolutePath());
}
defaultSearchDirs = new String[]{testFolder};
}
private Device getTestDevice(int id) {
Device.Builder builder = Device.Builder.newInstance();
return builder.setId(id)
.setDevPath("/mock/path")
.setMajorNumber(200)
.setMinorNumber(id)
.setBusID("0000:66:00.0")
.setHealthy(true)
.build();
}
private void verifyBinaryPathSet(Path expectedPath) {
assertEquals("Binary path", expectedPath.toString(),
plugin.getBinaryPath());
}
}