Revision b9e12624

b/lib/backend.py
1440 1440

  
1441 1441
  """
1442 1442
  output = {}
1443

  
1444 1443
  for hname in hypervisor_list:
1445 1444
    hvparams = all_hvparams[hname]
1446 1445
    iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
......
1465 1464
  return output
1466 1465

  
1467 1466

  
1467
def GetInstanceConsoleInfo(instance_param_dict,
1468
                           get_hv_fn=hypervisor.GetHypervisor):
1469
  """Gather data about the console access of a set of instances of this node.
1470

  
1471
  This function assumes that the caller already knows which instances are on
1472
  this node, by calling a function such as L{GetAllInstancesInfo} or
1473
  L{GetInstanceList}.
1474

  
1475
  For every instance, a large amount of configuration data needs to be
1476
  provided to the hypervisor interface in order to receive the console
1477
  information. Whether this could or should be cut down can be discussed.
1478
  The information is provided in a dictionary indexed by instance name,
1479
  allowing any number of instance queries to be done.
1480

  
1481
  @type instance_param_dict: dict of string to tuple of dictionaries, where the
1482
    dictionaries represent: L{objects.Instance}, L{objects.Node}, HvParams,
1483
    BeParams
1484
  @param instance_param_dict: mapping of instance name to parameters necessary
1485
    for console information retrieval
1486

  
1487
  @rtype: dict
1488
  @return: dictionary of instance: data, with data having the following keys:
1489
      - instance: instance name
1490
      - kind: console kind
1491
      - message: used with kind == CONS_MESSAGE, indicates console to be
1492
                 unavailable, supplies error message
1493
      - host: host to connect to
1494
      - port: port to use
1495
      - user: user for login
1496
      - command: the command, broken into parts as an array
1497
      - display: unknown, potentially unused?
1498

  
1499
  """
1500

  
1501
  output = {}
1502
  for inst_name in instance_param_dict:
1503
    instance = instance_param_dict[inst_name]["instance"]
1504
    pnode = instance_param_dict[inst_name]["node"]
1505
    hvparams = instance_param_dict[inst_name]["hvParams"]
1506
    beparams = instance_param_dict[inst_name]["beParams"]
1507

  
1508
    instance = objects.Instance.FromDict(instance)
1509
    pnode = objects.Node.FromDict(pnode)
1510

  
1511
    h = get_hv_fn(instance.hypervisor)
1512
    output[inst_name] = h.GetInstanceConsole(instance, pnode, hvparams,
1513
                                             beparams).ToDict()
1514

  
1515
  return output
1516

  
1517

  
1468 1518
def _InstanceLogName(kind, os_name, instance, component):
1469 1519
  """Compute the OS log filename for a given instance and operation.
1470 1520

  
b/lib/server/noded.py
166 166
    """Handle a request.
167 167

  
168 168
    """
169

  
169 170
    if req.request_method.upper() != http.HTTP_POST:
170 171
      raise http.HttpBadRequest("Only the POST method is supported")
171 172

  
......
714 715
    return backend.GetAllInstancesInfo(hypervisor_list, all_hvparams)
715 716

  
716 717
  @staticmethod
718
  def perspective_instance_console_info(params):
719
    """Query information on how to get console access to instances
720

  
721
    """
722
    return backend.GetInstanceConsoleInfo(params)
723

  
724
  @staticmethod
717 725
  def perspective_instance_list(params):
