Revision da124e89

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

  
755 755
  """
756
  hm = lu.proc.hmclass(lu.rpc.call_hooks_runner, lu)
756
  hm = lu.proc.BuildHooksManager(lu)
757 757
  try:
758 758
    hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
759 759
  except:
b/lib/mcpu.py
172 172
              if op.WITH_LU)
173 173

  
174 174

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

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

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

  
188

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

  
261
    hm = self.BuildHooksManager(lu)
247 262
    h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
248 263
    lu.HooksCallBack(constants.HOOKS_PHASE_PRE, h_results,
249 264
                     self.Log, None)
......
268 283

  
269 284
    return result
270 285

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

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

  
......
445 463

  
446 464

  
447 465
class HooksMaster(object):
448
  """Hooks master.
449

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

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

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

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

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

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

  
470 513
    (self.pre_nodes, self.post_nodes) = nodes
471 514

  
472 515
  def _BuildEnv(self, phase):
......
485 528

  
486 529
    env = {}
487 530

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

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

  
516
    This method fixes the environment before doing the rpc call.
560
    This method fixes the environment before executing the hooks.
517 561

  
518 562
    """
519
    cfg = self.lu.cfg
520

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

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

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

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

  
537 581
    if phase_env:
538 582
      env = utils.algo.JoinDisjointDicts(env, phase_env)
......
543 587
    assert compat.all(key == "PATH" or key.startswith("GANETI_")
544 588
                      for key in env)
545 589

  
546
    return self.callfn(node_list, hpath, phase, env)
590
    return self.hooks_execution_fn(node_list, hpath, phase, env)
547 591

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

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

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

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

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

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

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

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

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

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

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

  
686
    return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
687
                       _RpcResultsToHooksResults, lu.BuildHooksEnv,
688
                       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