using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PSProxmoxVE.Core.Authentication; using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Nodes; using PSProxmoxVE.Core.Models.Vms; using PSProxmoxVE.Core.Utilities; namespace PSProxmoxVE.Core.Services { /// /// Service for node-level and cluster-version Proxmox VE API operations. /// public class NodeService : PveServiceBase { /// /// Initializes a new instance of with no injected client. /// Each method will create and dispose its own . /// public NodeService() { } /// /// Initializes a new instance of with an injected HTTP client. /// The caller owns the client's lifetime; this service will not dispose it. /// /// The HTTP client to use for all requests. public NodeService(IPveHttpClient client) : base(client) { } /// /// Returns all cluster nodes. /// public PveNode[] GetNodes(PveSession session) { if (session == null) throw new ArgumentNullException(nameof(session)); return Invoke(session, client => { var response = client.GetAsync("nodes").GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"] ?? throw new InvalidOperationException("Response did not contain a 'data' field."); return data.ToObject() ?? Array.Empty(); }); } /// /// Returns detailed status for a specific node. /// public PveNodeStatus GetNodeStatus(PveSession session, string node) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); return Invoke(session, client => { var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/status").GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"] ?? throw new InvalidOperationException("Response did not contain a 'data' field."); var status = data.ToObject() ?? throw new InvalidOperationException("Failed to deserialize node status."); // The /nodes/{node}/status response does not include the node name. if (string.IsNullOrEmpty(status.Node)) status.Node = node; return status; }); } /// /// Returns the configuration of a specific node. /// /// The authenticated PVE session. /// The cluster node name. public Dictionary GetNodeConfig(PveSession session, string node) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); return Invoke(session, client => { var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/config").GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"]; return JsonHelper.ToDictionary(data as JObject); }); } /// /// Updates the configuration of a specific node. /// /// The authenticated PVE session. /// The cluster node name. /// Configuration parameters to update. public void SetNodeConfig(PveSession session, string node, Dictionary config) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); if (config == null) throw new ArgumentNullException(nameof(config)); Invoke(session, client => { client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/config", config).GetAwaiter().GetResult(); }); } /// /// Returns the DNS configuration of a specific node. /// /// The authenticated PVE session. /// The cluster node name. public Dictionary GetNodeDns(PveSession session, string node) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); return Invoke(session, client => { var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/dns").GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"]; return JsonHelper.ToDictionary(data as JObject); }); } /// /// Updates the DNS configuration of a specific node. /// /// The authenticated PVE session. /// The cluster node name. /// DNS configuration parameters to update. public void SetNodeDns(PveSession session, string node, Dictionary config) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); if (config == null) throw new ArgumentNullException(nameof(config)); Invoke(session, client => { client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/dns", config).GetAwaiter().GetResult(); }); } /// /// Starts all VMs and containers on a node. Returns the task UPID. /// /// The authenticated PVE session. /// The cluster node name. /// Optional parameters (e.g. vms to limit which VMs start). public PveTask StartAll(PveSession session, string node, Dictionary? config = null) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); var formData = config ?? new Dictionary(); return Invoke(session, client => { var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/startall", formData).GetAwaiter().GetResult(); return PveTaskResponse.Parse(response, node); }); } /// /// Stops all VMs and containers on a node. Returns the task UPID. /// /// The authenticated PVE session. /// The cluster node name. /// Optional parameters (e.g. vms, force-stop). public PveTask StopAll(PveSession session, string node, Dictionary? config = null) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); var formData = config ?? new Dictionary(); return Invoke(session, client => { var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/stopall", formData).GetAwaiter().GetResult(); return PveTaskResponse.Parse(response, node); }); } /// /// Returns the Proxmox VE version running on the server. /// public PveVersion GetVersion(PveSession session) { if (session == null) throw new ArgumentNullException(nameof(session)); return Invoke(session, client => { var response = client.GetAsync("version").GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"]; var versionStr = data?["version"]?.ToString(); if (string.IsNullOrEmpty(versionStr)) throw new InvalidOperationException("Failed to retrieve PVE version from API response."); return PveVersion.Parse(versionStr!); }); } } }