Revision 949dcb1d

b/lib/cmdlib.py
758 758
  """Runs the post-hook for an opcode on a single node.
759 759

  
760 760
  """
761
  hm = lu.proc.hmclass(lu.rpc.call_hooks_runner, lu)
761
  hm = lu.proc.BuildHooksManager(lu)
762 762
  try:
763 763
    hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
764 764
  except:
b/lib/mcpu.py
171 171
              if op.WITH_LU)
172 172

  
173 173

  
174
def _RpcResultsToHooksResults(rpc_results):
175
  """Function to convert RPC results to the format expected by HooksMaster.
176

  
177
  @type rpc_results: dict(node: L{rpc.RpcResult})
178
  @param rpc_results: RPC results
179
  @rtype: dict(node: (fail_msg, offline, hooks_results))
180
  @return: RPC results unpacked according to the format expected by
181
    L({mcpu.HooksMaster}
182

  
183
  """
184
  return dict((node, (rpc_res.fail_msg, rpc_res.offline, rpc_res.payload))
185
              for (node, rpc_res) in rpc_results.items())
186

  
187

  
174 188
class Processor(object):
175 189
  """Object which runs OpCodes"""
176 190
  DISPATCH_TABLE = _ComputeDispatchTable()
......
242 256
    """
243 257
    write_count = self.context.cfg.write_count
244 258
    lu.CheckPrereq()
245
    hm = HooksMaster(self.rpc.call_hooks_runner, lu)
259

  
260
    hm = self.BuildHooksManager(lu)
246 261
    h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
247 262
    lu.HooksCallBack(constants.HOOKS_PHASE_PRE, h_results,
248 263
                     self.Log, None)
......
267 282

  
268 283
    return result
269 284

  
285
  def BuildHooksManager(self, lu):
286
    return self.hmclass.BuildFromLu(lu.rpc.call_hooks_runner, lu)
287

  
270 288
  def _LockAndExecLU(self, lu, level, calc_timeout, priority):
271 289
    """Execute a Logical Unit, with the needed locks.
272 290

  
......
444 462

  
445 463

  
446 464
class HooksMaster(object):
447
  """Hooks master.
448

  
449
  This class distributes the run commands to the nodes based on the
450
  specific LU class.
465
  def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn,
466
    hooks_results_adapt_fn, build_env_fn, log_fn, htype=None, cluster_name=None,
467
    master_name=None):
468
    """Base class for hooks masters.
469

  
470
    This class invokes the execution of hooks according to the behaviour
471
    specified by its parameters.
472

  
473
    @type opcode: string
474
    @param opcode: opcode of the operation to which the hooks are tied
475
    @type hooks_path: string
476
    @param hooks_path: prefix of the hooks directories
477
    @type nodes: 2-tuple of lists
478
    @param nodes: 2-tuple of lists containing nodes on which pre-hooks must be
479
      run and nodes on which post-hooks must be run
480
    @type hooks_execution_fn: function that accepts the following parameters:
481
      (node_list, hooks_path, phase, environment)
482
    @param hooks_execution_fn: function that will execute the hooks; can be
483
      None, indicating that no conversion is necessary.
484
    @type hooks_results_adapt_fn: function
485
    @param hooks_results_adapt_fn: function that will adapt the return value of
486
      hooks_execution_fn to the format expected by RunPhase
487
    @type build_env_fn: function that returns a dictionary having strings as
488
      keys
489
    @param build_env_fn: function that builds the environment for the hooks
490
    @type log_fn: function that accepts a string
491
    @param log_fn: logging function
492
    @type htype: string or None
493
    @param htype: None or one of L{constants.HTYPE_CLUSTER},
494
     L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE}
495
    @type cluster_name: string
496
    @param cluster_name: name of the cluster
497
    @type master_name: string
498
    @param master_name: name of the master
451 499

  
452
  In order to remove the direct dependency on the rpc module, the
453
  constructor needs a function which actually does the remote
454
  call. This will usually be rpc.call_hooks_runner, but any function
455
  which behaves the same works.
500
    """
501
    self.opcode = opcode
502
    self.hooks_path = hooks_path
503
    self.hooks_execution_fn = hooks_execution_fn
504
    self.hooks_results_adapt_fn = hooks_results_adapt_fn
505
    self.build_env_fn = build_env_fn
506
    self.log_fn = log_fn
507
    self.htype = htype
508
    self.cluster_name = cluster_name
509
    self.master_name = master_name
456 510

  
457
  """
458
  def __init__(self, callfn, lu):
459
    self.callfn = callfn
460
    self.lu = lu
461
    self.op = lu.op
462 511
    self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE)
463

  
464
    if self.lu.HPATH is None:
465
      nodes = (None, None)
466
    else:
467
      nodes = map(frozenset, self.lu.BuildHooksNodes())
468

  
469 512
    (self.pre_nodes, self.post_nodes) = nodes
470 513

  
471 514
  def _BuildEnv(self, phase):
......
484 527

  
485 528
    env = {}
