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