Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add libvirt nodeinfo datasource #1073

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions libvirt/data_source_libvirt_capabilities.go
Original file line number Diff line number Diff line change
@@ -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 <host> 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)
}



103 changes: 103 additions & 0 deletions libvirt/data_source_libvirt_capabilities_test.go
Original file line number Diff line number Diff line change
@@ -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 = `
<capabilities>
<host>
<uuid>test-uuid</uuid>
<cpu>
<arch>x86_64</arch>
<model>model-name</model>
<vendor>vendor-name</vendor>
<topology sockets="2" dies="1" cores="4" threads="2"/>
<feature name="feature1"/>
<feature name="feature2"/>
<feature name="feature3"/>
</cpu>
<migration_features>
<live />
<uri_transports>
<uri_transport>transport#1</uri_transport>
<uri_transport>transport#2</uri_transport>
<uri_transport>transport#3</uri_transport>
</uri_transports>
</migration_features>
</host>
</capabilities>
`
// 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)
}
}
1 change: 1 addition & 0 deletions libvirt/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading