YARN-7487. Ensure volume to include GPU base libraries after created by plugin. Contributed by Wangda Tan.

This commit is contained in:
Sunil G 2017-12-01 13:36:28 +05:30
parent 4653aa3eb3
commit 556aea3f36
6 changed files with 305 additions and 72 deletions

View File

@ -337,7 +337,7 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
return false; return false;
} }
private void runDockerVolumeCommand(DockerVolumeCommand dockerVolumeCommand, private String runDockerVolumeCommand(DockerVolumeCommand dockerVolumeCommand,
Container container) throws ContainerExecutionException { Container container) throws ContainerExecutionException {
try { try {
String commandFile = dockerClient.writeCommandToTempFile( String commandFile = dockerClient.writeCommandToTempFile(
@ -351,6 +351,7 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
LOG.info("ContainerId=" + container.getContainerId() LOG.info("ContainerId=" + container.getContainerId()
+ ", docker volume output for " + dockerVolumeCommand + ": " + ", docker volume output for " + dockerVolumeCommand + ": "
+ output); + output);
return output;
} catch (ContainerExecutionException e) { } catch (ContainerExecutionException e) {
LOG.error("Error when writing command to temp file, command=" LOG.error("Error when writing command to temp file, command="
+ dockerVolumeCommand, + dockerVolumeCommand,
@ -378,14 +379,72 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
plugin.getDockerCommandPluginInstance(); plugin.getDockerCommandPluginInstance();
if (dockerCommandPlugin != null) { if (dockerCommandPlugin != null) {
DockerVolumeCommand dockerVolumeCommand = DockerVolumeCommand dockerVolumeCommand =
dockerCommandPlugin.getCreateDockerVolumeCommand(ctx.getContainer()); dockerCommandPlugin.getCreateDockerVolumeCommand(
ctx.getContainer());
if (dockerVolumeCommand != null) { if (dockerVolumeCommand != null) {
runDockerVolumeCommand(dockerVolumeCommand, container); runDockerVolumeCommand(dockerVolumeCommand, container);
// After volume created, run inspect to make sure volume properly
// created.
if (dockerVolumeCommand.getSubCommand().equals(
DockerVolumeCommand.VOLUME_CREATE_SUB_COMMAND)) {
checkDockerVolumeCreated(dockerVolumeCommand, container);
} }
} }
} }
} }
} }
}
private void checkDockerVolumeCreated(
DockerVolumeCommand dockerVolumeCreationCommand, Container container)
throws ContainerExecutionException {
DockerVolumeCommand dockerVolumeInspectCommand = new DockerVolumeCommand(
DockerVolumeCommand.VOLUME_LS_SUB_COMMAND);
dockerVolumeInspectCommand.setFormat("{{.Name}},{{.Driver}}");
String output = runDockerVolumeCommand(dockerVolumeInspectCommand,
container);
// Parse output line by line and check if it matches
String volumeName = dockerVolumeCreationCommand.getVolumeName();
String driverName = dockerVolumeCreationCommand.getDriverName();
if (driverName == null) {
driverName = "local";
}
for (String line : output.split("\n")) {
line = line.trim();
String[] arr = line.split(",");
String v = arr[0].trim();
String d = null;
if (arr.length > 1) {
d = arr[1].trim();
}
if (d != null && volumeName.equals(v) && driverName.equals(d)) {
// Good we found it.
LOG.info(
"Docker volume-name=" + volumeName + " driver-name=" + driverName
+ " already exists for container=" + container
.getContainerId() + ", continue...");
return;
}
}
// Couldn't find the volume
String message =
" Couldn't find volume=" + volumeName + " driver=" + driverName
+ " for container=" + container.getContainerId()
+ ", please check error message in log to understand "
+ "why this happens.";
LOG.error(message);
if (LOG.isDebugEnabled()) {
LOG.debug("All docker volumes in the system, command="
+ dockerVolumeInspectCommand.toString());
}
throw new ContainerExecutionException(message);
}
private void validateContainerNetworkType(String network) private void validateContainerNetworkType(String network)
throws ContainerExecutionException { throws ContainerExecutionException {

View File

@ -27,23 +27,50 @@ import java.util.regex.Pattern;
*/ */
public class DockerVolumeCommand extends DockerCommand { public class DockerVolumeCommand extends DockerCommand {
public static final String VOLUME_COMMAND = "volume"; public static final String VOLUME_COMMAND = "volume";
public static final String VOLUME_CREATE_COMMAND = "create"; public static final String VOLUME_CREATE_SUB_COMMAND = "create";
public static final String VOLUME_LS_SUB_COMMAND = "ls";
// Regex pattern for volume name // Regex pattern for volume name
public static final Pattern VOLUME_NAME_PATTERN = Pattern.compile( public static final Pattern VOLUME_NAME_PATTERN = Pattern.compile(
"[a-zA-Z0-9][a-zA-Z0-9_.-]*"); "[a-zA-Z0-9][a-zA-Z0-9_.-]*");
private String volumeName;
private String driverName;
private String subCommand;
public DockerVolumeCommand(String subCommand) { public DockerVolumeCommand(String subCommand) {
super(VOLUME_COMMAND); super(VOLUME_COMMAND);
this.subCommand = subCommand;
super.addCommandArguments("sub-command", subCommand); super.addCommandArguments("sub-command", subCommand);
} }
public DockerVolumeCommand setVolumeName(String volumeName) { public DockerVolumeCommand setVolumeName(String volumeName) {
super.addCommandArguments("volume", volumeName); super.addCommandArguments("volume", volumeName);
this.volumeName = volumeName;
return this; return this;
} }
public DockerVolumeCommand setDriverName(String driverName) { public DockerVolumeCommand setDriverName(String driverName) {
super.addCommandArguments("driver", driverName); super.addCommandArguments("driver", driverName);
this.driverName = driverName;
return this; return this;
} }
public String getVolumeName() {
return volumeName;
}
public String getDriverName() {
return driverName;
}
public String getSubCommand() {
return subCommand;
}
public DockerVolumeCommand setFormat(String format) {
super.addCommandArguments("format", format);
return this;
}
} }

View File

@ -301,7 +301,7 @@ public class NvidiaDockerV1CommandPlugin implements DockerCommandPlugin {
if (newVolumeName != null) { if (newVolumeName != null) {
DockerVolumeCommand command = new DockerVolumeCommand( DockerVolumeCommand command = new DockerVolumeCommand(
DockerVolumeCommand.VOLUME_CREATE_COMMAND); DockerVolumeCommand.VOLUME_CREATE_SUB_COMMAND);
command.setDriverName(volumeDriver); command.setDriverName(volumeDriver);
command.setVolumeName(newVolumeName); command.setVolumeName(newVolumeName);
return command; return command;

View File

@ -299,32 +299,22 @@ static int value_permitted(const struct configuration* executor_cfg,
int get_docker_volume_command(const char *command_file, const struct configuration *conf, char *out, int get_docker_volume_command(const char *command_file, const struct configuration *conf, char *out,
const size_t outlen) { const size_t outlen) {
int ret = 0; int ret = 0;
char *driver = NULL, *volume_name = NULL, *sub_command = NULL; char *driver = NULL, *volume_name = NULL, *sub_command = NULL, *format = NULL;
struct configuration command_config = {0, NULL}; struct configuration command_config = {0, NULL};
ret = read_and_verify_command_file(command_file, DOCKER_VOLUME_COMMAND, &command_config); ret = read_and_verify_command_file(command_file, DOCKER_VOLUME_COMMAND, &command_config);
if (ret != 0) { if (ret != 0) {
return ret; return ret;
} }
sub_command = get_configuration_value("sub-command", DOCKER_COMMAND_FILE_SECTION, &command_config); sub_command = get_configuration_value("sub-command", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (sub_command == NULL || 0 != strcmp(sub_command, "create")) {
fprintf(ERRORFILE, "\"create\" is the only acceptable sub-command of volume.\n"); if ((sub_command == NULL) || ((0 != strcmp(sub_command, "create")) &&
(0 != strcmp(sub_command, "ls")))) {
fprintf(ERRORFILE, "\"create/ls\" are the only acceptable sub-command of volume, input sub_command=\"%s\"\n",
sub_command);
ret = INVALID_DOCKER_VOLUME_COMMAND; ret = INVALID_DOCKER_VOLUME_COMMAND;
goto cleanup; goto cleanup;
} }
volume_name = get_configuration_value("volume", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (volume_name == NULL || validate_volume_name(volume_name) != 0) {
fprintf(ERRORFILE, "%s is not a valid volume name.\n", volume_name);
ret = INVALID_DOCKER_VOLUME_NAME;
goto cleanup;
}
driver = get_configuration_value("driver", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (driver == NULL) {
ret = INVALID_DOCKER_VOLUME_DRIVER;
goto cleanup;
}
memset(out, 0, outlen); memset(out, 0, outlen);
ret = add_docker_config_param(&command_config, out, outlen); ret = add_docker_config_param(&command_config, out, outlen);
@ -338,6 +328,20 @@ int get_docker_volume_command(const char *command_file, const struct configurati
goto cleanup; goto cleanup;
} }
if (0 == strcmp(sub_command, "create")) {
volume_name = get_configuration_value("volume", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (volume_name == NULL || validate_volume_name(volume_name) != 0) {
fprintf(ERRORFILE, "%s is not a valid volume name.\n", volume_name);
ret = INVALID_DOCKER_VOLUME_NAME;
goto cleanup;
}
driver = get_configuration_value("driver", DOCKER_COMMAND_FILE_SECTION, &command_config);
if (driver == NULL) {
ret = INVALID_DOCKER_VOLUME_DRIVER;
goto cleanup;
}
ret = add_to_buffer(out, outlen, " create"); ret = add_to_buffer(out, outlen, " create");
if (ret != 0) { if (ret != 0) {
goto cleanup; goto cleanup;
@ -369,11 +373,31 @@ int get_docker_volume_command(const char *command_file, const struct configurati
if (ret != 0) { if (ret != 0) {
goto cleanup; goto cleanup;
} }
} else if (0 == strcmp(sub_command, "ls")) {
format = get_configuration_value("format", DOCKER_COMMAND_FILE_SECTION, &command_config);
ret = add_to_buffer(out, outlen, " ls");
if (ret != 0) {
goto cleanup;
}
if (format) {
ret = add_to_buffer(out, outlen, " --format=");
if (ret != 0) {
goto cleanup;
}
ret = add_to_buffer(out, outlen, format);
if (ret != 0) {
goto cleanup;
}
}
}
cleanup: cleanup:
free(driver); free(driver);
free(volume_name); free(volume_name);
free(sub_command); free(sub_command);
free(format);
// clean up out buffer // clean up out buffer
if (ret != 0) { if (ret != 0) {

View File

@ -1132,12 +1132,15 @@ namespace ContainerExecutor {
file_cmd_vec.push_back(std::make_pair<std::string, std::string>( file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
"[docker-command-execution]\n docker-command=volume\n sub-command=create\n volume=volume1 \n driver=driver1", "[docker-command-execution]\n docker-command=volume\n sub-command=create\n volume=volume1 \n driver=driver1",
"volume create --name=volume1 --driver=driver1")); "volume create --name=volume1 --driver=driver1"));
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
"[docker-command-execution]\n docker-command=volume\n format={{.Name}},{{.Driver}}\n sub-command=ls",
"volume ls --format={{.Name}},{{.Driver}}"));
std::vector<std::pair<std::string, int> > bad_file_cmd_vec; std::vector<std::pair<std::string, int> > bad_file_cmd_vec;
// Wrong subcommand // Wrong subcommand
bad_file_cmd_vec.push_back(std::make_pair<std::string, int>( bad_file_cmd_vec.push_back(std::make_pair<std::string, int>(
"[docker-command-execution]\n docker-command=volume\n sub-command=ls\n volume=volume1 \n driver=driver1", "[docker-command-execution]\n docker-command=volume\n sub-command=inspect\n volume=volume1 \n driver=driver1",
static_cast<int>(INVALID_DOCKER_VOLUME_COMMAND))); static_cast<int>(INVALID_DOCKER_VOLUME_COMMAND)));
// Volume not specified // Volume not specified

View File

@ -1301,7 +1301,7 @@ public class TestDockerContainerRuntime {
//single invocation expected //single invocation expected
//due to type erasure + mocking, this verification requires a suppress //due to type erasure + mocking, this verification requires a suppress
// warning annotation on the entire method // warning annotation on the entire method
verify(mockExecutor, times(1)) verify(mockExecutor, times(2))
.executePrivilegedOperation(anyList(), opCaptor.capture(), any( .executePrivilegedOperation(anyList(), opCaptor.capture(), any(
File.class), anyMap(), anyBoolean(), anyBoolean()); File.class), anyMap(), anyBoolean(), anyBoolean());
@ -1309,7 +1309,9 @@ public class TestDockerContainerRuntime {
// hence, reset mock here // hence, reset mock here
Mockito.reset(mockExecutor); Mockito.reset(mockExecutor);
PrivilegedOperation op = opCaptor.getValue(); List<PrivilegedOperation> allCaptures = opCaptor.getAllValues();
PrivilegedOperation op = allCaptures.get(0);
Assert.assertEquals(PrivilegedOperation.OperationType Assert.assertEquals(PrivilegedOperation.OperationType
.RUN_DOCKER_CMD, op.getOperationType()); .RUN_DOCKER_CMD, op.getOperationType());
@ -1317,22 +1319,32 @@ public class TestDockerContainerRuntime {
FileInputStream fileInputStream = new FileInputStream(commandFile); FileInputStream fileInputStream = new FileInputStream(commandFile);
String fileContent = new String(IOUtils.toByteArray(fileInputStream)); String fileContent = new String(IOUtils.toByteArray(fileInputStream));
Assert.assertEquals("[docker-command-execution]\n" Assert.assertEquals("[docker-command-execution]\n"
+ " docker-command=volume\n" + " sub-command=create\n" + " docker-command=volume\n" + " driver=local\n"
+ " volume=volume1\n", fileContent); + " sub-command=create\n" + " volume=volume1\n", fileContent);
fileInputStream.close();
op = allCaptures.get(1);
Assert.assertEquals(PrivilegedOperation.OperationType
.RUN_DOCKER_CMD, op.getOperationType());
commandFile = new File(StringUtils.join(",", op.getArguments()));
fileInputStream = new FileInputStream(commandFile);
fileContent = new String(IOUtils.toByteArray(fileInputStream));
Assert.assertEquals("[docker-command-execution]\n"
+ " docker-command=volume\n" + " format={{.Name}},{{.Driver}}\n"
+ " sub-command=ls\n", fileContent);
fileInputStream.close();
} }
@Test private static class MockDockerCommandPlugin implements DockerCommandPlugin {
public void testDockerCommandPlugin() throws Exception { private final String volume;
DockerLinuxContainerRuntime runtime = private final String driver;
new DockerLinuxContainerRuntime(mockExecutor, mockCGroupsHandler);
Context nmContext = mock(Context.class); public MockDockerCommandPlugin(String volume, String driver) {
ResourcePluginManager rpm = mock(ResourcePluginManager.class); this.volume = volume;
Map<String, ResourcePlugin> pluginsMap = new HashMap<>(); this.driver = driver;
ResourcePlugin plugin1 = mock(ResourcePlugin.class); }
// Create the docker command plugin logic, which will set volume driver
DockerCommandPlugin dockerCommandPlugin = new DockerCommandPlugin() {
@Override @Override
public void updateDockerRunCommand(DockerRunCommand dockerRunCommand, public void updateDockerRunCommand(DockerRunCommand dockerRunCommand,
Container container) throws ContainerExecutionException { Container container) throws ContainerExecutionException {
@ -1344,15 +1356,123 @@ public class TestDockerContainerRuntime {
@Override @Override
public DockerVolumeCommand getCreateDockerVolumeCommand(Container container) public DockerVolumeCommand getCreateDockerVolumeCommand(Container container)
throws ContainerExecutionException { throws ContainerExecutionException {
return new DockerVolumeCommand("create").setVolumeName("volume1"); return new DockerVolumeCommand("create").setVolumeName(volume)
.setDriverName(driver);
} }
@Override @Override
public DockerVolumeCommand getCleanupDockerVolumesCommand(Container container) public DockerVolumeCommand getCleanupDockerVolumesCommand(
throws ContainerExecutionException { Container container) throws ContainerExecutionException {
return null; return null;
} }
}; }
private void testDockerCommandPluginWithVolumesOutput(
String dockerVolumeListOutput, boolean expectFail)
throws PrivilegedOperationException, ContainerExecutionException,
IOException {
mockExecutor = Mockito
.mock(PrivilegedOperationExecutor.class);
DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(
mockExecutor, mockCGroupsHandler);
when(mockExecutor
.executePrivilegedOperation(anyList(), any(PrivilegedOperation.class),
any(File.class), anyMap(), anyBoolean(), anyBoolean())).thenReturn(
null);
when(mockExecutor
.executePrivilegedOperation(anyList(), any(PrivilegedOperation.class),
any(File.class), anyMap(), anyBoolean(), anyBoolean())).thenReturn(
dockerVolumeListOutput);
Context nmContext = mock(Context.class);
ResourcePluginManager rpm = mock(ResourcePluginManager.class);
Map<String, ResourcePlugin> pluginsMap = new HashMap<>();
ResourcePlugin plugin1 = mock(ResourcePlugin.class);
// Create the docker command plugin logic, which will set volume driver
DockerCommandPlugin dockerCommandPlugin = new MockDockerCommandPlugin(
"volume1", "local");
when(plugin1.getDockerCommandPluginInstance()).thenReturn(
dockerCommandPlugin);
ResourcePlugin plugin2 = mock(ResourcePlugin.class);
pluginsMap.put("plugin1", plugin1);
pluginsMap.put("plugin2", plugin2);
when(rpm.getNameToPlugins()).thenReturn(pluginsMap);
when(nmContext.getResourcePluginManager()).thenReturn(rpm);
runtime.initialize(conf, nmContext);
ContainerRuntimeContext containerRuntimeContext = builder.build();
try {
runtime.prepareContainer(containerRuntimeContext);
checkVolumeCreateCommand();
runtime.launchContainer(containerRuntimeContext);
} catch (ContainerExecutionException e) {
if (expectFail) {
// Expected
return;
} else{
Assert.fail("Should successfully prepareContainers" + e);
}
}
if (expectFail) {
Assert.fail(
"Should fail because output is illegal");
}
}
@Test
public void testDockerCommandPluginCheckVolumeAfterCreation()
throws Exception {
// For following tests, we expect to have volume1,local in output
// Failure cases
testDockerCommandPluginWithVolumesOutput("", true);
testDockerCommandPluginWithVolumesOutput("volume1", true);
testDockerCommandPluginWithVolumesOutput("local", true);
testDockerCommandPluginWithVolumesOutput("volume2,local", true);
testDockerCommandPluginWithVolumesOutput("volum1,something", true);
testDockerCommandPluginWithVolumesOutput("volum1,something\nvolum2,local",
true);
// Success case
testDockerCommandPluginWithVolumesOutput("volume1,local\n", false);
testDockerCommandPluginWithVolumesOutput(
"volume_xyz,nvidia\nvolume1,local\n\n", false);
testDockerCommandPluginWithVolumesOutput(" volume1, local \n", false);
testDockerCommandPluginWithVolumesOutput(
"volume_xyz,\tnvidia\n volume1,\tlocal\n\n", false);
}
@Test
public void testDockerCommandPlugin() throws Exception {
DockerLinuxContainerRuntime runtime =
new DockerLinuxContainerRuntime(mockExecutor, mockCGroupsHandler);
when(mockExecutor
.executePrivilegedOperation(anyList(), any(PrivilegedOperation.class),
any(File.class), anyMap(), anyBoolean(), anyBoolean())).thenReturn(
null);
when(mockExecutor
.executePrivilegedOperation(anyList(), any(PrivilegedOperation.class),
any(File.class), anyMap(), anyBoolean(), anyBoolean())).thenReturn(
"volume1,local");
Context nmContext = mock(Context.class);
ResourcePluginManager rpm = mock(ResourcePluginManager.class);
Map<String, ResourcePlugin> pluginsMap = new HashMap<>();
ResourcePlugin plugin1 = mock(ResourcePlugin.class);
// Create the docker command plugin logic, which will set volume driver
DockerCommandPlugin dockerCommandPlugin = new MockDockerCommandPlugin(
"volume1", "local");
when(plugin1.getDockerCommandPluginInstance()).thenReturn( when(plugin1.getDockerCommandPluginInstance()).thenReturn(
dockerCommandPlugin); dockerCommandPlugin);