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!);
});
}
}
}