718 726
    """Query the list of running instances.
719 727

  
b/src/Ganeti/Rpc.hs
49 49
  , RpcCallAllInstancesInfo(..)
50 50
  , RpcResultAllInstancesInfo(..)
51 51

  
52
  , InstanceConsoleInfoParams(..)
53
  , InstanceConsoleInfo(..)
54
  , RpcCallInstanceConsoleInfo(..)
55
  , RpcResultInstanceConsoleInfo(..)
56

  
52 57
  , RpcCallInstanceList(..)
53 58
  , RpcResultInstanceList(..)
54 59

  
......
246 251

  
247 252
-- ** Instance info
248 253

  
249
-- | InstanceInfo
250
--   Returns information about a single instance.
251

  
254
-- | Returns information about a single instance
252 255
$(buildObject "RpcCallInstanceInfo" "rpcCallInstInfo"
253 256
  [ simpleField "instance" [t| String |]
254 257
  , simpleField "hname" [t| Hypervisor |]
......
287 290

  
288 291
-- ** AllInstancesInfo
289 292

  
290
-- | AllInstancesInfo
291
--   Returns information about all running instances on the given nodes
293
-- | Returns information about all running instances on the given nodes
292 294
$(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo"
293 295
  [ simpleField "hypervisors" [t| [(Hypervisor, HvParams)] |] ])
294 296

  
......
316 318
      _ -> Left $ JsonDecodeError
317 319
           ("Expected JSObject, got " ++ show (pp_value res))
318 320

  
321
-- ** InstanceConsoleInfo
322

  
323
-- | Returns information about how to access instances on the given node
324
$(buildObject "InstanceConsoleInfoParams" "instConsInfoParams"
325
  [ simpleField "instance"    [t| Instance |]
326
  , simpleField "node"        [t| Node |]
327
  , simpleField "hvParams"    [t| HvParams |]
328
  , simpleField "beParams"    [t| FilledBeParams |]
329
  ])
330

  
331
$(buildObject "RpcCallInstanceConsoleInfo" "rpcCallInstConsInfo"
332
  [ simpleField "instanceInfo" [t| [(String, InstanceConsoleInfoParams)] |] ])
333

  
334
$(buildObject "InstanceConsoleInfo" "instConsInfo"
335
  [ simpleField "instance"    [t| String |]
336
  , simpleField "kind"        [t| String |]
337
  , optionalField $
338
    simpleField "message"     [t| String |]
339
  , optionalField $
340
    simpleField "host"        [t| String |]
341
  , optionalField $
342
    simpleField "port"        [t| Int |]
343
  , optionalField $
344
    simpleField "user"        [t| String |]
345
  , optionalField $
346
    simpleField "command"     [t| [String] |]
347
  , optionalField $
348
    simpleField "display"     [t| String |]
349
  ])
350

  
351
$(buildObject "RpcResultInstanceConsoleInfo" "rpcResInstConsInfo"
352
  [ simpleField "instancesInfo" [t| [(String, InstanceConsoleInfo)] |] ])
353

  
354
instance RpcCall RpcCallInstanceConsoleInfo where
355
  rpcCallName _          = "instance_console_info"
356
  rpcCallTimeout _       = rpcTimeoutToRaw Urgent
357
  rpcCallAcceptOffline _ = False
358
  rpcCallData _ call     = J.encode .
359
    GenericContainer $ Map.fromList (rpcCallInstConsInfoInstanceInfo call)
360

  
361
instance Rpc RpcCallInstanceConsoleInfo RpcResultInstanceConsoleInfo where
362
  rpcResultFill _ res =
363
    case res of
364
      J.JSObject res' ->
365
        let res'' = map (second J.readJSON) (J.fromJSObject res')
366
                        :: [(String, J.Result InstanceConsoleInfo)] in
367
        case sanitizeDictResults res'' of
368
          Left err -> Left err
369
          Right instInfos -> Right $ RpcResultInstanceConsoleInfo instInfos
370
      _ -> Left $ JsonDecodeError
371
           ("Expected JSObject, got " ++ show (pp_value res))
372

  
319 373
-- ** InstanceList
320 374

  
321
-- | InstanceList
322
-- Returns the list of running instances on the given nodes.
375
-- | Returns the list of running instances on the given nodes
323 376
$(buildObject "RpcCallInstanceList" "rpcCallInstList"
324 377
  [ simpleField "hypervisors" [t| [Hypervisor] |] ])
325 378

  
......
337 390

  
338 391
-- ** NodeInfo
339 392

  
340
-- | NodeInfo
341
-- Return node information.
393
-- | Returns node information
342 394
$(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo"
343 395
  [ simpleField "storage_units" [t| Map.Map String [StorageUnit] |]
344 396
  , simpleField "hypervisors" [t| [ (Hypervisor, HvParams) ] |]
b/src/rpc-test.hs
80 80
  requestComp o = o { optShowComp = True }
81 81

  
82 82
-- | The rpcs we support. Sadly this duplicates the RPC list.
83
data KnownRpc = KRInstanceInfo      RpcCallInstanceInfo
84
              | KRAllInstancesInfo  RpcCallAllInstancesInfo
85
              | KRInstanceList      RpcCallInstanceList
86
              | KRNodeInfo          RpcCallNodeInfo
87
              | KRVersion           RpcCallVersion
88
              | KRStorageList       RpcCallStorageList
89
              | KRTestDelay         RpcCallTestDelay
90
              | KRExportList        RpcCallExportList
83
data KnownRpc = KRInstanceInfo        RpcCallInstanceInfo
84
              | KRAllInstancesInfo    RpcCallAllInstancesInfo
85
              | KRInstanceConsoleInfo RpcCallInstanceConsoleInfo
86
              | KRInstanceList        RpcCallInstanceList
87
              | KRNodeInfo            RpcCallNodeInfo
88
              | KRVersion             RpcCallVersion
89
              | KRStorageList         RpcCallStorageList
90
              | KRTestDelay           RpcCallTestDelay
91
              | KRExportList          RpcCallExportList
91 92
                deriving (Show)
92 93

  
93 94
-- | The command line options.
......
144 145
  fromJResult "parsing rpc" (decode f) >>= Ok . KRInstanceInfo
145 146
parseRpc "all_instances_info" f =
146 147
  fromJResult "parsing rpc" (decode f) >>= Ok . KRAllInstancesInfo
148
parseRpc "console_instance_info" f =
149
  fromJResult "parsing rpc" (decode f) >>= Ok . KRConsoleInstanceInfo
147 150
parseRpc "instance_list"      f =
148 151
  fromJResult "parsing rpc" (decode f) >>= Ok . KRInstanceList
149 152
parseRpc "node_info"          f =
......
162 165
-- polymorphism of 'executeRpcCall', and the binding of the result
163 166
-- based on the input rpc call.
164 167
execRpc :: [Node] -> KnownRpc -> IO [[String]]
165
execRpc n (KRInstanceInfo      v) = formatRpcRes `fmap` executeRpcCall n v
166
execRpc n (KRAllInstancesInfo  v) = formatRpcRes `fmap` executeRpcCall n v
167
execRpc n (KRInstanceList      v) = formatRpcRes `fmap` executeRpcCall n v
168
execRpc n (KRNodeInfo          v) = formatRpcRes `fmap` executeRpcCall n v
169
execRpc n (KRVersion           v) = formatRpcRes `fmap` executeRpcCall n v
170
execRpc n (KRStorageList       v) = formatRpcRes `fmap` executeRpcCall n v
171
execRpc n (KRTestDelay         v) = formatRpcRes `fmap` executeRpcCall n v
172
execRpc n (KRExportList        v) = formatRpcRes `fmap` executeRpcCall n v
168
execRpc n (KRInstanceInfo        v) = formatRpcRes `fmap` executeRpcCall n v
169
execRpc n (KRAllInstancesInfo    v) = formatRpcRes `fmap` executeRpcCall n v
170
execRpc n (KRConsoleInstanceInfo v) = formatRpcRes `fmap` executeRpcCall n v
171
execRpc n (KRInstanceList        v) = formatRpcRes `fmap` executeRpcCall n v
172
execRpc n (KRNodeInfo            v) = formatRpcRes `fmap` executeRpcCall n v
173
execRpc n (KRVersion             v) = formatRpcRes `fmap` executeRpcCall n v
174
execRpc n (KRStorageList         v) = formatRpcRes `fmap` executeRpcCall n v
175
execRpc n (KRTestDelay           v) = formatRpcRes `fmap` executeRpcCall n v
176
execRpc n (KRExportList          v) = formatRpcRes `fmap` executeRpcCall n v
173 177

  
174 178
-- | Helper to format the RPC result such that it can be printed by
175 179
-- 'printTable'.
b/test/hs/Test/Ganeti/Rpc.hs
36 36

  
37 37
import Test.Ganeti.TestHelper
38 38
import Test.Ganeti.TestCommon
39
import Test.Ganeti.Objects ()
39
import Test.Ganeti.Objects (genInst)
40 40

  
41 41
import qualified Ganeti.Rpc as Rpc
42 42
import qualified Ganeti.Objects as Objects
......
44 44
import qualified Ganeti.JSON as JSON
45 45
import Ganeti.Types
46 46

  
47
instance Arbitrary Rpc.RpcCallInstanceConsoleInfo where
48
  arbitrary = Rpc.RpcCallInstanceConsoleInfo <$> genConsoleInfoCallParams
49

  
47 50
genStorageUnit :: Gen StorageUnit
48 51
genStorageUnit = do
49 52
  storage_type <- arbitrary
......
86 89
instance Arbitrary Rpc.RpcCallNodeInfo where
87 90
  arbitrary = Rpc.RpcCallNodeInfo <$> genStorageUnitMap <*> genHvSpecs
88 91

  
89
-- | Monadic check that, for an offline node and a call that does not
92
-- | Generates per-instance console info params for the 'InstanceConsoleInfo'
93
-- call.
94
genConsoleInfoCallParams :: Gen [(String, Rpc.InstanceConsoleInfoParams)]
95
genConsoleInfoCallParams = do
96
  numInstances <- choose (0, 3)
97
  names <- vectorOf numInstances arbitrary
98
  params <- vectorOf numInstances genInstanceConsoleInfoParams
99
  return $ zip names params
100

  
101
-- | Generates parameters for the console info call, consisting of an instance
102
-- object, node object, 'HvParams', and 'FilledBeParams'.
103
genInstanceConsoleInfoParams :: Gen Rpc.InstanceConsoleInfoParams
104
genInstanceConsoleInfoParams = Rpc.InstanceConsoleInfoParams <$>
105
  genInst <*> arbitrary <*> genHvParams <*> arbitrary
106

  
107
-- | Monadic check that, for an offline node and a call that does not support
90 108
-- offline nodes, we get a OfflineNodeError response.
91
-- FIXME: We need a way of generalizing this, running it for
92
-- every call manually will soon get problematic
93
prop_noffl_request_allinstinfo :: Rpc.RpcCallAllInstancesInfo -> Property
94
prop_noffl_request_allinstinfo call =
109
runOfflineTest :: (Rpc.Rpc a b, Eq b, Show b) => a -> Property
110
runOfflineTest call =
95 111
  forAll (arbitrary `suchThat` Objects.nodeOffline) $ \node -> monadicIO $ do
96 112
      res <- run $ Rpc.executeRpcCall [node] call
97 113
      stop $ res ==? [(node, Left Rpc.OfflineNodeError)]
98 114

  
115
prop_noffl_request_allinstinfo :: Rpc.RpcCallAllInstancesInfo -> Property
116
prop_noffl_request_allinstinfo = runOfflineTest
117

  
118
prop_noffl_request_instconsinfo :: Rpc.RpcCallInstanceConsoleInfo -> Property
119
prop_noffl_request_instconsinfo = runOfflineTest
120

  
99 121
prop_noffl_request_instlist :: Rpc.RpcCallInstanceList -> Property
100
prop_noffl_request_instlist call =
101
  forAll (arbitrary `suchThat` Objects.nodeOffline) $ \node -> monadicIO $ do
102
      res <- run $ Rpc.executeRpcCall [node] call
103
      stop $ res ==? [(node, Left Rpc.OfflineNodeError)]
122
prop_noffl_request_instlist = runOfflineTest
104 123

  
105 124
prop_noffl_request_nodeinfo :: Rpc.RpcCallNodeInfo -> Property
106
prop_noffl_request_nodeinfo call =
107
  forAll (arbitrary `suchThat` Objects.nodeOffline) $ \node -> monadicIO $ do
108
      res <- run $ Rpc.executeRpcCall [node] call
109
      stop $ res ==? [(node, Left Rpc.OfflineNodeError)]
125
prop_noffl_request_nodeinfo = runOfflineTest
110 126

  
111 127
testSuite "Rpc"
112 128
  [ 'prop_noffl_request_allinstinfo
129
  , 'prop_noffl_request_instconsinfo
113 130
  , 'prop_noffl_request_instlist
114 131
  , 'prop_noffl_request_nodeinfo
115 132
  ]
b/test/py/ganeti.backend_unittest.py
33 33
from ganeti import errors
34 34
from ganeti import hypervisor
35 35
from ganeti import netutils
36
from ganeti import objects
36 37
from ganeti import utils
37 38

  
38 39

  
......
590 591
    self._test_hv.ListInstances.assert_called_with(hvparams=fake_hvparams)
591 592

  
592 593

  
594
class TestInstanceConsoleInfo(unittest.TestCase):
595

  
596
  def setUp(self):
597
    self._test_hv_a = self._TestHypervisor()
598
    self._test_hv_a.GetInstanceConsole = mock.Mock(
599
      return_value = objects.InstanceConsole(instance="inst", kind="aHy")
600
    )
601
    self._test_hv_b = self._TestHypervisor()
602
    self._test_hv_b.GetInstanceConsole = mock.Mock(
603
      return_value = objects.InstanceConsole(instance="inst", kind="bHy")
604
    )
605

  
606
  class _TestHypervisor(hypervisor.hv_base.BaseHypervisor):
607
    def __init__(self):
608
      hypervisor.hv_base.BaseHypervisor.__init__(self)
609

  
610
  def _GetHypervisor(self, name):
611
    if name == "a":
612
      return self._test_hv_a
613
    else:
614
      return self._test_hv_b
615

  
616
  def testRightHypervisor(self):
617
    dictMaker = lambda hyName: {
618
      "instance":{"hypervisor":hyName},
619
      "node":{},
620
      "hvParams":{},
621
      "beParams":{},
622
    }
623

  
624
    call = {
625
      'i1':dictMaker("a"),
626
      'i2':dictMaker("b"),
627
    }
628

  
629
    res = backend.GetInstanceConsoleInfo(call, get_hv_fn=self._GetHypervisor)
630

  
631
    self.assertTrue(res["i1"]["kind"] == "aHy")
632
    self.assertTrue(res["i2"]["kind"] == "bHy")
633

  
634

  
593 635
class TestGetHvInfo(unittest.TestCase):
594 636

  
595 637
  def setUp(self):

Also available in: Unified diff