using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Models.Network;
using PSProxmoxVE.Core.Models.Vms;
namespace PSProxmoxVE.Core.Services
{
///
/// Service for Proxmox VE node network and SDN (Software-Defined Networking) API operations.
/// SDN methods require PVE 8.0 or later. Version checks are performed at the cmdlet layer.
///
public class NetworkService
{
private readonly IPveHttpClient? _injectedClient;
/// Initializes a new instance that creates its own HTTP clients.
public NetworkService() { }
/// Initializes a new instance that uses the supplied HTTP client for all requests.
/// The HTTP client to use. The caller owns its lifetime.
public NetworkService(IPveHttpClient client)
{
_injectedClient = client ?? throw new ArgumentNullException(nameof(client));
}
// -------------------------------------------------------------------------
// Node network interfaces
// -------------------------------------------------------------------------
///
/// Returns network interface configurations for a node.
///
/// The authenticated PVE session.
/// The cluster node name.
///
/// Optional interface type filter (e.g. "bridge", "bond", "eth", "vlan", "alias").
///
public PveNetwork[] GetNetworks(PveSession session, string node, string? type = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var resource = $"nodes/{node}/network";
if (!string.IsNullOrEmpty(type))
resource += $"?type={Uri.EscapeDataString(type!)}";
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates a network interface on a node. Changes are pending until
/// is called. Returns the new interface config.
///
/// The authenticated PVE session.
/// The cluster node name.
/// Network interface configuration parameters.
public PveNetwork CreateNetwork(
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));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
var response = client.PostAsync($"nodes/{node}/network", formData)
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveNetwork();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an existing network interface on a node. Changes are pending until
/// is called.
///
/// The authenticated PVE session.
/// The cluster node name.
/// The network interface name.
/// Network interface configuration parameters to update.
public void SetNetwork(
PveSession session,
string node,
string iface,
Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(iface)) throw new ArgumentNullException(nameof(iface));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PutAsync($"nodes/{node}/network/{iface}", formData)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes a network interface from a node. Changes are pending until
/// is called.
///
/// The authenticated PVE session.
/// The cluster node name.
/// The network interface name to remove.
public void RemoveNetwork(PveSession session, string node, string iface)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(iface)) throw new ArgumentNullException(nameof(iface));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync($"nodes/{node}/network/{iface}").GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Applies pending network configuration changes on a node. Returns the task UPID.
///
/// The authenticated PVE session.
/// The cluster node name.
public PveTask ApplyNetworkConfig(PveSession session, string node)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.PutAsync($"nodes/{node}/network")
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// SDN — requires PVE 8.0+
// -------------------------------------------------------------------------
///
/// Returns all SDN zone definitions. Requires PVE 8.0+.
///
/// The authenticated PVE session.
public PveSdnZone[] GetSdnZones(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync("cluster/sdn/zones").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Returns all SDN VNet definitions. Requires PVE 8.0+.
///
/// The authenticated PVE session.
public PveSdnVnet[] GetSdnVnets(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync("cluster/sdn/vnets").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates an SDN zone. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// SDN zone configuration parameters.
public PveSdnZone CreateSdnZone(
PveSession session,
Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
var response = client.PostAsync("cluster/sdn/zones", formData)
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveSdnZone();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes an SDN zone. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// The SDN zone identifier to remove.
public void RemoveSdnZone(PveSession session, string zone)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(zone)) throw new ArgumentNullException(nameof(zone));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync($"cluster/sdn/zones/{zone}").GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates an SDN VNet. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// SDN VNet configuration parameters.
public PveSdnVnet CreateSdnVnet(
PveSession session,
Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
var response = client.PostAsync("cluster/sdn/vnets", formData)
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? new PveSdnVnet();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes an SDN VNet. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// The SDN VNet identifier to remove.
public void RemoveSdnVnet(PveSession session, string vnet)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync($"cluster/sdn/vnets/{vnet}").GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// SDN Subnets — requires PVE 8.0+
// -------------------------------------------------------------------------
///
/// Returns all subnets for an SDN VNet. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// The SDN VNet identifier.
public PveSdnSubnet[] GetSdnSubnets(PveSession session, string vnet)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates an SDN subnet on a VNet. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// The SDN VNet identifier.
/// Subnet configuration parameters including "subnet" (CIDR).
public void CreateSdnSubnet(
PveSession session,
string vnet,
Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var formData = config.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value?.ToString() ?? string.Empty);
client.PostAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets", formData)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes an SDN subnet from a VNet. Requires PVE 8.0+.
///
/// The authenticated PVE session.
/// The SDN VNet identifier.
/// The subnet CIDR to remove.
public void RemoveSdnSubnet(PveSession session, string vnet, string subnet)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync(
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}")
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// SDN IPAM
// -------------------------------------------------------------------------
///
/// Returns all SDN IPAM plugins. Requires PVE 8.0+.
///
public PveSdnIpam[] GetSdnIpams(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync("cluster/sdn/ipams").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates an SDN IPAM plugin. Requires PVE 8.0+.
///
public void CreateSdnIpam(PveSession session, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PostAsync("cluster/sdn/ipams", config).GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes an SDN IPAM plugin. Requires PVE 8.0+.
///
public void RemoveSdnIpam(PveSession session, string ipam)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}")
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// SDN DNS
// -------------------------------------------------------------------------
///
/// Returns all SDN DNS plugins. Requires PVE 8.0+.
///
public PveSdnDns[] GetSdnDnsPlugins(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync("cluster/sdn/dns").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates an SDN DNS plugin. Requires PVE 8.0+.
///
public void CreateSdnDnsPlugin(PveSession session, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PostAsync("cluster/sdn/dns", config).GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes an SDN DNS plugin. Requires PVE 8.0+.
///
public void RemoveSdnDnsPlugin(PveSession session, string dns)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}")
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// SDN Controllers
// -------------------------------------------------------------------------
///
/// Returns all SDN controllers. Requires PVE 8.0+.
///
public PveSdnController[] GetSdnControllers(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
var response = client.GetAsync("cluster/sdn/controllers").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject() ?? Array.Empty();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Creates an SDN controller. Requires PVE 8.0+.
///
public void CreateSdnController(PveSession session, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PostAsync("cluster/sdn/controllers", config).GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Removes an SDN controller. Requires PVE 8.0+.
///
public void RemoveSdnController(PveSession session, string controller)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.DeleteAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}")
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// SDN Update operations
// -------------------------------------------------------------------------
///
/// Applies pending SDN configuration changes cluster-wide.
///
public void ApplySdn(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync("cluster/sdn", new Dictionary())
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an SDN zone configuration.
///
public void UpdateSdnZone(PveSession session, string zone, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(zone)) throw new ArgumentNullException(nameof(zone));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync($"cluster/sdn/zones/{Uri.EscapeDataString(zone)}", config)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an SDN VNet configuration.
///
public void UpdateSdnVnet(PveSession session, string vnet, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}", config)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an SDN subnet configuration.
///
public void UpdateSdnSubnet(PveSession session, string vnet, string subnet, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync(
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}",
config).GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an SDN controller configuration.
///
public void UpdateSdnController(PveSession session, string controller, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}", config)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an SDN IPAM plugin configuration.
///
public void UpdateSdnIpam(PveSession session, string ipam, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}", config)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
///
/// Updates an SDN DNS plugin configuration.
///
public void UpdateSdnDns(PveSession session, string dns, Dictionary config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
if (config == null) throw new ArgumentNullException(nameof(config));
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
{
client.PutAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}", config)
.GetAwaiter().GetResult();
}
finally
{
if (_injectedClient == null) client.Dispose();
}
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
private static PveTask ParseTask(string response, string node)
{
var data = JObject.Parse(response)["data"];
if (data?.Type == JTokenType.String)
return new PveTask { Upid = data.ToString(), Node = node };
var task = data?.ToObject() ?? new PveTask();
task.Node = node;
return task;
}
}
}