~ubuntu-branches/ubuntu/precise/virt-manager/precise-updates

« back to all changes in this revision

Viewing changes to src/virtManager/console.py

  • Committer: Bazaar Package Importer
  • Author(s): Laurent Léonard
  • Date: 2010-03-25 09:14:38 UTC
  • mto: (1.2.1 upstream) (2.1.15 sid)
  • mto: This revision was merged to the branch mainline in revision 31.
  • Revision ID: james.westby@ubuntu.com-20100325091438-03vv382wj8jqgr26
Tags: upstream-0.8.4
Import upstream version 0.8.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import gobject
22
22
import gtk
23
23
import gtk.glade
 
24
 
24
25
import libvirt
25
 
import logging
26
 
import traceback
27
 
import sys
28
26
import dbus
29
27
import gtkvnc
 
28
 
30
29
import os
 
30
import sys
 
31
import signal
31
32
import socket
 
33
import logging
 
34
import traceback
32
35
 
 
36
from virtManager import util
33
37
from virtManager.error import vmmErrorDialog
34
38
 
35
39
# Console pages
443
447
    ########################
444
448
 
445
449
    def _vnc_disconnected(self, src):
 
450
        errout = ""
446
451
        if self.vncTunnel is not None:
 
452
            errout = self.get_tunnel_err_output()
447
453
            self.close_tunnel()
 
454
 
448
455
        self.vnc_connected = False
449
456
        logging.debug("VNC disconnected")
450
 
        if self.vm.status() in [ libvirt.VIR_DOMAIN_SHUTOFF,
451
 
                                 libvirt.VIR_DOMAIN_CRASHED ]:
 
457
 
 
458
        if (self.skip_connect_attempt() or
 
459
            self.guest_not_avail()):
 
460
            # Exit was probably for legitimate reasons
452
461
            self.view_vm_status()
453
462
            return
454
463
 
455
 
        self.activate_unavailable_page(_("TCP/IP error: VNC connection to hypervisor host got refused or disconnected!"))
456
 
 
457
 
        if not self.is_visible():
458
 
            return
459
 
 
460
 
        self.schedule_retry()
 
464
        error = _("Error: VNC connection to hypervisor host got refused "
 
465
                  "or disconnected!")
 
466
        if errout:
 
467
            logging.debug("Error output from closed console: %s" % errout)
 
468
            error += "\n\nError: %s" % errout
 
469
 
 
470
        self.activate_unavailable_page(error)
461
471
 
462
472
    def _vnc_initialized(self, src):
463
473
        self.vnc_connected = True
469
479
        self.vncViewerRetryDelay = 125
470
480
 
471
481
    def schedule_retry(self):
472
 
        self.vncViewerRetriesScheduled = self.vncViewerRetriesScheduled + 1
473
482
        if self.vncViewerRetriesScheduled >= 10:
474
483
            logging.error("Too many connection failures, not retrying again")
475
484
            return
476
 
        logging.warn("Retrying connection in %d ms", self.vncViewerRetryDelay)
477
 
        gobject.timeout_add(self.vncViewerRetryDelay, self.retry_login)
 
485
 
 
486
        util.safe_timeout_add(self.vncViewerRetryDelay, self.try_login)
 
487
 
478
488
        if self.vncViewerRetryDelay < 2000:
479
489
            self.vncViewerRetryDelay = self.vncViewerRetryDelay * 2
480
490
 
481
 
    def retry_login(self):
482
 
        if self.vnc_connected:
483
 
            return
484
 
 
485
 
        if self.vm.status() in [ libvirt.VIR_DOMAIN_SHUTOFF,
486
 
                                 libvirt.VIR_DOMAIN_CRASHED ]:
487
 
            return
488
 
 
489
 
        gtk.gdk.threads_enter()
490
 
        try:
491
 
            self.try_login()
492
 
            return
493
 
        finally:
494
 
            gtk.gdk.threads_leave()
495
 
 
496
491
    def open_tunnel(self, server, vncaddr, vncport, username, sshport):
497
492
        if self.vncTunnel is not None:
498
493
            return -1
505
500
        if username:
506
501
            argv += ['-l', username]
507
502
 
508
 
        argv += [ server, "nc", vncaddr, str(vncport) ]
509
 
 
510
 
        logging.debug("Creating SSH tunnel: %s" % argv)
511
 
 
512
 
        fds = socket.socketpair()
 
