Skip to content

Commit

Permalink
fix: inject component manager into kernel alts when required
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-mage committed Dec 12, 2024
1 parent ef28d66 commit 8f923e6
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package com.aws.greengrass.deployment.activator;

import com.aws.greengrass.componentmanager.ComponentManager;
import com.aws.greengrass.componentmanager.exceptions.PackageLoadingException;
import com.aws.greengrass.deployment.bootstrap.BootstrapManager;
import com.aws.greengrass.deployment.errorcode.DeploymentErrorCodeUtils;
import com.aws.greengrass.deployment.exceptions.DeploymentException;
Expand All @@ -19,12 +21,15 @@
import com.aws.greengrass.util.NucleusPaths;
import com.aws.greengrass.util.Pair;
import com.aws.greengrass.util.Utils;
import com.aws.greengrass.util.platforms.Platform;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;

Expand All @@ -36,6 +41,7 @@
import static com.aws.greengrass.deployment.bootstrap.BootstrapSuccessCode.REQUEST_RESTART;
import static com.aws.greengrass.deployment.model.Deployment.DeploymentStage.KERNEL_ROLLBACK;
import static com.aws.greengrass.deployment.model.Deployment.DeploymentStage.ROLLBACK_BOOTSTRAP;
import static com.aws.greengrass.lifecyclemanager.KernelAlternatives.KERNEL_BIN_DIR;

