Revision 25ce3ec4

b/Makefile.am
437 437
	test/ganeti.backend_unittest.py \
438 438
	test/ganeti.bdev_unittest.py \
439 439
	test/ganeti.cli_unittest.py \
440
	test/ganeti.client.gnt_instance_unittest.py \
440 441
	test/ganeti.daemon_unittest.py \
441 442
	test/ganeti.cmdlib_unittest.py \
442 443
	test/ganeti.compat_unittest.py \
b/lib/client/gnt_instance.py
27 27

  
28 28
import itertools
29 29
import simplejson
30
import logging
30 31
from cStringIO import StringIO
31 32

  
32 33
from ganeti.cli import *
......
36 37
from ganeti import utils
37 38
from ganeti import errors
38 39
from ganeti import netutils
40
from ganeti import ssh
41
from ganeti import objects
39 42

  
40 43

  
41 44
_SHUTDOWN_CLUSTER = "cluster"
......
886 889
  instance_name = args[0]
887 890

  
888 891
  op = opcodes.OpConnectConsole(instance_name=instance_name)
889
  cmd = SubmitOpCode(op, opts=opts)
890 892

  
891
  if opts.show_command:
892
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
893
  cl = GetClient()
894
  try:
895
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
896
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
897
  finally:
898
    # Ensure client connection is closed while external commands are run
899
    cl.Close()
900

  
901
  del cl
902

  
903
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
904
                    opts.show_command, cluster_name)
905

  
906

  
907
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
908
               _runcmd_fn=utils.RunCmd):
909
  """Acts based on the result of L{opcodes.OpConnectConsole}.
910

  
911
  @type console: L{objects.InstanceConsole}
912
  @param console: Console object
913
  @type show_command: bool
914
  @param show_command: Whether to just display commands
915
  @type cluster_name: string
916
  @param cluster_name: Cluster name as retrieved from master daemon
917

  
918
  """
919
  assert console.Validate()
920

  
921
  if console.kind == constants.CONS_MESSAGE:
922
    feedback_fn(console.message)
923
  elif console.kind == constants.CONS_VNC:
924
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
925
                " URL <vnc://%s:%s/>",
926
                console.instance, console.host, console.port,
927
                console.display, console.host, console.port)
928
  elif console.kind == constants.CONS_SSH:
929
    # Convert to string if not already one
930
    if isinstance(console.command, basestring):
931
      cmd = console.command
932
    else:
933
      cmd = utils.ShellQuoteArgs(console.command)
934

  
935
    srun = ssh.SshRunner(cluster_name=cluster_name)
936
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
937
                            batch=True, quiet=False, tty=True)
938

  
939
    if show_command:
940
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
941
    else:
942
      result = _runcmd_fn(ssh_cmd, interactive=True)
943
      if result.failed:
944
        logging.error("Console command \"%s\" failed with reason '%s' and"
945
                      " output %r", result.cmd, result.fail_reason,
946
                      result.output)
947
        raise errors.OpExecError("Connection to console of instance %s failed,"
948
                                 " please check cluster configuration" %
949
                                 console.instance)
893 950
  else:
894
    result = utils.RunCmd(cmd, interactive=True)
895
    if result.failed:
896
      raise errors.OpExecError("Console command \"%s\" failed: %s" %
897
                               (utils.ShellQuoteArgs(cmd), result.fail_reason))
951
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
898 952

  
899 953
  return constants.EXIT_SUCCESS
900 954

  
b/lib/cmdlib.py
7693 7693
    beparams = cluster.FillBE(instance)
7694 7694
    console_cmd = hyper.GetShellCommandForConsole(instance, hvparams, beparams)
7695 7695

  
7696
    # build ssh cmdline
7697
    return self.ssh.BuildCmd(node, "root", console_cmd, batch=True, tty=True)
7696
    console = objects.InstanceConsole(instance=instance.name,
7697
                                      kind=constants.CONS_SSH,
7698
                                      host=node,
7699
                                      user="root",
7700
                                      command=console_cmd)
7701

  
7702
    assert console.Validate()
7703

  
7704
    return console.ToDict()
7698 7705

  
7699 7706

  
7700 7707
class LUReplaceDisks(LogicalUnit):
b/lib/constants.py
223 223
SOCAT_USE_COMPRESS = _autoconf.SOCAT_USE_COMPRESS
224 224
SOCAT_ESCAPE_CODE = "0x1d"
225 225

  
226
#: Console as SSH command
227
CONS_SSH = "ssh"
228

  
229
#: Console as VNC server
230
CONS_VNC = "vnc"
231

  
232
#: Display a message for console access
233
CONS_MESSAGE = "msg"
234

  
235
#: All console types
236
CONS_ALL = frozenset([CONS_SSH, CONS_VNC, CONS_MESSAGE])
237

  
226 238
# For RSA keys more bits are better, but they also make operations more
227 239
# expensive. NIST SP 800-131 recommends a minimum of 2048 bits from the year
228 240
# 2010 on.
b/lib/objects.py
1466 1466
    ]
1467 1467

  
1468 1468

  
1469
class InstanceConsole(ConfigObject):
1470
  """Object describing how to access the console of an instance.
1471

  
1472
  """
