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
- Ansible 2.0.0.2
- Erlang/OTP 17.0
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:
FIELD | DESCRIPTION |
---|---|
rc | The Erlang RPC return code (0 => success, 1 => failure) |
stdout | The return value of the Erlang RPC |
stdout_lines | The return value of the Erlang RPC call, split in lines |
remote_output | The 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:
- Amir Moulavi
- Fabricio Leotti
For their precious contributions.