1 package kubecli
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "net/http"
8
9 v1 "k8s.io/api/core/v1"
10 k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11 "k8s.io/apimachinery/pkg/fields"
12 "k8s.io/apimachinery/pkg/labels"
13 netutils "k8s.io/utils/net"
14
15 virtv1 "kubevirt.io/api/core/v1"
16
17 "kubevirt.io/client-go/util"
18 )
19
20 const (
21 consoleTemplateURI = "wss://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/console"
22 usbredirTemplateURI = "wss://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/usbredir"
23 vncTemplateURI = "wss://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/vnc"
24 vsockTemplateURI = "wss://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/vsock"
25 pauseTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/pause"
26 unpauseTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/unpause"
27 freezeTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/freeze"
28 unfreezeTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/unfreeze"
29 softRebootTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/softreboot"
30 guestInfoTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/guestosinfo"
31 userListTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/userlist"
32 filesystemListTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/filesystemlist"
33
34 sevFetchCertChainTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/sev/fetchcertchain"
35 sevQueryLaunchMeasurementTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/sev/querylaunchmeasurement"
36 sevInjectLaunchSecretTemplateURI = "https://%s:%v/v1/namespaces/%s/virtualmachineinstances/%s/sev/injectlaunchsecret"
37 )
38
39 func NewVirtHandlerClient(virtCli KubevirtClient, httpCli *http.Client) VirtHandlerClient {
40 return &virtHandler{
41 virtCli: virtCli,
42 httpCli: httpCli,
43 virtHandlerPort: 0,
44 namespace: "",
45 }
46 }
47
48 type VirtHandlerClient interface {
49 ForNode(nodeName string) VirtHandlerConn
50 Port(port int) VirtHandlerClient
51 Namespace(namespace string) VirtHandlerClient
52 }
53
54 type VirtHandlerConn interface {
55 ConnectionDetails() (ip string, port int, err error)
56 ConsoleURI(vmi *virtv1.VirtualMachineInstance) (string, error)
57 USBRedirURI(vmi *virtv1.VirtualMachineInstance) (string, error)
58 VNCURI(vmi *virtv1.VirtualMachineInstance) (string, error)
59 VSOCKURI(vmi *virtv1.VirtualMachineInstance, port string, tls string) (string, error)
60 PauseURI(vmi *virtv1.VirtualMachineInstance) (string, error)
61 UnpauseURI(vmi *virtv1.VirtualMachineInstance) (string, error)
62 FreezeURI(vmi *virtv1.VirtualMachineInstance) (string, error)
63 UnfreezeURI(vmi *virtv1.VirtualMachineInstance) (string, error)
64 SoftRebootURI(vmi *virtv1.VirtualMachineInstance) (string, error)
65 SEVFetchCertChainURI(vmi *virtv1.VirtualMachineInstance) (string, error)
66 SEVQueryLaunchMeasurementURI(vmi *virtv1.VirtualMachineInstance) (string, error)
67 SEVInjectLaunchSecretURI(vmi *virtv1.VirtualMachineInstance) (string, error)
68 Pod() (pod *v1.Pod, err error)
69 Put(url string, body io.ReadCloser) error
70 Get(url string) (string, error)
71 GuestInfoURI(vmi *virtv1.VirtualMachineInstance) (string, error)
72 UserListURI(vmi *virtv1.VirtualMachineInstance) (string, error)
73 FilesystemListURI(vmi *virtv1.VirtualMachineInstance) (string, error)
74 }
75
76 type virtHandler struct {
77 virtCli KubevirtClient
78 httpCli *http.Client
79 virtHandlerPort int
80 namespace string
81 }
82
83 type virtHandlerConn struct {
84 pod *v1.Pod
85 err error
86 port int
87 httpClient *http.Client
88 }
89
90 func (v *virtHandler) Namespace(namespace string) VirtHandlerClient {
91 v.namespace = namespace
92 return v
93 }
94
95 func (v *virtHandler) Port(port int) VirtHandlerClient {
96 v.virtHandlerPort = port
97 return v
98 }
99 func (v *virtHandler) ForNode(nodeName string) VirtHandlerConn {
100 var err error
101
102 conn := &virtHandlerConn{
103 httpClient: v.httpCli,
104 }
105
106 namespace := v.namespace
107 if namespace == "" {
108 namespace, err = util.GetNamespace()
109 if err != nil {
110 conn.err = err
111 return conn
112 }
113 }
114 pod, found, err := v.getVirtHandler(nodeName, namespace)
115 if !found {
116 conn.err = fmt.Errorf("No virt-handler on node %s found", nodeName)
117 }
118 if err != nil {
119 conn.err = err
120 }
121 conn.pod = pod
122 conn.port = v.virtHandlerPort
123 return conn
124 }
125
126 func (v *virtHandler) getVirtHandler(nodeName string, namespace string) (*v1.Pod, bool, error) {
127
128 handlerNodeSelector := fields.ParseSelectorOrDie("spec.nodeName=" + nodeName)
129 labelSelector, err := labels.Parse(virtv1.AppLabel + " in (virt-handler)")
130 if err != nil {
131 return nil, false, err
132 }
133
134 pods, err := v.virtCli.CoreV1().Pods(namespace).List(context.Background(),
135 k8smetav1.ListOptions{
136 FieldSelector: handlerNodeSelector.String(),
137 LabelSelector: labelSelector.String()})
138 if err != nil {
139 return nil, false, err
140 }
141 if len(pods.Items) > 1 {
142 return nil, false, fmt.Errorf("Expected to find one Pod, found %d Pods", len(pods.Items))
143 }
144
145 if len(pods.Items) == 0 {
146 return nil, false, nil
147 }
148 return &pods.Items[0], true, nil
149 }
150
151 func (v *virtHandlerConn) ConnectionDetails() (ip string, port int, err error) {
152 if v.err != nil {
153 err = v.err
154 return
155 }
156
157 ip = v.pod.Status.PodIP
158
159 port = 8185
160 if v.port != 0 {
161 port = v.port
162 }
163 return
164 }
165
166 func (v *virtHandlerConn) formatURI(template string, vmi *virtv1.VirtualMachineInstance) (string, error) {
167 ip, port, err := v.ConnectionDetails()
168 if err != nil {
169 return "", err
170 }
171
172 return fmt.Sprintf(template, formatIpForUri(ip), port, vmi.ObjectMeta.Namespace, vmi.ObjectMeta.Name), nil
173 }
174
175
176 func (v *virtHandlerConn) ConsoleURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
177 return v.formatURI(consoleTemplateURI, vmi)
178 }
179
180 func (v *virtHandlerConn) USBRedirURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
181 return v.formatURI(usbredirTemplateURI, vmi)
182 }
183
184 func (v *virtHandlerConn) VNCURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
185 return v.formatURI(vncTemplateURI, vmi)
186 }
187
188 func (v *virtHandlerConn) VSOCKURI(vmi *virtv1.VirtualMachineInstance, port string, tls string) (string, error) {
189 baseURI, err := v.formatURI(vsockTemplateURI, vmi)
190 if err != nil {
191 return "", err
192 }
193 return fmt.Sprintf("%s?port=%s&tls=%s", baseURI, port, tls), nil
194 }
195
196 func (v *virtHandlerConn) FreezeURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
197 return v.formatURI(freezeTemplateURI, vmi)
198 }
199
200 func (v *virtHandlerConn) UnfreezeURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
201 return v.formatURI(unfreezeTemplateURI, vmi)
202 }
203
204 func (v *virtHandlerConn) SoftRebootURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
205 return v.formatURI(softRebootTemplateURI, vmi)
206 }
207
208 func (v *virtHandlerConn) PauseURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
209 return v.formatURI(pauseTemplateURI, vmi)
210 }
211
212 func (v *virtHandlerConn) UnpauseURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
213 return v.formatURI(unpauseTemplateURI, vmi)
214 }
215
216 func (v *virtHandlerConn) Pod() (pod *v1.Pod, err error) {
217 if v.err != nil {
218 err = v.err
219 return
220 }
221 return v.pod, err
222 }
223
224 func (v *virtHandlerConn) doRequest(req *http.Request) (response string, err error) {
225 resp, err := v.httpClient.Do(req)
226 if err != nil {
227 return
228 }
229 defer resp.Body.Close()
230
231 if resp.StatusCode < 200 || resp.StatusCode > 299 {
232 return "", fmt.Errorf("unexpected return code %d (%s)", resp.StatusCode, resp.Status)
233 }
234
235 responseBytes, err := io.ReadAll(resp.Body)
236 if err != nil {
237 return "", fmt.Errorf("cannot read response body %v", err)
238 }
239
240 return string(responseBytes), nil
241 }
242
243 func (v *virtHandlerConn) Put(url string, body io.ReadCloser) error {
244 req, err := http.NewRequest(http.MethodPut, url, body)
245 if err != nil {
246 return err
247 }
248
249 _, err = v.doRequest(req)
250 if err != nil {
251 return err
252 }
253
254 return nil
255 }
256
257 func (v *virtHandlerConn) Get(url string) (string, error) {
258 req, err := http.NewRequest(http.MethodGet, url, nil)
259 if err != nil {
260 return "", err
261 }
262
263 req.Header.Add("Accept", "application/json")
264 response, err := v.doRequest(req)
265 if err != nil {
266 return "", err
267 }
268
269 return response, nil
270 }
271
272 func (v *virtHandlerConn) GuestInfoURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
273 return v.formatURI(guestInfoTemplateURI, vmi)
274 }
275
276 func formatIpForUri(ip string) string {
277 if netutils.IsIPv6String(ip) {
278 return "[" + ip + "]"
279 }
280 return ip
281 }
282
283 func (v *virtHandlerConn) UserListURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
284 return v.formatURI(userListTemplateURI, vmi)
285 }
286
287 func (v *virtHandlerConn) FilesystemListURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
288 return v.formatURI(filesystemListTemplateURI, vmi)
289 }
290
291 func (v *virtHandlerConn) SEVFetchCertChainURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
292 return v.formatURI(sevFetchCertChainTemplateURI, vmi)
293 }
294
295 func (v *virtHandlerConn) SEVQueryLaunchMeasurementURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
296 return v.formatURI(sevQueryLaunchMeasurementTemplateURI, vmi)
297 }
298
299 func (v *virtHandlerConn) SEVInjectLaunchSecretURI(vmi *virtv1.VirtualMachineInstance) (string, error) {
300 return v.formatURI(sevInjectLaunchSecretTemplateURI, vmi)
301 }
302
View as plain text