486 529

  
487
    if self.lu.HPATH is not None:
488
      lu_env = self.lu.BuildHooksEnv()
489
      if lu_env:
490
        assert not compat.any(key.upper().startswith(prefix) for key in lu_env)
530
    if self.hooks_path is not None:
531
      phase_env = self.build_env_fn()
532
      if phase_env:
533
        assert not compat.any(key.upper().startswith(prefix)
534
                              for key in phase_env)
491 535
        env.update(("%s%s" % (prefix, key), value)
492
                   for (key, value) in lu_env.items())
536
                   for (key, value) in phase_env.items())
493 537

  
494 538
    if phase == constants.HOOKS_PHASE_PRE:
495 539
      assert compat.all((key.startswith("GANETI_") and
......
512 556
  def _RunWrapper(self, node_list, hpath, phase, phase_env):
513 557
    """Simple wrapper over self.callfn.
514 558

  
515
    This method fixes the environment before doing the rpc call.
559
    This method fixes the environment before executing the hooks.
516 560

  
517 561
    """
518
    cfg = self.lu.cfg
519

  
520 562
    env = {
521 563
      "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
522 564
      "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
523
      "GANETI_OP_CODE": self.op.OP_ID,
565
      "GANETI_OP_CODE": self.opcode,
524 566
      "GANETI_DATA_DIR": constants.DATA_DIR,
525 567
      "GANETI_HOOKS_PHASE": phase,
526 568
      "GANETI_HOOKS_PATH": hpath,
527 569
      }
528 570

  
529
    if self.lu.HTYPE:
530
      env["GANETI_OBJECT_TYPE"] = self.lu.HTYPE
571
    if self.htype:
572
      env["GANETI_OBJECT_TYPE"] = self.htype
573

  
574
    if self.cluster_name is not None:
575
      env["GANETI_CLUSTER"] = self.cluster_name
531 576

  
532
    if cfg is not None:
533
      env["GANETI_CLUSTER"] = cfg.GetClusterName()
534
      env["GANETI_MASTER"] = cfg.GetMasterNode()
577
    if self.master_name is not None:
578
      env["GANETI_MASTER"] = self.master_name
535 579

  
536 580
    if phase_env:
537 581
      env = utils.algo.JoinDisjointDicts(env, phase_env)
......
542 586
    assert compat.all(key == "PATH" or key.startswith("GANETI_")
543 587
                      for key in env)
544 588

  
545
    return self.callfn(node_list, hpath, phase, env)
589
    return self.hooks_execution_fn(node_list, hpath, phase, env)
546 590

  
547 591
  def RunPhase(self, phase, nodes=None):
548 592
    """Run all the scripts for a phase.
549 593

  
550 594
    This is the main function of the HookMaster.
595
    It executes self.hooks_execution_fn, and after running
596
    self.hooks_results_adapt_fn on its results it expects them to be in the form
597
    {node_name: (fail_msg, [(script, result, output), ...]}).
551 598

  
552 599
    @param phase: one of L{constants.HOOKS_PHASE_POST} or
553 600
        L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
......
574 621
      # even attempt to run, or this LU doesn't do hooks at all
575 622
      return
576 623

  
577
    results = self._RunWrapper(nodes, self.lu.HPATH, phase, env)
624
    results = self._RunWrapper(nodes, self.hooks_path, phase, env)
578 625
    if not results:
579 626
      msg = "Communication Failure"
580 627
      if phase == constants.HOOKS_PHASE_PRE:
581 628
        raise errors.HooksFailure(msg)
582 629
      else:
583
        self.lu.LogWarning(msg)
630
        self.log_fn(msg)
584 631
        return results
585 632

  
633
    converted_res = results
634
    if self.hooks_results_adapt_fn:
635
      converted_res = self.hooks_results_adapt_fn(results)
636

  
586 637
    errs = []
587
    for node_name in results:
588
      res = results[node_name]
589
      if res.offline:
638
    for node_name, (fail_msg, offline, hooks_results) in converted_res.items():
639
      if offline:
590 640
        continue
591 641

  
592
      msg = res.fail_msg
593
      if msg:
594
        self.lu.LogWarning("Communication failure to node %s: %s",
595
                           node_name, msg)
642
      if fail_msg:
643
        self.log_fn("Communication failure to node %s: %s", node_name, fail_msg)
596 644
        continue
597 645

  
598
      for script, hkr, output in res.payload:
646
      for script, hkr, output in hooks_results:
599 647
        if hkr == constants.HKR_FAIL:
600 648
          if phase == constants.HOOKS_PHASE_PRE:
601 649
            errs.append((node_name, script, output))
602 650
          else:
603 651
            if not output:
604 652
              output = "(no output)"
605
            self.lu.LogWarning("On %s script %s failed, output: %s" %
606
                               (node_name, script, output))
653
            self.log_fn("On %s script %s failed, output: %s" %
654
                        (node_name, script, output))
607 655

  
608 656
    if errs and phase == constants.HOOKS_PHASE_PRE:
609 657
      raise errors.HooksAbort(errs)
......
619 667
    """
620 668
    phase = constants.HOOKS_PHASE_POST
621 669
    hpath = constants.HOOKS_NAME_CFGUPDATE
622
    nodes = [self.lu.cfg.GetMasterNode()]
670
    nodes = [self.master_name]
623 671
    self._RunWrapper(nodes, hpath, phase, self.pre_env)
672

  
673
  @staticmethod
674
  def BuildFromLu(hooks_execution_fn, lu):
675
    if lu.HPATH is None:
676
      nodes = (None, None)
677
    else:
678
      nodes = map(frozenset, lu.BuildHooksNodes())
679

  
680
    master_name = cluster_name = None
681
    if lu.cfg:
682
      master_name = lu.cfg.GetMasterNode()
683
      cluster_name = lu.cfg.GetClusterName()
684

  
685
    return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
686
                       _RpcResultsToHooksResults, lu.BuildHooksEnv,
687
                       lu.LogWarning, lu.HTYPE, cluster_name, master_name)
b/test/ganeti.hooks_unittest.py
249 249

  
250 250
  def testTotalFalse(self):
251 251
    """Test complete rpc failure"""
252
    hm = mcpu.HooksMaster(self._call_false, self.lu)
252
    hm = mcpu.HooksMaster.BuildFromLu(self._call_false, self.lu)
253 253
    self.failUnlessRaises(errors.HooksFailure,
254 254
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
255 255
    hm.RunPhase(constants.HOOKS_PHASE_POST)
256 256

  
257 257
  def testIndividualFalse(self):
258 258
    """Test individual node failure"""
259
    hm = mcpu.HooksMaster(self._call_nodes_false, self.lu)
259
    hm = mcpu.HooksMaster.BuildFromLu(self._call_nodes_false, self.lu)
260 260
    hm.RunPhase(constants.HOOKS_PHASE_PRE)
261 261
    #self.failUnlessRaises(errors.HooksFailure,
262 262
    #                      hm.RunPhase, constants.HOOKS_PHASE_PRE)
......
264 264

  
265 265
  def testScriptFalse(self):
266 266
    """Test individual rpc failure"""
267
    hm = mcpu.HooksMaster(self._call_script_fail, self.lu)
267
    hm = mcpu.HooksMaster.BuildFromLu(self._call_script_fail, self.lu)
268 268
    self.failUnlessRaises(errors.HooksAbort,
269 269
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
270 270
    hm.RunPhase(constants.HOOKS_PHASE_POST)
271 271

  
272 272
  def testScriptSucceed(self):
273 273
    """Test individual rpc failure"""
274
    hm = mcpu.HooksMaster(FakeHooksRpcSuccess, self.lu)
274
    hm = mcpu.HooksMaster.BuildFromLu(FakeHooksRpcSuccess, self.lu)
275 275
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
276 276
      hm.RunPhase(phase)
277 277

  
......
322 322
  def testEmptyEnv(self):
323 323
    # Check pre-phase hook
324 324
    self.lu.hook_env = {}
325
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
325
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
326 326
    hm.RunPhase(constants.HOOKS_PHASE_PRE)
327 327

  
328 328
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
......
348 348
    self.lu.hook_env = {
349 349
      "FOO": "pre-foo-value",
350 350
      }
351
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
351
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
352 352
    hm.RunPhase(constants.HOOKS_PHASE_PRE)
353 353

  
354 354
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
......
395 395
      self.lu.hook_env = { name: "value" }
396 396

  
397 397
      # Test using a clean HooksMaster instance
398
      hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
398
      hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
399 399

  
400 400
      for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
401 401
        self.assertRaises(AssertionError, hm.RunPhase, phase)
......
403 403

  
404 404
  def testNoNodes(self):
405 405
    self.lu.hook_env = {}
406
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
406
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
407 407
    hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[])
408 408
    self.assertRaises(IndexError, self._rpcs.pop)
409 409

  
......
415 415
      "node93782.example.net",
416 416
      ]
417 417

  
418
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
418
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
419 419

  
420 420
    for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
421 421
      hm.RunPhase(phase, nodes=nodes)
......
433 433
      "FOO": "value",
434 434
      }
435 435

  
436
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
436
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
437 437
    hm.RunConfigUpdate()
438 438

  
439 439
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
......
452 452
      "FOO": "value",
453 453
      }
454 454

  
455
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
455
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
456 456
    hm.RunPhase(constants.HOOKS_PHASE_POST)
457 457

  
458 458
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
......
470 470
    self.assertRaises(AssertionError, self.lu.BuildHooksEnv)
471 471
    self.assertRaises(AssertionError, self.lu.BuildHooksNodes)
472 472

  
473
    hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
473
    hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
474 474
    self.assertEqual(hm.pre_env, {})
475 475
    self.assertRaises(IndexError, self._rpcs.pop)
476 476

  

Also available in: Unified diff