in src/library/Utilities/RemoteContainerConnectionDetailsResolver.cs [41:203]
public async Task<RemoteContainerConnectionDetails> ResolveConnectionDetails(
RemoteContainerConnectionDetails remoteContainerConnectionDetails,
CancellationToken cancellationToken)
{
// If we are in prep there is no need to resolve anything since we are only using the remoteContainerConnectionDetails to hold a namespace
// TODO (lolodi): this needs to be refactored so that we don't need to use such workarounds.
if (remoteContainerConnectionDetails.AgentHostingMode == RemoteAgentHostingMode.PrepConnect)
{
return remoteContainerConnectionDetails;
}
// Check if restoration pod is present and in running state. This is an indication that previous session is still connected or it has not finished restoring yet.
var allPods = (await _kubernetesClient.ListPodsInNamespaceAsync(remoteContainerConnectionDetails.NamespaceName, null, cancellationToken))?.Items;
if (allPods != null && allPods.Count > 0) {
var podsRunningRestorationJob = allPods.Where(p => StringComparer.OrdinalIgnoreCase.Equals(p.Status?.Phase, "Running") &&
p.Metadata.Name.StartsWith(remoteContainerConnectionDetails.ServiceName + "-restore") &&
(p.Status?.ContainerStatuses?.Where(cs => cs.Image.Contains(ImageProvider.DevHostRestorationJob.Name)).Any() ?? false));
if (podsRunningRestorationJob.Count() > 0)
{
_log.Warning("Restoration image {0} was found for '{1}' service. This means previous session is not restored yet. Try again once previous session is disconnected and restored.",
ImageProvider.DevHostRestorationJob.Name, remoteContainerConnectionDetails.ServiceName);
throw new UserVisibleException(_operationContext, Resources.PreviousSessionStillConnected, remoteContainerConnectionDetails.ServiceName);
}
}
// Our target in remoteContainerConnectionDetails can be specified in a variety of ways. The most common one is Service.
// If a service is specified we need to find the pod(s) that back that service and, if routing is disabled, the deployment that back the pods.
// The deployment is not used when routing is enabled because routing deploys a new standalone pod.
// If we are targeting a Service, we need to resolve the pod (or pods) that back the service.
if (remoteContainerConnectionDetails.SourceEntityType == KubernetesEntityType.Service)
{
var service = await _kubernetesClient.GetV1ServiceAsync(
remoteContainerConnectionDetails.NamespaceName,
remoteContainerConnectionDetails.ServiceName,
cancellationToken);
remoteContainerConnectionDetails.UpdateServiceDetails(service ?? throw new UserVisibleException(_operationContext, Resources.SpecifiedServiceCouldNotBeFoundFormat, remoteContainerConnectionDetails.ServiceName, remoteContainerConnectionDetails.NamespaceName));
var pods = await this._GetTargetPodsFromServiceAsync(
remoteContainerConnectionDetails.NamespaceName,
remoteContainerConnectionDetails.Service,
cancellationToken);
_log.Verbose($"Resolved {pods.Count()} from service.");
// We want to see how many containers each pod has. Our current assumption is that all pods backing a service have the same number of containers, but if this is not the case
// we will need to either improve how we resolve the pod, or provide a way for the user to tell us which container to use
var minNumberContainers = pods.Select(p => p.Spec.Containers.Count).Min();
var maxNumberContainers = pods.Select(p => p.Spec.Containers.Count).Max();
_log.Verbose($"Max number of containers in pod: {maxNumberContainers}, min number of containers in pod: {minNumberContainers}");
// If there are multiple pods backing the service it is probably because there is a deployment or statefulset with replicas > 1
// To be sure we check if the pods are backed by the same deployment and while we are at it we cache the deployment in case we need it later.
if (pods.Count() > 1)
{
await ResolveBackingObjectDetailsAsync(remoteContainerConnectionDetails, pods, cancellationToken);
}
// We now have the pod(s). We need to update the remoteContainerConnectionDetails with it.
// TODO: This system of setting the source entity multiple times (e.g. we start as "Service", get set to "Pod" and then later to "Deployment"/"StatefulSet")
// is confusing and hard to follow. We should find a better way to do this while still reusing logic between the different types.
var firstPod = pods.First();
_log.Verbose($"Chose pod '{new PII(firstPod?.Name())}' with {firstPod.Spec.Containers.Count} containers");
remoteContainerConnectionDetails.UpdateSourceEntityTypeToPod(firstPod);
V1Container container;
// If a containerName was specified let's make sure it exists
if (!string.IsNullOrWhiteSpace(remoteContainerConnectionDetails.ContainerName))
{
_log.Verbose($"Container {new PII(remoteContainerConnectionDetails.ContainerName)} was specified.");
container = remoteContainerConnectionDetails.Pod.Spec.Containers.Where(c => StringComparer.OrdinalIgnoreCase.Equals(c.Name, remoteContainerConnectionDetails.ContainerName)).FirstOrDefault();
remoteContainerConnectionDetails.UpdateContainerDetails(container ?? throw new UserVisibleException(_operationContext, Resources.SpecifiedContainerNotFoundFormat, remoteContainerConnectionDetails.ContainerName, remoteContainerConnectionDetails.PodName));
}
else
{
// The user did not specify a container so we need to infere it from the pod and service
container = (this.GetContainerFromPodAndService(remoteContainerConnectionDetails.Pod, remoteContainerConnectionDetails.Service)) ??
throw new UserVisibleException(_operationContext, Resources.FailedToIDContainerFormat, remoteContainerConnectionDetails.Pod.Metadata.Name);
}
remoteContainerConnectionDetails.UpdateContainerDetails(container);
}
// If we are targeting a Deployment, we need to fetch it from K8S and make sure the container is there
if (remoteContainerConnectionDetails.SourceEntityType == KubernetesEntityType.Deployment)
{
var deployment = await _kubernetesClient.GetV1DeploymentAsync(
remoteContainerConnectionDetails.NamespaceName,
remoteContainerConnectionDetails.DeploymentName,
cancellationToken);
remoteContainerConnectionDetails.UpdateDeploymentDetails(deployment ?? throw new UserVisibleException(_operationContext, Resources.FailedToFindDeploymentFormat, remoteContainerConnectionDetails.DeploymentName, remoteContainerConnectionDetails.NamespaceName));
// If a containerName was specified let's make sure it exists
if (!string.IsNullOrWhiteSpace(remoteContainerConnectionDetails.ContainerName))
{
var container = remoteContainerConnectionDetails.Deployment.Spec.Template.Spec.Containers.Where(c => StringComparer.OrdinalIgnoreCase.Equals(c.Name, remoteContainerConnectionDetails.ContainerName)).FirstOrDefault();
remoteContainerConnectionDetails.UpdateContainerDetails(container ?? throw new UserVisibleException(_operationContext, Resources.FailedToFindContainerInDeploymentFormat, remoteContainerConnectionDetails.DeploymentName, remoteContainerConnectionDetails.NamespaceName));
}
else
{
var container = this.GetContainerFromDeployment(remoteContainerConnectionDetails.Deployment);
remoteContainerConnectionDetails.UpdateContainerDetails(container ?? throw new UserVisibleException(_operationContext, Resources.FailedToFindContainerInDeploymentFormat, remoteContainerConnectionDetails.DeploymentName, remoteContainerConnectionDetails.NamespaceName));
}
}
// If we are targeting a Pod, it might be that is beacuse we were targeting a Service, and we moved to a Pod, or because the user specified a Pod manually (only the Pod name is known)
if (remoteContainerConnectionDetails.SourceEntityType == KubernetesEntityType.Pod)
{
if (remoteContainerConnectionDetails.Pod == null)
{
// We have a prefix of the pod name. We try to look for the pod based on what we have.
_log.Verbose($"Trying to resolve pod that matches {new PII(remoteContainerConnectionDetails.PodName)}...");
var pods = await _kubernetesClient.ListPodsInNamespaceAsync(remoteContainerConnectionDetails.NamespaceName, cancellationToken: cancellationToken);
var pod = pods.Items.FirstOrDefault(p => p.Metadata.Name.StartsWith(remoteContainerConnectionDetails.PodName));
remoteContainerConnectionDetails.UpdatePodDetails(pod ?? throw new UserVisibleException(_operationContext, Resources.FailedToFindPodInNamespaceFormat, remoteContainerConnectionDetails.PodName, remoteContainerConnectionDetails.NamespaceName));
}
// We know have the full pod, or because it was resolved from the service before, or because we just resolved it above.
if (remoteContainerConnectionDetails.Container == null)
{
// If a containerName was specified let's make sure it exists
if (!string.IsNullOrWhiteSpace(remoteContainerConnectionDetails.ContainerName))
{
var container = remoteContainerConnectionDetails.Pod.Spec.Containers.Where(c => StringComparer.OrdinalIgnoreCase.Equals(c.Name, remoteContainerConnectionDetails.ContainerName)).FirstOrDefault();
remoteContainerConnectionDetails.UpdateContainerDetails(container ?? throw new UserVisibleException(_operationContext, Resources.SpecifiedContainerNotFoundFormat, remoteContainerConnectionDetails.ContainerName, remoteContainerConnectionDetails.PodName));
}
else
{
var container = this.GetContainerFromPod(remoteContainerConnectionDetails.Pod);
remoteContainerConnectionDetails.UpdateContainerDetails(container ?? throw new UserVisibleException(_operationContext, Resources.FailedToFindContainerInPodFormat, remoteContainerConnectionDetails.ContainerName, remoteContainerConnectionDetails.PodName));
}
}
}
// If source entity is a Pod, and routing is OFF, try to find the deployment that is hosting the Pod
if (remoteContainerConnectionDetails.SourceEntityType == KubernetesEntityType.Pod &&
remoteContainerConnectionDetails.AgentHostingMode == RemoteAgentHostingMode.Replace)
{
try
{
// We might already have a backing object here if we came in as Service and then found multiple Pods backed by the same Deployment or Statefulset
if (remoteContainerConnectionDetails.Deployment == null && remoteContainerConnectionDetails.StatefulSet == null)
{
await ResolveBackingObjectDetailsAsync(remoteContainerConnectionDetails, remoteContainerConnectionDetails.Pod.AsEnumerable(), cancellationToken);
}
// The container at this point should already have been resolved when we processed the Pod in the block above, so no need to re-resolve it.
if (remoteContainerConnectionDetails.Deployment != null)
{
remoteContainerConnectionDetails.UpdateSourceEntityTypeToDeployment(remoteContainerConnectionDetails.Deployment);
}
else if (remoteContainerConnectionDetails.StatefulSet != null)
{
remoteContainerConnectionDetails.UpdateSourceEntityTypeToStatefulSet(remoteContainerConnectionDetails.StatefulSet);
}
}
catch (Exception e)
{
_log.Info($"Failed to resolve backing object for pod: {e.Message}. Proceeding to debug single pod.");
}
}
return remoteContainerConnectionDetails;
}