From 8f923e6a6e93257e7368f91150093c46c001743f Mon Sep 17 00:00:00 2001 From: Siddhant Srivastava Date: Wed, 11 Dec 2024 11:49:47 -0800 Subject: [PATCH] fix: inject component manager into kernel alts when required --- .../activator/KernelUpdateActivator.java | 65 ++++++++++++++-- .../lifecyclemanager/KernelAlternatives.java | 77 +++++-------------- .../activator/KernelUpdateActivatorTest.java | 2 +- .../KernelAlternativesTest.java | 2 +- 4 files changed, 81 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java b/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java index 07cda7efd7..86e9f1e9d1 100644 --- a/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java +++ b/src/main/java/com/aws/greengrass/deployment/activator/KernelUpdateActivator.java @@ -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; @@ -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; @@ -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. @@ -68,11 +74,21 @@ public void activate(Map 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)); @@ -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 localNucleusExecutablePaths = componentManager.unArchiveCurrentNucleusVersionArtifacts(); + if (!localNucleusExecutablePaths.isEmpty()) { + Optional 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; + } + } } diff --git a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java index 4e872ba294..7cc81b16c7 100644 --- a/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java +++ b/src/main/java/com/aws/greengrass/lifecyclemanager/KernelAlternatives.java @@ -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; @@ -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; @@ -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) { @@ -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 localNucleusExecutablePaths = componentManager.unArchiveCurrentNucleusVersionArtifacts(); - if (!localNucleusExecutablePaths.isEmpty()) { - Optional 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()) { @@ -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); diff --git a/src/test/java/com/aws/greengrass/deployment/activator/KernelUpdateActivatorTest.java b/src/test/java/com/aws/greengrass/deployment/activator/KernelUpdateActivatorTest.java index e62fb994f3..dc02b4ae53 100644 --- a/src/test/java/com/aws/greengrass/deployment/activator/KernelUpdateActivatorTest.java +++ b/src/test/java/com/aws/greengrass/deployment/activator/KernelUpdateActivatorTest.java @@ -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(); diff --git a/src/test/java/com/aws/greengrass/lifecyclemanager/KernelAlternativesTest.java b/src/test/java/com/aws/greengrass/lifecyclemanager/KernelAlternativesTest.java index a9fab1c26a..3df5c8b821 100644 --- a/src/test/java/com/aws/greengrass/lifecyclemanager/KernelAlternativesTest.java +++ b/src/test/java/com/aws/greengrass/lifecyclemanager/KernelAlternativesTest.java @@ -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