Howto

Creating Custom Triggers in Drupal

27 comments

So after playing around for several hours tonight trying to build my own custom trigger I think I've finally figured it out. I thought I'd post it here for reference.

Though triggers are included in core in Drupal 6 it seems they're very poorly documented. The extent of the documentation I could find was on this trigger page in the handbook. Pro Drupal Development also has a chapter on it but unfortunately isn't really explicit enough for me with some parts.

Disclaimer: I don't really know what I'm doing, so please let me know if I've done anything wrong.

Prereqs:

  • Drupal 6
  • The triggers module should be enabled.
  • A commenter also pointed out that the triggerunlock module is also necessary. If you have your own custom actions this isn't needed though.
  • If you're following this example, you'll also need to CCK type called 'script'.

What I want to do is create a custom trigger in my module. Let's say I want to execute a custom action on my server whenever a node of a type script (as defined in the 'script' module) is created. The first step is to define hook_hook_info.

/**
 * Implementation of hook_menu_alter().
 */
function script_hook_info() {
  return array(
    'script' => array(
      'script' => array(
        'insert' => array(
          'runs when' => t('After script is created'),
        ),
      ),
    ),
  );
}

This will create a new tab in my triggers page that will be named after the name of my module as set in my .info file.

Two things I still need to do to make sure this trigger actually does anything... define hook_script and call it from a module_invoke or module_invoke_all whenever the triggering event happens and call the actions using actions_do(). Since these hooks should be executed on node insert I'll need to use hook_nodeapi().

/**
 * Implementation of hook_nodeapi().
 */
function script_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'insert':
      module_invoke_all('script', 'insert', $node);
      break;
  }
}

The second aspect (calling the associated actions) requires an implementation of hook_script() (the alternative being to just replace the module_invoke line with this functions body.

/**
 * Implementation of hook_script().
 */
function script_script($op, $node) {
  $aids = _trigger_get_hook_aids('script', $op);
  $context = array(
    'hook' => 'script',
    'op' => $op,
    'node' => $node,
  );
  actions_do(array_keys($aids), $node, $context);
}

The first line in the function is a call to _trigger_get_hook_aids which returns the list of action ID's that have been assigned to this trigger. This is a private function, but seems to be the only way to get the list of actions. The last line in the function calls actions_do(), which processes all the actions assigned to this trigger.

In this particular example you'll need a custom content type called 'script'.

And that's all there is to creating your own custom triggers.

Alternatively, I don't need to create a hook for the trigger, instead I could just piggyback off of the nodeapi hook, since this is a node operation.

The hook_hook_info looks a little different with:

function script_hook_info() {
  $info['script'] = array(
    'nodeapi' => array(
      'script' => array(
        'runs when' => t('After script is created'),
      ),
    ),
  );
  return $info;
}

And then just a slight change to hook_script() to change the hook from 'script' to 'nodeapi'.

/**
 * Implementation of hook_script().
 */
function script_script($op, $node) {
  $aids = _trigger_get_hook_aids('nodeapi', $op);
  $context = array(
    'hook' => 'nodeapi',
    'op' => $op,
    'node' => $node,
  );
  actions_do(array_keys($aids), $node, $context);
}

One other thing that was bugging me with triggers was the inability to only use them on specific cck types. There's no way to setup a trigger to only get called when an event happens to a single content type (without using the workflow module, which I found has some problems using variables in actions). Put an "if ($node->type == 'script')" somewhere appropriate, in my code I'd wrap that around the call to module_invoke_all(), for example:

function script_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'insert':
      if ($node->type == 'script') {
        module_invoke_all('script', 'insert', $node);
      }
      break;
  }
}

----

After writing this I found this blog post which also looks quite useful.

I've made some updates to this example, so hopefully it will work for everyone now.

You can download the example module here.

GSoC - Loadtest module

2 comments

As some of you know, I did Google's Summer of Code this summer. The result of that ended up being the loadtest module. This entry is basically just a description of how to use the module... so if you're not interested, please move along :-P.

The first part of this module lets you define and configure "states" for your site that you can run tests against.

You can use these states in two different ways:

  1. When running a loadtest, using the loadtest module, you can choose the state you wish to test.
  2. If you want to run more comprehensive tests using an external application such as ab, you can switch between states remotely allowing you to more easily script your benchmark tests.

The following screenshot is of the main testing interface. Each part of this form is described below.

  • The label is used for you to identify your test run at a later date. Each label will automatically have the timestamp for the test run appended to it.
  • A test suite is the "type" of test to run. By default there are two: "Single run", which just runs the selected tests once and "Test individual modules" which autotmatically disables all of the modules and then enables each module one at a time running tests at each step. Test suites can be added by module programmers as well so they can build tests that are more suited to their specific modules.
  • You can select a state from any of your pre-configured states. Leaving this empty will simply use the sites current settings.
  • There are currently two default tests that are included with the module:
    1. RequestRandomNodes - which does just that, page requests on random nodes. This is mostly just for testing, not overly useful for running load tests as you would probably want to compare test runs against identical page requests.
    2. RequestPages - This test requests a set of select pages a certain number of times. By default the pages "frontpage" and "tracker" are requested 10 times.

Unlike simpletest tests, loadtest tests can have individual configuration options that can be set through Drupal. The RequestPages test lets a user define which pages they want to request and how many times each page should be requested.

Depending on the options you choose running a test could take several minutes. Since site settings may be modified on the fly during a test run, it is important that you don't use this module on a production site, and don't interrupt it while it's running.

Finally, once the test has been run, you can view your statistics and compare multiple tests.

The code can be downloaded from the tarball on the module page, but I'd recommend taking it directly from CVS:

cvs -z6 -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal-contrib checkout -d loadtest contributions/modules/loadtest

PCI Delegation in Xen

2 comments

I haven't been able to find any existing documentation on this, and since I had it working properly on a test server that I recently killed (and then I had to figure out how to do it again), I figured I'd document it here, for myself, and anyone else who's looking for it.

Disclaimer: If you don't know what Xen or PCI Delegation are, you can probably stop reading here.

PCI Delegation lets you hide PCI devices from your Dom0 system and access them directly in a DomU system. The first thing you'll need to do is ensure PCI backend is enabled for your Dom0 kernel and PCI frontend for you DomU kernel.

CONFIG_XEN_PCIDEV_FRONTEND=y
CONFIG_XEN_PCIDEV_BACKEND=y

To hide a device, you'll first need to note the PCI id of the device, run lspci to find this out. It's represented by the first column in the listing. In my case, I'm trying to hide one of my ethernet cards, my output looks something like this (non-relevant information snipped):

06:07.0 Ethernet controller: Intel Corporation 82541GI/PI Gigabit Ethernet Controller (rev 05)
07:08.0 Ethernet controller: Intel Corporation 82541GI/PI Gigabit Ethernet Controller (rev 05)

So the id I want to note is 07:08.0.

Now, add pciback.hide=(07:08.0) to your grub.conf kernel line, mine looks something like this:

module /vmlinuz-2.6.16.26-xen0 root=/dev/sda7 pciback.hide=(07:08.0)

When you reboot, your Dom0 system will no longer be aware of the specified PCI device. Run ifconfig ethX to verify this.

All you need to do now is add the device to your DomU system. In your Xen config file add the line:

pci = [ '07:08.0' ]

You also won't need any vif lines unless you still want virtual interfaces within the same DomU. Boot into your Dom0 and run lspci, you should see your PCI device listed there now.

Syndicate content