Commit b9e12624 authored by Hrvoje Ribicic's avatar Hrvoje Ribicic
Browse files

Add a console information RPC call



As the instance queries need console information, and the information
is retrieved through python classes that should not be ported yet, an
RPC call supplying the information has been added. Some tests as well.
Signed-off-by: default avatarHrvoje Ribicic <riba@google.com>
Reviewed-by: default avatarJose A. Lopes <jabolopes@google.com>
parent 1d3d454f
......@@ -1440,7 +1440,6 @@ def GetAllInstancesInfo(hypervisor_list, all_hvparams):
"""
output = {}
for hname in hypervisor_list:
hvparams = all_hvparams[hname]
iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
......@@ -1465,6 +1464,57 @@ def GetAllInstancesInfo(hypervisor_list, all_hvparams):
return output
def GetInstanceConsoleInfo(instance_param_dict,
get_hv_fn=hypervisor.GetHypervisor):
"""Gather data about the console access of a set of instances of this node.
This function assumes that the caller already knows which instances are on
this node, by calling a function such as L{GetAllInstancesInfo} or
L{GetInstanceList}.
For every instance, a large amount of configuration data needs to be
provided to the hypervisor interface in order to receive the console
information. Whether this could or should be cut down can be discussed.
The information is provided in a dictionary indexed by instance name,
allowing any number of instance queries to be done.
@type instance_param_dict: dict of string to tuple of dictionaries, where the
dictionaries represent: L{objects.Instance}, L{objects.Node}, HvParams,
BeParams
@param instance_param_dict: mapping of instance name to parameters necessary
for console information retrieval
@rtype: dict
@return: dictionary of instance: data, with data having the following keys:
- instance: instance name
- kind: console kind
- message: used with kind == CONS_MESSAGE, indicates console to be
unavailable, supplies error message
- host: host to connect to
- port: port to use
- user: user for login
- command: the command, broken into parts as an array
- display: unknown, potentially unused?
"""
output = {}
for inst_name in instance_param_dict:
instance = instance_param_dict[inst_name]["instance"]
pnode = instance_param_dict[inst_name]["node"]
hvparams = instance_param_dict[inst_name]["hvParams"]
beparams = instance_param_dict[inst_name]["beParams"]
instance = objects.Instance.FromDict(instance)
pnode = objects.Node.FromDict(pnode)
h = get_hv_fn(instance.hypervisor)
output[inst_name] = h.GetInstanceConsole(instance, pnode, hvparams,
beparams).ToDict()
return output
def _InstanceLogName(kind, os_name, instance, component):
"""Compute the OS log filename for a given instance and operation.
......
......@@ -166,6 +166,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
"""Handle a request.
"""
if req.request_method.upper() != http.HTTP_POST:
raise http.HttpBadRequest("Only the POST method is supported")
......@@ -713,6 +714,13 @@ class NodeRequestHandler(http.server.HttpServerHandler):
(hypervisor_list, all_hvparams) = params
return backend.GetAllInstancesInfo(hypervisor_list, all_hvparams)
@staticmethod
def perspective_instance_console_info(params):
"""Query information on how to get console access to instances
"""
return backend.GetInstanceConsoleInfo(params)
@staticmethod
def perspective_instance_list(params):
"""Query the list of running instances.
......
......@@ -49,6 +49,11 @@ module Ganeti.Rpc
, RpcCallAllInstancesInfo(..)
, RpcResultAllInstancesInfo(..)
, InstanceConsoleInfoParams(..)
, InstanceConsoleInfo(..)
, RpcCallInstanceConsoleInfo(..)
, RpcResultInstanceConsoleInfo(..)
, RpcCallInstanceList(..)
, RpcResultInstanceList(..)
......@@ -246,9 +251,7 @@ fromJSValueToRes val = fromJResultToRes (J.readJSON val)
-- ** Instance info
-- | InstanceInfo
-- Returns information about a single instance.
-- | Returns information about a single instance
$(buildObject "RpcCallInstanceInfo" "rpcCallInstInfo"
[ simpleField "instance" [t| String |]
, simpleField "hname" [t| Hypervisor |]
......@@ -287,8 +290,7 @@ instance Rpc RpcCallInstanceInfo RpcResultInstanceInfo where
-- ** AllInstancesInfo
-- | AllInstancesInfo
-- Returns information about all running instances on the given nodes
-- | Returns information about all running instances on the given nodes
$(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo"
[ simpleField "hypervisors" [t| [(Hypervisor, HvParams)] |] ])
......@@ -316,10 +318,61 @@ instance Rpc RpcCallAllInstancesInfo RpcResultAllInstancesInfo where
_ -> Left $ JsonDecodeError
("Expected JSObject, got " ++ show (pp_value res))
-- ** InstanceConsoleInfo
-- | Returns information about how to access instances on the given node
$(buildObject "InstanceConsoleInfoParams" "instConsInfoParams"
[ simpleField "instance" [t| Instance |]
, simpleField "node" [t| Node |]
, simpleField "hvParams" [t| HvParams |]
, simpleField "beParams" [t| FilledBeParams |]
])
$(buildObject "RpcCallInstanceConsoleInfo" "rpcCallInstConsInfo"
[ simpleField "instanceInfo" [t| [(String, InstanceConsoleInfoParams)] |] ])
$(buildObject "InstanceConsoleInfo" "instConsInfo"
[ simpleField "instance" [t| String |]
, simpleField "kind" [t| String |]
, optionalField $
simpleField "message" [t| String |]
, optionalField $
simpleField "host" [t| String |]
, optionalField $
simpleField "port" [t| Int |]
, optionalField $
simpleField "user" [t| String |]
, optionalField $
simpleField "command" [t| [String] |]
, optionalField $
simpleField "display" [t| String |]
])
$(buildObject "RpcResultInstanceConsoleInfo" "rpcResInstConsInfo"
[ simpleField "instancesInfo" [t| [(String, InstanceConsoleInfo)] |] ])
instance RpcCall RpcCallInstanceConsoleInfo where
rpcCallName _ = "instance_console_info"
rpcCallTimeout _ = rpcTimeoutToRaw Urgent
rpcCallAcceptOffline _ = False
rpcCallData _ call = J.encode .
GenericContainer $ Map.fromList (rpcCallInstConsInfoInstanceInfo call)
instance Rpc RpcCallInstanceConsoleInfo RpcResultInstanceConsoleInfo where
rpcResultFill _ res =
case res of
J.JSObject res' ->
let res'' = map (second J.readJSON) (J.fromJSObject res')
:: [(String, J.Result InstanceConsoleInfo)] in
case sanitizeDictResults res'' of
Left err -> Left err
Right instInfos -> Right $ RpcResultInstanceConsoleInfo instInfos
_ -> Left $ JsonDecodeError
("Expected JSObject, got " ++ show (pp_value res))
-- ** InstanceList
-- | InstanceList
-- Returns the list of running instances on the given nodes.
-- | Returns the list of running instances on the given nodes
$(buildObject "RpcCallInstanceList" "rpcCallInstList"
[ simpleField "hypervisors" [t| [Hypervisor] |] ])
......@@ -337,8 +390,7 @@ instance Rpc RpcCallInstanceList RpcResultInstanceList where
-- ** NodeInfo
-- | NodeInfo
-- Return node information.
-- | Returns node information
$(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo"
[ simpleField "storage_units" [t| Map.Map String [StorageUnit] |]
, simpleField "hypervisors" [t| [ (Hypervisor, HvParams) ] |]
......
......@@ -80,14 +80,15 @@ instance StandardOptions Options where
requestComp o = o { optShowComp = True }
-- | The rpcs we support. Sadly this duplicates the RPC list.
data KnownRpc = KRInstanceInfo RpcCallInstanceInfo
| KRAllInstancesInfo RpcCallAllInstancesInfo
| KRInstanceList RpcCallInstanceList
| KRNodeInfo RpcCallNodeInfo
| KRVersion RpcCallVersion
| KRStorageList RpcCallStorageList
| KRTestDelay RpcCallTestDelay
| KRExportList RpcCallExportList
data KnownRpc = KRInstanceInfo RpcCallInstanceInfo
| KRAllInstancesInfo RpcCallAllInstancesInfo
| KRInstanceConsoleInfo RpcCallInstanceConsoleInfo
| KRInstanceList RpcCallInstanceList
| KRNodeInfo RpcCallNodeInfo
| KRVersion RpcCallVersion
| KRStorageList RpcCallStorageList
| KRTestDelay RpcCallTestDelay
| KRExportList RpcCallExportList
deriving (Show)
-- | The command line options.
......@@ -144,6 +145,8 @@ parseRpc "instance_info" f =
fromJResult "parsing rpc" (decode f) >>= Ok . KRInstanceInfo
parseRpc "all_instances_info" f =
fromJResult "parsing rpc" (decode f) >>= Ok . KRAllInstancesInfo
parseRpc "console_instance_info" f =
fromJResult "parsing rpc" (decode f) >>= Ok . KRConsoleInstanceInfo
parseRpc "instance_list" f =
fromJResult "parsing rpc" (decode f) >>= Ok . KRInstanceList
parseRpc "node_info" f =
......@@ -162,14 +165,15 @@ parseRpc s _ = Bad $ "Unknown rpc '" ++ s ++ "'"
-- polymorphism of 'executeRpcCall', and the binding of the result
-- based on the input rpc call.
execRpc :: [Node] -> KnownRpc -> IO [[String]]
execRpc n (KRInstanceInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRAllInstancesInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRInstanceList v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRNodeInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRVersion v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRStorageList v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRTestDelay v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRExportList v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRInstanceInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRAllInstancesInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRConsoleInstanceInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRInstanceList v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRNodeInfo v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRVersion v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRStorageList v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRTestDelay v) = formatRpcRes `fmap` executeRpcCall n v
execRpc n (KRExportList v) = formatRpcRes `fmap` executeRpcCall n v
-- | Helper to format the RPC result such that it can be printed by
-- 'printTable'.
......
......@@ -36,7 +36,7 @@ import qualified Data.Map as Map
import Test.Ganeti.TestHelper
import Test.Ganeti.TestCommon
import Test.Ganeti.Objects ()
import Test.Ganeti.Objects (genInst)
import qualified Ganeti.Rpc as Rpc
import qualified Ganeti.Objects as Objects
......@@ -44,6 +44,9 @@ import qualified Ganeti.Types as Types
import qualified Ganeti.JSON as JSON
import Ganeti.Types
instance Arbitrary Rpc.RpcCallInstanceConsoleInfo where
arbitrary = Rpc.RpcCallInstanceConsoleInfo <$> genConsoleInfoCallParams
genStorageUnit :: Gen StorageUnit
genStorageUnit = do
storage_type <- arbitrary
......@@ -86,30 +89,44 @@ instance Arbitrary Rpc.RpcCallInstanceList where
instance Arbitrary Rpc.RpcCallNodeInfo where
arbitrary = Rpc.RpcCallNodeInfo <$> genStorageUnitMap <*> genHvSpecs
-- | Monadic check that, for an offline node and a call that does not
-- | Generates per-instance console info params for the 'InstanceConsoleInfo'
-- call.
genConsoleInfoCallParams :: Gen [(String, Rpc.InstanceConsoleInfoParams)]
genConsoleInfoCallParams = do
numInstances <- choose (0, 3)
names <- vectorOf numInstances arbitrary
params <- vectorOf numInstances genInstanceConsoleInfoParams
return $ zip names params
-- | Generates parameters for the console info call, consisting of an instance
-- object, node object, 'HvParams', and 'FilledBeParams'.
genInstanceConsoleInfoParams :: Gen Rpc.InstanceConsoleInfoParams
genInstanceConsoleInfoParams = Rpc.InstanceConsoleInfoParams <$>
genInst <*> arbitrary <*> genHvParams <*> arbitrary
-- | Monadic check that, for an offline node and a call that does not support
-- offline nodes, we get a OfflineNodeError response.
-- FIXME: We need a way of generalizing this, running it for
-- every call manually will soon get problematic
prop_noffl_request_allinstinfo :: Rpc.RpcCallAllInstancesInfo -> Property
prop_noffl_request_allinstinfo call =
runOfflineTest :: (Rpc.Rpc a b, Eq b, Show b) => a -> Property
runOfflineTest call =
forAll (arbitrary `suchThat` Objects.nodeOffline) $ \node -> monadicIO $ do
res <- run $ Rpc.executeRpcCall [node] call
stop $ res ==? [(node, Left Rpc.OfflineNodeError)]
prop_noffl_request_allinstinfo :: Rpc.RpcCallAllInstancesInfo -> Property
prop_noffl_request_allinstinfo = runOfflineTest
prop_noffl_request_instconsinfo :: Rpc.RpcCallInstanceConsoleInfo -> Property
prop_noffl_request_instconsinfo = runOfflineTest
prop_noffl_request_instlist :: Rpc.RpcCallInstanceList -> Property
prop_noffl_request_instlist call =
forAll (arbitrary `suchThat` Objects.nodeOffline) $ \node -> monadicIO $ do
res <- run $ Rpc.executeRpcCall [node] call
stop $ res ==? [(node, Left Rpc.OfflineNodeError)]
prop_noffl_request_instlist = runOfflineTest
prop_noffl_request_nodeinfo :: Rpc.RpcCallNodeInfo -> Property
prop_noffl_request_nodeinfo call =
forAll (arbitrary `suchThat` Objects.nodeOffline) $ \node -> monadicIO $ do
res <- run $ Rpc.executeRpcCall [node] call
stop $ res ==? [(node, Left Rpc.OfflineNodeError)]
prop_noffl_request_nodeinfo = runOfflineTest
testSuite "Rpc"
[ 'prop_noffl_request_allinstinfo
, 'prop_noffl_request_instconsinfo
, 'prop_noffl_request_instlist
, 'prop_noffl_request_nodeinfo
]
......@@ -33,6 +33,7 @@ from ganeti import constants
from ganeti import errors
from ganeti import hypervisor
from ganeti import netutils
from ganeti import objects
from ganeti import utils
......@@ -590,6 +591,47 @@ class TestGetInstanceList(unittest.TestCase):
self._test_hv.ListInstances.assert_called_with(hvparams=fake_hvparams)
class TestInstanceConsoleInfo(unittest.TestCase):
def setUp(self):
self._test_hv_a = self._TestHypervisor()
self._test_hv_a.GetInstanceConsole = mock.Mock(
return_value = objects.InstanceConsole(instance="inst", kind="aHy")
)
self._test_hv_b = self._TestHypervisor()
self._test_hv_b.GetInstanceConsole = mock.Mock(
return_value = objects.InstanceConsole(instance="inst", kind="bHy")
)
class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
def __init__(self):
hypervisor.hv_base.BaseHypervisor.__init__(self)
def _GetHypervisor(self, name):
if name == "a":
return self._test_hv_a
else:
return self._test_hv_b
def testRightHypervisor(self):
dictMaker = lambda hyName: {
"instance":{"hypervisor":hyName},
"node":{},
"hvParams":{},
"beParams":{},
}
call = {
'i1':dictMaker("a"),
'i2':dictMaker("b"),
}
res = backend.GetInstanceConsoleInfo(call, get_hv_fn=self._GetHypervisor)
self.assertTrue(res["i1"]["kind"] == "aHy")
self.assertTrue(res["i2"]["kind"] == "bHy")
class TestGetHvInfo(unittest.TestCase):
def 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