pod-resources-rs: Add kubelet Pod Resources API client

Add a gRPC client crate that speaks the kubelet PodResourcesLister
service (v1). The runtime-rs VFIO cold-plug path needs this to discover
which GPU devices the kubelet has assigned to a pod so they can be
passed through to the guest before the VM boots.

The crate is intentionally kept minimal: it wraps the upstream
pod_resources.proto, exposes a Unix-domain-socket client, and
re-exports the generated types.

Signed-off-by: Alex Lyn <alex.lyn@antgroup.com>
Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
This commit is contained in:
Alex Lyn
2026-04-04 11:22:49 +02:00
committed by Fabiano Fidêncio
parent bd6377a038
commit c12bf20dea
8 changed files with 663 additions and 0 deletions

View File

@@ -48,6 +48,7 @@ jobs:
path: src/runtime-rs
needs:
- rust
- protobuf-compiler
- name: libs
path: src/libs
needs:

View File

@@ -11,6 +11,7 @@ members = [
"src/libs/kata-types",
"src/libs/logging",
"src/libs/mem-agent",
"src/libs/pod-resources-rs",
"src/libs/protocols",
"src/libs/runtime-spec",
"src/libs/safe-path",
@@ -117,6 +118,7 @@ wasm_container = { path = "src/runtime-rs/crates/runtimes/wasm_container" }
# Local dependencies from `src/lib`
kata-sys-util = { path = "src/libs/kata-sys-util" }
pod-resources-rs = { path = "src/libs/pod-resources-rs" }
kata-types = { path = "src/libs/kata-types", features = ["safe-path"] }
logging = { path = "src/libs/logging" }
mem-agent = { path = "src/libs/mem-agent" }

View File

@@ -0,0 +1,22 @@
[package]
name = "pod-resources-rs"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.100"
tokio = "1.48"
tokio-util = "0.7.17"
tower = "0.5"
hyper-util = { version = "0.1", features = ["tokio"] }
# gRPC dependencies for kubelet pod-resources API
tonic = "0.14"
prost = "0.14"
tonic-prost = "0.14"
oci-spec = { version = "0.8.1", features = ["runtime"] }
container-device-interface = "0.1.2"
slog = "2.5.2"
slog-scope = "4.4.0"
[build-dependencies]
tonic-prost-build = "0.14"

View File

@@ -0,0 +1,16 @@
// Copyright (c) 2026 Ant Group
//
// SPDX-License-Identifier: Apache-2.0
//
/// This generates Device Plugin code (in v1beta1.rs) from pluginapi.proto
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_prost_build::configure()
.build_server(false) // We only need the client
.build_client(true)
.out_dir("src/pod_resources")
.compile_protos(&["proto/pod_resources.proto"], &["proto"])
.expect("failed to compile protos");
Ok(())
}

View File

