in src/library/Connect/KubernetesRemoteEnvironmentManager.cs [818:908]
private async Task<(V1Pod pod, V1Container container)> _PatchStatefulSetAsync(
RemoteContainerConnectionDetails remoteContainerConnectionDetails,
ILocalProcessConfig localProcessConfig,
CancellationToken cancellationToken)
{
using (var perfLogger = _log.StartPerformanceLogger(Events.KubernetesRemoteEnvironmentManager.AreaName, Events.KubernetesRemoteEnvironmentManager.Operations.PatchStatefulSet))
{
var namespaceName = remoteContainerConnectionDetails.NamespaceName;
var statefulSetName = remoteContainerConnectionDetails.StatefulSetName;
// Get pods for the statefulset, if there is no patch required we use the running pod under existing statefulset
var existingPods = (await _kubernetesClient.ListPodsForStatefulSetAsync(namespaceName, statefulSetName, cancellationToken))?.Items;
if (existingPods == null)
{
throw new InvalidOperationException(string.Format(Resources.FailedToListPodsFormat, namespaceName));
}
// Patch is null if there is no change in the statefulset spec
var (patch, reversePatch) = this._GetStatefulSetJsonPatch(remoteContainerConnectionDetails.StatefulSet, remoteContainerConnectionDetails.Container, _imageProvider.DevHostImage, localProcessConfig);
V1Pod result = null;
if (patch != null)
{
try
{
await _kubernetesClient.PatchV1StatefulSetAsync(namespaceName, statefulSetName, new V1Patch(JsonHelpers.SerializeObject(patch), PatchType.JsonPatch), cancellationToken: cancellationToken);
}
catch (Exception ex)
{
_log.Error($"Patch statefulSet {namespaceName}/{remoteContainerConnectionDetails.StatefulSet} failed. Patch is {JsonHelpers.SerializeForLoggingPurposeIndented(patch)}, {ex.Message}");
throw new UserVisibleException(_operationContext, ex, Resources.PatchResourceFailedFormat, KubernetesResourceType.StatefulSet.ToString(), namespaceName, statefulSetName, JsonHelpers.SerializeForLoggingPurposeIndented(patch), ex.Message);
}
var statefulSetPatch = new StatefulSetPatch(remoteContainerConnectionDetails.StatefulSet, reversePatch);
try
{
// Deploy remote restore job
await _remoteRestoreJobDeployer.CreateRemoteRestoreJobAsync(statefulSetName, namespaceName, statefulSetPatch, cancellationToken);
_patchState.AddStatefulSetPatch(statefulSetPatch);
}
catch (Exception ex)
{
// Try to clean up patched StatefulSet
this._ReportProgress(EventLevel.Warning, Resources.RestoringResourcePatchMessage, KubernetesResourceType.StatefulSet.ToString());
await _workloadRestorationService.Value.RestoreStatefulSetPatchAsync(
statefulSetPatch,
cancellationToken,
progressCallback: p => this._ReportProgress(p.Message, p.Level),
noThrow: true);
_log.Error($"Failed to deploy remote restore job for statefulSet {namespaceName}/{statefulSetName}. {ex.Message}");
throw new UserVisibleException(_operationContext, ex, Resources.FailedToDeployRemoteRestoreJobFormat, namespaceName, statefulSetName, ex.Message);
}
// Get new pod running under the StatefulSet
V1Pod newPod = null;
while (true)
{
await Task.Delay(200);
var pods = await _kubernetesClient.ListPodsForStatefulSetAsync(namespaceName, statefulSetName, cancellationToken);
newPod = pods.Items.Where(p => existingPods.All(p1 => p1.Metadata.CreationTimestamp < p.Metadata.CreationTimestamp)).FirstOrDefault();
if (newPod != null)
{
break;
}
}
result = await _WaitForPodToRunAsync(newPod, maxWaitTime: TimeSpan.FromMinutes(5), cancellationToken: cancellationToken);
this._ReportProgress(Resources.ResourcePatchedForAgentFormat, KubernetesResourceType.StatefulSet.ToString(), namespaceName, statefulSetName);
}
else
{
// If there's nothing to patch, it may mean we crashed during the last run.
// Try to get an existing patch state from the cluster.
var existingPatch = await _remoteRestoreJobDeployer.TryGetExistingPatchInfoAsync<StatefulSetPatch>(statefulSetName, namespaceName, cancellationToken);
if (existingPatch != null)
{
_patchState.AddStatefulSetPatch(existingPatch);
}
result = existingPods.Where(p => !this._IsPodTerminated(p) && p.Status?.StartTime != null).OrderBy(p => p.Status.StartTime).LastOrDefault();
}
if (result != null)
{
perfLogger.SetSucceeded();
}
if (result == null)
{
throw new UserVisibleException(_operationContext, Resources.FailedToGetRunningPodInDeploymentFormat, statefulSetName, namespaceName);
}
var container = result.Spec.Containers.Where(c => StringComparer.OrdinalIgnoreCase.Equals(c.Name, remoteContainerConnectionDetails.ContainerName)).First();
return (result, container);
}
}