// the maximum length of cloud-init version stdout
#define MAX_LENGTH_CLOUDINIT_VERSION 256
+// the maximum length of cloud-init status stdout
+#define MAX_LENGTH_CLOUDINIT_STATUS 256
+// the default timeout of waiting for cloud-init execution done
+#define DEFAULT_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE 30
/*
* Constant definitions
USE_CLOUDINIT_IGNORE,
} USE_CLOUDINIT_ERROR_CODE;
+// the user-visible cloud-init application status code
+typedef enum CLOUDINIT_STATUS_CODE {
+ CLOUDINIT_STATUS_NOT_RUN = 0,
+ CLOUDINIT_STATUS_RUNNING,
+ CLOUDINIT_STATUS_DONE,
+ CLOUDINIT_STATUS_ERROR,
+ CLOUDINIT_STATUS_DISABLED,
+ CLOUDINIT_STATUS_UNKNOWN,
+} CLOUDINIT_STATUS_CODE;
+
/*
* Linked list definition
*
LogFunction sLog = NoLogging;
static uint16 gProcessTimeout = DEPLOYPKG_PROCESSTIMEOUT_DEFAULT;
static bool gProcessTimeoutSetByLauncher = false;
+static uint16 gWaitForCloudinitDoneTimeout =
+ DEFAULT_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE;
// .....................................................................................
}
}
+/*
+ *------------------------------------------------------------------------------
+ *
+ * DeployPkg_SetWaitForCloudinitDoneTimeout
+ *
+ * Set the timeout value of customization process waits for cloud-init
+ * execution done before trigger reboot and after connect network adapters.
+ * Tools deployPkg plugin reads this timeout value from tools.conf and
+ * checks if the timeout value is valid, then calls this API to set the
+ * valid timeout value to gWaitForCloudinitDoneTimeout.
+ *
+ * @param timeout [in]
+ * timeout value to be used for waiting for cloud-init execution done
+ *
+ *------------------------------------------------------------------------------
+ */
+
+void
+DeployPkg_SetWaitForCloudinitDoneTimeout(uint16 timeout)
+{
+ gWaitForCloudinitDoneTimeout = timeout;
+ sLog(log_debug, "Wait for cloud-init execution done timeout value: %d.",
+ gWaitForCloudinitDoneTimeout);
+}
+
// .....................................................................................
/**
* - cloud-init is enabled.
*
* @param [IN] dirPath Path where the package is extracted.
+ * @param [IN] ignoreCloudInit whether ignore cloud-init workflow.
* @returns the error code to use cloud-init work flow
*
*----------------------------------------------------------------------------
* */
static USE_CLOUDINIT_ERROR_CODE
-UseCloudInitWorkflow(const char* dirPath)
+UseCloudInitWorkflow(const char* dirPath, bool ignoreCloudInit)
{
static const char cfgName[] = "cust.cfg";
static const char metadataName[] = "metadata";
char cloudInitCommandOutput[MAX_LENGTH_CLOUDINIT_VERSION];
int forkExecResult;
- if (NULL == dirPath) {
- return USE_CLOUDINIT_INTERNAL_ERROR;
- }
-
- // check if cust.cfg file exists
- if (!CheckFileExist(dirPath, cfgName)) {
- return USE_CLOUDINIT_NO_CUST_CFG;
- }
-
forkExecResult = ForkExecAndWaitCommand(cloudInitCommand,
false,
cloudInitCommandOutput,
sizeof(cloudInitCommandOutput));
if (forkExecResult != 0) {
- sLog(log_info, "cloud-init is not installed.");
+ sLog(log_info, "Cloud-init is not installed.");
return USE_CLOUDINIT_NOT_INSTALLED;
} else {
- sLog(log_info, "cloud-init is installed.");
+ sLog(log_info, "Cloud-init is installed.");
+ if (ignoreCloudInit) {
+ sLog(log_info,
+ "Ignoring cloud-init workflow according to header flags.");
+ return USE_CLOUDINIT_IGNORE;
+ }
+ }
+
+ if (NULL == dirPath) {
+ return USE_CLOUDINIT_INTERNAL_ERROR;
+ }
+
+ // check if cust.cfg file exists
+ if (!CheckFileExist(dirPath, cfgName)) {
+ return USE_CLOUDINIT_NO_CUST_CFG;
}
// If cloud-init metadata exists, check if cloud-init support to handle
if (CheckFileExist(dirPath, metadataName)) {
int major, minor;
GetCloudinitVersion(cloudInitCommandOutput, &major, &minor);
- sLog(log_info, "metadata exists, check cloud-init version...");
+ sLog(log_info, "Metadata exists, check cloud-init version...");
if (major < CLOUDINIT_SUPPORT_RAW_DATA_MAJOR_VERSION ||
(major == CLOUDINIT_SUPPORT_RAW_DATA_MAJOR_VERSION &&
minor < CLOUDINIT_SUPPORT_RAW_DATA_MINOR_VERSION)) {
sLog(log_info,
- "cloud-init version %d.%d is older than required version %d.%d",
+ "Cloud-init version %d.%d is older than required version %d.%d.",
major,
minor,
CLOUDINIT_SUPPORT_RAW_DATA_MAJOR_VERSION,
}
+/**
+ *
+ * Function which gets the current cloud-init execution status.
+ * The status messages are copied from cloud-init offcial upstream
+ * https://github.com/canonical/cloud-init/blob/main/cloudinit/cmd/status.py
+ * These status messages are consistent since year 2017
+ *
+ * @returns the status code of cloud-init application
+ *
+ **/
+
+static CLOUDINIT_STATUS_CODE
+GetCloudinitStatus() {
+ // Cloud-init execution status messages
+ static const char* NOT_RUN = "not run";
+ static const char* RUNNING = "running";
+ static const char* DONE = "done";
+ static const char* ERROR = "error";
+ static const char* DISABLED = "disabled";
+
+ static const char cloudinitStatusCmd[] = "/usr/bin/cloud-init status";
+ char cloudinitStatusCmdOutput[MAX_LENGTH_CLOUDINIT_STATUS];
+ int forkExecResult;
+
+ forkExecResult = ForkExecAndWaitCommand(cloudinitStatusCmd,
+ false,
+ cloudinitStatusCmdOutput,
+ MAX_LENGTH_CLOUDINIT_STATUS);
+ if (forkExecResult != 0) {
+ sLog(log_info, "Unable to get cloud-init status.");
+ return CLOUDINIT_STATUS_UNKNOWN;
+ } else {
+ if (strstr(cloudinitStatusCmdOutput, NOT_RUN) != NULL) {
+ sLog(log_info, "Cloud-init status is '%s'.", NOT_RUN);
+ return CLOUDINIT_STATUS_NOT_RUN;
+ } else if (strstr(cloudinitStatusCmdOutput, RUNNING) != NULL) {
+ sLog(log_info, "Cloud-init status is '%s'.", RUNNING);
+ return CLOUDINIT_STATUS_RUNNING;
+ } else if (strstr(cloudinitStatusCmdOutput, DONE) != NULL) {
+ sLog(log_info, "Cloud-init status is '%s'.", DONE);
+ return CLOUDINIT_STATUS_DONE;
+ } else if (strstr(cloudinitStatusCmdOutput, ERROR) != NULL) {
+ sLog(log_info, "Cloud-init status is '%s'.", ERROR);
+ return CLOUDINIT_STATUS_ERROR;
+ } else if (strstr(cloudinitStatusCmdOutput, DISABLED) != NULL) {
+ sLog(log_info, "Cloud-init status is '%s'.", DISABLED);
+ return CLOUDINIT_STATUS_DISABLED;
+ } else {
+ sLog(log_warning, "Cloud-init status is unknown.");
+ return CLOUDINIT_STATUS_UNKNOWN;
+ }
+ }
+}
+
+
+/**
+ *
+ * Function which waits for cloud-init execution done.
+ *
+ * This function is called only when below conditions are fulfilled:
+ * - cloud-init is installed
+ * - guest os reboot is not skipped (so traditional GOSC workflow only)
+ * - deployment processed successfully in guest
+ *
+ * Default waiting timeout is DEFAULT_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE seconds,
+ * when the timeout is reached, reboot will be triggered no matter what the
+ * cloud-init execution status is then.
+ * The timeout can be overwritten by the value which is set in tools.conf,
+ * if 0 is set in tools.conf, no waiting will be performed.
+ *
+ **/
+
+static void
+WaitForCloudinitDone() {
+ const int CheckStatusInterval = 5;
+ int timeoutSec = 0;
+ int elapsedSec = 0;
+ CLOUDINIT_STATUS_CODE cloudinitStatus = CLOUDINIT_STATUS_UNKNOWN;
+
+ // No waiting when gWaitForCloudinitDoneTimeout is set to 0
+ if (gWaitForCloudinitDoneTimeout == 0) {
+ return;
+ }
+
+ timeoutSec = gWaitForCloudinitDoneTimeout;
+
+ while (1) {
+ if (elapsedSec == timeoutSec) {
+ sLog(log_info, "Timed out waiting for cloud-init execution done.");
+ return;
+ }
+ if (elapsedSec % CheckStatusInterval == 0) {
+ cloudinitStatus = GetCloudinitStatus();
+ // CLOUDINIT_STATUS_NOT_RUN and CLOUDINIT_STATUS_RUNNING represent
+ // cloud-init execution has not finished
+ if (cloudinitStatus != CLOUDINIT_STATUS_NOT_RUN &&
+ cloudinitStatus != CLOUDINIT_STATUS_RUNNING) {
+ sLog(log_info, "Cloud-init execution is not on-going.");
+ return;
+ }
+ }
+ sleep(1);
+ elapsedSec++;
+ }
+}
+
+
/**
*
* Function which cleans up the deployment directory imcDirPath.
char *imcDirPath = NULL;
USE_CLOUDINIT_ERROR_CODE useCloudInitWorkflow = USE_CLOUDINIT_IGNORE;
int imcDirPathSize = 0;
+ bool ignoreCloudInit = false;
TransitionState(NULL, INPROGRESS);
// Notify the vpx of customization in-progress state
}
}
- if (!(flags & VMWAREDEPLOYPKG_HEADER_FLAGS_IGNORE_CLOUD_INIT)) {
- useCloudInitWorkflow = UseCloudInitWorkflow(imcDirPath);
- } else {
- sLog(log_info, "Ignoring cloud-init.");
- }
+ ignoreCloudInit = flags & VMWAREDEPLOYPKG_HEADER_FLAGS_IGNORE_CLOUD_INIT;
+ useCloudInitWorkflow = UseCloudInitWorkflow(imcDirPath, ignoreCloudInit);
sLog(log_info, "UseCloudInitWorkflow return: %d", useCloudInitWorkflow);
//Reset the guest OS
if (!sSkipReboot && !deploymentResult) {
+ if (useCloudInitWorkflow != USE_CLOUDINIT_NOT_INSTALLED) {
+ sLog(log_info, "Do not trigger reboot if cloud-init is executing.");
+ WaitForCloudinitDone();
+ }
pid_t pid = fork();
if (pid == -1) {
sLog(log_error, "Failed to fork: '%s'.", strerror(errno));
// Using 3600s as the upper limit of timeout value in tools.conf.
#define MAX_TIMEOUT_FROM_TOOLCONF 3600
+// Using 600s as the upper limit of waiting for cloud-init execution done
+// timeout value in tools.conf.
+#define MAX_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE 600
static char *DeployPkgGetTempDir(void);
ToolsDeployPkgError ret = TOOLSDEPLOYPKG_ERROR_SUCCESS;
#ifndef _WIN32
int processTimeout;
+ int waitForCloudinitDoneTimeout;
#endif
/*
* Using 0 as the default value of CONFNAME_DEPLOYPKG_PROCESSTIMEOUT in tools.conf
*/
processTimeout =
- VMTools_ConfigGetInteger(ctx->config,
- CONFGROUPNAME_DEPLOYPKG,
- CONFNAME_DEPLOYPKG_PROCESSTIMEOUT,
- 0);
+ VMTools_ConfigGetInteger(ctx->config,
+ CONFGROUPNAME_DEPLOYPKG,
+ CONFNAME_DEPLOYPKG_PROCESSTIMEOUT,
+ 0);
if (processTimeout > 0 && processTimeout <= MAX_TIMEOUT_FROM_TOOLCONF) {
DeployPkgLog_Log(log_debug, "[%s] %s in tools.conf: %d",
CONFGROUPNAME_DEPLOYPKG,
DeployPkgLog_Log(log_debug, "The valid timeout value range: 1 ~ %d",
MAX_TIMEOUT_FROM_TOOLCONF);
}
+
+ /*
+ * Get timeout of waiting for cloud-init execution done from tools.conf.
+ * Only when a valid 'timeout' got from tools.conf, deployPkg will call
+ * DeployPkg_SetWaitForCloudinitDoneTimeout to overwrite the default timeout
+ * of waiting for cloud-init execution done.
+ * The valid value range is from 0 to MAX_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE.
+ * Return an invalid value -1 if CONFNAME_DEPLOYPKG_WAIT_CLOUDINIT_TIMEOUT is
+ * not set in tools.conf.
+ */
+ waitForCloudinitDoneTimeout =
+ VMTools_ConfigGetInteger(ctx->config,
+ CONFGROUPNAME_DEPLOYPKG,
+ CONFNAME_DEPLOYPKG_WAIT_CLOUDINIT_TIMEOUT,
+ -1);
+ if (waitForCloudinitDoneTimeout >= 0 &&
+ waitForCloudinitDoneTimeout <= MAX_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE) {
+ DeployPkgLog_Log(log_debug, "[%s] %s in tools.conf: %d",
+ CONFGROUPNAME_DEPLOYPKG,
+ CONFNAME_DEPLOYPKG_WAIT_CLOUDINIT_TIMEOUT,
+ waitForCloudinitDoneTimeout);
+ DeployPkg_SetWaitForCloudinitDoneTimeout(waitForCloudinitDoneTimeout);
+ } else {
+ if (waitForCloudinitDoneTimeout != -1) {
+ DeployPkgLog_Log(log_debug,
+ "Ignore invalid value %d from tools.conf [%s] %s",
+ waitForCloudinitDoneTimeout,
+ CONFGROUPNAME_DEPLOYPKG,
+ CONFNAME_DEPLOYPKG_WAIT_CLOUDINIT_TIMEOUT);
+ }
+ DeployPkgLog_Log(log_debug, "The valid [%s] %s value range: 0 ~ %d",
+ CONFGROUPNAME_DEPLOYPKG,
+ CONFNAME_DEPLOYPKG_WAIT_CLOUDINIT_TIMEOUT,
+ MAX_TIMEOUT_WAIT_FOR_CLOUDINIT_DONE);
+ }
#endif
if (0 != DeployPkg_DeployPackageFromFile(pkgFile)) {