TestVEDeviceDiscoverer.java
/**
* 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.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyChar;
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.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.util.Lists;
import org.apache.hadoop.util.Shell.CommandExecutor;
import org.apache.hadoop.yarn.server.nodemanager.api.deviceplugin.Device;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Unit tests for VEDeviceDiscoverer class.
*
*/
@RunWith(MockitoJUnitRunner.class)
public class TestVEDeviceDiscoverer {
private static final Comparator<Device> DEVICE_COMPARATOR =
Comparator.comparingInt(Device::getId);
@Rule
public ExpectedException expected = ExpectedException.none();
@Mock
private UdevUtil udevUtil;
@Mock
private CommandExecutor mockCommandExecutor;
private String testFolder;
private VEDeviceDiscoverer discoverer;
@Before
public void setup() throws IOException {
Function<String[], CommandExecutor> commandExecutorProvider =
(String[] cmd) -> mockCommandExecutor;
discoverer = new VEDeviceDiscoverer(udevUtil);
discoverer.setCommandExecutorProvider(commandExecutorProvider);
setupTestDirectory();
}
@After
public void teardown() throws IOException {
if (testFolder != null) {
File f = new File(testFolder);
FileUtils.deleteDirectory(f);
}
}
@Test
public void testDetectSingleOnlineDevice() throws IOException {
createVeSlotFile(0);
createOsStateFile(0);
when(mockCommandExecutor.getOutput())
.thenReturn("8:1:character special file");
when(udevUtil.getSysPath(anyInt(), anyChar())).thenReturn(testFolder);
Set<Device> devices = discoverer.getDevicesFromPath(testFolder);
assertEquals("Number of devices", 1, devices.size());
Device device = devices.iterator().next();
assertEquals("Device ID", 0, device.getId());
assertEquals("Major number", 8, device.getMajorNumber());
assertEquals("Minor number", 1, device.getMinorNumber());
assertEquals("Status", "ONLINE", device.getStatus());
assertTrue("Device is not healthy", device.isHealthy());
}
@Test
public void testDetectMultipleOnlineDevices() throws IOException {
createVeSlotFile(0);
createVeSlotFile(1);
createVeSlotFile(2);
createOsStateFile(0);
when(mockCommandExecutor.getOutput()).thenReturn(
"8:1:character special file",
"9:1:character special file",
"a:1:character special file");
when(udevUtil.getSysPath(anyInt(), anyChar())).thenReturn(testFolder);
Set<Device> devices = discoverer.getDevicesFromPath(testFolder);
assertEquals("Number of devices", 3, devices.size());
List<Device> devicesList = Lists.newArrayList(devices);
devicesList.sort(DEVICE_COMPARATOR);
Device device0 = devicesList.get(0);
assertEquals("Device ID", 0, device0.getId());
assertEquals("Major number", 8, device0.getMajorNumber());
assertEquals("Minor number", 1, device0.getMinorNumber());
assertEquals("Status", "ONLINE", device0.getStatus());
assertTrue("Device is not healthy", device0.isHealthy());
Device device1 = devicesList.get(1);
assertEquals("Device ID", 1, device1.getId());
assertEquals("Major number", 9, device1.getMajorNumber());
assertEquals("Minor number", 1, device1.getMinorNumber());
assertEquals("Status", "ONLINE", device1.getStatus());
assertTrue("Device is not healthy", device1.isHealthy());
Device device2 = devicesList.get(2);
assertEquals("Device ID", 2, device2.getId());
assertEquals("Major number", 10, device2.getMajorNumber());
assertEquals("Minor number", 1, device2.getMinorNumber());
assertEquals("Status", "ONLINE", device2.getStatus());
assertTrue("Device is not healthy", device2.isHealthy());
}
@Test
public void testNegativeDeviceStateNumber() throws IOException {
createVeSlotFile(0);
createOsStateFile(-1);
when(mockCommandExecutor.getOutput())
.thenReturn("8:1:character special file");
when(udevUtil.getSysPath(anyInt(), anyChar())).thenReturn(testFolder);
Set<Device> devices = discoverer.getDevicesFromPath(testFolder);
assertEquals("Number of devices", 1, devices.size());
Device device = devices.iterator().next();
assertEquals("Device ID", 0, device.getId());
assertEquals("Major number", 8, device.getMajorNumber());
assertEquals("Minor number", 1, device.getMinorNumber());
assertEquals("Status", "Unknown (-1)", device.getStatus());
assertFalse("Device should not be healthy", device.isHealthy());
}
@Test
public void testDeviceStateNumberTooHigh() throws IOException {
createVeSlotFile(0);
createOsStateFile(5);
when(mockCommandExecutor.getOutput())
.thenReturn("8:1:character special file");
when(udevUtil.getSysPath(anyInt(), anyChar())).thenReturn(testFolder);
Set<Device> devices = discoverer.getDevicesFromPath(testFolder);
assertEquals("Number of devices", 1, devices.size());
Device device = devices.iterator().next();
assertEquals("Device ID", 0, device.getId());
assertEquals("Major number", 8, device.getMajorNumber());
assertEquals("Minor number", 1, device.getMinorNumber());
assertEquals("Status", "Unknown (5)", device.getStatus());
assertFalse("Device should not be healthy", device.isHealthy());
}
@Test
public void testDeviceNumberFromMajorAndMinor() throws IOException {
createVeSlotFile(0);
createVeSlotFile(1);
createVeSlotFile(2);
createOsStateFile(0);
when(mockCommandExecutor.getOutput()).thenReturn(
"10:1:character special file",
"1d:2:character special file",
"4:3c:character special file");
when(udevUtil.getSysPath(anyInt(), anyChar())).thenReturn(testFolder);
Set<Device> devices = discoverer.getDevicesFromPath(testFolder);
List<Device> devicesList = Lists.newArrayList(devices);
devicesList.sort(DEVICE_COMPARATOR);
Device device0 = devicesList.get(0);
assertEquals("Major number", 16, device0.getMajorNumber());
assertEquals("Minor number", 1, device0.getMinorNumber());
Device device1 = devicesList.get(1);
assertEquals("Major number", 29, device1.getMajorNumber());
assertEquals("Minor number", 2, device1.getMinorNumber());
Device device2 = devicesList.get(2);
assertEquals("Major number", 4, device2.getMajorNumber());
assertEquals("Minor number", 60, device2.getMinorNumber());
}
@Test
public void testNonVESlotFilesAreSkipped() throws IOException {
createVeSlotFile(0);
createOsStateFile(0);
createFile("abcde");
createFile("vexlot");
createFile("xyzveslot");
when(mockCommandExecutor.getOutput()).thenReturn(
"8:1:character special file",
"9:1:character special file",
"10:1:character special file",
"11:1:character special file",
"12:1:character special file");
when(udevUtil.getSysPath(anyInt(), anyChar())).thenReturn(testFolder);
Set<Device> devices = discoverer.getDevicesFromPath(testFolder);
assertEquals("Number of devices", 1, devices.size());
Device device = devices.iterator().next();
assertEquals("Device ID", 0, device.getId());
assertEquals("Major number", 8, device.getMajorNumber());
assertEquals("Minor number", 1, device.getMinorNumber());
assertEquals("Status", "ONLINE", device.getStatus());
assertTrue("Device is not healthy", device.isHealthy());
}
@Test
public void testNonBlockOrCharFilesAreRejected() throws IOException {
expected.expect(IllegalArgumentException.class);
expected.expectMessage("File is neither a char nor block device");
createVeSlotFile(0);
when(mockCommandExecutor.getOutput()).thenReturn(
"0:0:regular file");
discoverer.getDevicesFromPath(testFolder);
}
private void setupTestDirectory() throws IOException {
String path = "target/temp/" +
TestVEDeviceDiscoverer.class.getName();
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());
}
}
private void createVeSlotFile(int slot) throws IOException {
Files.createFile(Paths.get(testFolder, "veslot" + String.valueOf(slot)));
}
private void createFile(String name) throws IOException {
Files.createFile(Paths.get(testFolder, name));
}
private void createOsStateFile(int state) throws IOException {
Path path = Paths.get(testFolder, "os_state");
Files.createFile(path);
Files.write(path, new byte[]{(byte) state});
}
}