@@ -0,0 +1,117 @@
// To regenerate api.pb.go run `hack/update-codegen.sh protobindings`
syntax = "proto3";
package v1;
option go_package = "k8s.io/kubelet/pkg/apis/podresources/v1";
// PodResourcesLister is a service provided by the kubelet that provides information about the
// node resources consumed by pods and containers on the node
service PodResourcesLister {
/// List returns the node resources assigned to pods and containers.
rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
/// GetAllocatableResources returns the node resources that are available for assignment to pods and containers.
rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
/// Get returns the node resources assigned to a specific pod.
rpc Get(GetPodResourcesRequest) returns (GetPodResourcesResponse) {}
}
// AllocatableResourcesRequest is the request made to the GetAllocatableResources service
message AllocatableResourcesRequest {}
// AllocatableResourcesResponses contains informations about all the devices known by the kubelet
message AllocatableResourcesResponse {
repeated ContainerDevices devices = 1;
repeated int64 cpu_ids = 2;
repeated ContainerMemory memory = 3;
}
// ListPodResourcesRequest is the request made to the PodResources service
message ListPodResourcesRequest {}
// ListPodResourcesResponse is the response returned by List function
message ListPodResourcesResponse {
repeated PodResources pod_resources = 1;
}
// GetPodResourcesRequest is the request made to the Get service
message GetPodResourcesRequest {
string pod_name = 1;
string pod_namespace = 2;
}
// GetPodResourcesResponse is the response returned by Get function
message GetPodResourcesResponse {
PodResources pod_resources = 1;
}
// PodResources contains information about the node resources assigned to a pod
message PodResources {
string name = 1;
string namespace = 2;
repeated ContainerResources containers = 3;
}
// ContainerResources contains information about the resources assigned to a container
message ContainerResources {
string name = 1;
repeated ContainerDevices devices = 2;
repeated int64 cpu_ids = 3;
repeated ContainerMemory memory = 4;
repeated DynamicResource dynamic_resources = 5;
}
// ContainerDevices contains information about the devices assigned to a container
message ContainerDevices {
string resource_name = 1;
repeated string device_ids = 2;
TopologyInfo topology = 3;
}
// ContainerMemory contains information about memory and hugepages assigned to a container
message ContainerMemory {
string memory_type = 1;
uint64 size = 2;
TopologyInfo topology = 3;
}
// DynamicResource contains information about the devices assigned to a container by DRA
message DynamicResource {
// tombstone: removed in 1.31 because claims are no longer associated with one class
// string class_name = 1;
string claim_name = 2;
string claim_namespace = 3;
repeated ClaimResource claim_resources = 4;
}
// ClaimResource contains resource information. The driver name/pool name/device name
// triplet uniquely identifies the device. Should DRA get extended to other kinds
// of resources, then device_name will be empty and other fields will get added.
// Each device at the DRA API level may map to zero or more CDI devices.
message ClaimResource {
repeated CDIDevice cdi_devices = 1;
string driver_name = 2;
string pool_name = 3;
string device_name = 4;
}
// Topology describes hardware topology of the resource
message TopologyInfo {
repeated NUMANode nodes = 1;
}
// NUMA representation of NUMA node
message NUMANode {
int64 ID = 1;
}
// CDIDevice specifies a CDI device information
message CDIDevice {
// Fully qualified CDI device name
// for example: vendor.com/gpu=gpudevice1
// see more details in the CDI specification:
// https://github.com/container-orchestrated-devices/container-device-interface/blob/main/SPEC.md
string name = 1;
}

View File

@@ -0,0 +1,84 @@
// Copyright (c) 2026 Ant Group
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod pod_resources;
use anyhow::{Result, anyhow};
use cdi::specs::config::DeviceNode;
// use cdi::container_edits::DeviceNode;
use cdi::cache::{CdiOption, new_cache, with_auto_refresh};
use cdi::spec_dirs::with_spec_dirs;
use container_device_interface as cdi;
use slog::info;
use std::sync::Arc;
use tokio::time;
/// DEFAULT_DYNAMIC_CDI_SPEC_PATH is the default directory for dynamic CDI Specs,
/// which can be overridden by specifying a different path when creating the cache.
const DEFAULT_DYNAMIC_CDI_SPEC_PATH: &str = "/var/run/cdi";
/// DEFAULT_STATIC_CDI_SPEC_PATH is the default directory for static CDI Specs,
/// which can be overridden by specifying a different path when creating the cache.
const DEFAULT_STATIC_CDI_SPEC_PATH: &str = "/etc/cdi";
#[macro_export]
macro_rules! sl {
() => {
slog_scope::logger()
};
}
pub async fn handle_cdi_devices(
devices: &[String],
_cdi_timeout: time::Duration,
) -> Result<Vec<DeviceNode>> {
if devices.is_empty() {
info!(sl!(), "no pod CDI devices requested.");
return Ok(vec![]);
}
// Explicitly set the cache options to disable auto-refresh and
// to use the default spec dirs for dynamic and static CDI Specs
let options: Vec<CdiOption> = vec![
with_auto_refresh(false),
with_spec_dirs(&[DEFAULT_DYNAMIC_CDI_SPEC_PATH, DEFAULT_STATIC_CDI_SPEC_PATH]),
];
let cache: Arc<std::sync::Mutex<cdi::cache::Cache>> = new_cache(options);
let target_devices = {
let mut target_devices = vec![];
// Lock cache within this scope, std::sync::Mutex has no Send
// and await will not work with time::sleep
let mut cache = cache.lock().unwrap();
match cache.refresh() {
Ok(_) => {}
Err(e) => {
return Err(anyhow!("Refreshing cache failed: {:?}", e));
}
}
for dev in devices.iter() {
info!(sl!(), "Requested CDI device with FQN: {}", dev);
match cache.get_device(dev) {
Some(device) => {
info!(sl!(), "Target CDI device: {}", device.get_qualified_name());
if let Some(devnodes) = device.edits().container_edits.device_nodes {
target_devices.extend(devnodes.iter().cloned());
}
}
None => {
return Err(anyhow!(
"Failed to get device node for CDI device: {} in cache",
dev
));
}
}
}
target_devices
};
info!(sl!(), "target CDI devices to inject: {:?}", target_devices);
Ok(target_devices)
}

