Home

Awesome

README

An Ansible module to interact with Erlang nodes via Erlang RPC.

Overview

If your architecture includes one or more Erlang nodes and you use Ansible to orchestrate them, you may find this Ansible module helpful.

Minimum Requirements

Installation

Clone the ansible-nodetool repository:

git clone https://github.com/robertoaloi/ansible-nodetool.git /path/to/ansible-nodetool

Then, you need to tell Ansible where to find the new module. You can do this by appending the repository path to the library value in your ~/.ansible.cfg file. You can find the library value under the defaults group. If you do not have a defaults group in your ~/.ansible.cfg file (or if you do not have a ~/.ansible.cfg file at all) add one. You can find more information about configuring Ansible here.

[defaults]
library = CURRENT_PATH:/path/to/ansible-nodetool

Alternatively, you can specify the -M option when invoking a playbook. Example:

$ ansible-playbook -M /path/to/ansible-nodetool my_playbook.yml

Or when running an ad-hoc command. Example:

$ ansible -m nodetool \
         -M /path/to/ansible-nodetool \
         -a 'node=alice@localhost cookie=secret action=ping' \
         localhost

Parameters

 node:
     description: The remote Erlang node
     required:    true
 action:
     description: The action to be performed
     choices:     [getpid, ping, stop, restart, reboot, eval]
     required:    true
 nametype:
     description: Nametype to be used
     choices:     [longnames, shortnames]
     required:    false
     default:     shortnames
 cookie:
     description: Erlang Cookie to be used for the connection
     required:    false
 timeout:
     description: Timeout (in ms) for the actions
     required:    false
     default:     60000

Usage

The ansible-nodetool module is typically used from an Ansible playbook.

To try things out, start a sample Erlang node named 'alice' and with a 'secret' cookie:

$ erl -sname alice@localhost -setcookie secret

You can now ping the node using the following playbook:

---

- hosts: localhost
  tasks:
  - name: "Ping the 'alice' Erlang node"
    nodetool:
      action: ping
      cookie: secret
      node:   alice@{{ inventory_hostname_short }}

Example:

$ ansible-playbook ping.yml

PLAY
***************************************************************************

TASK [setup]
*******************************************************************
ok: [localhost]

TASK [Ping the 'alice' Erlang node]
********************************************
changed: [localhost]

PLAY RECAP
*********************************************************************
localhost                  : ok=2    changed=1    unreachable=0
failed=0

But the ansible-nodetool module is not only about pinging nodes. You can also evaluate custom Erlang expressions on a remote Erlang node using the eval action.

The following playbook gets the list of running applications in an Erlang node, registers the result into an Ansible variable and prints the result:

---

- hosts: localhost
  tasks:
  - name: "Return a list of running applications"
    nodetool:
      action:  eval
      command: application:which_applications()
      cookie:  secret
      node:    alice@{{ inventory_hostname_short }}
    register: applications
  - debug:
      msg: "{{ applications.stdout_lines }}"

Let's see it in action:

$ ansible-playbook applications.yml

PLAY
***************************************************************************

TASK [setup]
*******************************************************************
ok: [localhost]

TASK [Return a list of running applications]
***********************************
changed: [localhost]

TASK [debug]
*******************************************************************
ok: [localhost] => {
  "msg": [
    "[{sasl,\"SASL  CXC 138 11\",\"2.4.1\"},",
    " {stdlib,\"ERTS  CXC 138 10\",\"2.4\"},",
    " {kernel,\"ERTS  CXC 138 10\",\"3.2.0.1\"}]"
  ]
}

PLAY RECAP
*********************************************************************
localhost                  : ok=3    changed=1    unreachable=0
failed=0

Another available action is getpid, which returns the process identifier of the current Erlang emulator:

---

- hosts: localhost
  tasks:
  - name: "Return the process identifier of the current Erlang emulator"
    nodetool:
      action: getpid
      cookie: secret
      node:   alice@{{ inventory_hostname_short }}
    register: pid
  - debug:
      msg: "{{ pid.stdout }}"

There are also other available actions, such as stop, restart or reboot to control a remote Erlang node.

Result

As part of the Ansible result, the ansible-nodetool provides a JSON structure which contains the following fields:

FIELDDESCRIPTION
rcThe Erlang RPC return code (0 => success, 1 => failure)
stdoutThe return value of the Erlang RPC
stdout_linesThe return value of the Erlang RPC call, split in lines
remote_outputThe stdout on the remote Erlang node, captured via group leader

I have seen many people trying to assert the return value of the Erlang RPC in complicated ways. Remember that, if you want to verify that the return value of a given Erlang expression corresponds to a specific value you can simply use the pattern matching operator for this. No need to find complicated Ansible-based solutions!

Example:

---

- hosts: localhost
  tasks:
  - name: "Ensure that the reverse of 'foo' is 'oof'"
    nodetool:
      action:  eval
      command: "\"oof\"=lists:reverse(\"foo\")"
      cookie:  secret
      node:    alice@{{ inventory_hostname_short }}

Notice the usage of the pattern matching operator (=) above and the presence of backslashes to escape the double quotes.

Idempotence

Idempotence is a crucial principle in Ansible. Given the nature of the Ansible nodetool module, which allow the operator to perform custom Erlang expressions via the eval action, idempotence cannot be guaranteed by the module. It is up to the user to avoid violating it.

Let's consider the following example:

---

# THIS PLAYBOOK IS NOT IDEMPOTENT!!!
# RUNNING IT TWICE WOULD CAUSE A FAILURE!!!
- hosts: localhost
  tasks:
  - name: "Start the SASL application"
    nodetool:
      action:  eval
      command: ok=application:start(sasl).
      cookie:  secret
      node:    alice@{{ inventory_hostname_short }}

The above playbook expects the application:start/1 function to return ok. This will only happen if the application is not already started. The following is a much better approach:

---

- hosts: localhost
  tasks:
  - name: "Start the SASL application"
    nodetool:
      action:  eval
      command: ok=application:ensure_started(sasl)
      cookie:  secret
      node:    alice@{{ inventory_hostname_short }}

The application:ensure_started/1 function will only start the application if it is not already started. Much better!

Credits

The work is based on the nodetool escript from the Yaws project:

https://github.com/klacke/yaws

Contributors

A big thanks to:

For their precious contributions.