Skip to content

Commit 4dc3f14

Browse files
Add support for libvirt domaincapabilities
1 parent 84719f4 commit 4dc3f14

File tree

9 files changed

+1330
-8
lines changed

9 files changed

+1330
-8
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
SPDX-FileCopyrightText: Copyright 2025 SAP SE or an SAP affiliate company and cobaltcore-dev contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, LibVirtVersion 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package domcapabilities
19+
20+
import (
21+
"encoding/xml"
22+
"fmt"
23+
24+
v1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
25+
libvirt "github.com/digitalocean/go-libvirt"
26+
"sigs.k8s.io/controller-runtime/pkg/log"
27+
)
28+
29+
// Client that returns the domain capabilities of the host we are mounted on.
30+
type Client interface {
31+
// Return the capabilities status of the host we are mounted on.
32+
Get(virt *libvirt.Libvirt) (v1.DomainCapabilities, error)
33+
}
34+
35+
// Implementation of the Client interface.
36+
type client struct{}
37+
38+
// Create a new domain capabilities client.
39+
func NewClient() Client {
40+
return &client{}
41+
}
42+
43+
// Return the domain capabilities of the host we are mounted on.
44+
func (m *client) Get(virt *libvirt.Libvirt) (v1.DomainCapabilities, error) {
45+
capabilitiesXMLBytes, err := virt.Capabilities()
46+
if err != nil {
47+
log.Log.Error(err, "failed to get libvirt capabilities")
48+
return v1.DomainCapabilities{}, err
49+
}
50+
var capabilities DomainCapabilities
51+
if err := xml.Unmarshal(capabilitiesXMLBytes, &capabilities); err != nil {
52+
log.Log.Error(err, "failed to unmarshal libvirt capabilities")
53+
return v1.DomainCapabilities{}, err
54+
}
55+
return convert(capabilities)
56+
}
57+
58+
// Emulated domain capabilities client returning an embedded capabilities xml.
59+
type clientEmulator struct{}
60+
61+
// Create a new emulated domain capabilities client.
62+
func NewClientEmulator() Client {
63+
return &clientEmulator{}
64+
}
65+
66+
// Get the domain capabilities of the host we are mounted on.
67+
func (c *clientEmulator) Get(virt *libvirt.Libvirt) (v1.DomainCapabilities, error) {
68+
var capabilities DomainCapabilities
69+
if err := xml.Unmarshal(exampleXML, &capabilities); err != nil {
70+
log.Log.Error(err, "failed to unmarshal example capabilities")
71+
return v1.DomainCapabilities{}, err
72+
}
73+
return convert(capabilities)
74+
}
75+
76+
// Convert the libvirt capabilities to the API format.
77+
func convert(in DomainCapabilities) (out v1.DomainCapabilities, err error) {
78+
out.Arch = in.Arch
79+
out.HypervisorType = in.Domain
80+
81+
// Convert the supported cpu modes into a flat list of supported cpu types.
82+
// - <mode name="example" supported="yes"><enum name="1"/></mode> becomes
83+
// "mode/example" and "mode/example/1"
84+
// - <mode name="example2" supported="no"><enum name="1"/></mode> is ignored
85+
// - <mode name="example3" supported="yes"></mode> becomes "mode/example3"
86+
out.SupportedCpuModes = []string{}
87+
for _, cpuMode := range in.CPU.Modes {
88+
if cpuMode.Supported != "yes" {
89+
continue
90+
}
91+
out.SupportedCpuModes = append(out.SupportedCpuModes,
92+
fmt.Sprintf("mode/%s", cpuMode.Name))
93+
for _, enum := range cpuMode.Enums {
94+
for _, cpuType := range enum.Values {
95+
out.SupportedCpuModes = append(out.SupportedCpuModes,
96+
fmt.Sprintf("mode/%s/%s", cpuMode.Name, cpuType))
97+
}
98+
}
99+
}
100+
101+
// Convert the supported devices into a flat list.
102+
// - <video supported="yes"><enum name="v1"/></video>
103+
// becomes "video" and "video/v1"
104+
// - <video supported="no"><enum name="v2"/></video> is ignored
105+
// - <video supported="yes"></video> becomes "video".
106+
out.SupportedDevices = []string{}
107+
for _, device := range in.Devices.Devices {
108+
if device.Supported != "yes" {
109+
continue
110+
}
111+
out.SupportedDevices = append(out.SupportedDevices, device.XMLName.Local)
112+
for _, enum := range device.Enums {
113+
for _, deviceType := range enum.Values {
114+
out.SupportedDevices = append(out.SupportedDevices,
115+
fmt.Sprintf("%s/%s", device.XMLName.Local, deviceType))
116+
}
117+
}
118+
}
119+
120+
// Convert the supported features into a flat list.
121+
out.SupportedFeatures = []string{}
122+
for _, feature := range in.Features.Features {
123+
if feature.Supported == "yes" {
124+
out.SupportedFeatures = append(out.SupportedFeatures, feature.XMLName.Local)
125+
}
126+
}
127+
128+
return out, nil
129+
}

0 commit comments

Comments
 (0)