in src/adu_workflow/src/agent_workflow.c [426:612]
void ADUC_Workflow_HandlePropertyUpdate(
ADUC_WorkflowData* currentWorkflowData, const unsigned char* propertyUpdateValue, bool forceUpdate)
{
ADUC_WorkflowHandle nextWorkflow;
ADUC_Result result = workflow_init((const char*)propertyUpdateValue, true /* shouldValidate */, &nextWorkflow);
workflow_set_force_update(nextWorkflow, forceUpdate);
ADUC_Result_t rootkeyErc = RootKeyUtility_GetReportingErc();
if (rootkeyErc != 0)
{
// Save this rootkeyErc for later informational purposes, since getting this far
// means processing of rootkey package actually succeeded.
workflow_add_erc(nextWorkflow, rootkeyErc);
}
// N.B. Do NOT do IsAducResultCodeFailure() check on rootkeyErc.
if (IsAducResultCodeFailure(result.ResultCode))
{
Log_Error("Invalid desired update action data. Update data: (%s)", propertyUpdateValue);
ADUC_Workflow_SetUpdateStateWithResult(currentWorkflowData, ADUCITF_State_Failed, result);
return;
}
ADUCITF_UpdateAction nextUpdateAction = workflow_get_action(nextWorkflow);
//
// Take lock until goto done.
//
// N.B.
// Lock must *NOT* be taken in HandleStartupWorkflowData and HandleUpdateAction or any functions they call
//
s_workflow_lock();
if (currentWorkflowData->WorkflowHandle != NULL)
{
if (nextUpdateAction == ADUCITF_UpdateAction_Cancel)
{
ADUC_WorkflowCancellationType currentCancellationType =
workflow_get_cancellation_type(currentWorkflowData->WorkflowHandle);
if (currentCancellationType == ADUC_WorkflowCancellationType_None)
{
workflow_set_cancellation_type(
currentWorkflowData->WorkflowHandle, ADUC_WorkflowCancellationType_Normal);
// call into handle update action for cancellation logic to invoke ADUC_Workflow_MethodCall_Cancel
ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
goto done;
}
else
{
Log_Info(
"Ignoring duplicate '%s' action. Current cancellation type is already '%s'.",
ADUCITF_UpdateActionToString(nextUpdateAction),
ADUC_Workflow_CancellationTypeToString(currentCancellationType));
goto done;
}
}
else if (nextUpdateAction == ADUCITF_UpdateAction_ProcessDeployment)
{
if (!forceUpdate && workflow_id_compare(currentWorkflowData->WorkflowHandle, nextWorkflow) == 0)
{
// Possible retry of the current workflow.
const char* currentRetryToken = workflow_peek_retryTimestamp(currentWorkflowData->WorkflowHandle);
const char* newRetryToken = workflow_peek_retryTimestamp(nextWorkflow);
if (!AgentOrchestration_IsRetryApplicable(currentRetryToken, newRetryToken))
{
Log_Warn(
"Ignoring Retry. currentRetryToken '%s', nextRetryToken '%s'.",
newRetryToken ? newRetryToken : "(NULL)",
currentRetryToken ? currentRetryToken : "(NULL)");
goto done;
}
Log_Debug("Retry %s is applicable", newRetryToken);
// Sets both cancellation type to Retry and updates the current retry token
workflow_update_retry_deployment(currentWorkflowData->WorkflowHandle, newRetryToken);
// call into handle update action for cancellation logic to invoke ADUC_Workflow_MethodCall_Cancel
ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
goto done;
}
else
{
// Possible replacement with a new workflow.
ADUCITF_State currentState = ADUC_WorkflowData_GetLastReportedState(currentWorkflowData);
ADUCITF_WorkflowStep currentWorkflowStep =
workflow_get_current_workflowstep(currentWorkflowData->WorkflowHandle);
if (currentState != ADUCITF_State_Idle && currentState != ADUCITF_State_Failed
&& currentWorkflowStep != ADUCITF_WorkflowStep_Undefined)
{
Log_Info(
"Replacement. workflow '%s' is being replaced with workflow '%s'.",
workflow_peek_id(currentWorkflowData->WorkflowHandle),
workflow_peek_id(nextWorkflow));
// If operation is in progress, then in the same critical section we set cancellation type to replacement
// and set the pending workflow on the handle for use by WorkCompletionCallback to continue on with the
// replacement deployment instead of going to idle and reporting the results as a cancel failure.
// Otherwise, if the operation is not in progress, in the same critical section it transfers the
// workflow handle of the new deployment into the current workflow data, so that we can handle the update action.
bool deferredReplacement =
workflow_update_replacement_deployment(currentWorkflowData->WorkflowHandle, nextWorkflow);
if (deferredReplacement)
{
Log_Info(
"Deferred Replacement workflow id [%s] since current workflow id [%s] was still in progress.",
workflow_peek_id(nextWorkflow),
workflow_peek_id(currentWorkflowData->WorkflowHandle));
// Ownership was transferred to current workflow so ensure it doesn't get freed.
nextWorkflow = NULL;
// call into handle update action for cancellation logic to invoke ADUC_Workflow_MethodCall_Cancel
ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
goto done;
}
Log_Debug("deferral not needed. Processing '%s' now", workflow_peek_id(nextWorkflow));
workflow_transfer_data(
currentWorkflowData->WorkflowHandle /* wfTarget */, nextWorkflow /* wfSource */);
ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
goto done;
}
// Fall through to handle new workflow
}
}
}
else
{
// This is a top level workflow, make sure that we set the working folder correctly.
const ADUC_ConfigInfo* config = ADUC_ConfigInfo_GetInstance();
if (config != NULL)
{
workflow_set_workfolder(nextWorkflow, "%s/%s", config->downloadsFolder, workflow_peek_id(nextWorkflow));
ADUC_ConfigInfo_ReleaseInstance(config);
}
else
{
Log_Error("Cannot set workfolder. Config is NULL.");
goto done;
}
}
// Continue with the new workflow.
workflow_free(currentWorkflowData->WorkflowHandle);
currentWorkflowData->WorkflowHandle = nextWorkflow;
nextWorkflow = NULL;
workflow_set_cancellation_type(
currentWorkflowData->WorkflowHandle,
nextUpdateAction == ADUCITF_UpdateAction_Cancel ? ADUC_WorkflowCancellationType_Normal
: ADUC_WorkflowCancellationType_None);
// If the agent has just started up but we have yet to report the installedUpdateId along with a state of 'Idle'
// we want to ignore any further action received so we don't confuse the workflow which would interpret a state of 'Idle'
// not accompanied with an installedUpdateId as a failed end state in some cases.
// In this case we will go through our startup logic which would report the installedUpdateId with a state of 'Idle',
// if we can determine that the update has been installed successfully (by calling IsInstalled()).
// Otherwise we will honor and process the action requested.
if (!currentWorkflowData->StartupIdleCallSent)
{
ADUC_Workflow_HandleStartupWorkflowData(currentWorkflowData);
}
else
{
ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
}
done:
s_workflow_unlock();
workflow_free(nextWorkflow);
Log_Debug("PropertyUpdated event handler completed.");
}