1473
  __slots__ = [
1474
    "instance",
1475
    "kind",
1476
    "message",
1477
    "host",
1478
    "port",
1479
    "user",
1480
    "command",
1481
    "display",
1482
    ]
1483

  
1484
  def Validate(self):
1485
    """Validates contents of this object.
1486

  
1487
    """
1488
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1489
    assert self.instance, "Missing instance name"
1490
    assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
1491
    assert self.host or self.kind == constants.CONS_MESSAGE
1492
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1493
                                      constants.CONS_SSH]
1494
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1495
                                      constants.CONS_VNC]
1496
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1497
                                         constants.CONS_VNC]
1498
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1499
                                         constants.CONS_SSH]
1500
    return True
1501

  
1502

  
1469 1503
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1470 1504
  """Simple wrapper over ConfigParse that allows serialization.
1471 1505

  
b/test/ganeti.client.gnt_instance_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for testing ganeti.client.gnt_instance"""
23

  
24
import unittest
25

  
26
from ganeti import constants
27
from ganeti import utils
28
from ganeti import errors
29
from ganeti import objects
30
from ganeti.client import gnt_instance
31

  
32
import testutils
33

  
34

  
35
class TestConsole(unittest.TestCase):
36
  def setUp(self):
37
    self._output = []
38
    self._cmds = []
39
    self._next_cmd_exitcode = 0
40

  
41
  def _Test(self, *args):
42
    return gnt_instance._DoConsole(*args,
43
                                   feedback_fn=self._Feedback,
44
                                   _runcmd_fn=self._FakeRunCmd)
45

  
46
  def _Feedback(self, msg, *args):
47
    if args:
48
      msg = msg % args
49
    self._output.append(msg)
50

  
51
  def _FakeRunCmd(self, cmd, interactive=None):
52
    self.assertTrue(interactive)
53
    self.assertTrue(isinstance(cmd, list))
54
    self._cmds.append(cmd)
55
    return utils.RunResult(self._next_cmd_exitcode, None, "", "", "cmd",
56
                           utils._TIMEOUT_NONE, 5)
57

  
58
  def testMessage(self):
59
    cons = objects.InstanceConsole(instance="inst98.example.com",
60
                                   kind=constants.CONS_MESSAGE,
61
                                   message="Hello World")
62
    self.assertEqual(self._Test(cons, False, "cluster.example.com"),
63
                     constants.EXIT_SUCCESS)
64
    self.assertEqual(len(self._cmds), 0)
65
    self.assertEqual(self._output, ["Hello World"])
66

  
67
  def testVnc(self):
68
    cons = objects.InstanceConsole(instance="inst1.example.com",
69
                                   kind=constants.CONS_VNC,
70
                                   host="node1.example.com",
71
                                   port=5901,
72
                                   display=1)
73
    self.assertEqual(self._Test(cons, False, "cluster.example.com"),
74
                     constants.EXIT_SUCCESS)
75
    self.assertEqual(len(self._cmds), 0)
76
    self.assertEqual(len(self._output), 1)
77
    self.assertTrue(" inst1.example.com " in self._output[0])
78
    self.assertTrue(" node1.example.com:5901 " in self._output[0])
79
    self.assertTrue("vnc://node1.example.com:5901/" in self._output[0])
80

  
81
  def testSshShow(self):
82
    cons = objects.InstanceConsole(instance="inst31.example.com",
83
                                   kind=constants.CONS_SSH,
84
                                   host="node93.example.com",
85
                                   user="user_abc",
86
                                   command="xm console x.y.z")
87
    self.assertEqual(self._Test(cons, True, "cluster.example.com"),
88
                     constants.EXIT_SUCCESS)
89
    self.assertEqual(len(self._cmds), 0)
90
    self.assertEqual(len(self._output), 1)
91
    self.assertTrue(" user_abc@node93.example.com " in self._output[0])
92
    self.assertTrue("'xm console x.y.z'" in self._output[0])
93

  
94
  def testSshRun(self):
95
    cons = objects.InstanceConsole(instance="inst31.example.com",
96
                                   kind=constants.CONS_SSH,
97
                                   host="node93.example.com",
98
                                   user="user_abc",
99
                                   command=["xm", "console", "x.y.z"])
100
    self.assertEqual(self._Test(cons, False, "cluster.example.com"),
101
                     constants.EXIT_SUCCESS)
102
    self.assertEqual(len(self._cmds), 1)
103
    self.assertEqual(len(self._output), 0)
104

  
105
    # This is very important to prevent escapes from the console
106
    self.assertTrue("-oEscapeChar=none" in self._cmds[0])
107

  
108
  def testSshRunFail(self):
109
    cons = objects.InstanceConsole(instance="inst31.example.com",
110
                                   kind=constants.CONS_SSH,
111
                                   host="node93.example.com",
112
                                   user="user_abc",
113
                                   command=["xm", "console", "x.y.z"])
114

  
115
    self._next_cmd_exitcode = 100
116
    self.assertRaises(errors.OpExecError, self._Test,
117
                      cons, False, "cluster.example.com")
118
    self.assertEqual(len(self._cmds), 1)
119
    self.assertEqual(len(self._output), 0)
120

  
121

  
122
if __name__ == "__main__":
123
  testutils.GanetiTestProgram()

Also available in: Unified diff