private async Task _PatchStatefulSetAsync()

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);
            }
        }