diff --git a/libvirt/data_source_libvirt_capabilities.go b/libvirt/data_source_libvirt_capabilities.go new file mode 100644 index 000000000..1958ca01a --- /dev/null +++ b/libvirt/data_source_libvirt_capabilities.go @@ -0,0 +1,250 @@ +package libvirt + +import ( + "log" + "encoding/xml" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// a data source for getting libvirt capability set +// +// Example usage: +// +// data "libvirt_capabilities" "capabilities" { +// uri = "qemu+ssh://target-machine/system?" +// } +// +// locals { +// arch = data.external.capabilities.result["arch"] +// images = { +// "x86_64" = "/srv/images/debian-12-backports-generic-amd64.qcow2", +// "aarch64" = "/srv/images/debian-12-backports-generic-arm64.qcow2", +// } +// image_path = local.images["debian-${local.arch}"] +// } + +func datasourceLibvirtCapabilities() *schema.Resource { + return &schema.Resource{ + Read: datasourceLibvirtCapabilitiesRead, + Schema: map[string]*schema.Schema{ + "live_migration_support" : { + Type: schema.TypeBool, + Computed: true, + }, + "live_migration_transports" : { + Type: schema.TypeList, + Elem: &schema.Schema{ Type: schema.TypeString, }, + Computed: true, + }, + "arch" : { + Type: schema.TypeString, + Computed: true, + }, + "model" : { + Type: schema.TypeString, + Computed: true, + }, + "vendor" : { + Type: schema.TypeString, + Computed: true, + }, + "sockets" : { + Type: schema.TypeInt, + Computed: true, + }, + "dies" : { + Type: schema.TypeInt, + Computed: true, + }, + "cores" : { + Type: schema.TypeInt, + Computed: true, + }, + "threads" : { + Type: schema.TypeInt, + Computed: true, + }, + "features": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, + "topology" : { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Optional: true, // or Required: true, based on your use case + }, + "memory": { + Type: schema.TypeInt, + Optional: true, // or Required: true, based on your use case + }, + "cpu": { + Type: schema.TypeList, + Optional: true, // or Required: true, based on your use case + Elem: &schema.Schema{ + Type: schema.TypeMap, + Elem: schema.TypeInt, + + }, + }, + }, + }, + Computed: true, + }, + }, + } +} + +type Host struct { + XMLName xml.Name `xml:"host"` + UUID string `xml:"uuid"` + CPU CPU `xml:"cpu"` + Migration MigrationFeatures `xml:"migration_features"` + Topology Topology `xml:"topology"` +} + +type CPU struct { + Arch string `xml:"arch"` + Model string `xml:"model"` + Vendor string `xml:"vendor"` + Topology CPUtopology `xml:"topology"` + Features []struct{ + Name string `xml:"name,attr"` + } `xml:"feature"` +} + +type MigrationFeatures struct { + XMLName xml.Name `xml:"migration_features"` + Live *bool `xml:"live"` + Transports []string `xml:"uri_transports>uri_transport"` +} + +type CPUtopology struct { + Sockets int `xml:"sockets,attr"` + Dies int `xml:"dies,attr"` + Cores int `xml:"cores,attr"` + Threads int `xml:"threads,attr"` +} + +type Topology struct { + Cells []Cell `xml:"cells>cell"` +} + +type Cell struct { + ID int `xml:"id,attr"` + Memory Memory `xml:"memory"` + CPUs CPUs `xml:"cpus"` +} + +type Memory struct { + Unit string `xml:"unit,attr"` + Value int `xml:",chardata"` +} + +type CPUs struct { + Num string `xml:"num,attr"` + CPU []CPUInfo `xml:"cpu"` +} + +type CPUInfo struct { + ID string `xml:"id,attr"` + SocketID string `xml:"socket_id,attr"` + DieID string `xml:"die_id,attr"` + CoreID string `xml:"core_id,attr"` + Siblings string `xml:"siblings,attr"` +} + +// Data represents the top-level structure of the XML that includes the node. +type Capabilities struct { + Host Host `xml:"host"` +} + +type LibvirtClientInterface interface { + ConnectGetCapabilities() (string, error) +} + +func getCapabilities(d *schema.ResourceData, libvirt LibvirtClientInterface) error { + caps, err := libvirt.ConnectGetCapabilities() + if err != nil { + log.Printf("[ERROR] Failed to get node capabilities: %v", err) + return err + } + + log.Printf("[DEBUG] full caps: %v", caps) + var capabilities Capabilities + + err = xml.Unmarshal([]byte(caps), &capabilities) + if err != nil { + log.Printf("[ERROR] Error unmarshalling XML: %v", err) + return err + } + + d.SetId(capabilities.Host.UUID) + + d.Set("arch", capabilities.Host.CPU.Arch) + d.Set("model", capabilities.Host.CPU.Model) + d.Set("vendor", capabilities.Host.CPU.Vendor) + d.Set("sockets", capabilities.Host.CPU.Topology.Sockets) + d.Set("dies", capabilities.Host.CPU.Topology.Dies) + d.Set("cores", capabilities.Host.CPU.Topology.Cores) + d.Set("threads", capabilities.Host.CPU.Topology.Threads) + + d.Set("live_migration_support", capabilities.Host.Migration.Live != nil) + if err := d.Set("live_migration_transports", capabilities.Host.Migration.Transports); err != nil { + log.Printf("[ERROR] error setting features: %s", err) + return nil + } + + features := []string{} + // Iterate over the cpu.Features slice + for _, feature := range capabilities.Host.CPU.Features { + // Append the 'Name' field of each 'Feature' struct to the slice + features = append(features, feature.Name) + } + + if err := d.Set("features", features); err != nil { + log.Printf("[ERROR] error setting features: %s", err) + return err + } + + // topology := make([]map[string]interface{}, 0) + // for _, t := range capabilities.Host.Topology { + // topologyItem := make(map[string]interface{}) + // if t.ID != nil { // Assuming `ID`, `Memory`, and `CPU` are fields in your topology data structure + // topologyItem["id"] = *t.ID + // } + // if t.Memory != nil { + // topologyItem["memory"] = *t.Memory + // } + // if t.CPU != nil { + // cpuList := make([]map[string]int, 0) + // for _, cpu := range *t.CPU { // Assuming `CPU` is a slice of maps or similar structure + // cpuItem := make(map[string]int) + // for key, value := range cpu { + // cpuItem[key] = value + // } + // cpuList = append(cpuList, cpuItem) + // } + // topologyItem["cpu"] = cpuList + // } + // topology = append(topology, topologyItem) + // } + + d.Set("topology", nil) + log.Printf("[DEBUG] Host topology not yet implemented") + + return nil +} + +func datasourceLibvirtCapabilitiesRead(d *schema.ResourceData, meta interface{}) error { + virtConn := meta.(*Client).libvirt + return getCapabilities(d, virtConn) +} + + + diff --git a/libvirt/data_source_libvirt_capabilities_test.go b/libvirt/data_source_libvirt_capabilities_test.go new file mode 100644 index 000000000..a3a9559cf --- /dev/null +++ b/libvirt/data_source_libvirt_capabilities_test.go @@ -0,0 +1,103 @@ +package libvirt + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// mockCapabilitiesXML provides a sample XML for libvirt capabilities. +const mockCapabilitiesXML = ` + + + test-uuid + + x86_64 + model-name + vendor-name + + + + + + + + + transport#1 + transport#2 + transport#3 + + + + +` +// Mock implementation of the libvirt client. +type MockLibvirtClient struct{} +// Implement the ConnectGetCapabilities method. +func (m *MockLibvirtClient) ConnectGetCapabilities() (string, error) { + return mockCapabilitiesXML, nil +} + +func Test_datasourceLibvirtCapabilitiesRead(t *testing.T) { + // Create a mock libvirt client. + mockLibvirtClient := &MockLibvirtClient{} + + // Initialize a Terraform resource data schema + d := schema.TestResourceDataRaw(t, datasourceLibvirtCapabilities().Schema, map[string]interface{}{}) + + // Call the function under test + err := getCapabilities(d, mockLibvirtClient) + + if err != nil { + t.Fatalf("resourceLibvirtHostCapabilitiesRead() error = %v", err) + } + + // Define expected values based on mockCapabilitiesXML + expectedUUID := "test-uuid" + expectedArch := "x86_64" + expectedModel := "model-name" + expectedVendor := "vendor-name" + expectedSockets := 2 + expectedDies := 1 + expectedCores := 4 + expectedThreads := 2 + expectedLiveMigrationSupport := true + expectedLiveMigrationTransports := []interface{}{"transport#1", "transport#2", "transport#3"} + expectedFeatures := []interface{}{"feature1", "feature2", "feature3"} + + // Assert that the resource data is set correctly + if got := d.Get("arch"); got != expectedArch { + t.Errorf("resourceLibvirtNodeInfoRead() arch = %v, want %v", got, expectedArch) + } + if got := d.Get("model"); got != expectedModel { + t.Errorf("resourceLibvirtNodeInfoRead() model = %v, want %v", got, expectedModel) + } + if got := d.Get("vendor"); got != expectedVendor { + t.Errorf("resourceLibvirtNodeInfoRead() vendor = %v, want %v", got, expectedVendor) + } + if got := d.Get("sockets"); got != expectedSockets { + t.Errorf("resourceLibvirtNodeInfoRead() sockets = %v, want %v", got, expectedSockets) + } + if got := d.Get("dies"); got != expectedDies { + t.Errorf("resourceLibvirtNodeInfoRead() dies = %v, want %v", got, expectedDies) + } + if got := d.Get("cores"); got != expectedCores { + t.Errorf("resourceLibvirtNodeInfoRead() cores = %v, want %v", got, expectedCores) + } + if got := d.Get("threads"); got != expectedThreads { + t.Errorf("resourceLibvirtNodeInfoRead() threads = %v, want %v", got, expectedThreads) + } + if got := d.Get("live_migration_support"); got != expectedLiveMigrationSupport { + t.Errorf("resourceLibvirtNodeInfoRead() live_migration_support = %v, want %v", got, expectedLiveMigrationSupport) + } + if got := d.Get("live_migration_transports"); !reflect.DeepEqual(got, expectedLiveMigrationTransports) { + t.Errorf("resourceLibvirtNodeInfoRead() live_migration_transports = %v, want %v", got, expectedLiveMigrationTransports) + } + if got := d.Get("features"); !reflect.DeepEqual(got, expectedFeatures) { + t.Errorf("resourceLibvirtNodeInfoRead() features = %v, want %v", got, expectedFeatures) + } + if got := d.Id(); got != expectedUUID { + t.Errorf("resourceLibvirtNodeInfoRead() UUID = %v, want %v", got, expectedUUID) + } +} diff --git a/libvirt/provider.go b/libvirt/provider.go index 8cb43a8ed..a7a082e3e 100644 --- a/libvirt/provider.go +++ b/libvirt/provider.go @@ -35,6 +35,7 @@ func Provider() *schema.Provider { "libvirt_node_info": datasourceLibvirtNodeInfo(), "libvirt_node_device_info": datasourceLibvirtNodeDeviceInfo(), "libvirt_node_devices": datasourceLibvirtNodeDevices(), + "libvirt_capabilities": datasourceLibvirtCapabilities(), }, ConfigureFunc: providerConfigure,