mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-09-06 12:09:01 +00:00
Delete CloudInitService.GetCloudInitConfig, ClusterConfigService.GetTotem/
GetQdevice/GetApiVersion, ContainerService.GetContainer, HaService.GetManagerStatus,
NodeService.GetVersion, TaskService.GetTaskLog, UserService.GetUser, and
VmService.ShutdownVm. #207 found these had no caller outside tests/; re-confirmed by
grep against main at 3ea0e11 before this change. Also deletes the xUnit tests that
existed only to exercise each method, CloudInitService's now-unused CloudInitFields
array, and HaService's now-unused JsonHelper using directive.
Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3ea0e11988
commit
832aa6e4df
@@ -14,14 +14,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
/// </summary>
|
||||
public class CloudInitService : PveServiceBase
|
||||
{
|
||||
// Cloud-Init field names as used in the PVE API
|
||||
private static readonly string[] CloudInitFields =
|
||||
{
|
||||
"ciuser", "cipassword", "sshkeys",
|
||||
"ipconfig0", "ipconfig1", "ipconfig2", "ipconfig3",
|
||||
"nameserver", "searchdomain", "cicustom"
|
||||
};
|
||||
|
||||
private readonly VmService _vmService;
|
||||
|
||||
/// <summary>
|
||||
@@ -57,34 +49,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
return _vmService.GetVmConfig(session, node, vmid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Cloud-Init specific configuration fields for a VM.
|
||||
/// Internally fetches the full VM config and extracts the CI fields.
|
||||
/// </summary>
|
||||
public PveCloudInitConfig GetCloudInitConfig(PveSession session, string node, int vmid)
|
||||
{
|
||||
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)}/qemu/{vmid}/config")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
if (data == null) return new PveCloudInitConfig();
|
||||
|
||||
// Extract only the Cloud-Init fields into a reduced JObject for deserialization
|
||||
var ciObj = new JObject();
|
||||
foreach (var field in CloudInitFields)
|
||||
{
|
||||
if (data[field] != null)
|
||||
ciObj[field] = data[field];
|
||||
}
|
||||
|
||||
return ciObj.ToObject<PveCloudInitConfig>() ?? new PveCloudInitConfig();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates Cloud-Init configuration fields on a VM. Only fields present in
|
||||
/// <paramref name="config"/> are changed; unspecified fields are left as-is.
|
||||
|
||||
@@ -229,51 +229,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Corosync totem configuration (GET /cluster/config/totem).
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> GetTotem(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var response = client.GetAsync("cluster/config/totem").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return JsonHelper.ToDictionary(data as JObject);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the external quorum device (qdevice) status (GET /cluster/config/qdevice).
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> GetQdevice(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var response = client.GetAsync("cluster/config/qdevice").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return JsonHelper.ToDictionary(data as JObject);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cluster API version (GET /cluster/config/apiversion).
|
||||
/// </summary>
|
||||
public int GetApiVersion(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var response = client.GetAsync("cluster/config/apiversion").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<int>() ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cluster-wide options (GET /cluster/options).
|
||||
/// </summary>
|
||||
|
||||
@@ -96,22 +96,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single container by its ID on the specified node.
|
||||
/// </summary>
|
||||
public PveContainer GetContainer(PveSession session, string node, int vmid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
var containers = GetContainersOnNode(session, node);
|
||||
var ct = containers.FirstOrDefault(c => c.VmId == vmid);
|
||||
if (ct == null)
|
||||
throw new InvalidOperationException($"Container {vmid} not found on node '{node}'.");
|
||||
ct.Node ??= node;
|
||||
return ct;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full configuration of a container.
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,6 @@ using Newtonsoft.Json.Linq;
|
||||
using PSProxmoxVE.Core.Authentication;
|
||||
using PSProxmoxVE.Core.Client;
|
||||
using PSProxmoxVE.Core.Models.HA;
|
||||
using PSProxmoxVE.Core.Utilities;
|
||||
|
||||
namespace PSProxmoxVE.Core.Services
|
||||
{
|
||||
@@ -285,21 +284,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full HA manager status as a raw JSON object.
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> GetManagerStatus(PveSession session)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var response = client.GetAsync("cluster/ha/status/manager_status").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return JsonHelper.ToDictionary(data as JObject);
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Rules (PVE 9.0+)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -175,23 +175,5 @@ namespace PSProxmoxVE.Core.Services
|
||||
return PveTaskResponse.Parse(response, node);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Proxmox VE version running on the server.
|
||||
/// </summary>
|
||||
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!);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,25 +83,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all log lines for a task identified by its UPID.
|
||||
/// </summary>
|
||||
public PveTaskLog[] GetTaskLog(PveSession session, string node, string upid)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var encodedUpid = Uri.EscapeDataString(upid);
|
||||
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}/log")
|
||||
.GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
return data?.ToObject<PveTaskLog[]>() ?? Array.Empty<PveTaskLog>();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls the task status until it completes, throws on timeout or failure. One HTTP
|
||||
/// client is held open for the whole wait.
|
||||
|
||||
@@ -44,27 +44,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Returns a single user by their user ID (e.g. "admin@pam").</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="userId">The user ID in "username@realm" format.</param>
|
||||
public PveUser GetUser(PveSession session, string userId)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var encodedId = Uri.EscapeDataString(userId);
|
||||
var response = client.GetAsync($"access/users/{encodedId}").GetAwaiter().GetResult();
|
||||
var data = JObject.Parse(response)["data"];
|
||||
var user = data?.ToObject<PveUser>() ?? new PveUser();
|
||||
// The single-user endpoint may not echo back the userid
|
||||
if (string.IsNullOrEmpty(user.UserId))
|
||||
user.UserId = userId;
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new user account.
|
||||
/// </summary>
|
||||
|
||||
@@ -309,28 +309,6 @@ namespace PSProxmoxVE.Core.Services
|
||||
public PveTask StopVm(PveSession session, string node, int vmid)
|
||||
=> PostStatus(session, node, vmid, "stop");
|
||||
|
||||
/// <summary>Gracefully shuts down a VM. Returns the task UPID.</summary>
|
||||
/// <param name="session">The authenticated PVE session.</param>
|
||||
/// <param name="node">The cluster node name.</param>
|
||||
/// <param name="vmid">The VM ID.</param>
|
||||
/// <param name="timeoutSeconds">Optional shutdown timeout in seconds.</param>
|
||||
public PveTask ShutdownVm(PveSession session, string node, int vmid, int? timeoutSeconds = null)
|
||||
{
|
||||
if (session == null) throw new ArgumentNullException(nameof(session));
|
||||
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
|
||||
|
||||
var formData = new Dictionary<string, string>();
|
||||
if (timeoutSeconds.HasValue)
|
||||
formData["timeout"] = timeoutSeconds.Value.ToString();
|
||||
|
||||
return Invoke(session, client =>
|
||||
{
|
||||
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/shutdown", formData)
|
||||
.GetAwaiter().GetResult();
|
||||
return PveTaskResponse.Parse(response, node);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reboots a VM through PVE's native reboot endpoint. Returns the task UPID.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,61 +17,6 @@ namespace PSProxmoxVE.Core.Tests.Services
|
||||
"root@pam!testtoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCloudInitConfig_ReturnsConfig()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{""data"": {
|
||||
""ciuser"": ""ubuntu"",
|
||||
""ipconfig0"": ""ip=dhcp"",
|
||||
""nameserver"": ""8.8.8.8"",
|
||||
""searchdomain"": ""example.com"",
|
||||
""sshkeys"": ""ssh-rsa%20AAAA...%20user%40host"",
|
||||
""boot"": ""order=scsi0;net0"",
|
||||
""cores"": 4,
|
||||
""memory"": 8192
|
||||
}}";
|
||||
var mockClient = new Mock<IPveHttpClient>();
|
||||
mockClient.Setup(c => c.GetAsync("nodes/pve1/qemu/100/config")).ReturnsAsync(json);
|
||||
var service = new CloudInitService(mockClient.Object);
|
||||
|
||||
// Act
|
||||
var config = service.GetCloudInitConfig(CreateSession(), "pve1", 100);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(config);
|
||||
Assert.Equal("ubuntu", config.CiUser);
|
||||
Assert.Equal("ip=dhcp", config.IpConfig0);
|
||||
Assert.Equal("8.8.8.8", config.Nameserver);
|
||||
Assert.Equal("example.com", config.Searchdomain);
|
||||
Assert.Equal("ssh-rsa%20AAAA...%20user%40host", config.SshKeys);
|
||||
mockClient.Verify(c => c.GetAsync("nodes/pve1/qemu/100/config"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCloudInitConfig_ExcludesNonCiFields()
|
||||
{
|
||||
// Arrange — response includes non-CI fields that should be ignored
|
||||
var json = @"{""data"": {
|
||||
""ciuser"": ""admin"",
|
||||
""cores"": 4,
|
||||
""memory"": 8192,
|
||||
""boot"": ""order=scsi0""
|
||||
}}";
|
||||
var mockClient = new Mock<IPveHttpClient>();
|
||||
mockClient.Setup(c => c.GetAsync("nodes/pve1/qemu/200/config")).ReturnsAsync(json);
|
||||
var service = new CloudInitService(mockClient.Object);
|
||||
|
||||
// Act
|
||||
var config = service.GetCloudInitConfig(CreateSession(), "pve1", 200);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("admin", config.CiUser);
|
||||
// Non-CI fields should not appear in the result
|
||||
Assert.Null(config.IpConfig0);
|
||||
Assert.Null(config.Nameserver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCloudInitConfig_CallsPutAsyncWithCorrectResource()
|
||||
{
|
||||
|
||||
@@ -225,43 +225,6 @@ namespace PSProxmoxVE.Core.Tests.Services
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTotem_ReturnsJObject()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{""data"": {""version"": ""2"", ""secauth"": ""on"", ""cluster_name"": ""pve-cluster""}}";
|
||||
var mockClient = new Mock<IPveHttpClient>();
|
||||
mockClient.Setup(c => c.GetAsync("cluster/config/totem")).ReturnsAsync(json);
|
||||
var service = new ClusterConfigService(mockClient.Object);
|
||||
|
||||
// Act
|
||||
var totem = service.GetTotem(CreateSession());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(totem);
|
||||
Assert.Equal("2", totem["version"]?.ToString());
|
||||
Assert.Equal("on", totem["secauth"]?.ToString());
|
||||
Assert.Equal("pve-cluster", totem["cluster_name"]?.ToString());
|
||||
mockClient.Verify(c => c.GetAsync("cluster/config/totem"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiVersion_ReturnsInt()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{""data"": 10}";
|
||||
var mockClient = new Mock<IPveHttpClient>();
|
||||
mockClient.Setup(c => c.GetAsync("cluster/config/apiversion")).ReturnsAsync(json);
|
||||
var service = new ClusterConfigService(mockClient.Object);
|
||||
|
||||
// Act
|
||||
var version = service.GetApiVersion(CreateSession());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(10, version);
|
||||
mockClient.Verify(c => c.GetAsync("cluster/config/apiversion"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetClusterOptions_ReturnsPveClusterOptions()
|
||||
{
|
||||
|
||||
@@ -337,25 +337,6 @@ namespace PSProxmoxVE.Core.Tests.Services
|
||||
mockClient.Verify(c => c.GetAsync("cluster/ha/status/current"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetManagerStatus_ReturnsJObject()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{""data"": {""manager_status"": {""master_node"": ""pve1""}, ""quorum"": {""node"": ""pve1""}}}";
|
||||
var mockClient = new Mock<IPveHttpClient>();
|
||||
mockClient.Setup(c => c.GetAsync("cluster/ha/status/manager_status")).ReturnsAsync(json);
|
||||
var service = new HaService(mockClient.Object);
|
||||
|
||||
// Act
|
||||
var status = service.GetManagerStatus(CreateSession());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(status);
|
||||
Assert.NotNull(status["manager_status"]);
|
||||
Assert.NotNull(status["quorum"]);
|
||||
mockClient.Verify(c => c.GetAsync("cluster/ha/status/manager_status"), Times.Once);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Rules
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
@@ -58,35 +58,6 @@ namespace PSProxmoxVE.Core.Tests.Services
|
||||
Assert.True(task.IsSuccessful);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTaskLog_HappyPath_ReturnsLogEntries()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{
|
||||
""data"": [
|
||||
{ ""n"": 1, ""t"": ""starting task qmstart"" },
|
||||
{ ""n"": 2, ""t"": ""VM 100 started"" },
|
||||
{ ""n"": 3, ""t"": ""TASK OK"" }
|
||||
]
|
||||
}";
|
||||
|
||||
var mockClient = new Mock<IPveHttpClient>();
|
||||
mockClient.Setup(c => c.GetAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(json);
|
||||
|
||||
var service = new TaskService(mockClient.Object);
|
||||
var session = CreateSession();
|
||||
|
||||
// Act
|
||||
var logs = service.GetTaskLog(session, TestNode, TestUpid);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, logs.Length);
|
||||
Assert.Equal(1, logs[0].LineNumber);
|
||||
Assert.Equal("starting task qmstart", logs[0].Text);
|
||||
Assert.Equal("TASK OK", logs[2].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTasks_HappyPath_ReturnsCorrectCount()
|
||||
{
|
||||
|
||||
@@ -53,28 +53,6 @@ namespace PSProxmoxVE.Core.Tests.Services
|
||||
_mockClient.Verify(c => c.GetAsync("access/users"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUser_ReturnsSingleUser()
|
||||
{
|
||||
// Arrange
|
||||
_mockClient.Setup(c => c.GetAsync("access/users/root%40pam"))
|
||||
.ReturnsAsync(@"{""data"":{""email"":""root@example.com"",""firstname"":""Root"",""lastname"":""Admin"",""enable"":1}}");
|
||||
|
||||
// Act
|
||||
var result = _service.GetUser(_session, "root@pam");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("root@pam", result.UserId);
|
||||
Assert.Equal("root@example.com", result.Email);
|
||||
Assert.Equal(1, result.Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetUser_NullUserId_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _service.GetUser(_session, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateUser_PostsFormData()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user