zig libvirt: start adding tests, and improve Domain

This commit is contained in:
Jeeves 2024-06-26 07:28:38 -06:00
parent 02bd0b6ccd
commit c4f178bc1f
6 changed files with 287 additions and 47 deletions

View file

@ -1,4 +1,6 @@
const std = @import("std");
const Build = std.Build;
const Step = Build.Step;
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
@ -38,14 +40,35 @@ pub fn build(b: *std.Build) !void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
const libvirt_test = b.addTest(.{
.root_source_file = b.path("src/libvirt.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
const libvirt_connection_test = b.addTest(.{
.root_source_file = b.path("src/libvirt-connection.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
const libvirt_unit_tests = [_]*Step.Compile{
libvirt_test,
libvirt_connection_test,
};
for (libvirt_unit_tests) |tests| {
tests.root_module.addIncludePath(b.path("libvirt/include"));
tests.addLibraryPath(b.path("libvirt/lib"));
tests.addObjectFile(b.path("libvirt/lib/libvirt.so.0.10000.0"));
tests.addObjectFile(b.path("libvirt/lib/libvirt-admin.so.0.10000.0"));
tests.addObjectFile(b.path("libvirt/lib/libvirt-qemu.so.0.10000.0"));
}
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
const run_libvirt_tests = [_]*Step.Run{
b.addRunArtifact(libvirt_test),
b.addRunArtifact(libvirt_connection_test),
};
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
for (run_libvirt_tests) |tests| test_step.dependOn(&tests.step);
}

View file

@ -25,7 +25,7 @@ pub fn deinit(self: *const Connection) void {
_ = c.virConnectClose(self.ptr);
}
pub fn getURI(self: *const Connection) ![]u8 {
pub fn getURI(self: *const Connection) Error![]u8 {
const uri = c.virConnectGetURI(self.ptr);
defer std.c.free(uri);
const str = h.string(uri);
@ -35,24 +35,13 @@ pub fn freeURI(self: *const Connection, uri: []u8) void {
self.allocator.free(uri);
}
// pub fn numOfPools(self: *const Connection) !u32 {
// return numOf(c.virConnectPtr, self.ptr, c.virConnectNumOfStoragePools);
// }
// pub fn numOfDefinedPools(self: *const Connection) !u32 {
// return numOf(c.virConnectPtr, self.ptr, c.virConnectNumOfDefinedStoragePools);
// }
test "connection" {
const conn = try Connection.init("qemu:///system", std.testing.allocator);
defer conn.deinit();
// pub fn iteratePools(self: *const Connection, flags: []const Pool.ListFlags) !Pool.PoolIterator {
// return Pool.PoolIterator.init(
// c.virConnect,
// Connection,
// Pool.ListFlags,
// self,
// flags,
// self.allocator,
// c.virConnectListAllStoragePools,
// );
// }
const uri = try conn.getURI();
defer conn.freeURI(uri);
}
// pub fn createPool(self: *const Connection, xml: String, flags: []const Pool.CreateFlags) !Pool {
// const pool = c.virStoragePoolCreateXML(self.ptr, @ptrCast(xml.str), intFromFlags(Pool.CreateFlags, flags));
@ -63,6 +52,44 @@ pub fn freeURI(self: *const Connection, uri: []u8) void {
// return if (pool) |p| Pool.init(p, self.allocator) else handleError();
// }
pub const Error = mem.Allocator.Error || VirError;
pub fn pools(self: *const Connection) Error![]Pool {
const list = try Pool.listAllStoragePools(self, &[_]Pool.ListFlags{});
var l = try self.allocator.alloc(Pool, list.len);
for (list, 0..) |p, i| l[i] = Pool.init(p, self.allocator);
return l;
}
pub fn freePools(self: *const Connection, slice: []Pool) void {
// for (slice) |pool| pool.deinit();
self.allocator.free(slice);
}
pub fn iteratePools(self: *const Connection) Error!PoolIterator {
return .{
.allocator = self.allocator,
.values = try self.pools(),
};
}
pub const PoolIterator = struct {
allocator: mem.Allocator,
values: []Pool,
index: usize = 0,
pub fn deinit(it: *PoolIterator) void {
for (it.values) |v| v.free() catch @panic("pool free error");
it.allocator.free(it.values);
}
pub fn next(it: *PoolIterator) ?Pool {
if (it.index >= it.values.len) return null;
const v = it.values[it.index];
it.index += 1;
return v;
}
pub fn reset(it: *PoolIterator) void {
it.index = 0;
}
};
pub fn numOfDomains(self: *const Connection) VirError!u32 {
return Domain.numOfDomains(self);
}
@ -70,18 +97,6 @@ pub fn numOfDefinedDomains(self: *const Connection) VirError!u32 {
return Domain.numOfDefinedDomains(self);
}
// pub fn iterateDomains(self: *const Connection, flags: []const Domain.ListFlags) !Domain.DomainIterator {
// return Domain.DomainIterator.init(
// c.virConnect,
// Connection,
// Domain.ListFlags,
// self,
// flags,
// self.allocator,
// c.virConnectListAllDomains,
// );
// }
// pub fn createDomain(self: *const Connection, xml: String, flags: []const Domain.CreateFlags) !Domain {
// const domain = c.virDomainCreateXML(self.ptr, @ptrCast(xml.str), intFromFlags(Domain.CreateFlags, flags));
// return if (domain) |d| Domain.init(d, self.allocator) else handleError();
@ -94,3 +109,133 @@ pub fn numOfDefinedDomains(self: *const Connection) VirError!u32 {
// const domain = c.virDomainDefineXMLFlags(self.ptr, @ptrCast(xml.str), intFromFlags(Domain.DefineFlags, flags));
// return if (domain) |d| Domain.init(d, self.allocator) else handleError();
// }
pub fn createDomain(self: *const Connection, xml: h.String, flags: []const Domain.CreateFlags) VirError!Domain {
const ptr = try Domain.createXML(self, xml, flags);
return Domain.init(ptr, self.allocator);
}
pub fn domains(self: *const Connection) Error![]Domain {
const list = try Domain.listAllDomains(self, &[_]Domain.ListFlags{});
var l = try self.allocator.alloc(Domain, list.len);
for (list, 0..) |p, i| l[i] = Domain.init(p, self.allocator);
return l;
}
pub fn freeDomains(self: *const Connection, slice: []Domain) void {
// for (slice) |domain| domain.deinit();
self.allocator.free(slice);
}
pub fn iterateDomains(self: *const Connection) Error!DomainIterator {
return .{
.allocator = self.allocator,
.values = try self.domains(),
};
}
pub const DomainIterator = struct {
allocator: mem.Allocator,
values: []Domain,
index: usize = 0,
pub fn deinit(it: *DomainIterator) void {
for (it.values) |v| v.free() catch @panic("domain free error");
it.allocator.free(it.values);
}
pub fn next(it: *DomainIterator) ?Domain {
if (it.index >= it.values.len) return null;
const v = it.values[it.index];
it.index += 1;
return v;
}
pub fn reset(it: *DomainIterator) void {
it.index = 0;
}
};
test "domains" {
const conn = try Connection.init("qemu:///system", std.testing.allocator);
defer conn.deinit();
// Enumerate domains
const num_active = try conn.numOfDomains();
const num_inactive = try conn.numOfDefinedDomains();
_ = .{ num_active, num_inactive };
// Iterate domains
var domain_it = try conn.iterateDomains();
defer domain_it.deinit();
while (domain_it.next()) |domain| {
const active = try domain.isActive();
const name = try domain.getName();
_ = .{ active, name };
}
// Create and destory domain
const xml = try h.String.init(std.testing.allocator,
\\<domain type="kvm">
\\ <name>vm1</name>
\\ <uuid>48dd0909-6346-4028-b922-1b3a13571360</uuid>
\\ <memory unit="KiB">1048576</memory>
\\ <currentMemory unit="KiB">1048576</currentMemory>
\\ <vcpu placement="static">1</vcpu>
\\ <os>
\\ <type arch="x86_64" machine="pc-i440fx-8.2">hvm</type>
\\ <boot dev="hd"/>
\\ </os>
\\ <features>
\\ <acpi/>
\\ <apic/>
\\ <vmport state="off"/>
\\ </features>
\\ <cpu mode="host-passthrough" check="none" migratable="on"/>
\\ <clock offset="utc">
\\ <timer name="rtc" tickpolicy="catchup"/>
\\ <timer name="pit" tickpolicy="delay"/>
\\ <timer name="hpet" present="no"/>
\\ </clock>
\\ <on_poweroff>destroy</on_poweroff>
\\ <on_reboot>restart</on_reboot>
\\ <on_crash>destroy</on_crash>
\\ <pm>
\\ <suspend-to-mem enabled="no"/>
\\ <suspend-to-disk enabled="no"/>
\\ </pm>
\\ <devices>
\\ <emulator>/run/libvirt/nix-emulators/qemu-system-x86_64</emulator>
\\ <controller type="usb" index="0" model="ich9-ehci1">
\\ <address type="pci" domain="0x0000" bus="0x00" slot="0x02" function="0x7"/>
\\ </controller>
\\ <controller type="usb" index="0" model="ich9-uhci1">
\\ <master startport="0"/>
\\ <address type="pci" domain="0x0000" bus="0x00" slot="0x02" function="0x0" multifunction="on"/>
\\ </controller>
\\ <controller type="usb" index="0" model="ich9-uhci2">
\\ <master startport="2"/>
\\ <address type="pci" domain="0x0000" bus="0x00" slot="0x02" function="0x1"/>
\\ </controller>
\\ <controller type="usb" index="0" model="ich9-uhci3">
\\ <master startport="4"/>
\\ <address type="pci" domain="0x0000" bus="0x00" slot="0x02" function="0x2"/>
\\ </controller>
\\ <controller type="pci" index="0" model="pci-root"/>
\\ <serial type="pty">
\\ <target type="isa-serial" port="0">
\\ <model name="isa-serial"/>
\\ </target>
\\ </serial>
\\ <console type="pty">
\\ <target type="serial" port="0"/>
\\ </console>
\\ <input type="mouse" bus="ps2"/>
\\ <input type="keyboard" bus="ps2"/>
\\ <audio id="1" type="none"/>
\\ <memballoon model="virtio">
\\ <address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x0"/>
\\ </memballoon>
\\ </devices>
\\</domain>
);
defer xml.deinit();
const domain = try conn.createDomain(xml, &.{});
try domain.destroy();
// TODO with flags
}

View file

@ -29,7 +29,11 @@ pub fn xmlFromNative() void {}
pub fn xmlToNative() void {}
pub fn getAllDomainStats() void {}
pub fn getDomainCapabilities() void {}
pub fn listAllDomains() void {}
pub fn listAllDomains(conn: *const Connection, flags: []const ListFlags) VirError![]c.virDomainPtr {
var list: [*]c.virDomainPtr = undefined;
const num = c.virConnectListAllDomains(conn.ptr, @ptrCast(&list), h.intFromFlags(ListFlags, flags));
return if (num < 0) err.handleError() else list[0..@intCast(num)];
}
pub fn listDefinedDomains() void {}
pub fn listDomains() void {}
pub fn numOfDefinedDomains(conn: *const Connection) VirError!u32 {
@ -67,13 +71,23 @@ pub fn create() void {}
pub fn createLinux() void {}
pub fn createWithFiles() void {}
pub fn createWithFlags() void {}
pub fn createXML() void {}
pub fn createXML(conn: *const Connection, xml: h.String, flags: []const CreateFlags) VirError!c.virDomainPtr {
return if (c.virDomainCreateXML(
conn.ptr,
@ptrCast(xml.str),
h.intFromFlags(CreateFlags, flags),
)) |p| p else err.handleError();
}
pub fn createXMLWithFiles() void {}
pub fn defineXML() void {}
pub fn defineXMLFlags() void {}
pub fn delIOThread() void {}
pub fn destroy() void {}
pub fn destroyFlags() void {}
pub fn destroy(self: *const Domain) VirError!void {
if (c.virDomainDestroy(self.ptr) < 0) return err.handleError();
}
pub fn destroyFlags(self: *const Domain, flags: []const DestroyFlags) VirError!void {
if (c.virDomainDestroyFlags(self.ptr, h.intFromFlags(DestroyFlags, flags)) < 0) return err.handleError();
}
pub fn detachDevice() void {}
pub fn detachDeviceAlias() void {}
pub fn detachDeviceFlags() void {}
@ -142,9 +156,12 @@ pub fn interfaceAddresses() void {}
pub fn interfaceFree() void {}
pub fn interfaceStats() void {}
pub fn isActive(self: *const Domain) void {
const active = c.virDomainIsActive(self.ptr);
return if (active == 0) false else true;
pub fn isActive(self: *const Domain) VirError!bool {
return switch (c.virDomainIsActive(self.ptr)) {
0 => false,
1 => true,
else => err.handleError(),
};
}
pub fn isPersistent() void {}
pub fn isUpdated() void {}
@ -226,6 +243,20 @@ pub fn updateDeviceFlags() void {}
pub const ListFlags = enum(c_uint) {
Active = c.VIR_CONNECT_LIST_DOMAINS_ACTIVE,
Inactive = c.VIR_CONNECT_LIST_DOMAINS_INACTIVE,
Persistent = c.VIR_CONNECT_LIST_DOMAINS_PERSISTENT,
Transient = c.VIR_CONNECT_LIST_DOMAINS_TRANSIENT,
Running = c.VIR_CONNECT_LIST_DOMAINS_RUNNING,
Paused = c.VIR_CONNECT_LIST_DOMAINS_PAUSED,
Shutoff = c.VIR_CONNECT_LIST_DOMAINS_SHUTOFF,
Other = c.VIR_CONNECT_LIST_DOMAINS_OTHER,
ManagedSave = c.VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE,
NoManagedSave = c.VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE,
Autostart = c.VIR_CONNECT_LIST_DOMAINS_AUTOSTART,
NoAutostart = c.VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART,
HasSnapshot = c.VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT,
NoSnapshot = c.VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT,
HasCheckpoint = c.VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT,
NoCheckpoint = c.VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT,
};
pub const AbortJobFlagsValues = enum(c_uint) {};
pub const AgentResponseTimeoutValues = enum(c_uint) {};
@ -254,6 +285,11 @@ pub const CreateFlags = enum(c_uint) {
pub const DefineFlags = enum(c_uint) {
Validate = c.VIR_DOMAIN_DEFINE_VALIDATE,
};
pub const DestroyFlags = enum(c_uint) {
Default = c.VIR_DOMAIN_DESTROY_DEFAULT,
Graceful = c.VIR_DOMAIN_DESTROY_GRACEFUL,
RemoveLogs = c.VIR_DOMAIN_DESTROY_REMOVE_LOGS,
};
// TODO rest
pub const BlockInfo = struct {};

View file

@ -36,15 +36,16 @@ pub fn deinit(self: *const Pool) void {
// ) VirError![]u8 {} // TODO
pub fn listAllStoragePools(
conn: *const Connection,
pools: [*]c.virStoragePoolPtr,
// pools: [*]c.virStoragePoolPtr,
flags: []const ListFlags,
) VirError![]const c.virStoragePoolPtr {
) VirError![]c.virStoragePoolPtr {
var list: [*]c.virStoragePoolPtr = undefined;
const num = c.virConnectListAllStoragePools(
conn.ptr,
@ptrCast(&pools),
@ptrCast(&list),
h.intFromFlags(ListFlags, flags),
);
return if (num < 0) err.handleError() else pools[0..num];
return if (num < 0) err.handleError() else list[0..@intCast(num)];
}
// pub fn listDefinedStoragePools(
// conn: *const Connection,

View file

@ -2,8 +2,12 @@ const std = @import("std");
const mem = std.mem;
const heap = std.heap;
const h = @import("libvirt-helper.zig");
const err = @import("libvirt-error.zig");
pub const String = h.String;
pub const Error = err.VirError;
pub const Connection = @import("libvirt-connection.zig");
pub const Pool = @import("libvirt-pool.zig");
pub const Domain = @import("libvirt-domain.zig");

View file

@ -18,9 +18,9 @@ pub fn main() !void {
var nix = process.Child.init(&[_][]const u8{ "nix", "build", ".#volume", "-o", "volume" }, allocator);
nix.cwd_dir = flake_dir;
_ = try nix.spawnAndWait();
// _ = try nix.spawnAndWait();
nix.argv = &[_][]const u8{ "nix", "build", ".#beforeInstall", "-o", "beforeInstall" };
_ = try nix.spawnAndWait();
// _ = try nix.spawnAndWait();
nix.argv = &[_][]const u8{ "nix", "build", ".#afterInstall", "-o", "afterInstall" };
// _ = try nix.spawnAndWait();
nix.argv = &[_][]const u8{ "nix", "build", ".#beforeBoot", "-o", "beforeBoot" };
@ -49,6 +49,23 @@ pub fn main() !void {
std.debug.print("active: {d}, inactive: {d}\n", .{ num_active, num_inactive });
_ = .{ num_active, num_inactive };
const domains = try connection.domains();
defer connection.freeDomains(domains);
for (domains) |domain| {
defer domain.free() catch @panic("domain free fail");
const active = try domain.isActive();
const name = try domain.getName();
std.debug.print("domain name: {s}, active: {any}\n", .{ name, active });
}
var domain_it = try connection.iterateDomains();
defer domain_it.deinit();
while (domain_it.next()) |domain| {
const active = try domain.isActive();
const name = try domain.getName();
std.debug.print("domain name: {s}, active: {any}\n", .{ name, active });
}
// var domain_iter = try connection.iterateDomains(&[_]libvirt.Domain.ListFlags{
// libvirt.Domain.ListFlags.Active,
// libvirt.Domain.ListFlags.Inactive,
@ -62,6 +79,20 @@ pub fn main() !void {
// _ = .{ name, active };
// }
const pools = try connection.pools();
defer connection.freePools(pools);
for (pools) |pool| {
defer pool.free() catch @panic("pool free fail");
const name = try pool.getName();
std.debug.print("pool name {s}\n", .{name});
// if (mem.eql(u8, name, "default")) {
// const vol = try pool.createVolumeXML(xml, &[_]libvirt.Volume.CreateFlags{});
// _ = vol;
// break;
// }
}
// var pool_iter = try connection.iteratePools(&[_]libvirt.Pool.ListFlags{
// libvirt.Pool.ListFlags.Active,
// libvirt.Pool.ListFlags.Inactive,