Executing commands on nodes

EnOSlib uses internally Ansible to perform some predefined tasks (e.g network emulation). The APIs allowing this are exposed so that anyone can use them. User can thus :

  • run a single command on a subset of the nodes and gather the outputs (and erros)
  • or run a set of actions ( ansible playbook) programmatically

Hint

The presented methods are provider agnostic.

Run a single command on nodes and gather results

Let’s consider the following script :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from enoslib.api import discover_networks, run_command
from enoslib.infra.enos_vagrant.provider import Enos_vagrant
from enoslib.infra.enos_vagrant.configuration import Configuration

import json
import logging


logging.basicConfig(level=logging.DEBUG)

provider_conf = {
    "backend": "libvirt",
    "box": "generic/debian9",
    "resources": {
        "machines": [{
            "roles":  ["control1"],
            "flavour": "tiny",
            "number": 1,
        },{
            "roles": ["control2"],
            "flavour": "tiny",
            "number": 1,
        }],
        "networks": [{"roles": ["rn1"], "cidr": "172.16.0.1/16"}]
    }
}

conf = Configuration.from_dictionnary(provider_conf)
provider = Enos_vagrant(conf)
roles, networks = provider.init()
roles = discover_networks(roles, networks)
result = run_command("date",
                     pattern_hosts="control*",
                     roles=roles)
with open("result_ok", "w") as f:
    json.dump(result["ok"], f, indent=2)
with open("result_failed", "w") as f:
    json.dump(result["failed"], f, indent=2)
  • run_command() takes at least 3 parameters :

  • The result variable is a dict containing the results of the execution of the command.

  • Inspecting the ok key will give you the stderr and stdout where the commands where successfully launched.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "enos-0": {
    "stdout": "Fri Feb 28 21:57:51 UTC 2020",
    "stderr": ""
  },
  "enos-1": {
    "stdout": "Fri Feb 28 21:57:51 UTC 2020",
    "stderr": ""
  }
}
  • Inspecting the failed key will give you the failed hosts. Hosts that were unreachable or the hosts on which the command the command failed (e.g syntax error).
  • An extra key result is available and will give you the raw results that the internal Ansible APIs would have give you if using directly.

Run a set of actions on nodes

Using python exclusively

Let’s consider the following script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from enoslib.api import discover_networks, play_on
from enoslib.infra.enos_vagrant.provider import Enos_vagrant
from enoslib.infra.enos_vagrant.configuration import Configuration

import logging


logging.basicConfig(level=logging.DEBUG)

# The conf let us define the resources wanted.
# This is provider specific
conf = (
    Configuration
    .from_settings(backend="libvirt")
    .add_machine(
        roles=["server"],
        flavour="tiny",
        number=1
    )
    .add_machine(
        roles=["client"],
        flavour="tiny",
        number=1
    )
    .add_network(
        roles=["mynetwork"],
        cidr="192.168.42.0/24")
    .finalize()
)

provider = Enos_vagrant(conf)

# The code below is intended to be provider agnostic

# Start the resources
roles, networks = provider.init()

# Add some specific knowledge to the returned roles (e.g on the server the ip
# for mynetwork is 192.168.42.254)
roles = discover_networks(roles, networks)

# Experimentation logic starts here
with play_on(roles=roles) as p:
    # flent requires python3, so we default python to python3
    p.shell("update-alternatives --install /usr/bin/python python /usr/bin/python3 1")
    p.apt_repository(repo="deb http://deb.debian.org/debian stretch main contrib non-free",
                     state="present")
    p.apt(name=["flent", "netperf", "python3-setuptools", "python3-matplotlib"],
          state="present")

with play_on(pattern_hosts="server", roles=roles) as p:
    p.shell("nohup netperf &")

with play_on(pattern_hosts="client", roles=roles) as p:
    p.shell("flent rrul -p all_scaled "
            + "-l 60 "
            + "-H {{ hostvars[groups['server'][0]].ansible_default_ipv4.address }} "
            + "-t 'bufferbloat test' "
            + "-o result.png")
    p.fetch(src="result.png",
            dest="result")

In this example each play_on block run a playbook generated from the ansible module calls made. Any ansible module can be called here, the exact keyword arguments to pass depend on each module and you’ll need to refer to the ansible modules documentation (e.g https://docs.ansible.com/ansible/latest/modules/apt_module.html).

Using a yaml playbook

In addition to run custom command on nodes, EnOSlib can trigger predefined playbook stored in your filesystem. This let you to use Ansible DSL to describe your tasks or launch reusable playbook.

Let’s consider the following script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from enoslib.api import run_ansible
from enoslib.infra.enos_vagrant.provider import Enos_vagrant
from enoslib.infra.enos_vagrant.configuration import Configuration

import logging

logging.basicConfig(level=logging.DEBUG)

provider_conf = {
    "resources": {
        "machines": [{
            "roles": ["control1"],
            "flavour": "tiny",
            "number": 1,
        },{
            "roles": ["control2"],
            "flavour": "tiny",
            "number": 1,
        }],
        "networks": [{"roles": ["rn1"], "cidr": "172.16.0.1/16"}]
    }
}

conf = Configuration.from_dictionnary(provider_conf)
provider = Enos_vagrant(conf)
roles, networks = provider.init()

run_ansible(["site.yml"], roles=roles)

And the corresponding minimal playbook site.yml:

1
2
3
4
5
6
7
---
- name: This is a play
  hosts: control*
  tasks:
    - name: One task
      debug:
        msg: "I'm running on {{ inventory_hostname }}"

What you should see at the end is:

PLAY [This is a play] ******************************************************

TASK [Gathering Facts] *****************************************************
ok: [enos-0]
ok: [enos-1]

TASK [One task] ************************************************************
ok: [enos-0] => {
    "msg": "I'm running on enos-0"
}
ok: [enos-1] => {
    "msg": "I'm running on enos-1"
}

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

What’s next ?

If you aren’t familiar with Ansible, we encourage you to go through the Ansible documentation, write your own playbooks, or integrate one existing from ansible galaxy.