503
        argv += [ server ]
 
504
 
 
505
        # Build 'nc' command run on the remote host
 
506
        #
 
507
        # This ugly thing is a shell script to detect availability of
 
508
        # the -q option for 'nc': debian and suse based distros need this
 
509
        # flag to ensure the remote nc will exit on EOF, so it will go away
 
510
        # when we close the VNC tunnel. If it doesn't go away, subsequent
 
511
        # VNC connection attempts will hang.
 
512
        #
 
513
        # Fedora's 'nc' doesn't have this option, and apparently defaults
 
514
        # to the desired behavior.
 
515
        #
 
516
        nc_params = "%s %s" % (vncaddr, str(vncport))
 
517
        nc_cmd = [\
 
518
            "nc -q 2>&1 | grep -q 'requires an argument';"
 
519
            "if [ $? -eq 0 ] ; then"
 
520
            "   CMD='nc -q 0 %(nc_params)s';"
 
521
            "else"
 
522
            "   CMD='nc %(nc_params)s';"
 
523
            "fi;"
 
524
            "$CMD;" % {'nc_params': nc_params}
 
525
        ]
 
526
 
 
527
        argv += nc_cmd
 
528
 
 
529
        argv_str = reduce(lambda x, y: x + " " + y, argv[1:])
 
530
        logging.debug("Creating SSH tunnel: %s" % argv_str)
 
531
 
 
532
        fds      = socket.socketpair()
 
533
        errorfds = socket.socketpair()
 
534
 
513
535
        pid = os.fork()
514
536
        if pid == 0:
515
537
            fds[0].close()
 
538
            errorfds[0].close()
 
539
 
516
540
            os.close(0)
517
541
            os.close(1)
518
 
            os.dup(fds[1].fileno())
519
 
            os.dup(fds[1].fileno())
 
542
            os.close(2)
 
543
            os.dup(fds[1].fileno())
 
544
            os.dup(fds[1].fileno())
 
545
            os.dup(errorfds[1].fileno())
520
546
            os.execlp(*argv)
521
547
            os._exit(1)
522
548
        else:
523
549
            fds[1].close()
524
 
 
525
 
        logging.debug("Tunnel PID %d FD %d" % (fds[0].fileno(), pid))
526
 
        self.vncTunnel = [fds[0], pid]
 
550
            errorfds[1].close()
 
551
 
 
552
        logging.debug("Tunnel PID=%d OUTFD=%d ERRFD=%d" %
 
553
                      (pid, fds[0].fileno(), errorfds[0].fileno()))
 
554
        errorfds[0].setblocking(0)
 
555
        self.vncTunnel = [fds[0], errorfds[0], pid]
 
556
 
527
557
        return fds[0].fileno()
528
558
 
529
559
    def close_tunnel(self):
530
560
        if self.vncTunnel is None:
531
561
            return
532
562
 
533
 
        logging.debug("Shutting down tunnel PID %d FD %d" %
534
 
                      (self.vncTunnel[1], self.vncTunnel[0].fileno()))
 
563
        logging.debug("Shutting down tunnel PID=%d OUTFD=%d ERRFD=%d" %
 
564
                      (self.vncTunnel[2], self.vncTunnel[0].fileno(),
 
565
                       self.vncTunnel[1].fileno()))
535
566
        self.vncTunnel[0].close()
536
 
        os.waitpid(self.vncTunnel[1], 0)
 
567
        self.vncTunnel[1].close()
 
568
 
 
569
        os.kill(self.vncTunnel[2], signal.SIGKILL)
537
570
        self.vncTunnel = None
538
571
 
 
572
    def get_tunnel_err_output(self):
 
573
        errfd = self.vncTunnel[1]
 
574
        errout = ""
 
575
        while True:
 
576
            new = errfd.recv(1024)
 
577
            if not new:
 
578
                break
 
579
 
 
580
            errout += new
 
581
 
 
582
        return errout
 
583
 
 
584
    def skip_connect_attempt(self):
 
585
        return (self.vnc_connected or
 
586
                not self.is_visible())
 
587
 
 
588
    def guest_not_avail(self):
 
589
        return (not self.vm.get_handle() or
 
590
                self.vm.status() in [ libvirt.VIR_DOMAIN_SHUTOFF,
 
591
                                      libvirt.VIR_DOMAIN_CRASHED ] or
 
592
                self.vm.get_id() < 0)
 
593
 
539
594
    def try_login(self, src=None):