View File

@@ -0,0 +1,120 @@
// Copyright (c) 2026 Ant Group
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod v1;
use v1::pod_resources_lister_client::PodResourcesListerClient;
use std::collections::HashMap;
use anyhow::{Context, Result, anyhow};
use hyper_util::rt::TokioIo;
use tokio::net::UnixStream;
use tokio::time::{Duration, timeout};
use tonic::transport::{Channel, Endpoint, Uri};
use tower::service_fn;
use crate::pod_resources::v1::GetPodResourcesRequest;
const SANDBOX_NAME_ANNOTATION: &str = "io.kubernetes.cri.sandbox-name";
const SANDBOX_NAMESPACE_ANNOTATION: &str = "io.kubernetes.cri.sandbox-namespace";
pub const DEFAULT_POD_RESOURCES_PATH: &str = "/var/lib/kubelet/pod-resources";
pub const DEFAULT_POD_RESOURCES_TIMEOUT: Duration = Duration::from_secs(10);
pub const CDI_K8S_PREFIX: &str = "cdi.k8s.io/";
const MAX_RECV_MSG_SIZE: usize = 16 * 1024 * 1024; // 16MB
// Create a gRPC channel to the specified Unix socket
async fn create_grpc_channel(socket_path: &str) -> Result<Channel> {
let socket_path = socket_path.trim_start_matches("unix://");
let socket_path_owned = socket_path.to_string();
// Create a gRPC endpoint with a timeout
let endpoint = Endpoint::try_from("http://[::]:50051")
.context("failed to create endpoint")?
.timeout(DEFAULT_POD_RESOURCES_TIMEOUT);
// Connect to the Unix socket using a custom connector
let channel = endpoint
.connect_with_connector(service_fn(move |_: Uri| {
let socket_path = socket_path_owned.clone();
async move {
let stream = UnixStream::connect(&socket_path).await.map_err(|e| {
std::io::Error::new(
e.kind(),
format!("failed to connect to {}: {}", socket_path, e),
)
})?;
Ok::<_, std::io::Error>(TokioIo::new(stream))
}
}))
.await
.context("failed to connect to unix socket")?;
Ok(channel)
}
pub async fn get_pod_cdi_devices(
socket: &str,
annotations: &HashMap<String, String>,
) -> Result<Vec<String>> {
let pod_name = annotations.get(SANDBOX_NAME_ANNOTATION).ok_or_else(|| {
anyhow::anyhow!("cold plug: missing annotation {}", SANDBOX_NAME_ANNOTATION)
})?;
let pod_namespace = annotations
.get(SANDBOX_NAMESPACE_ANNOTATION)
.ok_or_else(|| {
anyhow::anyhow!(
"cold plug: missing annotation {}",
SANDBOX_NAMESPACE_ANNOTATION
)
})?;
// Create gRPC channel to kubelet pod-resources socket
let channel = create_grpc_channel(socket)
.await
.context("cold plug: failed to connect to kubelet")?;
// Create PodResourcesLister client
let mut client = PodResourcesListerClient::new(channel)
.max_decoding_message_size(MAX_RECV_MSG_SIZE)
.max_encoding_message_size(MAX_RECV_MSG_SIZE);
// Prepare and send GetPodResources request
let request = tonic::Request::new(GetPodResourcesRequest {
pod_name: pod_name.to_string(),
pod_namespace: pod_namespace.to_string(),
});
// Await response with timeout
let response = timeout(DEFAULT_POD_RESOURCES_TIMEOUT, client.get(request))
.await
.context("cold plug: GetPodResources timeout")?
.context("cold plug: GetPodResources RPC failed")?;
// Extract PodResources from response
let pod_resources = response
.into_inner()
.pod_resources
.ok_or_else(|| anyhow!("cold plug: PodResources is nil"))?;
// Format device specifications
let format_cdi_device_ids = |resource_name: &str, device_ids: &[String]| -> Vec<String> {
device_ids
.iter()
.map(|id| format!("{}={}", resource_name, id))
.collect()
};
// Collect all device specifications from all containers
let mut devices = Vec::new();
for container in &pod_resources.containers {
for device in &container.devices {
let cdi_devices = format_cdi_device_ids(&device.resource_name, &device.device_ids);
devices.extend(cdi_devices);
}
}
Ok(devices)
}

