use of com.oracle.bedrock.runtime.ApplicationProcess in project oracle-bedrock by coherence-community.
the class AbstractRemoteApplicationLauncher method launch.
@Override
public A launch(Platform platform, MetaClass<A> metaClass, OptionsByType optionsByType) {
// establish the diagnostics output table
Table diagnosticsTable = new Table();
diagnosticsTable.getOptions().add(Table.orderByColumn(0));
if (platform != null) {
diagnosticsTable.addRow("Target Platform", platform.getName());
}
// ----- establish the launch Options for the Application -----
// add the platform options
OptionsByType launchOptions = OptionsByType.of(platform.getOptions()).addAll(optionsByType);
// add the meta-class options
metaClass.onLaunching(platform, launchOptions);
// ---- establish the default Options ----
// define the PlatformSeparators as Unix if they are not already defined
launchOptions.addIfAbsent(PlatformSeparators.forUnix());
// define the default Platform Shell (assume BASH)
launchOptions.addIfAbsent(Shell.is(Shell.Type.BASH));
// define the "local.address" variable so that is can be used for resolving this platform address
launchOptions.add(Variable.with("local.address", LocalPlatform.get().getAddress().getHostAddress()));
// ----- establish an identity for the application -----
// add a unique runtime id for expression support
launchOptions.add(Variable.with("bedrock.runtime.id", UUID.randomUUID()));
// ----- establish default Profiles for this Platform (and Builder) -----
// auto-detect and add externally defined profiles
launchOptions.addAll(Profiles.getProfiles());
for (Profile profile : launchOptions.getInstancesOf(Profile.class)) {
profile.onLaunching(platform, metaClass, launchOptions);
}
// ----- add the diagnostic table to the options so it can be used by the terminal -----
launchOptions.add(diagnosticsTable);
// ----- prior to launching the application, let the implementation enhance the launch options -----
onLaunching(launchOptions);
// ----- give the MetaClass a last chance to manipulate any options -----
metaClass.onLaunch(platform, launchOptions);
// ----- determine the display name for the application -----
DisplayName displayName = getDisplayName(launchOptions);
// determine the Executable
Executable executable = launchOptions.get(Executable.class);
// ----- deploy remote application artifacts -----
// determine the DeploymentArtifacts based on those specified by the Deployment option
ArrayList<DeploymentArtifact> artifactsToDeploy = new ArrayList<>();
Deployment deployment = launchOptions.get(Deployment.class);
if (deployment != null) {
try {
artifactsToDeploy.addAll(deployment.getDeploymentArtifacts(platform, launchOptions));
} catch (Exception e) {
throw new RuntimeException("Failed to determine artifacts to deploy", e);
}
}
// determine the separators for the platform
PlatformSeparators separators = launchOptions.get(PlatformSeparators.class);
// assume the remote directory is the working directory
WorkingDirectory workingDirectory = launchOptions.getOrSetDefault(WorkingDirectory.class, WorkingDirectory.temporaryDirectory());
File remoteDirectoryFile = workingDirectory.resolve(platform, launchOptions);
if (remoteDirectoryFile == null) {
remoteDirectoryFile = WorkingDirectory.temporaryDirectory().resolve(platform, launchOptions);
}
String remoteDirectory = separators.asPlatformFileName(remoteDirectoryFile.toString());
// Set the resolved working directory back into the options
launchOptions.add(WorkingDirectory.at(remoteDirectoryFile));
if (remoteDirectoryFile != null) {
diagnosticsTable.addRow("Working Directory", remoteDirectoryFile.toString());
}
// Obtain the RemoteShell that will be used to launch the process
RemoteTerminalBuilder terminalBuilder = launchOptions.getOrSetDefault(RemoteTerminalBuilder.class, RemoteTerminals.ssh());
RemoteTerminal terminal = terminalBuilder.build(platform);
// create the working directory
terminal.makeDirectories(remoteDirectory, launchOptions);
// deploy any artifacts required
Deployer deployer = launchOptions.getOrSetDefault(Deployer.class, new SftpDeployer());
DeployedArtifacts deployedArtifacts = deployer.deploy(artifactsToDeploy, remoteDirectory, platform, launchOptions.asArray());
// add the remote directory as something to clean up
deployedArtifacts.add(remoteDirectoryFile);
if (!deployedArtifacts.isEmpty()) {
// when we've deployed artifacts we need to add a listener to clean them up
launchOptions.add(Decoration.of(new ApplicationListener<A>() {
@Override
public void onClosing(A application, OptionsByType optionsByType) {
// nothing to do on closing
}
@Override
public void onClosed(A application, OptionsByType optionsByType) {
Level logLevel = optionsByType.get(LaunchLogging.class).isEnabled() ? Level.INFO : Level.OFF;
try (DiagnosticsRecording diagnostics = DiagnosticsRecording.create("Undeploy Diagnostics for " + application.getName() + " on platform " + platform.getName()).using(LOGGER, logLevel)) {
diagnostics.add("Platform", "Resource");
try (DiagnosticsRecording local = DiagnosticsRecording.section("Local Platform")) {
// clean up the locally created temporary artifacts
artifactsToDeploy.stream().filter(DeploymentArtifact::isTemporary).forEach(artifact -> {
try {
// attempt to remove the local file
artifact.getSourceFile().delete();
// include diagnostics
local.add(artifact.getSourceFile().toString());
} catch (Exception e) {
// log exceptions when attempting to remove local sources
LOGGER.log(Level.WARNING, "Failed to remove temporary " + artifact.toString() + " for application " + application.getName(), e);
// include diagnostics
local.add(artifact.getSourceFile() + " (failed to undeploy)");
}
});
}
// undeploy the deployed artifacts
deployer.undeploy(deployedArtifacts, platform, launchOptions.asArray());
}
}
@Override
public void onLaunched(A application) {
// nothing to do after launching
}
}));
}
// Realize the application arguments
Arguments arguments = launchOptions.get(Arguments.class);
List<String> argList = arguments.resolve(platform, launchOptions);
// Set the actual arguments used back into the options
launchOptions.add(Arguments.of(argList));
// TODO: put a try/catch around the terminal.launch here so we can clean up the RemoteExecutor if
// the application failed to launch
// determine the application class that will represent the running application
Class<? extends A> applicationClass = metaClass.getImplementationClass(platform, launchOptions);
diagnosticsTable.addRow("Application", displayName.resolve(launchOptions));
if (argList.size() > 0) {
diagnosticsTable.addRow("Application Arguments ", argList.stream().collect(Collectors.joining(" ")));
}
diagnosticsTable.addRow("Application Launch Time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// ----- start the process and establish the application -----
// launch the remote process
RemoteApplicationProcess remoteProcess = terminal.launch(this, applicationClass, launchOptions);
// adapt the remote process into something that the application can use
ApplicationProcess process = adapt(remoteProcess);
// create the Application based on the RemoteApplicationProcess
A application;
try {
// attempt to find a constructor(Platform, JavaApplicationProcess, Options)
Constructor<? extends A> constructor = ReflectionHelper.getCompatibleConstructor(applicationClass, platform.getClass(), process.getClass(), OptionsByType.class);
// create the application
application = constructor.newInstance(platform, process, launchOptions);
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate the Application class specified by the MetaClass:" + metaClass, e);
}
// ----- after launching the application, let the implementation interact with the application -----
onLaunched(application, launchOptions);
// ----- notify the MetaClass that the application has been launched -----
metaClass.onLaunched(platform, application, launchOptions);
for (Profile profile : launchOptions.getInstancesOf(Profile.class)) {
profile.onLaunched(platform, application, launchOptions);
}
// notify the ApplicationListener-based Options that the application has been launched
for (ApplicationListener listener : launchOptions.getInstancesOf(ApplicationListener.class)) {
listener.onLaunched(application);
}
return application;
}
use of com.oracle.bedrock.runtime.ApplicationProcess in project oracle-bedrock by coherence-community.
the class DockerRemoteTerminal method runContainer.
/**
* Run a container using the specified image.
*
* @param containerName the name of the container
* @param launchable the {@link RemoteTerminal.Launchable} that will give the command to execute
* @param image the image to use to run the container
* @param docker the {@link Docker} environment to use
* @param optionsByType the {@link OptionsByType} to use
*
* @return a {@link DockerContainer} representing the running image
*/
protected ApplicationProcess runContainer(String containerName, Launchable launchable, DockerImage image, Docker docker, OptionsByType optionsByType) {
Timeout timeout = optionsByType.get(Timeout.class);
WorkingDirectory workingDirectory = optionsByType.get(WorkingDirectory.class);
String workingDirectoryName = workingDirectory.resolve(platform, optionsByType).toString();
optionsByType.add(PlatformSeparators.forUnix());
optionsByType.add(new CPModifier(workingDirectoryName));
// ----- give the container a random UUID as a name -----
DisplayName displayName = optionsByType.getOrSetDefault(DisplayName.class, DisplayName.of("Container"));
// ----- create the arguments to pass to the container as the command to execute
String command = launchable.getCommandToExecute(platform, optionsByType);
List<?> args = launchable.getCommandLineArguments(platform, optionsByType);
Arguments containerArgs = Arguments.of(command).with(args);
// ----- get any captured ports to map -----
Ports ports = optionsByType.get(Ports.class);
List<Integer> portList = ports.getPorts().stream().map(Ports.Port::getActualPort).collect(Collectors.toList());
// ----- create the Run command -----
Run runCommand = Run.image(image, containerName).interactive().net(docker.getDefaultNetworkName()).hostName(containerName).env(launchable.getEnvironmentVariables(platform, optionsByType)).publish(portList).autoRemove();
OptionsByType containerOptions = OptionsByType.of(optionsByType).addAll(displayName, docker, WorkingDirectory.at(tmpFolder), ContainerCloseBehaviour.none(), ImageCloseBehaviour.remove(), containerArgs);
// ----- start the application to capture Docker events so that we know when the container is in the running state -----
EventsApplicationConsole.CountDownListener latch = new EventsApplicationConsole.CountDownListener(1);
Predicate<String> predicate = (line) -> line.contains("container start");
EventsApplicationConsole eventConsole = new EventsApplicationConsole().withStdOutListener(predicate, latch);
try (Application events = platform.launch(Events.fromContainer(containerName), docker, Console.of(eventConsole))) {
// ----- launch the container -----
ContainerApplication application = platform.launch(new ContainerMetaClass(runCommand), containerOptions.asArray());
// ----- get the container feature from the application -----
DockerContainer container = application.get(DockerContainer.class);
FeatureAddingProfile profile = new FeatureAddingProfile(image, container);
// ----- add the container and default close behaviour to the options
optionsByType.add(profile);
optionsByType.add(ImageCloseBehaviour.remove());
try {
if (!latch.await(timeout.to(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Failed to detect container start event within " + timeout);
}
} catch (InterruptedException e) {
// ignored
}
// ----- obtain the port mappings from the container -----
JsonObject jsonNet = (JsonObject) container.inspect("{{json .NetworkSettings}}");
if (!jsonNet.get("Ports").getValueType().equals(JsonValue.ValueType.NULL)) {
JsonObject jsonPorts = jsonNet.getJsonObject("Ports");
List<Ports.Port> mappedPorts = ports.getPorts().stream().map((port) -> {
String key = port.getActualPort() + "/tcp";
String hostPort = jsonPorts.getJsonArray(key).getJsonObject(0).getString("HostPort");
return new Ports.Port(port.getName(), port.getActualPort(), Integer.parseInt(hostPort));
}).collect(Collectors.toList());
// ----- update the options with the correctly mapped ports -----
optionsByType.remove(Ports.class);
optionsByType.add(Ports.of(mappedPorts));
}
// ----- return the process from the container application -----
return application.getProcess();
}
}
use of com.oracle.bedrock.runtime.ApplicationProcess in project oracle-bedrock by coherence-community.
the class DockerRemoteTerminal method launch.
@Override
public RemoteApplicationProcess launch(Launchable launchable, Class<? extends Application> applicationClass, OptionsByType optionsByType) {
String imageTag = UUID.randomUUID().toString();
String containerName = UUID.randomUUID().toString();
Docker docker = optionsByType.get(Docker.class);
String baseImage = docker.getBaseImage(applicationClass);
if (baseImage == null || baseImage.trim().isEmpty()) {
throw new RuntimeException("Cannot find a suitable base image for application class " + applicationClass);
}
try {
InetAddress localAddress = docker.getValidLocalAddress();
optionsByType.add(Variable.with("local.address", localAddress.getHostAddress()));
Files.createDirectories(tmpFolder.toPath());
// Write the Dockerfile
File dockerFile = writeDockerFile(launchable, baseImage, optionsByType);
// build image
DockerImage image = createImage(imageTag, dockerFile, docker, optionsByType);
// run the container
ApplicationProcess containerProcess = runContainer(containerName, launchable, image, docker, optionsByType);
if (containerProcess instanceof RemoteApplicationProcess) {
return (RemoteApplicationProcess) containerProcess;
}
return new WrapperRemoteApplicationProcess(containerProcess);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "An error occurred. Attempting to kill and remove container " + containerName + " and remove image " + imageTag);
safelyRemoveContainer(containerName, docker);
safelyRemoveImage(imageTag, docker);
throw new RuntimeException("An error occurred launching the application inside Docker", e);
} finally {
FileHelper.recursiveDelete(tmpFolder);
}
}
Aggregations