540
 
        if not self.vm.get_handle():
541
 
            # VM was removed, skip login attempt
 
595
        if self.skip_connect_attempt():
 
596
            # Don't try and login for these cases
542
597
            return
543
598
 
544
 
        if self.vm.get_id() < 0:
 
599
        if self.guest_not_avail():
 
600
            # Guest isn't running, schedule another try
545
601
            self.activate_unavailable_page(_("Guest not running"))
546
602
            self.schedule_retry()
547
603
            return
548
604
 
549
605
        try:
550
 
            (protocol, host,
551
 
             port, trans, username) = self.vm.get_graphics_console()
 
606
            (protocol, connhost,
 
607
             vncport, trans, username,
 
608
             connport, vncuri) = self.vm.get_graphics_console()
552
609
        except Exception, e:
553
610
            # We can fail here if VM is destroyed: xen is a bit racy
554
611
            # and can't handle domain lookups that soon after
555
612
            logging.debug("Getting graphics console failed: %s" % str(e))
556
613
            return
557
614
 
558
 
        connport = None
559
 
        if host.count(":"):
560
 
            host, connport = host.split(":", 1)
561
 
 
562
615
        if protocol is None:
563
616
            logging.debug("No graphics configured in guest")
564
 
            self.activate_unavailable_page(_("Graphical console not configured for guest"))
 
617
            self.activate_unavailable_page(
 
618
                            _("Graphical console not configured for guest"))
565
619
            return
566
620
 
567
 
        uri = str(protocol) + "://"
568
 
        if username:
569
 
            uri = uri + str(username) + '@'
570
 
        uri = uri + str(host) + ":" + str(port)
571
 
 
572
 
        logging.debug("Graphics console configured at " + uri)
573
 
 
574
621
        if protocol != "vnc":
575
622
            logging.debug("Not a VNC console, disabling")
576
 
            self.activate_unavailable_page(_("Graphical console not supported for guest"))
 
623
            self.activate_unavailable_page(
 
624
                            _("Graphical console not supported for guest"))
577
625
            return
578
626
 
579
 
        if int(port) == -1:
580
 
            self.activate_unavailable_page(_("Graphical console is not yet active for guest"))
 
627
        if vncport == -1:
 
628
            self.activate_unavailable_page(
 
629
                            _("Graphical console is not yet active for guest"))
581
630
            self.schedule_retry()
582
631
            return
583
632
 
584
 
        self.activate_unavailable_page(_("Connecting to graphical console for guest"))
585
 
        logging.debug("Starting connect process for %s %s" % (host, str(port)))
 
633
        self.activate_unavailable_page(
 
634
                _("Connecting to graphical console for guest"))
 
635
        logging.debug("Starting connect process for %s: %s %s" %
 
636
                      (vncuri, connhost, str(vncport)))
 
637
 
586
638
        try:
587
 
            if trans is not None and trans in ("ssh", "ext"):
 
639
            if trans in ("ssh", "ext"):
588
640
                if self.vncTunnel:
589
 
                    logging.debug("Tunnel already open, skipping open_tunnel.")
 
641
                    # Tunnel already open, no need to continue
590
642
                    return
591
643
 
592
 
                fd = self.open_tunnel(host, "127.0.0.1", port, username,
593
 
                                      connport)
 
644
                fd = self.open_tunnel(connhost, "127.0.0.1", vncport,
 
645
                                      username, connport)
594
646
                if fd >= 0:
595
647
                    self.vncViewer.open_fd(fd)
 
648
 
596
649
            else:
597
 
                self.vncViewer.open_host(host, str(port))
 
650
                self.vncViewer.open_host(connhost, str(vncport))
 
651
 
598
652
        except:
599
653
            (typ, value, stacktrace) = sys.exc_info ()
600
654
            details = \
673
727
 
674
728
        def unset_cb(src):
675
729
            src.queue_resize_no_redraw()
676
 
            gobject.idle_add(restore_scroll, src)
 
730
            util.safe_idle_add(restore_scroll, src)
677
731
            return False
678
732
 
679
733
        def request_cb(src, req):
683
737
 
684
738
            src.disconnect(signal_id)
685
739
 
686
 
            gobject.idle_add(unset_cb, widget)
 
740
            util.safe_idle_add(unset_cb, widget)
687
741
            return False
688
742
 
689
743
        # Disable scroll bars while we resize, since resizing to the VM's