Commit b82d4c5e authored by Michael Hanselmann's avatar Michael Hanselmann

Add RAPI resource for instance console

Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent 5d28cb6f
......@@ -984,6 +984,37 @@ Body parameters:
Whether to force an unknown variant.
``/2/instances/[instance_name]/console``
++++++++++++++++++++++++++++++++++++++++
Request information for connecting to instance's console.
Supports the following commands: ``GET``.
``GET``
~~~~~~~
Returns a dictionary containing information about the instance's
console. Contained keys:
``instance``
Instance name.
``kind``
Console type, one of ``ssh``, ``vnc`` or ``msg``.
``message``
Message to display (``msg`` type only).
``host``
Host to connect to (``ssh`` and ``vnc`` only).
``port``
TCP port to connect to (``vnc`` only).
``user``
Username to use (``ssh`` only).
``command``
Command to execute on machine (``ssh`` only)
``display``
VNC display number (``vnc`` only).
``/2/instances/[instance_name]/tags``
+++++++++++++++++++++++++++++++++++++
......
......@@ -1124,6 +1124,17 @@ class GanetiRapiClient(object): # pylint: disable-msg=R0904
("/%s/instances/%s/rename" %
(GANETI_RAPI_VERSION, instance)), None, body)
def GetInstanceConsole(self, instance):
"""Request information for connecting to instance's console.
@type instance: string
@param instance: Instance name
"""
return self._SendRequest(HTTP_GET,
("/%s/instances/%s/console" %
(GANETI_RAPI_VERSION, instance)), None, None)
def GetJobs(self):
"""Gets all jobs for the cluster.
......
......@@ -217,6 +217,8 @@ def GetHandlers(node_name_pattern, instance_name_pattern,
re.compile(r"^/2/instances/(%s)/disk/(%s)/grow$" %
(instance_name_pattern, disk_pattern)):
rlib2.R_2_instances_name_disk_grow,
re.compile(r'^/2/instances/(%s)/console$' % instance_name_pattern):
rlib2.R_2_instances_name_console,
"/2/groups": rlib2.R_2_groups,
re.compile(r'^/2/groups/(%s)$' % group_name_pattern):
......
......@@ -1336,6 +1336,30 @@ class R_2_instances_name_disk_grow(baserlib.R_Generic):
return baserlib.SubmitJob([op])
class R_2_instances_name_console(baserlib.R_Generic):
"""/2/instances/[instance_name]/console resource.
"""
GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
def GET(self):
"""Request information for connecting to instance's console.
@return: Serialized instance console description, see
L{objects.InstanceConsole}
"""
client = baserlib.GetClient()
((console, ), ) = client.QueryInstances([self.items[0]], ["console"], False)
if console is None:
raise http.HttpServiceUnavailable("Instance console unavailable")
assert isinstance(console, dict)
return console
class _R_Tags(baserlib.R_Generic):
""" Quasiclass for tagging resources
......
......@@ -196,6 +196,8 @@ def RunCommonInstanceTests(instance):
"""
RunTestIf("instance-shutdown", qa_instance.TestInstanceShutdown, instance)
RunTestIf(["instance-shutdown", "instance-console", "rapi"],
qa_rapi.TestRapiStoppedInstanceConsole, instance)
RunTestIf("instance-shutdown", qa_instance.TestInstanceStartup, instance)
RunTestIf("instance-list", qa_instance.TestInstanceList)
......@@ -207,6 +209,8 @@ def RunCommonInstanceTests(instance):
qa_rapi.TestRapiInstanceModify, instance)
RunTestIf("instance-console", qa_instance.TestInstanceConsole, instance)
RunTestIf(["instance-console", "rapi"],
qa_rapi.TestRapiInstanceConsole, instance)
RunTestIf("instance-reinstall", qa_instance.TestInstanceShutdown, instance)
RunTestIf("instance-reinstall", qa_instance.TestInstanceReinstall, instance)
......
......@@ -29,6 +29,7 @@ from ganeti import constants
from ganeti import errors
from ganeti import cli
from ganeti import rapi
from ganeti import objects
import ganeti.rapi.client # pylint: disable-msg=W0611
import ganeti.rapi.client_utils
......@@ -492,6 +493,25 @@ def TestRapiInstanceModify(instance):
})
def TestRapiInstanceConsole(instance):
"""Test getting instance console information via RAPI"""
result = _rapi_client.GetInstanceConsole(instance["name"])
console = objects.InstanceConsole.FromDict(result)
AssertEqual(console.Validate(), True)
AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance["name"]))
def TestRapiStoppedInstanceConsole(instance):
"""Test getting stopped instance's console information via RAPI"""
try:
_rapi_client.GetInstanceConsole(instance["name"])
except rapi.client.GanetiApiError, err:
AssertEqual(err.code, 503)
else:
raise qa_error.Error("Getting console for stopped instance didn't"
" return HTTP 503")
def TestInterClusterInstanceMove(src_instance, dest_instance,
pnode, snode, tnode):
"""Test tools/move-instance"""
......
......@@ -1123,6 +1123,15 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
self.assertHandler(rlib2.R_2_instances_name_deactivate_disks)
self.assertFalse(self.rapi.GetLastHandler().queryargs)
def testGetInstanceConsole(self):
self.rapi.AddResponse("26876")
job_id = self.client.GetInstanceConsole("inst21491")
self.assertEqual(job_id, 26876)
self.assertItems(["inst21491"])
self.assertHandler(rlib2.R_2_instances_name_console)
self.assertFalse(self.rapi.GetLastHandler().queryargs)
self.assertFalse(self.rapi.GetLastRequestData())
def testGrowInstanceDisk(self):
for idx, wait_for_sync in enumerate([None, False, True]):
amount = 128 + (512 * idx)
......
......@@ -31,12 +31,21 @@ from ganeti import constants
from ganeti import opcodes
from ganeti import compat
from ganeti import http
from ganeti import query
from ganeti.rapi import rlib2
import testutils
class TestConstants(unittest.TestCase):
def testConsole(self):
# Exporting the console field without authentication might expose
# information
assert "console" in query.INSTANCE_FIELDS
self.assertTrue("console" not in rlib2.I_FIELDS)
class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
def setUp(self):
testutils.GanetiTestCase.setUp(self)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment