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

feat(virtualization): serial/vnc reconnect #67

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
40 changes: 27 additions & 13 deletions internal/virtualization/cmd/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Initially copied from https://github.com/kubevirt/kubevirt/blob/main/pkg/virtctl
package console

import (
"errors"
"fmt"
"io"
"os"
Expand All @@ -33,6 +34,7 @@ import (
"github.com/deckhouse/deckhouse-cli/internal/virtualization/templates"
"github.com/deckhouse/deckhouse-cli/internal/virtualization/util"
"github.com/deckhouse/virtualization/api/client/kubeclient"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
)

var timeout int
Expand Down Expand Up @@ -89,11 +91,32 @@ func (c *Console) Run(args []string) error {
stdinReader, stdinWriter := io.Pipe()
stdoutReader, stdoutWriter := io.Pipe()

for {
err := connect(name, namespace, virtCli, stdinReader, stdoutReader, stdinWriter, stdoutWriter)
if err != nil {
if errors.Is(err, util.ErrorInterrupt) || k8serrors.IsNotFound(err) {
return nil
}

if e, ok := err.(*websocket.CloseError); ok && e.Code == websocket.CloseAbnormalClosure {
fmt.Fprint(os.Stderr, "\nYou were disconnected from the console. This has one of the following reasons:"+
"\n - another user connected to the console of the target vm"+
"\n - network issues"+
"\nNext try in 5 seconds\n")
}

time.Sleep(time.Second * 5)
}
}
}

func connect(name string, namespace string, virtCli kubeclient.Client, stdinReader, stdoutReader *io.PipeReader, stdinWriter, stdoutWriter *io.PipeWriter) error {
// in -> stdinWriter | stdinReader -> console
// out <- stdoutReader | stdoutWriter <- console
// Wait until the virtual machine is in running phase, user interrupt or timeout
resChan := make(chan error)
runningChan := make(chan error)

waitInterrupt := make(chan os.Signal, 1)
signal.Notify(waitInterrupt, os.Interrupt)

Expand All @@ -116,22 +139,13 @@ func (c *Console) Run(args []string) error {
// Make a new line in the terminal
fmt.Println()
return nil
case err = <-runningChan:
case err := <-runningChan:
if err != nil {
fmt.Println(err)
return err
}
}
err = util.AttachConsole(stdinReader, stdoutReader, stdinWriter, stdoutWriter,
fmt.Sprint("Successfully connected to ", name, " console. The escape sequence is ^]\n"),
resChan)

if err != nil {
if e, ok := err.(*websocket.CloseError); ok && e.Code == websocket.CloseAbnormalClosure {
fmt.Fprint(os.Stderr, "\nYou were disconnected from the console. This has one of the following reasons:"+
"\n - another user connected to the console of the target vm"+
"\n - network issues\n")
}
return err
}
return nil
err := util.AttachConsole(stdinReader, stdoutReader, stdinWriter, stdoutWriter, name, resChan)
return err
}
60 changes: 48 additions & 12 deletions internal/virtualization/cmd/vnc/vnc.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Initially copied from https://github.com/kubevirt/kubevirt/blob/main/pkg/virtctl
package vnc

import (
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -34,7 +35,10 @@ import (

"github.com/deckhouse/deckhouse-cli/internal/virtualization/templates"
"github.com/deckhouse/virtualization/api/client/kubeclient"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/gorilla/websocket"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
)
Expand Down Expand Up @@ -109,11 +113,6 @@ func (o *VNC) Run(cmd *cobra.Command, args []string) error {
return err
}

// setup connection with VM
vnc, err := virtCli.VirtualMachines(namespace).VNC(vmName)
if err != nil {
return fmt.Errorf("can't access VM %s: %s", vmName, err.Error())
}
// Format the listening address to account for the port (ex: 127.0.0.0:5900)
// Set listenAddress to localhost if proxy-only flag is not set
if !proxyOnly {
Expand All @@ -134,6 +133,42 @@ func (o *VNC) Run(cmd *cobra.Command, args []string) error {
// End of pre-flight checks. Everything looks good, we can start
// the goroutines and let the data flow

for {
err := connect(ln, virtCli, cmd, namespace, vmName)
if err != nil {
if e, ok := err.(*websocket.CloseError); ok && e.Code == websocket.CloseAbnormalClosure {
fmt.Fprint(os.Stderr, "\nYou were disconnected from the console. This has one of the following reasons:"+
"\n - another user connected to the console of the target vm"+
"\n - network issues"+
"\nNext try in 5 seconds\n")
} else {
fmt.Println(err)
}

time.Sleep(time.Second * 5)
continue
}

return nil
}
}

func connect(ln *net.TCPListener, virtCli kubeclient.Client, cmd *cobra.Command, namespace, vmName string) (err error) {
vm, err := virtCli.VirtualMachines(namespace).Get(context.Background(), vmName, v1.GetOptions{})
if err != nil {
return err
}

if vm.Status.Phase != v1alpha2.MachineRunning {
return errors.New("VM is not running")
}

// setup connection with VM
vnc, err := virtCli.VirtualMachines(namespace).VNC(vmName)
if err != nil {
return fmt.Errorf("can't access VM %s: %s", vmName, err.Error())
}

// -> pipeInWriter -> pipeInReader
// remote-viewer -> unix sock connection
// <- pipeOutReader <- pipeOutWriter
Expand Down Expand Up @@ -197,6 +232,8 @@ func (o *VNC) Run(cmd *cobra.Command, args []string) error {

port := ln.Addr().(*net.TCPAddr).Port

ctx, cancelCtx := context.WithCancel(context.Background())

if proxyOnly {
defer close(doneChan)
optionString, err := json.Marshal(struct {
Expand All @@ -208,7 +245,7 @@ func (o *VNC) Run(cmd *cobra.Command, args []string) error {
fmt.Fprintln(cmd.OutOrStdout(), string(optionString))
} else {
// execute VNC Viewer
go checkAndRunVNCViewer(doneChan, viewResChan, port)
go checkAndRunVNCViewer(ctx, doneChan, viewResChan, port)
}

go func() {
Expand All @@ -227,13 +264,12 @@ func (o *VNC) Run(cmd *cobra.Command, args []string) error {
case err = <-listenResChan:
}

if err != nil {
return fmt.Errorf("error encountered: %s", err.Error())
}
return nil
cancelCtx()

return err
}

func checkAndRunVNCViewer(doneChan chan struct{}, viewResChan chan error, port int) {
func checkAndRunVNCViewer(ctx context.Context, doneChan chan struct{}, viewResChan chan error, port int) {
defer close(doneChan)
var err error
args := []string{}
Expand Down Expand Up @@ -293,7 +329,7 @@ func checkAndRunVNCViewer(doneChan chan struct{}, viewResChan chan error, port i
} else {
klog.V(4).Infof("Executing commandline: '%s %v'", vncBin, args)
// #nosec No risk for attacket injection. vncBin and args include predefined strings
cmd := exec.Command(vncBin, args...)
cmd := exec.CommandContext(ctx, vncBin, args...)
output, err := cmd.CombinedOutput()
if err != nil {
klog.Errorf("%s execution failed: %v, output: %v", vncBin, err, string(output))
Expand Down
12 changes: 9 additions & 3 deletions internal/virtualization/util/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Initially copied from https://github.com/kubevirt/kubevirt/blob/main/pkg/virtctl
package util

import (
"errors"
"fmt"
"io"
"os"
Expand All @@ -28,7 +29,9 @@ import (
"golang.org/x/term"
)

func AttachConsole(stdinReader, stdoutReader *io.PipeReader, stdinWriter, stdoutWriter *io.PipeWriter, message string, resChan <-chan error) (err error) {
var ErrorInterrupt = errors.New("interrupt")

func AttachConsole(stdinReader, stdoutReader *io.PipeReader, stdinWriter, stdoutWriter *io.PipeWriter, name string, resChan <-chan error) (err error) {
stopChan := make(chan struct{}, 1)
writeStop := make(chan error)
readStop := make(chan error)
Expand All @@ -39,6 +42,7 @@ func AttachConsole(stdinReader, stdoutReader *io.PipeReader, stdinWriter, stdout
}
defer term.Restore(int(os.Stdin.Fd()), state)
}
message := fmt.Sprintf("Successfully connected to %s console. The escape sequence is ^]\n", name)
fmt.Fprint(os.Stderr, message)

in := os.Stdin
Expand Down Expand Up @@ -84,10 +88,12 @@ func AttachConsole(stdinReader, stdoutReader *io.PipeReader, stdinWriter, stdout

select {
case <-stopChan:
return ErrorInterrupt
case err = <-readStop:
return ErrorInterrupt
case err = <-writeStop:
return ErrorInterrupt
case err = <-resChan:
return err
}

return err
}