github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/tools/vnc.go (about) 1 /* 2 Copyright 2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tools 18 19 import ( 20 "bufio" 21 "bytes" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 "os/signal" 27 "strconv" 28 "strings" 29 "syscall" 30 31 "github.com/renstrom/dedent" 32 "github.com/spf13/cobra" 33 ) 34 35 const ( 36 vncProtocolName = "vnc" 37 expectedHost = "127.0.0.1" 38 maxDisplayNumber = 0xffff - 5900 39 ) 40 41 // vncCommand provides access to the VNC console of a VM pod 42 type vncCommand struct { 43 client KubeClient 44 podName string 45 port uint16 46 output io.Writer 47 waitForInterrupt bool 48 } 49 50 // NewVNCCmd returns a cobra.Command that provides access to the VNC console of a VM pod. 51 func NewVNCCmd(client KubeClient, output io.Writer, waitForInterrupt bool) *cobra.Command { 52 vnc := &vncCommand{client: client, output: output, waitForInterrupt: waitForInterrupt} 53 cmd := &cobra.Command{ 54 Use: "vnc pod [port]", 55 Short: "Provide access to the VNC console of a VM pod", 56 Long: dedent.Dedent(` 57 This command forwards a local port to the VNC port used by the 58 specified VM pod. If no local port number is provided, a random 59 available port is picked instead. The port number is displayed 60 after the forwarding is set up, after which the commands enters 61 an endless loop until it's interrupted with Ctrl-C. 62 `), 63 RunE: func(cmd *cobra.Command, args []string) error { 64 if len(args) < 1 { 65 return errors.New("pod name not specified") 66 } 67 vnc.podName = args[0] 68 69 switch { 70 case len(args) > 2: 71 return errors.New("more than 2 options") 72 case len(args) == 2: 73 // port should be unprivileged and below the high ports 74 // range 75 port, err := strconv.Atoi(args[1]) 76 if err != nil || port < 1024 || port > 61000 { 77 return errors.New("port parameter must be an integer number in range 1000-61000") 78 } 79 vnc.port = uint16(port) 80 } 81 return vnc.Run() 82 }, 83 } 84 return cmd 85 } 86 87 // Run executes the command. 88 func (v *vncCommand) Run() error { 89 vmPodInfo, err := v.client.GetVMPodInfo(v.podName) 90 if err != nil { 91 return fmt.Errorf("can't get VM pod info for %q: %v", v.podName, err) 92 } 93 94 var buffer bytes.Buffer 95 virshOutput := bufio.NewWriter(&buffer) 96 exitCode, err := v.client.ExecInContainer( 97 vmPodInfo.VirtletPodName, "libvirt", "kube-system", 98 nil, virshOutput, os.Stderr, 99 []string{"virsh", "domdisplay", vmPodInfo.LibvirtDomainName()}, 100 ) 101 if err != nil { 102 return fmt.Errorf("error executing virsh in Virtlet pod %q: %v", vmPodInfo.VirtletPodName, err) 103 } 104 if exitCode != 0 { 105 return fmt.Errorf("virsh returned non-zero exit code %d", exitCode) 106 } 107 108 virshOutput.Flush() 109 parts := strings.Split(strings.Trim(buffer.String(), "\n"), ":") 110 switch { 111 case len(parts) != 3: 112 return fmt.Errorf("virsh returned %q, while expected to return a value of a form %q", buffer.String(), "vnc://127.0.0.1:0") 113 case parts[0] != vncProtocolName: 114 return fmt.Errorf("virsh returned %q as a display protocol instead of expected %q", parts[0], vncProtocolName) 115 case parts[1][:2] != "//": 116 return fmt.Errorf("virsh returned %q after first ':' instead of expected %q", parts[1][:2], "//") 117 case parts[1][2:] != expectedHost: 118 return fmt.Errorf("virsh returned %q as a display host instead of expected %q", parts[1], expectedHost) 119 } 120 121 displayNumber, err := strconv.Atoi(parts[2]) 122 if err != nil || displayNumber < 0 || displayNumber > maxDisplayNumber { 123 return fmt.Errorf("virsh returned %q as a display number which is expected to be in range 0..%d", parts[2], maxDisplayNumber) 124 } 125 126 pf := &ForwardedPort{ 127 RemotePort: 5900 + uint16(displayNumber), 128 LocalPort: v.port, 129 } 130 stopCh, err := v.client.ForwardPorts(vmPodInfo.VirtletPodName, "kube-system", []*ForwardedPort{pf}) 131 if err != nil { 132 return fmt.Errorf("error forwarding the vnc port: %v", err) 133 } 134 defer close(stopCh) 135 136 fmt.Fprintf(v.output, "VNC console for pod %q is available on local port %d\n", v.podName, pf.LocalPort) 137 fmt.Fprintf(v.output, "Press ctrl-c or kill the process stop.\n") 138 139 // if waitForInterrupt is set to false do not wait for interrupt (e.x. in tests). 140 if v.waitForInterrupt { 141 c := make(chan os.Signal, 2) 142 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 143 144 <-c 145 } 146 147 return nil 148 }