...

Source file src/kubevirt.io/client-go/kubecli/handler.go

Documentation: kubevirt.io/client-go/kubecli

     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  	// TODO depending on in which network namespace virt-handler runs, we might have to choose the NodeIPt d
   157  	ip = v.pod.Status.PodIP
   158  	// TODO get rid of the hardcoded port
   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  // TODO move the actual ws handling in here, and work with channels
   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