public inbox for pdm-devel@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal