Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,25 @@ protected void copyScriptFile(String nodeAddress, final int sshPort, File file,
}
}

/**
* Deletes the local temporary copies of the scripts created by {@link #retrieveScriptFiles()}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: fairly detailed javadoc for a small private helper that just fans out to deleteScriptFileQuietly calls, could be trimmed.

* once they have been copied to the cluster node(s). Without this, every deploy/autoscale/PV-cleanup
* action leaks a *.sh file in the management server's tmp directory.
*/
protected void cleanupScriptFiles() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could this loop over a list of the 5 fields instead of calling deleteScriptFileQuietly 5 times by hand? Would be one line instead of five, and less to update if a new script field gets added later.

deleteScriptFileQuietly(deploySecretsScriptFile);
deleteScriptFileQuietly(deployProviderScriptFile);
deleteScriptFileQuietly(deployCsiDriverScriptFile);
deleteScriptFileQuietly(deletePvScriptFile);
deleteScriptFileQuietly(autoscaleScriptFile);
}

protected void deleteScriptFileQuietly(File file) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the field be set to null after deleting? Right now it still points to a file that no longer exists. If it's used again before retrieveScriptFiles() runs, it could silently try to work with a deleted file.

if (file != null && file.exists() && !file.delete()) {
logger.debug("Failed to delete temporary Kubernetes cluster script file: {}", file.getAbsolutePath());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this represents a leaked temp file on disk, feels like it should log at warn rather than debug.

}
}

protected boolean taintControlNodes() {
StringBuilder commands = new StringBuilder();
List<KubernetesClusterVmMapVO> vmMapVOList = getKubernetesClusterVMMaps();
Expand Down Expand Up @@ -820,7 +839,11 @@ protected boolean deployProvider() {
if (!result.first()) {
logMessage(Level.INFO, "Provider files missing. Adding them now", null);
retrieveScriptFiles();
copyScripts(publicIpAddress, sshPort);
try {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This same try { copyScripts(...) } finally { cleanupScriptFiles(); } block shows up 3 times (here, deployCsiDriver(), and autoscaleCluster()). Could this be a small shared helper, like copyScriptsWithCleanup(nodeAddress, sshPort), to avoid repeating it?

copyScripts(publicIpAddress, sshPort);
} finally {
cleanupScriptFiles();
}

if (!createCloudStackSecret(keys)) {
logTransitStateAndThrow(Level.ERROR, String.format("Failed to setup keys for Kubernetes cluster %s",
Expand Down Expand Up @@ -857,7 +880,11 @@ protected boolean deployCsiDriver() {
if (!result.first()) {
logMessage(Level.INFO, "CSI files missing. Adding them now", null);
retrieveScriptFiles();
copyScripts(publicIpAddress, sshPort);
try {
copyScripts(publicIpAddress, sshPort);
} finally {
cleanupScriptFiles();
}

if (!createCloudStackSecret(keys)) {
logTransitStateAndThrow(Level.ERROR, String.format("Failed to setup keys for Kubernetes cluster %s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,11 @@ protected boolean autoscaleCluster(boolean enable, Long minSize, Long maxSize) {
if (!result.first()) {
logMessage(Level.INFO, "Autoscaling files missing. Adding them now", null);
retrieveScriptFiles();
copyScripts(publicIpAddress, sshPort);
try {
copyScripts(publicIpAddress, sshPort);
} finally {
cleanupScriptFiles();
}

if (!createCloudStackSecret(keys)) {
logTransitStateAndThrow(Level.ERROR, String.format("Failed to setup keys for Kubernetes cluster %s",
Expand Down Expand Up @@ -1003,19 +1007,23 @@ public boolean deletePVsWithReclaimPolicyDelete() {
if (Boolean.FALSE.equals(result.first())) {
logMessage(Level.INFO, "PV delete script missing. Adding it now", null);
retrieveScriptFiles();
if (deletePvScriptFile != null) {
copyScriptFile(publicIpAddress, sshPort, deletePvScriptFile, deletePvScriptFilename);
logMessage(Level.INFO, "Executing PV deletion script (this may take several minutes)...", null);
result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
pkFile, null, command, 10000, 10000, 600000); // 10 minute timeout
if (Boolean.FALSE.equals(result.first())) {
logMessage(Level.ERROR, "PV deletion script failed: " + result.second(), null);
throw new CloudRuntimeException(result.second());
try {
if (deletePvScriptFile != null) {
copyScriptFile(publicIpAddress, sshPort, deletePvScriptFile, deletePvScriptFilename);
logMessage(Level.INFO, "Executing PV deletion script (this may take several minutes)...", null);
result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
pkFile, null, command, 10000, 10000, 600000); // 10 minute timeout
if (Boolean.FALSE.equals(result.first())) {
logMessage(Level.ERROR, "PV deletion script failed: " + result.second(), null);
throw new CloudRuntimeException(result.second());
}
logMessage(Level.INFO, "PV deletion script completed successfully", null);
} else {
logMessage(Level.WARN, "PV delete script file not found in resources, skipping PV deletion", null);
return false;
}
logMessage(Level.INFO, "PV deletion script completed successfully", null);
} else {
logMessage(Level.WARN, "PV delete script file not found in resources, skipping PV deletion", null);
return false;
} finally {
cleanupScriptFiles();
}
} else {
logMessage(Level.INFO, "PV deletion script completed successfully", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ protected void retrieveScriptFiles() {
upgradeScriptFile = retrieveScriptFile(upgradeScriptFilename);
}

@Override
protected void cleanupScriptFiles() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this delete upgradeScriptFile too early? deployProvider() runs per node in the loop and calls cleanupScriptFiles() in its own finally, and since this is an UpgradeWorker, that hits this override and deletes upgradeScriptFile before runInstallScriptOnVM() (next line) SCPs it. Could this break the upgrade the first time a node needs the provider re-deployed?

super.cleanupScriptFiles();
deleteScriptFileQuietly(upgradeScriptFile);
}

private Pair<Boolean, String> runInstallScriptOnVM(final UserVm vm, final int index) throws Exception {
int nodeSshPort = sshPort == 22 ? sshPort : sshPort + index;
String nodeAddress = (index > 0 && sshPort == 22) ? vm.getPrivateIpAddress() : publicIpAddress;
Expand Down Expand Up @@ -176,7 +182,11 @@ public boolean upgradeCluster() throws CloudRuntimeException {
retrieveScriptFiles();
stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.UpgradeRequested);
attachIsoKubernetesVMs(clusterVMs, upgradeVersion);
upgradeKubernetesClusterNodes();
try {
upgradeKubernetesClusterNodes();
} finally {
cleanupScriptFiles();
}
detachIsoKubernetesVMs(clusterVMs);
KubernetesClusterVO kubernetesClusterVO = kubernetesClusterDao.findById(kubernetesCluster.getId());
kubernetesClusterVO.setKubernetesVersionId(upgradeVersion.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package com.cloud.kubernetes.cluster.actionworkers;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -229,4 +230,48 @@ public void testGetMergedAffinityGroupIdsExplicitDedicationAlreadyInList() {
Assert.assertTrue(result.contains(99L));
Assert.assertTrue(result.contains(2L));
}

@Test
public void testCleanupScriptFilesDeletesAllTempFiles() throws Exception {
actionWorker.deploySecretsScriptFile = File.createTempFile("deploy-cloudstack-secret", ".sh");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these strings duplicate actionWorker.deploySecretsScriptFilename and friends. Could reuse those constants instead?

actionWorker.deployProviderScriptFile = File.createTempFile("deploy-provider", ".sh");
actionWorker.deployCsiDriverScriptFile = File.createTempFile("deploy-csi-driver", ".sh");
actionWorker.deletePvScriptFile = File.createTempFile("delete-pv-reclaimpolicy-delete", ".sh");
actionWorker.autoscaleScriptFile = File.createTempFile("autoscale-kube-cluster", ".sh");

List<File> files = Arrays.asList(actionWorker.deploySecretsScriptFile, actionWorker.deployProviderScriptFile,
actionWorker.deployCsiDriverScriptFile, actionWorker.deletePvScriptFile, actionWorker.autoscaleScriptFile);
for (File f : files) {
Assert.assertTrue(f.exists());
}

actionWorker.cleanupScriptFiles();

for (File f : files) {
Assert.assertFalse("Temporary script file should have been deleted: " + f.getAbsolutePath(), f.exists());
}
}

@Test
public void testDeleteScriptFileQuietlyHandlesNullAndMissingFiles() throws Exception {
// null must not throw
actionWorker.deleteScriptFileQuietly(null);

// a File that does not exist must not throw
File missing = new File(System.getProperty("java.io.tmpdir"), "cks-nonexistent-" + UUID.randomUUID() + ".sh");
Assert.assertFalse(missing.exists());
actionWorker.deleteScriptFileQuietly(missing);

// an existing file is deleted
File present = File.createTempFile("cks-present", ".sh");
Assert.assertTrue(present.exists());
actionWorker.deleteScriptFileQuietly(present);
Assert.assertFalse(present.exists());
}

@Test
public void testCleanupScriptFilesWithNullFieldsDoesNotThrow() {
// No retrieveScriptFiles() was called, so all file fields are null.
actionWorker.cleanupScriptFiles();
}
}
Loading