Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 0da7556f authored by SIMONIN Matthieu's avatar SIMONIN Matthieu
Browse files

ACMREP: initial review

parent 02d444f8
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id:functioning-experience tags:
# Remote actions and variables
Changing the state of remote resources
---
- Website: https://discovery.gitlabpages.inria.fr/enoslib/index.html
- Instant chat: https://framateam.org/enoslib
- Source code: https://gitlab.inria.fr/discovery/enoslib
---
## Prerequisites
<div class="alert alert-block alert-warning">
Make sure you've run the one time setup for your environment
<ul>
<li>⚠️ Make sure you've run the one time setup for your environment</li>
<li>⚠️ Make sure you're running this notebook under the right kernel</li>
</ul>
</div>
%% Cell type:code id:funded-azerbaijan tags:
``` python
import enoslib as en
# Display some general information about the library
en.check()
# Enable rich logging
_ = en.init_logging()
```
%% Cell type:markdown id:familiar-intention tags:
## Setup on Grid'5000
EnOSlib uses `Providers` to ... provide resources. They transform an abstract resource configuration into a concrete one.
To do so, they interact with an infrastructure where they get the resources from. There are different providers in EnOSlib:
- Vbox/KVM to work with locally hosted virtual machines
- Openstack/Chameleon to work with bare-metal resources hosted in the Chameleon platform
- FiT/IOT lab to work with sensors or low profile machines
- VmonG5k to work with virtual machines on Grid'5000
- Distem to work with lxc containers on Grid'5000
- **Grid'5000**, with options to configure several networks easily
A providers eases the use of the platform by internalizing some of the configuration tasks (e.g automatically managing the reservation on G5k, network configuration ...)
### Describing the resources
For the purpose of the tutorial we'll reserve 2 nodes in the production environment.
First we build a configuration object describing the wanted resources: `machines` and `networks`.
%% Cell type:code id:bulgarian-honolulu tags:
``` python
network = en.G5kNetworkConf(type="prod", roles=["my_network"], site="rennes")
conf = (
en.G5kConf.from_settings(job_type=[], job_name="rsd-01")
.add_network_conf(network)
.add_machine(
roles=["control"], cluster="parasilo", nodes=1, primary_network=network
roles=["control"], cluster="parasilo", nodes=1
)
.add_machine(
roles=["compute"],
cluster="parasilo",
nodes=1,
primary_network=network,
nodes=1
)
.finalize()
)
conf
```
%% Cell type:markdown id:green-producer tags:
### Reserving the resources
We can pass the `Configuration` object to the `G5k` provider.
%% Cell type:code id:alike-immigration tags:
``` python
provider = en.G5k(conf)
roles, networks = provider.init()
```
%% Cell type:markdown id:asian-stretch tags:
Inspecting the ressources we own for the experiment's lifetime:
- roles: this is somehow a dictionnary whose keys are the role names and the associated values are the corresponding list of hosts
- networks: similar to roles but for networks
%% Cell type:code id:sunset-voluntary tags:
``` python
roles
```
%% Cell type:code id:regional-procedure tags:
``` python
# list of host on a given role
roles["control"]
```
%% Cell type:code id:nonprofit-excess tags:
``` python
# a single host
roles["control"][0]
```
%% Cell type:code id:latest-affiliate tags:
``` python
networks
```
%% Cell type:markdown id:danish-circle tags:
`provider.init` is idempotent. In the Grid'5000 case, you can call it several time in a row. The same reservation will reloaded and the roles and networks will be the same.
%% Cell type:code id:furnished-angle tags:
``` python
roles, networks = provider.init()
roles
```
%% Cell type:code id:blocked-marker tags:
``` python
# sync some more information in the host data structure (for illustration purpose here)
roles = en.sync_info(roles, networks)
```
%% Cell type:code id:fbbfe094-766b-4ddf-8e2c-78225d88761f tags:
``` python
# the hosts have been populated with some new information
roles
```
%% Cell type:markdown id:abandoned-british tags:
## Acting on remote nodes
### run a command, filter results
%% Cell type:code id:suited-nickname tags:
``` python
results = en.run_command("whoami", roles=roles)
results
```
%% Cell type:markdown id:c4102290-f5cd-4eb9-a6f4-00e1aed01baa tags:
<div class="alert alert-info">
💡 The `run_command` function is polymorphic in its `roles` parameter. You can pass a `Roles` object as return by `provider.init`, a list of `Host` or a single `Host`. Check its documentation using SHIFT+TAB for instance.
</div>
%% Cell type:code id:4ac51dc2-c096-46f2-bbaa-34087e111dff tags:
``` python
# a list of hosts
results = en.run_command("whoami", roles=roles["control"])
results
```
%% Cell type:markdown id:dd0ae419-feeb-4b9a-97da-fe778ce2c3f6 tags:
<div class="alert alert-info">
💡 The results is a list of result. Each result is an object with host, task, status, payload attributes.
You can filter the result given host, task and or status
</div>
%% Cell type:code id:favorite-updating tags:
``` python
one_result = results.filter(host=roles["control"][0].alias)[0]
one_result
# filter by host
some_results = results.filter(host=roles["control"][0].alias)
some_results
```
%% Cell type:code id:prepared-liverpool tags:
``` python
# take the first result
one_result = some_results[0]
one_result.payload["stdout"]
```
%% Cell type:markdown id:hollow-bulgaria tags:
There are some specific shortcuts when the remote actions is a remote (shell) command: `.stdout`, `.stderr`, `.rc`
<div class="alert alert-info">
💡 There are some specific shortcuts when the remote actions is a remote (shell) command: `.stdout`, `.stderr`, `.rc`
</div>
%% Cell type:code id:reported-intro tags:
``` python
print(f"stdout = {one_result.stdout}\n", f"stderr={one_result.stderr}\n", f"return code = {one_result.rc}")
```
%% Cell type:markdown id:operating-testimony tags:
By default the user is `root` (this is common to all EnOSlib's provider).
<div class="alert alert-info">
💡 By default the user is `root` (this is common to all EnOSlib's provider).
If you want to run command as your regular Grid'5000 user you can tell the command to `sudo` back to your regular user using `run_as` (the SSH login is still `root` though)
</div>
%% Cell type:code id:surprising-junior tags:
``` python
# get my username on g5k (this line is generic and useful if you share your code with someone else)
my_g5k_login = en.g5k_api_utils.get_api_username()
results = en.run_command("whoami", roles=roles, run_as=my_g5k_login)
results
```
%% Cell type:markdown id:inner-monster tags:
### Filtering hosts on which the command is run
`run_command` acts on remote hosts. Those hosts can be given as a `Roles` type (output of `provider.init`) or as a list of `Host` or a single `Host`.
%% Cell type:code id:spectacular-wellington tags:
``` python
# some roles
en.run_command("date", roles = roles)
```
%% Cell type:code id:accompanied-coverage tags:
``` python
# a list of hosts
en.run_command("date", roles = roles["control"])
```
%% Cell type:code id:mineral-transparency tags:
``` python
# a single host
en.run_command("date", roles=roles["control"][0])
```
%% Cell type:markdown id:invisible-gabriel tags:
A `pattern_hosts` can also be supplied. The pattern can be a regexp but [other patterns are possible](
https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html#common-patterns)
%% Cell type:code id:detected-wholesale tags:
``` python
# co* matches all hosts
en.run_command("date", roles=roles, pattern_hosts="co*")
# com* only matches host with `compute` tags
en.run_command("date", roles=roles, pattern_hosts="com*")
```
%% Cell type:code id:every-albany tags:
``` python
# you can forge some host yourself
# Here we run the command on the frontend: this should work if your SSH parameters are correct
en.run_command("date", roles=en.Host("rennes.grid5000.fr", user=en.g5k_api_utils.get_api_username()))
```
%% Cell type:markdown id:33bca3c7-0f2a-4350-b7bb-d40a31bb805f tags:
### Dealing with failures
By default, failures (command failure, host unreachable) raises on exception: this breaks your execution flow.
Sometime you just want to allow some failures to happen. For this purpose you can add `on_error_continue=True`
%% Cell type:code id:be5eb4e5-6ce6-4233-b545-d4cd99e6b151 tags:
``` python
en.run_command("non existing command", roles=roles, on_error_continue=True)
print("This is printed, so the execution can continue")
```
%% Cell type:markdown id:extreme-carol tags:
### Remote actions
Tools like Ansible, Puppet, Chef, Terraform ... are shipped with a set of predefined remote actions to ease the administrator life.
Actions like copying file, adding some users, managing packages, making sure a line is absent from a configuration file, managing docker containers ... are first-class citizens actions and brings some nice garantees of correctness and idempotency.
There are 1000+ modules available:
https://docs.ansible.com/ansible/2.9/modules/list_of_all_modules.html
---
EnOSlib wraps Ansible module and let you use them from Python (without writting any YAML file). You can call any module by using the `actions` context manager:
In the following we install docker (using g5k provided script) and a docker container. We also need to install the python docker binding on the remote machine so that Ansible can interact with the docker daemons on the remote machines. This block of actions is idempotent.
%% Cell type:code id:organic-vanilla tags:
``` python
with en.actions(roles=roles) as a:
# installing the docker daemon
# prepending with a guard to make the command idempotent
a.shell("which docker || /grid5000/code/bin/g5k-setup-docker")
# install the python docker binding on the remote host
# mandatory by the docker_container module
a.pip(name="docker", state="present")
# fire up a container (forward port 80 at the host level)
a.docker_container(name="myserver", image="nginx", state="started", ports=["80:80"])
# install nginx on the remote machines
a.apt(name="nginx", state="present")
# wait for the connection on the port 80 to be ready
a.wait_for(port=80, state="started")
# keep track of the result of each modules
# not mandatory but nice :)
results = a.results
```
%% Cell type:code id:enhanced-highlight tags:
``` python
results.filter(task="docker_container")[0]
results.filter(task="apt")[0]
```
%% Cell type:code id:a44e9b11-dcb2-4eda-a8bd-6e6a851fc03e tags:
``` python
import requests
from IPython.display import HTML
response = requests.get("http://parasilo-4:80")
HTML(response.text)
```
%% Cell type:markdown id:liquid-quest tags:
### Background actions
Sometime you need to fire a process on some remote machines that needs to survive the remote connection that started it. EnOSlib provides a `keyword` argument for this purpose and can be used when calling modules (when supported).
%% Cell type:code id:thorough-heritage tags:
``` python
# synchronous execution, will wait until the end of the shell command
results = en.run_command("for i in $(seq 1 10); do sleep 1; echo toto; done", roles=roles)
results
```
%% Cell type:code id:protected-complexity tags:
``` python
# The remote command will be daemonize on the remote machines
results = en.run_command("for i in $(seq 1 10); do sleep 1; echo toto; done", roles=roles, background=True)
results
```
%% Cell type:code id:returning-trout tags:
``` python
# you can get back the status of the daemonized process by reading the remote results_file
# but we need to wait the completion, so forcing a sleep here (one could poll the status)
import time
time.sleep(15)
h = roles["control"][0]
result_file = results.filter(host=h.alias)[0].results_file
cat_result = en.run_command(f"cat {result_file}",roles=h)
cat_result
```
%% Cell type:code id:executed-philosophy tags:
``` python
# the result_file content is json encoded so decoding it
import json
print(json.loads(cat_result[0].stdout)["stdout"])
```
%% Cell type:markdown id:tracked-dialogue tags:
## Using variables
%% Cell type:markdown id:alternate-assistant tags:
### Same variable value for everyone
Nothing surprising here, you can use regular python interpolation (e.g a `f-string`).
String are interpolated by the interpreter before being manipulated.
%% Cell type:code id:proved-finger tags:
``` python
host_to_ping = roles["control"][0].alias
host_to_ping
results = en.run_command(f"ping -c 5 {host_to_ping}", roles=roles)
results
```
%% Cell type:code id:young-sierra tags:
``` python
[(r.host, r.stdout) for r in results]
```
%% Cell type:markdown id:thrown-arkansas tags:
### Using templates / Ansible variables
There's an alternative way to pass a variable to a task: using `extra_vars`.
The difference with the previous case (python interpreted variables) is the fact that the variable is interpolated right before execution happens on the remote node.
One could imagine the the value is broadcasted to all nodes and replaced right before the execution.
To indicate that we want to use this kind of variables, we need to pass its value using the `extra_vars` dictionnary and use a template (`{{ ... }}`) in the task description.
%% Cell type:code id:intensive-parcel tags:
``` python
host_to_ping = roles["control"][0].alias
host_to_ping
results = en.run_command("ping -c 5 {{ my_template_variable }}", roles=roles, extra_vars=dict(my_template_variable=host_to_ping))
results
```
%% Cell type:markdown id:mysterious-separate tags:
### Host specific variables
In the above, we've seen how a common value can be broadcasted to all remote nodes. What if we want host specific value ?
For instance in our case we'd like `host 1` to ping `host 2` and `host 2` to ping `host 1`. That make the `host_to_ping` variable host-specific.
For this purpose you can use the `extra` attribute of the `Host` objects and use a template as before.
%% Cell type:code id:southeast-panel tags:
``` python
control_host = roles["control"][0]
compute_host = roles["compute"][0]
control_host.set_extra(host_to_ping=compute_host.address)
compute_host.set_extra(host_to_ping=control_host.address)
control_host
```
%% Cell type:code id:17295b0c-cb42-4752-b063-0c88f5580cb3 tags:
``` python
compute_host
```
%% Cell type:markdown id:material-charm tags:
> Note that the `extra` variable can be reset to their initial states with `Host.reset_extra()`
%% Cell type:code id:emotional-avenue tags:
``` python
results = en.run_command("ping -c 5 {{ host_to_ping }}", roles=roles)
results
```
%% Cell type:code id:english-answer tags:
``` python
[(r.host, r.stdout) for r in results]
```
%% Cell type:markdown id:infectious-packing tags:
## Cleaning
%% Cell type:code id:spoken-modeling tags:
``` python
provider.destroy()
```
%% Cell type:code id:961ea738-c4f5-42f0-a061-0eca5e606b65 tags:
``` python
```
......
%% Cell type:markdown id:b46992a6-b75c-45d8-bacb-a8d54ea8de44 tags:
# One time setup for use in Grid'5000
---
- Website: https://discovery.gitlabpages.inria.fr/enoslib/index.html
- Instant chat: https://framateam.org/enoslib
- Source code: https://gitlab.inria.fr/discovery/enoslib
---
Prerequisites:
- Connect to this Jupyter lab instance https://intranet.grid5000.fr/notebooks
- Host the instance on the frontend or a node
- Clone the enoslib tutorias source: `git clone https://gitlab.inria.fr/msimonin/enoslib-tutorials` somewhere in your home directory
- Open this notebook at `enoslib-tutorials/`
%% Cell type:markdown id:9499ca23-b2c7-4b52-a5f2-6c661e8e3497 tags:
## Software dependencies
First things first, you'll need EnOSlib library to go through this tutorial.
- EnOSlib is installed by default, but might not be the latest version
- If you want to install the latest version, follow the steps below
%% Cell type:markdown id:409c2084-6fd2-4db2-9084-470b07d5a9e7 tags:
## Installing the latest version of EnOSlib (might be wild :) )
This installs EnOSlib from the latest version (+ some extra libs) in a virtual environment and install a new Jupyter kernel.
%% Cell type:code id:f78e397c-52bd-4c78-94ef-eacc16170832 tags:
``` python
%%bash
virtualenv -p python3 my_venv
source my_venv/bin/activate
python3 -m pip install -U git+https://gitlab.inria.fr/discovery/enoslib
python3 -m pip install matplotlib seaborn pandas scapy
python3 -m pip install ipykernel
python3 -m ipykernel install --user --name my_venv
```
%% Cell type:markdown id:11e5fb48-fd0d-4fa1-8b5a-5e9f892e9d8a tags:
## Activate this environment
<div class="alert alert-warning">
Goal: Make sure we're using the library installed in the previous virtual environment.
- Refresh the browser
- Switch the kernel to `my_venv`: `kernel > Change kernel` and select `my_venv`
- Re-execute this notebook
</div>
%% Cell type:markdown id:7d60c757-118c-44e3-8374-051d2e2c2bcd tags:
## Optional setup if jupyter is hosted on a node
Regarding the access to the REST API, a node on G5k is considered to belong to the external world.
So you need to set your credentials to be able to connect to the REST API.
**Do this if your jupyter instance is hosted in a dedicated node**
%% Cell type:code id:7e31bc5c-86e2-4db1-a905-841d7d93e7b3 tags:
``` python
from grid5000.cli import auth
# CHANGE THE LOGIN!
auth("msimonin")
```
%% Cell type:markdown id:39a47440-449a-47df-acb5-0c3b760171be tags:
# Checking the library
The following must succeed after switching to the new kernel `my_venv`
%% Cell type:code id:c1f7a79e-9d32-44b1-a805-88cbdcea174c tags:
``` python
# This must not fail (restart the kernel after installing the library)
import enoslib as en
en.check()
```
%% Cell type:code id:9c6b846d-d580-41c5-bdde-eda9f159ff48 tags:
``` python
```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment