Skip to content

Commit 4596b38

Browse files
committed
fix issue with exited container
1 parent 1e4dbd0 commit 4596b38

File tree

1 file changed

+271
-4
lines changed

1 file changed

+271
-4
lines changed

src/container_runtime.rs

Lines changed: 271 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,14 @@ impl ContainerRuntimeManager {
8181
let mut map: HashMap<String, String> = std::collections::HashMap::new();
8282
map.insert("io.kubernetes.pod.uid".to_string(), pod_uid.clone());
8383

84+
85+
let ready_state = cri::PodSandboxStateValue{
86+
state: 0, // PodSandboxState::SandboxReady
87+
};
88+
8489
let pod_filter = cri::PodSandboxFilter {
8590
label_selector: map,
91+
state: Some(ready_state),
8692
..Default::default()
8793
};
8894

@@ -120,11 +126,16 @@ impl ContainerRuntimeManager {
120126
sandbox_id
121127
);
122128

129+
let container_ready = cri::ContainerStateValue {
130+
state: 1, // ContainerState::ContainerRunning
131+
};
132+
123133
// 2. List containers in that sandbox
124134
let containers = match client
125135
.list_containers(ListContainersRequest {
126136
filter: Some(cri::ContainerFilter {
127137
pod_sandbox_id: sandbox_id.clone(),
138+
state: Some(container_ready),
128139
..Default::default()
129140
}),
130141
})
@@ -194,6 +205,14 @@ impl ContainerRuntimeManager {
194205
pids.push(pid.pid);
195206
}
196207

208+
if pids.is_empty() {
209+
tracing::error!("No running containers found for pod UID: {}", pod_uid);
210+
return Err(tonic::Status::not_found(format!(
211+
"No running containers found for pod UID: {}",
212+
pod_uid
213+
)));
214+
}
215+
197216
tracing::info!(
198217
"Successfully collected {} PIDs for pod UID: {}",
199218
pids.len(),
@@ -283,12 +302,18 @@ mod tests {
283302
sandboxes
284303
.into_iter()
285304
.filter(|sandbox| {
286-
filter.label_selector.iter().all(|(key, expected_value)| {
305+
// Filter by label selector
306+
let labels_match = filter.label_selector.iter().all(|(key, expected_value)| {
287307
sandbox
288308
.labels
289309
.get(key)
290310
.map_or(false, |actual_value| actual_value == expected_value)
291-
})
311+
});
312+
// Filter by state if specified
313+
let state_match = filter.state.as_ref().map_or(true, |state_filter| {
314+
sandbox.state == state_filter.state
315+
});
316+
labels_match && state_match
292317
})
293318
.collect()
294319
} else {
@@ -302,10 +327,33 @@ mod tests {
302327

303328
async fn list_containers(
304329
&self,
305-
_request: tonic::Request<ListContainersRequest>,
330+
request: tonic::Request<ListContainersRequest>,
306331
) -> std::result::Result<tonic::Response<ListContainersResponse>, tonic::Status> {
307332
let containers = self.containers.lock().await.clone();
308-
Ok(Response::new(ListContainersResponse { containers }))
333+
let req = request.into_inner();
334+
335+
let filtered = if let Some(filter) = req.filter {
336+
containers
337+
.into_iter()
338+
.filter(|container| {
339+
// Filter by sandbox ID if specified
340+
let sandbox_match = if filter.pod_sandbox_id.is_empty() {
341+
true
342+
} else {
343+
container.pod_sandbox_id == filter.pod_sandbox_id
344+
};
345+
// Filter by state if specified
346+
let state_match = filter.state.as_ref().map_or(true, |state_filter| {
347+
container.state == state_filter.state
348+
});
349+
sandbox_match && state_match
350+
})
351+
.collect()
352+
} else {
353+
containers
354+
};
355+
356+
Ok(Response::new(ListContainersResponse { containers: filtered }))
309357
}
310358

311359
async fn container_status(
@@ -338,6 +386,7 @@ mod tests {
338386
lock.push(Container {
339387
id: "fake-id-123".into(),
340388
pod_sandbox_id: "fake-sandbox-123".into(),
389+
state: 1, // CONTAINER_RUNNING
341390
..Default::default()
342391
});
343392

@@ -429,4 +478,222 @@ mod tests {
429478
server_handle.abort();
430479
let _ = std::fs::remove_file(socket_path);
431480
}
481+
482+
/// Helper to create a mock server and client for testing
483+
async fn setup_mock_server(
484+
mock: MockRuntimeService,
485+
socket_path: &str,
486+
) -> (RuntimeServiceClient<tonic::transport::Channel>, tokio::task::JoinHandle<()>) {
487+
let _ = std::fs::remove_file(socket_path);
488+
let uds = UnixListener::bind(socket_path).expect("failed to bind UDS");
489+
let incoming = UnixListenerStream::new(uds);
490+
491+
let svc = RuntimeServiceServer::new(mock);
492+
let server_handle = tokio::spawn(async move {
493+
Server::builder()
494+
.add_service(svc)
495+
.serve_with_incoming(incoming)
496+
.await
497+
.expect("server failed");
498+
});
499+
500+
let socket_path_str = socket_path.to_string();
501+
let endpoint = Endpoint::try_from("http://[::]:50051").unwrap();
502+
let channel = endpoint
503+
.connect_with_connector(service_fn(move |_uri: Uri| {
504+
let path = socket_path_str.clone();
505+
async move {
506+
let stream = UnixStream::connect(path).await?;
507+
Ok::<_, std::io::Error>(TokioIo::new(stream))
508+
}
509+
}))
510+
.await
511+
.expect("failed to connect to mock UDS server");
512+
513+
(RuntimeServiceClient::new(channel), server_handle)
514+
}
515+
516+
#[tokio::test]
517+
async fn test_sandbox_not_ready_returns_not_found() {
518+
// Test that a sandbox in NotReady state (state=1) is filtered out
519+
let mock = MockRuntimeService::default();
520+
{
521+
let mut lock = mock.sandboxes.lock().await;
522+
lock.push(cri::PodSandbox {
523+
id: "not-ready-sandbox".into(),
524+
state: 1, // SANDBOX_NOTREADY
525+
labels: {
526+
let mut m = HashMap::new();
527+
m.insert("io.kubernetes.pod.uid".into(), "not-ready-pod-uid".into());
528+
m
529+
},
530+
..Default::default()
531+
});
532+
}
533+
534+
let socket_path = "/tmp/test-cri-sandbox-not-ready.sock";
535+
let (client, server_handle) = setup_mock_server(mock, socket_path).await;
536+
537+
let cri_manager = ContainerRuntimeManager::new_with_client(client);
538+
let result = cri_manager
539+
.get_pids_for_pod("not-ready-pod-uid".to_string())
540+
.await;
541+
542+
// Should return not found because sandbox is not in Ready state
543+
assert!(result.is_err());
544+
let err = result.unwrap_err();
545+
assert_eq!(err.code(), tonic::Code::NotFound);
546+
547+
server_handle.abort();
548+
let _ = std::fs::remove_file(socket_path);
549+
}
550+
551+
#[tokio::test]
552+
async fn test_exited_containers_filtered_out() {
553+
// Test that containers not in Running state are filtered out
554+
let mock = MockRuntimeService::default();
555+
{
556+
// Add a ready sandbox
557+
let mut lock = mock.sandboxes.lock().await;
558+
lock.push(cri::PodSandbox {
559+
id: "ready-sandbox-123".into(),
560+
state: 0, // SANDBOX_READY
561+
labels: {
562+
let mut m = HashMap::new();
563+
m.insert("io.kubernetes.pod.uid".into(), "pod-with-exited-containers".into());
564+
m
565+
},
566+
..Default::default()
567+
});
568+
569+
// Add containers that are NOT running (exited)
570+
let mut lock = mock.containers.lock().await;
571+
lock.push(Container {
572+
id: "exited-container-1".into(),
573+
pod_sandbox_id: "ready-sandbox-123".into(),
574+
state: 2, // CONTAINER_EXITED
575+
..Default::default()
576+
});
577+
lock.push(Container {
578+
id: "exited-container-2".into(),
579+
pod_sandbox_id: "ready-sandbox-123".into(),
580+
state: 0, // CONTAINER_CREATED
581+
..Default::default()
582+
});
583+
584+
// Add container statuses (should not be queried since containers are filtered)
585+
let mut lock = mock.container_statuses.lock().await;
586+
lock.insert(
587+
"exited-container-1".into(),
588+
cri::ContainerStatusResponse {
589+
info: {
590+
let mut m = HashMap::new();
591+
m.insert("info".into(), r#"{"pid": 0}"#.into());
592+
m
593+
},
594+
..Default::default()
595+
},
596+
);
597+
}
598+
599+
let socket_path = "/tmp/test-cri-exited-containers.sock";
600+
let (client, server_handle) = setup_mock_server(mock, socket_path).await;
601+
602+
let cri_manager = ContainerRuntimeManager::new_with_client(client);
603+
let result = cri_manager
604+
.get_pids_for_pod("pod-with-exited-containers".to_string())
605+
.await;
606+
607+
// Should return NotFound error since no containers are running
608+
assert!(result.is_err());
609+
let err = result.unwrap_err();
610+
assert_eq!(err.code(), tonic::Code::NotFound);
611+
assert!(err.message().contains("No running containers found"));
612+
613+
server_handle.abort();
614+
let _ = std::fs::remove_file(socket_path);
615+
}
616+
617+
#[tokio::test]
618+
async fn test_mixed_container_states_returns_only_running() {
619+
// Test that only running containers are returned when there's a mix
620+
let mock = MockRuntimeService::default();
621+
{
622+
let mut lock = mock.sandboxes.lock().await;
623+
lock.push(cri::PodSandbox {
624+
id: "mixed-sandbox".into(),
625+
state: 0, // SANDBOX_READY
626+
labels: {
627+
let mut m = HashMap::new();
628+
m.insert("io.kubernetes.pod.uid".into(), "mixed-state-pod".into());
629+
m
630+
},
631+
..Default::default()
632+
});
633+
634+
let mut lock = mock.containers.lock().await;
635+
// Running container
636+
lock.push(Container {
637+
id: "running-container".into(),
638+
pod_sandbox_id: "mixed-sandbox".into(),
639+
state: 1, // CONTAINER_RUNNING
640+
..Default::default()
641+
});
642+
// Exited container
643+
lock.push(Container {
644+
id: "exited-container".into(),
645+
pod_sandbox_id: "mixed-sandbox".into(),
646+
state: 2, // CONTAINER_EXITED
647+
..Default::default()
648+
});
649+
// Created but not started container
650+
lock.push(Container {
651+
id: "created-container".into(),
652+
pod_sandbox_id: "mixed-sandbox".into(),
653+
state: 0, // CONTAINER_CREATED
654+
..Default::default()
655+
});
656+
657+
let mut lock = mock.container_statuses.lock().await;
658+
lock.insert(
659+
"running-container".into(),
660+
cri::ContainerStatusResponse {
661+
info: {
662+
let mut m = HashMap::new();
663+
m.insert("info".into(), r#"{"pid": 9999}"#.into());
664+
m
665+
},
666+
..Default::default()
667+
},
668+
);
669+
// Exited container would have PID 0
670+
lock.insert(
671+
"exited-container".into(),
672+
cri::ContainerStatusResponse {
673+
info: {
674+
let mut m = HashMap::new();
675+
m.insert("info".into(), r#"{"pid": 0}"#.into());
676+
m
677+
},
678+
..Default::default()
679+
},
680+
);
681+
}
682+
683+
let socket_path = "/tmp/test-cri-mixed-states.sock";
684+
let (client, server_handle) = setup_mock_server(mock, socket_path).await;
685+
686+
let cri_manager = ContainerRuntimeManager::new_with_client(client);
687+
let result = cri_manager
688+
.get_pids_for_pod("mixed-state-pod".to_string())
689+
.await;
690+
691+
// Should only return the PID of the running container
692+
assert!(result.is_ok());
693+
let pids = result.unwrap();
694+
assert_eq!(pids, vec![9999]);
695+
696+
server_handle.abort();
697+
let _ = std::fs::remove_file(socket_path);
698+
}
432699
}

0 commit comments

Comments
 (0)