/**
* Activation and rollback of Kernel update deployments.
Expand Down Expand Up @@ -68,11 +74,21 @@ public void activate(Map<String, Object> newConfig, Deployment deployment,
try {
kernelAlternatives.validateLaunchDirSetupVerbose();
} catch (DirectoryValidationException e) {
totallyCompleteFuture.complete(
new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE,
new DeploymentException("Unable to process deployment. Greengrass launch directory"
+ " is not set up or Greengrass is not set up as a system service", e)));
return;
if (!canRecoverMissingLaunchDirSetup()) {
totallyCompleteFuture.complete(
new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE,
new DeploymentException("Unable to process deployment. Greengrass launch directory"
+ " is not set up or Greengrass is not set up as a system service", e)));
return;
}

try {
kernelAlternatives.validateLoaderAsExecutable();
} catch (DeploymentException ex) {
totallyCompleteFuture.complete(
new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e));
return;
}
} catch (DeploymentException e) {
totallyCompleteFuture.complete(
new DeploymentResult(DeploymentResult.DeploymentStatus.FAILED_NO_STATE_CHANGE, e));
Expand Down Expand Up @@ -152,4 +168,43 @@ void rollback(Deployment deployment, Throwable failureCause) {
// Restart Kernel regardless and rely on loader orchestration
kernel.shutdown(30, REQUEST_RESTART);
}

protected boolean canRecoverMissingLaunchDirSetup() {
/*
Try and relink launch dir with the following replacement criteria
1. check if current Nucleus execution package is valid
2. un-archive current Nucleus version from component store
3. fail with DirectoryValidationException if above steps do not satisfy
*/
try {
Path currentNucleusExecutablePath = KernelAlternatives.locateCurrentKernelUnpackDir();
if (Files.exists(currentNucleusExecutablePath.resolve(KERNEL_BIN_DIR)
.resolve(Platform.getInstance().loaderFilename()))) {
logger.atDebug().kv("path", currentNucleusExecutablePath)
.log("Current Nucleus executable is valid, setting up launch dir");
kernelAlternatives.relinkInitLaunchDir(currentNucleusExecutablePath, true);
return true;
}

ComponentManager componentManager = kernel.getContext().get(ComponentManager.class);
List<Path> localNucleusExecutablePaths = componentManager.unArchiveCurrentNucleusVersionArtifacts();
if (!localNucleusExecutablePaths.isEmpty()) {
Optional<Path> validNucleusExecutablePath = localNucleusExecutablePaths.stream()
.filter(path -> Files.exists(path.resolve(KERNEL_BIN_DIR)
.resolve(Platform.getInstance().loaderFilename())))
.findFirst();
if (validNucleusExecutablePath.isPresent()) {
logger.atDebug().kv("path", validNucleusExecutablePath.get())
.log("Un-archived current Nucleus artifact");
kernelAlternatives.relinkInitLaunchDir(validNucleusExecutablePath.get(), true);
return true;
}
}
logger.atInfo().log("Cannot recover missing launch dir setup as no local Nucleus artifact is present");
return false;
} catch (IOException | URISyntaxException | PackageLoadingException e) {
logger.atWarn().setCause(e).log("Could not recover missing launch dir setup");
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

package com.aws.greengrass.lifecyclemanager;

import com.aws.greengrass.componentmanager.ComponentManager;
import com.aws.greengrass.componentmanager.exceptions.PackageLoadingException;
import com.aws.greengrass.config.Configuration;
import com.aws.greengrass.config.Topics;
import com.aws.greengrass.dependency.Context;
Expand Down Expand Up @@ -37,8 +35,6 @@
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;

Expand Down Expand Up @@ -74,18 +70,15 @@ public class KernelAlternatives {
private static final String BOOTSTRAP_ON_ROLLBACK_CONFIG_KEY = "bootstrapOnRollback";

private final NucleusPaths nucleusPaths;
private final ComponentManager componentManager;

/**
* Constructor for KernelAlternatives, which manages the alternative launch directory of Kernel.
*
* @param nucleusPaths nucleus paths
* @param componentManager component manager
*/
@Inject
public KernelAlternatives(NucleusPaths nucleusPaths, ComponentManager componentManager) {
public KernelAlternatives(NucleusPaths nucleusPaths) {
this.nucleusPaths = nucleusPaths;
this.componentManager = componentManager;
try {
setupInitLaunchDirIfAbsent();
} catch (IOException e) {
Expand Down Expand Up @@ -165,62 +158,16 @@ public void writeLaunchParamsToFile(String content) throws IOException {
}
}

public boolean isLaunchDirSetup() {
private boolean isLaunchDirSetup() {
return Files.isSymbolicLink(getCurrentDir()) && validateLaunchDirSetup(getCurrentDir());
}

protected boolean canRecoverMissingLaunchDirSetup()
throws IOException, URISyntaxException, PackageLoadingException {
/*
Try and relink launch dir with the following replacement criteria
1. check if current Nucleus execution package is valid
2. un-archive current Nucleus version from component store
3. fail with DirectoryValidationException if above steps do not satisfy
*/
Path currentNucleusExecutablePath = locateCurrentKernelUnpackDir();
if (Files.exists(currentNucleusExecutablePath.resolve(KERNEL_BIN_DIR)
.resolve(Platform.getInstance().loaderFilename()))) {
logger.atDebug().kv("path", currentNucleusExecutablePath)
.log("Current Nucleus executable is valid, setting up launch dir");
relinkInitLaunchDir(currentNucleusExecutablePath, true);
return true;
}

List<Path> localNucleusExecutablePaths = componentManager.unArchiveCurrentNucleusVersionArtifacts();
if (!localNucleusExecutablePaths.isEmpty()) {
Optional<Path> validNucleusExecutablePath = localNucleusExecutablePaths.stream()
.filter(path -> Files.exists(path.resolve(KERNEL_BIN_DIR)
.resolve(Platform.getInstance().loaderFilename())))
.findFirst();
if (validNucleusExecutablePath.isPresent()) {
logger.atDebug().kv("path", validNucleusExecutablePath.get())
.log("Un-archived current Nucleus artifact");
relinkInitLaunchDir(validNucleusExecutablePath.get(), true);
return true;
}
}
throw new PackageLoadingException("Could not find a valid Nucleus package to recover launch dir setup");
}

/**
* Validate that launch directory is set up.
* Validate that loader file's permissions are set to be executable. If not attempt to do so.
*
* @throws DirectoryValidationException when a file is missing
* @throws DeploymentException when user is not allowed to change file permission
* @throws DeploymentException if unable to make loader an executable
*/
public void validateLaunchDirSetupVerbose() throws DirectoryValidationException, DeploymentException {
try {
if (!Files.isSymbolicLink(getCurrentDir()) || !Files.exists(getLoaderPathFromLaunchDir(getCurrentDir()))) {
logger.atInfo().log("Current launch dir setup is missing, attempting to recover");
canRecoverMissingLaunchDirSetup();
}
} catch (PackageLoadingException | IOException ex) {
throw new DirectoryValidationException("Unable to relink init launch directory", ex);
} catch (URISyntaxException ex) {
// TODO: Fix usage of root path with spaces on linux
throw new DeploymentException("Could not parse init launch directory path", ex);
}

public void validateLoaderAsExecutable() throws DeploymentException {
Path currentDir = getCurrentDir();
Path loaderPath = getLoaderPathFromLaunchDir(currentDir);
if (!loaderPath.toFile().canExecute()) {
Expand All @@ -235,6 +182,20 @@ public void validateLaunchDirSetupVerbose() throws DirectoryValidationException,
}
}

/**
* Validate that launch directory is set up.
*
* @throws DirectoryValidationException when a file is missing
* @throws DeploymentException when user is not allowed to change file permission
*/
public void validateLaunchDirSetupVerbose() throws DeploymentException {
if (!Files.isSymbolicLink(getCurrentDir()) || !Files.exists(getLoaderPathFromLaunchDir(getCurrentDir()))) {
logger.atInfo().log("Current launch dir setup is missing, attempting to recover");
throw new DirectoryValidationException("Current launch dir setup missing");
}
validateLoaderAsExecutable();
}

@SuppressWarnings("PMD.ConfusingTernary")
private boolean validateLaunchDirSetup(Path path) {
Path loaderPath = getLoaderPathFromLaunchDir(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class KernelUpdateActivatorTest {
KernelUpdateActivator kernelUpdateActivator;

@BeforeEach
void beforeEach() throws IOException {
void beforeEach() {
doReturn(deploymentDirectoryManager).when(context).get(eq(DeploymentDirectoryManager.class));
doReturn(kernelAlternatives).when(context).get(eq(KernelAlternatives.class));
doReturn(nucleusPaths).when(kernel).getNucleusPaths();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class KernelAlternativesTest {
void beforeEach() throws IOException {
NucleusPaths paths = new NucleusPaths("mock_loader_logs.log");
paths.setKernelAltsPath(altsDir);
kernelAlternatives = spy(new KernelAlternatives(paths, componentManager));
kernelAlternatives = spy(new KernelAlternatives(paths));
}

@Test
Expand Down

0 comments on commit 8f923e6

Please sign in to comment.