View File

@@ -0,0 +1,301 @@
// This file is @generated by prost-build.
/// AllocatableResourcesRequest is the request made to the GetAllocatableResources service
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct AllocatableResourcesRequest {}
/// AllocatableResourcesResponses contains informations about all the devices known by the kubelet
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AllocatableResourcesResponse {
#[prost(message, repeated, tag = "1")]
pub devices: ::prost::alloc::vec::Vec<ContainerDevices>,
#[prost(int64, repeated, tag = "2")]
pub cpu_ids: ::prost::alloc::vec::Vec<i64>,
#[prost(message, repeated, tag = "3")]
pub memory: ::prost::alloc::vec::Vec<ContainerMemory>,
}
/// ListPodResourcesRequest is the request made to the PodResources service
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ListPodResourcesRequest {}
/// ListPodResourcesResponse is the response returned by List function
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ListPodResourcesResponse {
#[prost(message, repeated, tag = "1")]
pub pod_resources: ::prost::alloc::vec::Vec<PodResources>,
}
/// GetPodResourcesRequest is the request made to the Get service
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct GetPodResourcesRequest {
#[prost(string, tag = "1")]
pub pod_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub pod_namespace: ::prost::alloc::string::String,
}
/// GetPodResourcesResponse is the response returned by Get function
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetPodResourcesResponse {
#[prost(message, optional, tag = "1")]
pub pod_resources: ::core::option::Option<PodResources>,
}
/// PodResources contains information about the node resources assigned to a pod
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PodResources {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub namespace: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
pub containers: ::prost::alloc::vec::Vec<ContainerResources>,
}
/// ContainerResources contains information about the resources assigned to a container
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContainerResources {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "2")]
pub devices: ::prost::alloc::vec::Vec<ContainerDevices>,
#[prost(int64, repeated, tag = "3")]
pub cpu_ids: ::prost::alloc::vec::Vec<i64>,
#[prost(message, repeated, tag = "4")]
pub memory: ::prost::alloc::vec::Vec<ContainerMemory>,
#[prost(message, repeated, tag = "5")]
pub dynamic_resources: ::prost::alloc::vec::Vec<DynamicResource>,
}
/// ContainerDevices contains information about the devices assigned to a container
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContainerDevices {
#[prost(string, tag = "1")]
pub resource_name: ::prost::alloc::string::String,
#[prost(string, repeated, tag = "2")]
pub device_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(message, optional, tag = "3")]
pub topology: ::core::option::Option<TopologyInfo>,
}
/// ContainerMemory contains information about memory and hugepages assigned to a container
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContainerMemory {
#[prost(string, tag = "1")]
pub memory_type: ::prost::alloc::string::String,
#[prost(uint64, tag = "2")]
pub size: u64,
#[prost(message, optional, tag = "3")]
pub topology: ::core::option::Option<TopologyInfo>,
}
/// DynamicResource contains information about the devices assigned to a container by DRA
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DynamicResource {
/// tombstone: removed in 1.31 because claims are no longer associated with one class
/// string class_name = 1;
#[prost(string, tag = "2")]
pub claim_name: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub claim_namespace: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "4")]
pub claim_resources: ::prost::alloc::vec::Vec<ClaimResource>,
}
/// ClaimResource contains resource information. The driver name/pool name/device name
/// triplet uniquely identifies the device. Should DRA get extended to other kinds
/// of resources, then device_name will be empty and other fields will get added.
/// Each device at the DRA API level may map to zero or more CDI devices.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClaimResource {
#[prost(message, repeated, tag = "1")]
pub cdi_devices: ::prost::alloc::vec::Vec<CdiDevice>,
#[prost(string, tag = "2")]
pub driver_name: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub pool_name: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub device_name: ::prost::alloc::string::String,
}
/// Topology describes hardware topology of the resource
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TopologyInfo {
#[prost(message, repeated, tag = "1")]
pub nodes: ::prost::alloc::vec::Vec<NumaNode>,
}
/// NUMA representation of NUMA node
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct NumaNode {
#[prost(int64, tag = "1")]
pub id: i64,
}
/// CDIDevice specifies a CDI device information
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct CdiDevice {
/// Fully qualified CDI device name
/// for example: vendor.com/gpu=gpudevice1
/// see more details in the CDI specification:
/// <https://github.com/container-orchestrated-devices/container-device-interface/blob/main/SPEC.md>
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
}
/// Generated client implementations.
pub mod pod_resources_lister_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
/// PodResourcesLister is a service provided by the kubelet that provides information about the
/// node resources consumed by pods and containers on the node
#[derive(Debug, Clone)]
pub struct PodResourcesListerClient<T> {
inner: tonic::client::Grpc<T>,
}
impl PodResourcesListerClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> PodResourcesListerClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> PodResourcesListerClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
PodResourcesListerClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
/// / List returns the node resources assigned to pods and containers.
pub async fn list(
&mut self,
request: impl tonic::IntoRequest<super::ListPodResourcesRequest>,
) -> std::result::Result<
tonic::Response<super::ListPodResourcesResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/v1.PodResourcesLister/List",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("v1.PodResourcesLister", "List"));
self.inner.unary(req, path, codec).await
}
/// / GetAllocatableResources returns the node resources that are available for assignment to pods and containers.
pub async fn get_allocatable_resources(
&mut self,
request: impl tonic::IntoRequest<super::AllocatableResourcesRequest>,
) -> std::result::Result<
tonic::Response<super::AllocatableResourcesResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/v1.PodResourcesLister/GetAllocatableResources",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new("v1.PodResourcesLister", "GetAllocatableResources"),
);
self.inner.unary(req, path, codec).await
}
/// / Get returns the node resources assigned to a specific pod.
pub async fn get(
&mut self,
request: impl tonic::IntoRequest<super::GetPodResourcesRequest>,
) -> std::result::Result<
tonic::Response<super::GetPodResourcesResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/v1.PodResourcesLister/Get",
);
let mut req = request.into_request();
req.extensions_mut().insert(GrpcMethod::new("v1.PodResourcesLister", "Get"));
self.inner.unary(req, path, codec).await
}
}
}