all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: Lukas Wagner <l.wagner@proxmox.com>, pdm-devel@lists.proxmox.com
Subject: Re: [PATCH datacenter-manager v2 1/9] lib: api types: add new ResourceView type and move accessors there
Date: Thu, 20 Aug 2026 13:20:44 +0200	[thread overview]
Message-ID: <45e83dcf-5fd8-46f4-acdc-8978d8738760@proxmox.com> (raw)
In-Reply-To: <DKTQ1PNHRLZ3.2TH9QV54RP7F1@proxmox.com>



On 8/20/26 1:06 PM, Lukas Wagner wrote:
> Hi Dominik,
> 
> looking good - some of the code is missing doc-strings that could be
> added, but that's just a minor nit-pick.

yes of course, i'll send a v3

> 
> On Tue Aug 18, 2026 at 3:25 PM CEST, Dominik Csapak wrote:
>> This is intended as a view type for Resource and PveResource, which will
>> always share some enum type. This way, we can reuse all accessors
>> cheaply for PveResource values without cloning or consuming.
>>
>> This makes it easier to share code for both of them, for instance in the
>> UI where we want to render properties consistently for resources.
>>
>> Introduces also a AsResourceView trait to make converting more
>> ergonomic.
>>
>> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
>> ---
>>   lib/pdm-api-types/src/resource.rs | 261 +++++++++++++++++++++---------
>>   1 file changed, 184 insertions(+), 77 deletions(-)
>>
>> diff --git a/lib/pdm-api-types/src/resource.rs b/lib/pdm-api-types/src/resource.rs
>> index 303ac3cb..4447aedb 100644
>> --- a/lib/pdm-api-types/src/resource.rs
>> +++ b/lib/pdm-api-types/src/resource.rs
>> @@ -45,102 +45,31 @@ impl Resource {
>>       /// Returns the local ID, not a globally unique one, e.g.
>>       /// `qemu/<vmid>`
>>       pub fn id(&self) -> String {
>> -        match self {
>> -            Resource::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage),
>> -            Resource::PveQemu(r) => format!("qemu/{}", r.vmid),
>> -            Resource::PveLxc(r) => format!("lxc/{}", r.vmid),
>> -            Resource::PveNode(r) => format!("node/{}", r.node),
>> -            Resource::PveNetwork(r) => {
>> -                if let PveNetworkResource::Zone(z) = r {
>> -                    if z.legacy {
>> -                        return format!("sdn/{}/{}", r.node(), r.name());
>> -                    }
>> -                }
>> -
>> -                format!("network/{}/{}/{}", r.node(), r.network_type(), r.name())
>> -            }
>> -            Resource::PbsNode(r) => format!("node/{}", r.name),
>> -            Resource::PbsDatastore(r) => r.name.clone(),
>> -        }
>> +        self.as_resource_view().id()
>>       }
>>   
>>       /// Returns the PDM global ID for the resource, e.g.
>>       /// `remote/<remote-id>/guest/<vmid>`
>>       pub fn global_id(&self) -> &str {
>> -        match self {
>> -            Resource::PveStorage(r) => r.id.as_str(),
>> -            Resource::PveQemu(r) => r.id.as_str(),
>> -            Resource::PveLxc(r) => r.id.as_str(),
>> -            Resource::PveNode(r) => r.id.as_str(),
>> -            Resource::PveNetwork(r) => r.id(),
>> -            Resource::PbsNode(r) => r.id.as_str(),
>> -            Resource::PbsDatastore(r) => r.id.as_str(),
>> -        }
>> +        self.as_resource_view().global_id()
>>       }
>>   
>>       /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or
>>       /// the hostname for nodes
>>       pub fn name(&self) -> &str {
>> -        match self {
>> -            Resource::PveStorage(r) => r.storage.as_str(),
>> -            Resource::PveQemu(r) => r.name.as_str(),
>> -            Resource::PveLxc(r) => r.name.as_str(),
>> -            Resource::PveNode(r) => r.node.as_str(),
>> -            Resource::PveNetwork(r) => r.name(),
>> -            Resource::PbsNode(r) => r.name.as_str(),
>> -            Resource::PbsDatastore(r) => r.name.as_str(),
>> -        }
>> +        self.as_resource_view().name()
>>       }
>>   
> 
> While at it, you could add some doc strings here.
> 
>>       pub fn resource_type(&self) -> ResourceType {
>> -        match self {
>> -            Resource::PveStorage(_) => ResourceType::PveStorage,
>> -            Resource::PveQemu(_) => ResourceType::PveQemu,
>> -            Resource::PveLxc(_) => ResourceType::PveLxc,
>> -            Resource::PveNetwork(_) => ResourceType::PveNetwork,
>> -            Resource::PveNode(_) | Resource::PbsNode(_) => ResourceType::Node,
>> -            Resource::PbsDatastore(_) => ResourceType::PbsDatastore,
>> -        }
>> +        self.as_resource_view().resource_type()
>>       }
>>   
> While at it, you could add some doc strings here.
> 
>>       pub fn status(&self) -> &str {
>> -        match self {
>> -            Resource::PveStorage(r) => r.status.as_str(),
>> -            Resource::PveQemu(r) => r.status.as_str(),
>> -            Resource::PveLxc(r) => r.status.as_str(),
>> -            Resource::PveNode(r) => r.status.as_str(),
>> -            Resource::PveNetwork(r) => r.status(),
>> -            Resource::PbsNode(r) => {
>> -                if r.uptime > 0 {
>> -                    "online"
>> -                } else {
>> -                    "offline"
>> -                }
>> -            }
>> -            Resource::PbsDatastore(r) => {
>> -                if r.maintenance.is_none() {
>> -                    "online"
>> -                } else {
>> -                    "under-maintenance"
>> -                }
>> -            }
>> -        }
>> +        self.as_resource_view().status()
>>       }
>>   
> While at it, you could add some doc strings here.
> 
>>       pub fn properties(&self) -> String {
>> -        let mut properties = Vec::new();
>> -        if let Resource::PbsDatastore(r) = self {
>> -            if let Some(backend_type) = &r.backend_type {
>> -                properties.push(backend_type.to_string());
>> -            }
>> -            if r.backing_device.is_some() {
>> -                properties.push("removable".to_string());
>> -            }
>> -            if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD {
>> -                properties.push("high-usage".to_string());
>> -            }
>> -        }
>> -        properties.join(",")
>> +        self.as_resource_view().properties()
>>       }
>>   }
>>   
>> @@ -229,6 +158,184 @@ pub enum PveResource {
>>       Network(PveNetworkResource),
>>   }
>>   
>> +/// A borrowed view of a resource that can come from a [Resource] or a [PveResource]
>> +/// This is useful for having a single implementation of field access regardless which
>> +/// of the types is used.
>> +#[derive(Clone, Debug, PartialEq)]
>> +pub enum ResourceView<'a> {
>> +    /// Storage resource in PVE.
>> +    PveStorage(&'a PveStorageResource),
>> +    /// QEMU guest resource in PVE.
>> +    PveQemu(&'a PveQemuResource),
>> +    /// LXC guest resource in PVE.
>> +    PveLxc(&'a PveLxcResource),
>> +    /// A PVE node.
>> +    PveNode(&'a PveNodeResource),
>> +    /// Network in PVE.
>> +    PveNetwork(&'a PveNetworkResource),
>> +    /// A PBS node.
>> +    PbsNode(&'a PbsNodeResource),
>> +    /// Datastore on a PBS node.
>> +    PbsDatastore(&'a PbsDatastoreResource),
>> +}
>> +
>> +impl<'a> ResourceView<'a> {
>> +    /// Returns the local ID, not a globally unique one, e.g.
>> +    /// `qemu/<vmid>`
>> +    pub fn id(&self) -> String {
>> +        match self {
>> +            ResourceView::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage),
>> +            ResourceView::PveQemu(r) => format!("qemu/{}", r.vmid),
>> +            ResourceView::PveLxc(r) => format!("lxc/{}", r.vmid),
>> +            ResourceView::PveNode(r) => format!("node/{}", r.node),
>> +            ResourceView::PveNetwork(r) => {
>> +                if let PveNetworkResource::Zone(z) = r {
>> +                    if z.legacy {
>> +                        return format!("sdn/{}/{}", r.node(), r.name());
>> +                    }
>> +                }
>> +
>> +                format!("network/{}/{}/{}", r.node(), r.network_type(), r.name())
>> +            }
>> +            ResourceView::PbsNode(r) => format!("node/{}", r.name),
>> +            ResourceView::PbsDatastore(r) => r.name.clone(),
>> +        }
>> +    }
>> +
>> +    /// Returns the PDM global ID for the resource, e.g.
>> +    /// `remote/<remote-id>/guest/<vmid>`
>> +    pub fn global_id(&self) -> &'a str {
>> +        match self {
>> +            ResourceView::PveStorage(r) => r.id.as_str(),
>> +            ResourceView::PveQemu(r) => r.id.as_str(),
>> +            ResourceView::PveLxc(r) => r.id.as_str(),
>> +            ResourceView::PveNode(r) => r.id.as_str(),
>> +            ResourceView::PveNetwork(r) => r.id(),
>> +            ResourceView::PbsNode(r) => r.id.as_str(),
>> +            ResourceView::PbsDatastore(r) => r.id.as_str(),
>> +        }
>> +    }
>> +
>> +    /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or
>> +    /// the hostname for nodes
>> +    pub fn name(&self) -> &'a str {
>> +        match self {
>> +            ResourceView::PveStorage(r) => r.storage.as_str(),
>> +            ResourceView::PveQemu(r) => r.name.as_str(),
>> +            ResourceView::PveLxc(r) => r.name.as_str(),
>> +            ResourceView::PveNode(r) => r.node.as_str(),
>> +            ResourceView::PveNetwork(r) => r.name(),
>> +            ResourceView::PbsNode(r) => r.name.as_str(),
>> +            ResourceView::PbsDatastore(r) => r.name.as_str(),
>> +        }
>> +    }
>> +
> 
> Missing doc strings here.
>> +    pub fn resource_type(&self) -> ResourceType {
>> +        match self {
>> +            ResourceView::PveStorage(_) => ResourceType::PveStorage,
>> +            ResourceView::PveQemu(_) => ResourceType::PveQemu,
>> +            ResourceView::PveLxc(_) => ResourceType::PveLxc,
>> +            ResourceView::PveNetwork(_) => ResourceType::PveNetwork,
>> +            ResourceView::PveNode(_) | ResourceView::PbsNode(_) => ResourceType::Node,
>> +            ResourceView::PbsDatastore(_) => ResourceType::PbsDatastore,
>> +        }
>> +    }
>> +
> 
> here as well
>> +    pub fn status(&self) -> &'a str {
>> +        match self {
>> +            ResourceView::PveStorage(r) => r.status.as_str(),
>> +            ResourceView::PveQemu(r) => r.status.as_str(),
>> +            ResourceView::PveLxc(r) => r.status.as_str(),
>> +            ResourceView::PveNode(r) => r.status.as_str(),
>> +            ResourceView::PveNetwork(r) => r.status(),
>> +            ResourceView::PbsNode(r) => {
>> +                if r.uptime > 0 {
>> +                    "online"
>> +                } else {
>> +                    "offline"
>> +                }
>> +            }
>> +            ResourceView::PbsDatastore(r) => {
>> +                if r.maintenance.is_none() {
>> +                    "online"
>> +                } else {
>> +                    "under-maintenance"
>> +                }
>> +            }
>> +        }
>> +    }
>> +
> 
> here as well
>> +    pub fn properties(&self) -> String {
>> +        let mut properties = Vec::new();
>> +        if let ResourceView::PbsDatastore(r) = self {
>> +            if let Some(backend_type) = &r.backend_type {
>> +                properties.push(backend_type.to_string());
>> +            }
>> +            if r.backing_device.is_some() {
>> +                properties.push("removable".to_string());
>> +            }
>> +            if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD {
>> +                properties.push("high-usage".to_string());
>> +            }
>> +        }
>> +        properties.join(",")
>> +    }
>> +}
>> +
>> +impl<'a> From<&'a Resource> for ResourceView<'a> {
>> +    fn from(value: &'a Resource) -> Self {
>> +        match value {
>> +            Resource::PveStorage(r) => ResourceView::PveStorage(r),
>> +            Resource::PveQemu(r) => ResourceView::PveQemu(r),
>> +            Resource::PveLxc(r) => ResourceView::PveLxc(r),
>> +            Resource::PveNode(r) => ResourceView::PveNode(r),
>> +            Resource::PveNetwork(r) => ResourceView::PveNetwork(r),
>> +            Resource::PbsNode(r) => ResourceView::PbsNode(r),
>> +            Resource::PbsDatastore(r) => ResourceView::PbsDatastore(r),
>> +        }
>> +    }
>> +}
>> +
>> +impl<'a> From<&'a PveResource> for ResourceView<'a> {
>> +    fn from(value: &'a PveResource) -> Self {
>> +        match value {
>> +            PveResource::Storage(r) => ResourceView::PveStorage(r),
>> +            PveResource::Qemu(r) => ResourceView::PveQemu(r),
>> +            PveResource::Lxc(r) => ResourceView::PveLxc(r),
>> +            PveResource::Node(r) => ResourceView::PveNode(r),
>> +            PveResource::Network(r) => ResourceView::PveNetwork(r),
>> +        }
>> +    }
>> +}
>> +
> 
> missing doc string here
> 
>> +pub trait AsResourceView {
>> +    fn as_resource_view(&self) -> ResourceView<'_>;
>> +}
>> +
>> +impl AsResourceView for Resource {
>> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
>> +        ResourceView::from(self)
>> +    }
>> +}
>> +
>> +impl AsResourceView for &Resource {
>> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
>> +        ResourceView::from(*self)
>> +    }
>> +}
>> +
>> +impl AsResourceView for PveResource {
>> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
>> +        ResourceView::from(self)
>> +    }
>> +}
>> +
>> +impl AsResourceView for &PveResource {
>> +    fn as_resource_view<'a>(&'a self) -> ResourceView<'a> {
>> +        ResourceView::from(*self)
>> +    }
>> +}
>> +
>>   #[api(
>>       properties: {
>>           tags: {
> 





  reply	other threads:[~2026-08-20 11:20 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-18 13:25 [PATCH datacenter-manager v2 0/9] refactor and partially fix #7371 Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 1/9] lib: api types: add new ResourceView type and move accessors there Dominik Csapak
2026-08-20 11:07   ` Lukas Wagner
2026-08-20 11:20     ` Dominik Csapak [this message]
2026-08-18 13:25 ` [PATCH datacenter-manager v2 2/9] lib: api types: resource: add 'node' helper to ResourceView Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 3/9] lib: api types: add guest specific getter " Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 4/9] ui: pve: factor out the pve-manager version extraction Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 5/9] ui: renderer: use ResourceView for rendering Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 6/9] ui: pve: tree: reuse `PveResource` for `PveTreeNode` Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 7/9] ui: pve: show ha maintenance mode for nodes Dominik Csapak
2026-08-20 11:07   ` Lukas Wagner
2026-08-20 11:20     ` Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 8/9] ui: pve: node selector: show maintenance badge with node name Dominik Csapak
2026-08-18 13:25 ` [PATCH datacenter-manager v2 9/9] ui: pve: node: show HA maintenance badge Dominik Csapak
2026-08-20 11:08 ` [PATCH datacenter-manager v2 0/9] refactor and partially fix #7371 Lukas Wagner
2026-08-21 12:51 ` superseded: " Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=45e83dcf-5fd8-46f4-acdc-8978d8738760@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=l.wagner@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal