diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7fec3f148e52794ae46e9937e9fd0be882461598..a45ef0e03f9ca8111eec4a8af4873d66e973910f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,11 +3,7 @@ pages: tags: [qlf-ci.inria.fr] script: - mkdir -p public - - cp index.html public/index.html - - cp timeline.css public/. - - cp -r exercices public/ - - cp -r figs public/ - + - cp -r . public/ || true artifacts: paths: - public diff --git a/index.html b/index.html index f69b6efaeed60d17525568a37ce78897011a2d49..880546810510c57460569a930cb06052ac53c685 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> -<!-- 2019-11-15 ven. 14:14 --> +<!-- 2019-11-29 ven. 00:28 --> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Distributed experiments on Grid'5000 … and beyond !</title> @@ -231,1056 +231,14 @@ for the JavaScript code in this tag. <body> <div id="content"> <h1 class="title">Distributed experiments on Grid'5000 … and beyond !</h1> -<div id="table-of-contents"> -<h2>Table of Contents</h2> -<div id="text-table-of-contents"> -<ul> -<li><a href="#orgb1aa660">1. Foreword</a> -<ul> -<li><a href="#org33af631">1.1. Existing tools (Grid'5000)</a></li> -<li><a href="#org769cfcb">1.2. EnOSlib quicktour</a></li> -<li><a href="#org4a53df5">1.3. Contributing</a></li> -</ul> -</li> -<li><a href="#orgc7e91b9">2. Before you start</a></li> -<li><a href="#org782c78a">3. Setup on Grid'5000</a></li> -<li><a href="#org441c9dd">4. Your first experiment on Grid'5000</a> -<ul> -<li><a href="#orgf5c5450">4.1. First iteration</a></li> -<li><a href="#org993bcaf">4.2. Let's observe in real-time what is happening</a></li> -<li><a href="#org830e31f">4.3. Discussion</a></li> -<li><a href="#org5f35d8c">4.4. A bit better approach</a></li> -<li><a href="#orgdddf696">4.5. Ninja level</a></li> -<li><a href="#org1193557">4.6. Some references</a></li> -</ul> -</li> -<li><a href="#orgf965b96">5. Providers: to replicate your experiment</a> -<ul> -<li><a href="#orgad471c7">5.1. iperf3 on virtual machines on Grid'5000</a></li> -<li><a href="#org08faf88">5.2. References</a></li> -</ul> -</li> -<li><a href="#org56f9c08">6. Variables in EnOSlib</a> -<ul> -<li><a href="#org2281689">6.1. Discover the <code>run</code> command and its variants</a></li> -<li><a href="#orgd2c7291">6.2. Advanced usages</a></li> -<li><a href="#orgd78a6d8">6.3. Ninja level</a></li> -<li><a href="#orge739be4">6.4. Putting all together</a></li> -<li><a href="#org5dafa08">6.5. Some references</a></li> -</ul> -</li> -<li><a href="#orga167566">7. Modules: for safer remote actions</a> -<ul> -<li><a href="#orgc30170a">7.1. Idempotency</a></li> -<li><a href="#org334a244">7.2. One reason why idempotency is important</a></li> -<li><a href="#orgcf77118">7.3. Idempotency trick</a></li> -<li><a href="#orgcaa5aa0">7.4. General idempotency</a></li> -</ul> -</li> -<li><a href="#orgb39fe8f">8. Tasks: to organize your experiment</a></li> -</ul> -</div> -</div> - -<div id="outline-container-orgb1aa660" class="outline-2"> -<h2 id="orgb1aa660"><span class="section-number-2">1</span> Foreword</h2> -<div class="outline-text-2" id="text-1"> -</div> -<div id="outline-container-org33af631" class="outline-3"> -<h3 id="org33af631"><span class="section-number-3">1.1</span> Existing tools (Grid'5000)</h3> -<div class="outline-text-3" id="text-1-1"> -<ul class="org-ul"> -<li>EnOSlib falls under the <b><b>Experiment management tools</b></b> of the following -list: -<a href="https://www.grid5000.fr/w/Grid5000:Software">https://www.grid5000.fr/w/Grid5000:Software</a></li> - -<li>EnOSlib can target Grid'5000 but also other testbeds (Chameleon, local machines…)</li> - -<li>EnOSlib provides high level constructs to help you with your experiments</li> -</ul> -</div> -</div> - -<div id="outline-container-org769cfcb" class="outline-3"> -<h3 id="org769cfcb"><span class="section-number-3">1.2</span> EnOSlib quicktour</h3> -<div class="outline-text-3" id="text-1-2"> -<ul class="org-ul"> -<li>Documentation: <a href="https://discovery.gitlabpages.inria.fr/enoslib/index.html">https://discovery.gitlabpages.inria.fr/enoslib/index.html</a></li> -<li>Source: <a href="https://gitlab.inria.fr/discovery/enoslib">https://gitlab.inria.fr/discovery/enoslib</a></li> -<li>Reach us on: -<ul class="org-ul"> -<li><a href="https://framateam.org/enoslib">https://framateam.org/enoslib</a></li> -<li><a href="https://gitlab.inria.fr/discovery/enoslib/issues">https://gitlab.inria.fr/discovery/enoslib/issues</a></li> -</ul></li> -</ul> -</div> -</div> - -<div id="outline-container-org4a53df5" class="outline-3"> -<h3 id="org4a53df5"><span class="section-number-3">1.3</span> Contributing</h3> -<div class="outline-text-3" id="text-1-3"> -<p> -<b>Before experimenting</b> -</p> - -<ul class="org-ul"> -<li>Tell us what your plans are: -<ul class="org-ul"> -<li>There might be already users doing similar thing</li> -<li>There might be some missing/hidden pieces in the library you might need</li> -</ul></li> -</ul> - -<p> -<b>While experimenting</b> -</p> - -<ul class="org-ul"> -<li>Write bug reports / ask questions</li> -<li>Fix bugs / add your features</li> -</ul> - -<p> -<b>After experimenting</b> -</p> - -<ul class="org-ul"> -<li>Give your feedback</li> -<li>Add yourself to the list: <a href="https://discovery.gitlabpages.inria.fr/enoslib/theyuseit.html">https://discovery.gitlabpages.inria.fr/enoslib/theyuseit.html</a></li> -</ul> -</div> -</div> -</div> - - -<div id="outline-container-orgc7e91b9" class="outline-2"> -<h2 id="orgc7e91b9"><span class="section-number-2">2</span> Before you start</h2> -<div class="outline-text-2" id="text-2"> -<div class="note"> -<p> -make sure you are familiar with the grid'5000 architecture. see section 1 & 2 of -<a href="https://www.grid5000.fr/w/Getting_Started">https://www.grid5000.fr/w/Getting_Started</a>. note that we won't do this tutorial -we'll prefer to use higher level tools for now. -</p> - -</div> -</div> -</div> - -<div id="outline-container-org782c78a" class="outline-2"> -<h2 id="org782c78a"><span class="section-number-2">3</span> Setup on Grid'5000</h2> -<div class="outline-text-2" id="text-3"> -<p> -Connect to a Grid'5000 frontend of your choice (e.g rennes, nancy …) -</p> - -<ul class="org-ul"> -<li>create a new directory to host all the scripts of the session</li> -<li>bootstrap a new python3 virtualenv</li> -<li>install EnOSlib and configure the access to the API</li> -</ul> - -<div class="org-src-container"> -<pre class="src src-bash">$<span style="color: #7590db;">frontend</span>: mkdir enoslib_seminar -$<span style="color: #7590db;">frontend</span>: cd enoslib_seminar -$<span style="color: #7590db;">frontend</span>: virtualenv --python=python3 venv -$<span style="color: #7590db;">frontend</span>: source venv/bin/activate -$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: pip install enoslib -$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: echo <span style="color: #2d9574;">'</span> -<span style="color: #2d9574;">verify_ssl: False</span> -<span style="color: #2d9574;">'</span> > ~/.python-grid5000.yaml -</pre> -</div> -</div> -</div> - -<div id="outline-container-org441c9dd" class="outline-2"> -<h2 id="org441c9dd"><span class="section-number-2">4</span> Your first experiment on Grid'5000</h2> -<div class="outline-text-2" id="text-4"> -<p> -Let's experiment with <a href="https://iperf.fr/">iperf3</a>: a network bandwidth measuring tool. The goal is -to deploy a simple benchmark between two hosts. -</p> - -<p> -We'll also instrument the deployment in order to visualize in real-time the -network traffic between the hosts. Since this is super common, EnOSlib -exposes a <i>monitoring service</i> that lets you deploy very quickly what is -needed. -</p> -</div> - -<div id="outline-container-orgf5c5450" class="outline-3"> -<h3 id="orgf5c5450"><span class="section-number-3">4.1</span> First iteration</h3> -<div class="outline-text-3" id="text-4-1"> -<p> -We consider the following script -</p> -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.api <span style="color: #4f97d7; font-weight: bold;">import</span> run_command, wait_ssh -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.provider <span style="color: #4f97d7; font-weight: bold;">import</span> G5k -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.configuration <span style="color: #4f97d7; font-weight: bold;">import</span> Configuration, NetworkConfiguration -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.service <span style="color: #4f97d7; font-weight: bold;">import</span> Monitoring - -<span style="color: #4f97d7; font-weight: bold;">import</span> logging - - -<span style="color: #4f97d7; font-weight: bold;">def</span> <span style="color: #bc6ec5; font-weight: bold;">pprint</span><span style="color: #4f97d7;">(</span>d<span style="color: #4f97d7;">)</span>: - <span style="color: #2aa1ae;">"""Utils fonction to pretty print the results"""</span> - <span style="color: #4f97d7; font-weight: bold;">for</span> k, v <span style="color: #4f97d7; font-weight: bold;">in</span> d<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"ok"</span><span style="color: #4f97d7;">]</span>.items<span style="color: #4f97d7;">()</span>: - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"Result for {k}"</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"-"</span> * <span style="color: #a45bad;">70</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDOUT:"</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stdout"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDERR:"</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stderr"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> - - -logging.basicConfig<span style="color: #4f97d7;">(</span>level=logging.INFO<span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Some parameters.</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 1: that you don't need to be on rennes frontend to use nodes</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">from rennes</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 2: Adapt the site/cluster according to the availibility</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">see the Gantt in https://www.grid5000.fr/w/Status</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">SITE</span> = <span style="color: #2d9574;">"rennes"</span> -<span style="color: #7590db;">CLUSTER</span> = <span style="color: #2d9574;">"paravance"</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Configuration object describes the resource we want</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">here: 2 machines on the same cluster using the production network</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #7590db;">network</span> = NetworkConfiguration<span style="color: #4f97d7;">(</span><span style="color: #4f97d7;">id</span>=<span style="color: #2d9574;">"n1"</span>, - <span style="color: #4f97d7;">type</span>=<span style="color: #2d9574;">"prod"</span>, - roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"my_network"</span><span style="color: #bc6ec5;">]</span>, - site=SITE<span style="color: #4f97d7;">)</span> - -<span style="color: #7590db;">conf</span> = Configuration.from_settings<span style="color: #4f97d7;">(</span>job_name=<span style="color: #2d9574;">"enoslib_tutorial"</span>, - job_type=<span style="color: #2d9574;">"allow_classic_ssh"</span><span style="color: #4f97d7;">)</span>\ - .add_network_conf<span style="color: #4f97d7;">(</span>network<span style="color: #4f97d7;">)</span>\ - .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, - cluster=CLUSTER, - nodes=<span style="color: #a45bad;">1</span>, - primary_network=network<span style="color: #4f97d7;">)</span>\ - .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, - cluster=CLUSTER, - nodes=<span style="color: #a45bad;">1</span>, - primary_network=network<span style="color: #4f97d7;">)</span>\ - .finalize<span style="color: #4f97d7;">()</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Reserve the ressources corresponding to the configuration</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">you'll get two **physical machine** (not virtual)</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">the roles object is a dictionnary of the concrete compute resources</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">roles = {"server": [host1], "client": [host2] }</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">provider</span> = G5k<span style="color: #4f97d7;">(</span>conf<span style="color: #4f97d7;">)</span> -<span style="color: #7590db;">roles</span>, <span style="color: #7590db;">networks</span> = provider.init<span style="color: #4f97d7;">()</span> -wait_ssh<span style="color: #4f97d7;">(</span>roles<span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Below is the experimentation logic</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- It installs the bare minimum to run iperf3</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'server' is used to run a iperf3 server</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">started in the background (using tmux)</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'client' connects to that server and initiate a</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">transfer for 30s (duration variable)</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- Report is printed in stdout</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> -<span style="color: #7590db;">duration</span> = <span style="color: #a45bad;">30</span> -run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"apt update && apt install -y iperf3 tmux"</span>, roles=roles<span style="color: #4f97d7;">)</span> -run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"tmux new-session -d 'exec iperf3 -s'"</span>, pattern_hosts=<span style="color: #2d9574;">"server"</span>, roles=roles<span style="color: #4f97d7;">)</span> -<span style="color: #7590db;">result</span> = run_command<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t {duration}"</span>, pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> -pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Destroy the reservation, uncomment when needed</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">provider.destroy()</span> -</pre> -</div> - -<div class="question"> -<p> -How fast is the network between the nodes you have chosen ? -</p> - -</div> - -<div class="note"> -<p> -Before moving to the next questions, you'll need to clean the reservation. -You can either uncomment the line <code>provider.destroy()</code> at the end of the script. -You can also do it manually using the low-level <code>oarstat</code> / <code>oardel</code> tools. -</p> - -<div class="org-src-container"> -<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">get you reservation id</span> -$<span style="color: #7590db;">frontend</span>: oarstat -u -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">release the resources / kill the reservation</span> -$<span style="color: #7590db;">frontend</span>: oardel <the id of the reservation goes here> -</pre> -</div> - -</div> - -<div class="question"> -<p> -Can you adapt the script so that: -</p> -<ol class="org-ol"> -<li>The two nodes are in two different cluster in the same site ?</li> -<li>The two nodes are in two different sites ?</li> -</ol> - -</div> -</div> -</div> - - -<div id="outline-container-org993bcaf" class="outline-3"> -<h3 id="org993bcaf"><span class="section-number-3">4.2</span> Let's observe in real-time what is happening</h3> -<div class="outline-text-3" id="text-4-2"> -<div class="note"> -<p> -Make sure you have cleaned your previous reservations. -</p> - -</div> - -<p> -The following script installs a monitoring stack on your nodes. This is almost -the same script as before except the lines corresponding to the configuration -of the monitoring stack. -</p> - -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.api <span style="color: #4f97d7; font-weight: bold;">import</span> run_command, wait_ssh -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.provider <span style="color: #4f97d7; font-weight: bold;">import</span> G5k -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.configuration <span style="color: #4f97d7; font-weight: bold;">import</span> Configuration, NetworkConfiguration -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.service <span style="color: #4f97d7; font-weight: bold;">import</span> Monitoring - -<span style="color: #4f97d7; font-weight: bold;">import</span> logging - - -<span style="color: #4f97d7; font-weight: bold;">def</span> <span style="color: #bc6ec5; font-weight: bold;">pprint</span><span style="color: #4f97d7;">(</span>d<span style="color: #4f97d7;">)</span>: - <span style="color: #2aa1ae;">"""Utils fonction to pretty print the results"""</span> - <span style="color: #4f97d7; font-weight: bold;">for</span> k, v <span style="color: #4f97d7; font-weight: bold;">in</span> d<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"ok"</span><span style="color: #4f97d7;">]</span>.items<span style="color: #4f97d7;">()</span>: - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"Result for {k}"</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"-"</span> * <span style="color: #a45bad;">70</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDOUT:"</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stdout"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDERR:"</span><span style="color: #4f97d7;">)</span> - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stderr"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> - - -logging.basicConfig<span style="color: #4f97d7;">(</span>level=logging.INFO<span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Some parameters.</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 1: that you don't need to be on rennes frontend to use nodes</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">from rennes</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 2: Adapt the site/cluster according to the availibility</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">see the Gantt in https://www.grid5000.fr/w/Status</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">SITE</span> = <span style="color: #2d9574;">"rennes"</span> -<span style="color: #7590db;">CLUSTER</span> = <span style="color: #2d9574;">"paravance"</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Configuration object describes the resource we want</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">here: 2 machines on the same cluster using the production network</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #7590db;">network</span> = NetworkConfiguration<span style="color: #4f97d7;">(</span><span style="color: #4f97d7;">id</span>=<span style="color: #2d9574;">"n1"</span>, - <span style="color: #4f97d7;">type</span>=<span style="color: #2d9574;">"prod"</span>, - roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"my_network"</span><span style="color: #bc6ec5;">]</span>, - site=SITE<span style="color: #4f97d7;">)</span> - -<span style="color: #7590db;">conf</span> = Configuration.from_settings<span style="color: #4f97d7;">(</span>job_name=<span style="color: #2d9574;">"enoslib_tutorial"</span>, - job_type=<span style="color: #2d9574;">"allow_classic_ssh"</span><span style="color: #4f97d7;">)</span>\ - .add_network_conf<span style="color: #4f97d7;">(</span>network<span style="color: #4f97d7;">)</span>\ - .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, - cluster=CLUSTER, - nodes=<span style="color: #a45bad;">1</span>, - primary_network=network<span style="color: #4f97d7;">)</span>\ - .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, - cluster=CLUSTER, - nodes=<span style="color: #a45bad;">1</span>, - primary_network=network<span style="color: #4f97d7;">)</span>\ - .finalize<span style="color: #4f97d7;">()</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Reserve the ressources corresponding to the configuration</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">you'll get two **physical machine** (not virtual)</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">the roles object is a dictionnary of the concrete compute resources</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">roles = {"server": [host1], "client": [host2] }</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">provider</span> = G5k<span style="color: #4f97d7;">(</span>conf<span style="color: #4f97d7;">)</span> -<span style="color: #7590db;">roles</span>, <span style="color: #7590db;">networks</span> = provider.init<span style="color: #4f97d7;">()</span> -wait_ssh<span style="color: #4f97d7;">(</span>roles<span style="color: #4f97d7;">)</span> - - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">This deploys a monitoring stack. It is composed of</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- some agents on each monitored nodes</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- one collector that collects the metrics from the agents</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- one UI to visualize</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">m</span> = Monitoring<span style="color: #4f97d7;">(</span>collector=roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, - agent=roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span> + roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, - ui=roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span><span style="color: #4f97d7;">)</span> -m.deploy<span style="color: #4f97d7;">()</span> - - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Below is the experimentation logic</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- It installs the bare minimum to run iperf3</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'server' is used to run a iperf3 server</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">started in the background (using tmux)</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'client' connects to that server and initiate a</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">transfer for 600s (duration variable)</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- Report is printed in stdout</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> -<span style="color: #7590db;">duration</span> = <span style="color: #a45bad;">600</span> -run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"apt update && apt install -y iperf3 tmux"</span>, roles=roles<span style="color: #4f97d7;">)</span> -run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"tmux new-session -d 'exec iperf3 -s'"</span>, pattern_hosts=<span style="color: #2d9574;">"server"</span>, roles=roles<span style="color: #4f97d7;">)</span> -<span style="color: #7590db;">result</span> = run_command<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t {duration}"</span>, pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> -pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Destroy the reservation, uncomment when needed</span> -<span style="color: #2aa1ae; background-color: #292e34;">##</span> -<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">provider.destroy()</span> -</pre> -</div> - -<p> -Now, let's visualize the network traffic in real-time ! -</p> -<div class="note"> -<p> -Usually I follow this to access services running inside Grid'5000: -<a href="https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000">https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000</a>. -</p> - - -<p> -Today you can just create a tunnel like this (from your local machine). -</p> - -<div class="org-src-container"> -<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Adapt the node names with the node where grafana (the UI) has been installed</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Replace <login> by your Grid'5000 login</span> -$<span style="color: #7590db;">yourmachine</span>: ssh -NL <span style="color: #a45bad;">3000:paravance-16.rennes.grid5000.fr:3000</span> <login>@access.grid5000.fr - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">point your browser to localhost:3000</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">username/mdp: admin/admin</span> -</pre> -</div> - -</div> - -<p> -Part of the experimenter work also consists in analysing the data. Here it -corresponds in writing the right request to monitor the traffic (check the -Fig. <a href="#orga4a34ec">1</a>). You should be able to visualize such a thing (after a bit -of point and clicks). -</p> - - -<div id="orga4a34ec" class="figure"> -<p><a href="figs/iperf3.png" width="100%" style="border:1px solid black;"><img src="figs/iperf3.png" alt="iperf3.png" width="100%" style="border:1px solid black;" /></a> -</p> -<p><span class="figure-number">Figure 1: </span>iperf3 / monitoring</p> -</div> -</div> -</div> - - -<div id="outline-container-org830e31f" class="outline-3"> -<h3 id="org830e31f"><span class="section-number-3">4.3</span> Discussion</h3> -<div class="outline-text-3" id="text-4-3"> -<p> -So, far this seems (at least for me) very handy. But there might be some problems in our setup: -</p> <ul class="org-ul"> -<li>we aren't isolated from the other users</li> -<li>we aren't isolated from ourself in the sense that the monitoring stack generates its own -network traffic (yes, this is negligible in our case)</li> +<li>First tutorial: <a href="./tuto1/index.html">./tuto1/index.html</a></li> +<li>Second tutorial: <a href="./tuto2/index.html">./tuto2/index.html</a></li> </ul> - -<p> -Sometimes it's desirable to have the following setup (see Fig. <a href="#orgf90cb97">2</a>). -</p> - - -<div id="orgf90cb97" class="figure"> -<p><a href="figs/skydive_enoslib.png"><img src="figs/skydive_enoslib.png" alt="skydive_enoslib.png" /></a> -</p> -<p><span class="figure-number">Figure 2: </span>nodes are using two network interfaces. Monitoring traffic and benchmark traffic are separated.</p> -</div> -</div> -</div> - -<div id="outline-container-org5f35d8c" class="outline-3"> -<h3 id="org5f35d8c"><span class="section-number-3">4.4</span> A bit better approach</h3> -<div class="outline-text-3" id="text-4-4"> -<p> -Analyse/Understand the following script <a href="exercices/iperf3_better.py">exercices/iperf3_better.py</a> -Launch it. -</p> - -<div class="note"> -<p> -On Grid'5000, using the secondary interfaces requires to <b>deploy</b> the nodes: -an new OS will be installed on your nodes. This will give you full control on -the physical machine (root access). This might be longer to run the -experiment due to this deployment phase. -</p> - -</div> -</div> -</div> - -<div id="outline-container-orgdddf696" class="outline-3"> -<h3 id="orgdddf696"><span class="section-number-3">4.5</span> Ninja level</h3> -<div class="outline-text-3" id="text-4-5"> -<p> -Add the <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html#skydive">Skydive</a> service to your deployment. -It should be accessible on the port <code>8082</code> of the analyzer node. You should -get something like Fig. <a href="#orgf90cb97">2</a>. -</p> -</div> -</div> - -<div id="outline-container-org1193557" class="outline-3"> -<h3 id="org1193557"><span class="section-number-3">4.6</span> Some references</h3> -<div class="outline-text-3" id="text-4-6"> -<ul class="org-ul"> -<li>Services: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html</a></li> -</ul> -</div> -</div> -</div> - -<div id="outline-container-orgf965b96" class="outline-2"> -<h2 id="orgf965b96"><span class="section-number-2">5</span> Providers: to replicate your experiment</h2> -<div class="outline-text-2" id="text-5"> -<div class="note"> -<p> -The resources that are used for your experiment are acquired through a -provider. Providers are a mean to decouple the infrastructure code (the code -that gets the resources) from the code that runs the experiment. Changing the -provider allows to replicate the experiment on another testbed. -</p> - -</div> - -<p> -Originally it was used to iterate on the code locally (using the Vagrant -provider) and to only test on Grid'5000 when necessary. -</p> - -<p> -We now have couple of providers that you may picked or mixed. -</p> -</div> - -<div id="outline-container-orgad471c7" class="outline-3"> -<h3 id="orgad471c7"><span class="section-number-3">5.1</span> iperf3 on virtual machines on Grid'5000</h3> -<div class="outline-text-3" id="text-5-1"> -<p> -We'll adapt the initial iperf3 example to use virtual machines instead of -bare-metal machine. -</p> - -<p> -Note that: -</p> - -<ul class="org-ul"> -<li>The configuration object is different</li> -<li>The experimentation logic is the same</li> -<li>Some part have been rewritten using modules (see later in the dedicated section).</li> -</ul> - -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.api <span style="color: #4f97d7; font-weight: bold;">import</span> play_on, wait_ssh -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_vmong5k.provider <span style="color: #4f97d7; font-weight: bold;">import</span> VMonG5k -<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_vmong5k.configuration <span style="color: #4f97d7; font-weight: bold;">import</span> Configuration - -<span style="color: #4f97d7; font-weight: bold;">import</span> logging -<span style="color: #4f97d7; font-weight: bold;">import</span> os - -logging.basicConfig<span style="color: #4f97d7;">(</span>level=logging.DEBUG<span style="color: #4f97d7;">)</span> - -<span style="color: #7590db;">CLUSTER</span> = <span style="color: #2d9574;">"paravance"</span> - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">path to the inventory</span> -<span style="color: #7590db;">inventory</span> = os.path.join<span style="color: #4f97d7;">(</span>os.getcwd<span style="color: #bc6ec5;">()</span>, <span style="color: #2d9574;">"hosts"</span><span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">claim the resources</span> -<span style="color: #7590db;">conf</span> = Configuration.from_settings<span style="color: #4f97d7;">(</span>job_name=<span style="color: #2d9574;">"enoslib_tutorial"</span>, gateway=<span style="color: #a45bad;">True</span><span style="color: #4f97d7;">)</span>\ - .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, - cluster=CLUSTER, - number=<span style="color: #a45bad;">1</span>, - flavour=<span style="color: #2d9574;">"large"</span><span style="color: #4f97d7;">)</span>\ - .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, - cluster=CLUSTER, - number=<span style="color: #a45bad;">1</span>, - flavour=<span style="color: #2d9574;">"medium"</span><span style="color: #4f97d7;">)</span>\ - .finalize<span style="color: #4f97d7;">()</span> - -<span style="color: #7590db;">provider</span> = VMonG5k<span style="color: #4f97d7;">(</span>conf<span style="color: #4f97d7;">)</span> - -<span style="color: #7590db;">roles</span>, <span style="color: #7590db;">networks</span> = provider.init<span style="color: #4f97d7;">()</span> -wait_ssh<span style="color: #4f97d7;">(</span>roles<span style="color: #4f97d7;">)</span> - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Below is the experimentation logic</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">It installs the bare minimum to run iperf3</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">The machine with the role 'server' is used to run a iperf3 server</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">started in the background in a tmux</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">The machine with the role 'client' connects to that server</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Report is printed in stdout</span> -<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> - -<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: - p.apt<span style="color: #4f97d7;">(</span>name=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"iperf3"</span>, <span style="color: #2d9574;">"tmux"</span><span style="color: #bc6ec5;">]</span>, state=<span style="color: #2d9574;">"present"</span><span style="color: #4f97d7;">)</span> - -<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>pattern_hosts=<span style="color: #2d9574;">"server"</span>, roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: - p.shell<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"tmux new-session -d 'exec iperf3 -s'"</span><span style="color: #4f97d7;">)</span> - -<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: - p.shell<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t 30"</span><span style="color: #4f97d7;">)</span> - -<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: - p.shell<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t 30 --logfile iperf3.out"</span><span style="color: #4f97d7;">)</span> - p.fetch<span style="color: #4f97d7;">(</span>src=<span style="color: #2d9574;">"iperf3.out"</span>, dest=<span style="color: #2d9574;">"iperf3.out"</span><span style="color: #4f97d7;">)</span> -</pre> -</div> - -<p> -Using module using the <code>play_on</code> context manager does not bring back the -results of the commands. Iperf3 let's you write the result of the command on -a file. We just need to scp the file back to our local machine using the -<code>fetch</code> module. -</p> -</div> -</div> - - -<div id="outline-container-org08faf88" class="outline-3"> -<h3 id="org08faf88"><span class="section-number-3">5.2</span> References</h3> -<div class="outline-text-3" id="text-5-2"> -<ul class="org-ul"> -<li>Doc: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html</a></li> -<li>Sources: <a href="https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra">https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra</a></li> -</ul> -</div> -</div> -</div> - - -<div id="outline-container-org56f9c08" class="outline-2"> -<h2 id="org56f9c08"><span class="section-number-2">6</span> Variables in EnOSlib</h2> -<div class="outline-text-2" id="text-6"> -<p> -Learn how to get 2 nodes from Grid'5000 and start launching remote commands. -</p> -</div> - -<div id="outline-container-org2281689" class="outline-3"> -<h3 id="org2281689"><span class="section-number-3">6.1</span> Discover the <code>run</code> command and its variants</h3> -<div class="outline-text-3" id="text-6-1"> -<p> -Before proceeding you can add this util function to your code. It is only -used to pretty print a python dictionnary. -</p> -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">def</span> <span style="color: #bc6ec5; font-weight: bold;">pprint</span><span style="color: #4f97d7;">(</span>d<span style="color: #4f97d7;">)</span>: - <span style="color: #4f97d7; font-weight: bold;">import</span> json - <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>json.dumps<span style="color: #bc6ec5;">(</span>d, indent=<span style="color: #a45bad;">4</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> -</pre> -</div> - -<p> -And use the <code>enoslib.api.run</code> function -</p> -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Using run</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> -<span style="color: #7590db;">result</span> = run<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"ping -c 5 {server.address}"</span>, roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span><span style="color: #4f97d7;">)</span> -pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> -</pre> -</div> - -<p> -Or the <code>enoslib.api.run_command</code> function -</p> -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Using run_command 1/2</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> -<span style="color: #7590db;">result</span> = run_command<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"ping -c 5 {server.address}"</span>, - pattern_hosts=<span style="color: #2d9574;">"client"</span>, - roles=roles<span style="color: #4f97d7;">)</span> -pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> -</pre> -</div> - -<div class="note"> -<p> -<code>enoslib.api.run</code> is a specialisation of <code>enoslib.api.run_command</code>. -The latter let's you use <a href="https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html">some fancy patterns</a> to determine the list of hosts to run the command on. -</p> - -<p> -And yes, it uses Ansible behind the scene. -</p> - -</div> -</div> -</div> - -<div id="outline-container-orgd2c7291" class="outline-3"> -<h3 id="orgd2c7291"><span class="section-number-3">6.2</span> Advanced usages</h3> -<div class="outline-text-3" id="text-6-2"> -<div class="note"> -<p> -For all the remote interactions, EnOSlib relies on <a href="https://docs.ansible.com/ansible/latest/index.html">Ansible</a>. Ansible -has it own variables management system. -For instance the task <code>Gather Facts</code> at the beginning of the previous tasks -gathers informations about all/some remote hosts and store them in the -Ansible management system. -</p> - -</div> - -<p> -Let's see what Ansible is gathering about the hosts: -</p> - -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Gather facts</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> -<span style="color: #7590db;">result</span> = gather_facts<span style="color: #4f97d7;">(</span>roles=roles<span style="color: #4f97d7;">)</span> -pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> -</pre> -</div> - -<div class="note"> -<p> -EnOSlib sits in between two worlds: the Python world and the Ansible -world. One common need is to pass a variables from one world to another. -</p> -<ul class="org-ul"> -<li><code>enoslib.api.gather_facts</code> is a way to get, in Python, the variables known -by Ansible about each host.</li> -<li><code>extra_vars</code> keyword argument of <code>enoslib.api.run</code> or <code>enoslib.api.run_command</code> will -pass variables from Python world to Ansible world (global variable)</li> -<li>Injecting a key/value in a <code>Host.extra</code> attribute will make the variable <code>key</code> available to Ansible. -This makes the variables Host specific.</li> -</ul> - -</div> - -<p> -The following inject a global variable in the Ansible world -</p> -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Passing a variable to the Ansible World using a global level variable</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> -<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> -<span style="color: #7590db;">extra_vars</span>=<span style="color: #4f97d7;">{</span><span style="color: #2d9574;">"server_ip"</span>: server.address<span style="color: #4f97d7;">}</span> -<span style="color: #7590db;">result</span> = run<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"ping -c 5 {{ server_ip }}"</span>, roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, extra_vars=extra_vars<span style="color: #4f97d7;">)</span> -</pre> -</div> -</div> -</div> - -<div id="outline-container-orgd78a6d8" class="outline-3"> -<h3 id="orgd78a6d8"><span class="section-number-3">6.3</span> Ninja level</h3> -<div class="outline-text-3" id="text-6-3"> -<p> -The following is valid and inject in the <code>client</code> host a specific variable to -keep track of the server IP. -</p> - -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Passing a variable to the Ansible World using a host level variable</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> -<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> -<span style="color: #7590db;">client</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> -client.extra.update<span style="color: #4f97d7;">(</span>server_ip=server.address<span style="color: #4f97d7;">)</span> -<span style="color: #7590db;">result</span> = run<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"ping -c 5 {{ server_ip }}"</span>, roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span><span style="color: #4f97d7;">)</span> -</pre> -</div> - -<div class="note"> -<p> -Host level variables are interesting to introduce some dissymetry between -hosts while still using one single command to reach all of them. -</p> - -</div> - -<div class="question"> -<p> -How to perform simultaneously the ping to the other machine in calling only -once <code>run</code> or <code>run_command</code> and using host level variables? -</p> - -</div> - -<div class="question"> -<p> -We'd like to create 5 <code>server</code> machines and 5 <code>client</code> machines and start 5 -<b>parallel</b> streams of data using <code>iperf3</code>. To answer this we'll need to learn -a bit more on how variables are handled in EnOSlib. -</p> - -</div> -</div> -</div> - -<div id="outline-container-orge739be4" class="outline-3"> -<h3 id="orge739be4"><span class="section-number-3">6.4</span> Putting all together</h3> -<div class="outline-text-3" id="text-6-4"> -<p> -Access the full file: <a href="exercices/run.py">exercices/run.py</a> -</p> -</div> -</div> - -<div id="outline-container-org5dafa08" class="outline-3"> -<h3 id="org5dafa08"><span class="section-number-3">6.5</span> Some references</h3> -<div class="outline-text-3" id="text-6-5"> -<ul class="org-ul"> -<li>G5k configuration schema: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema</a></li> -<li>API Reference: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html</a></li> -</ul> -</div> -</div> -</div> - -<div id="outline-container-orga167566" class="outline-2"> -<h2 id="orga167566"><span class="section-number-2">7</span> Modules: for safer remote actions</h2> -<div class="outline-text-2" id="text-7"> -<p> -In this section we'll discover the idiomatic way of managing resources on the -remote hosts. A resource can be anything: a user, a file, a line in a file, a -repo on Gitlab, a firewall rule … -</p> -</div> - - -<div id="outline-container-orgc30170a" class="outline-3"> -<h3 id="orgc30170a"><span class="section-number-3">7.1</span> Idempotency</h3> -<div class="outline-text-3" id="text-7-1"> -<p> -Let's assume you want to create a user (<code>foo</code>). With the <code>run_command</code> this would look like: -</p> - -<div class="org-src-container"> -<pre class="src src-python">run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"useradd -m foo"</span>, roles=role<span style="color: #4f97d7;">)</span> -</pre> -</div> - -<p> -The main issue with this code is that it is not <b>idempotent</b>. Running it once -will applied the effect (create the user). But, as soon as the user exist in -the system, this will raise an error. -</p> -</div> -</div> - -<div id="outline-container-org334a244" class="outline-3"> -<h3 id="org334a244"><span class="section-number-3">7.2</span> One reason why idempotency is important</h3> -<div class="outline-text-3" id="text-7-2"> -<p> -Let's consider the following snippet (mispelling the second command is intentional) -</p> -<div class="org-src-container"> -<pre class="src src-python">run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"useradd -m foo"</span>, roles=role<span style="color: #4f97d7;">)</span> -run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"mkdirz plop"</span><span style="color: #4f97d7;">)</span> -</pre> -</div> -<p> -Executing the above leads the system with the user <code>foo</code> created but the the -directory <code>plop</code> not created since the second command fails. -</p> - -<p> -So what you want to do is to fix the second command and re-run the snippet again. -But, you can't do that because <code>useradd</code> isn't idempotent. -</p> -</div> -</div> - -<div id="outline-container-orgcf77118" class="outline-3"> -<h3 id="orgcf77118"><span class="section-number-3">7.3</span> Idempotency trick</h3> -<div class="outline-text-3" id="text-7-3"> -<p> -One easy solution is to protect your call to non idempotent commands with -some ad'hoc tricks -</p> - -<p> -Here it can look like this: -</p> - -<div class="org-src-container"> -<pre class="src src-python">run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"id foo || useradd -m foo"</span>, roles=role<span style="color: #4f97d7;">)</span> -run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"mkdir -p plop"</span><span style="color: #4f97d7;">)</span> -</pre> -</div> - -<p> -<b>What's wrong with that</b> -</p> - -<ul class="org-ul"> -<li>The trick depends on the command</li> -<li>Re-reading the code is more complex: the code focus on the <b><b>how</b></b> not the <b><b>what</b></b></li> -</ul> -</div> -</div> - -<div id="outline-container-orgcaa5aa0" class="outline-3"> -<h3 id="orgcaa5aa0"><span class="section-number-3">7.4</span> General idempotency</h3> -<div class="outline-text-3" id="text-7-4"> -<p> -The idiomatic solution is to use modules (inherited from the Ansible -Modules). The modules are specified in a <b>declarative</b> way and they ensure -<b>idempotency</b> for most of them. -</p> - -<p> -So rewriting the example with modules looks like: -</p> -<div class="org-src-container"> -<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: - p.user<span style="color: #4f97d7;">(</span>name=<span style="color: #2d9574;">"foo"</span>, state=<span style="color: #2d9574;">"present"</span>, create_home=<span style="color: #2d9574;">"yes"</span><span style="color: #4f97d7;">)</span> - p.<span style="color: #4f97d7;">file</span><span style="color: #4f97d7;">(</span>name=<span style="color: #2d9574;">"plop"</span>, state=<span style="color: #2d9574;">"directory"</span><span style="color: #4f97d7;">)</span> -</pre> -</div> - -<p> -<code>enoslib.api.play_on</code> is the entry point to the module system. -</p> - -<p> -You can run this code as many times as you want without any error. You'll -eventually find one user <code>foo</code> and one directory <code>plop</code> in your target -systems. -</p> - -<p> -They are more than 2500 modules: <a href="https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html">https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html</a> -</p> - -<p> -If you can't find what you want you must know that: -</p> -<ul class="org-ul"> -<li>Writing your own module is possible</li> -<li>Falling back to the idempotency trick is reasonable</li> -</ul> -</div> -</div> -</div> - -<div id="outline-container-orgb39fe8f" class="outline-2"> -<h2 id="orgb39fe8f"><span class="section-number-2">8</span> Tasks: to organize your experiment</h2> -<div class="outline-text-2" id="text-8"> -<p> -To discover the Task API, head to <a href="https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html">https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html</a>. -</p> - -<p> -The examples are written for Vagrant but may be changed to whatever provider you like/have. -</p> - -<div class="question"> -<p> -Adapt the <code>iperf3</code> example to provide a command line -</p> -<ul class="org-ul"> -<li><p> -Either using G5k physical machines: -</p> -<div class="org-src-container"> -<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">deploy the dependencies of the experimentation using the G5k provider</span> -myperf g5k - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">launch a performance measurement</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">ideally exposes all the iperf3 client options there ;)</span> -myperf bench -t <span style="color: #a45bad;">120</span> - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Backup the reports / influxdb database</span> -myperf backup -</pre> -</div> - -<p> -myperf destroy -</p></li> - -<li><p> -Either using the virtual machines on Grid'5000: -</p> -<div class="org-src-container"> -<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">deploy the dependencies of the experimentation using the G5k provider</span> -myperf vm5k - -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Subsequent command line should be the same as above</span> -<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">enjoy :)</span> -</pre> -</div></li> -</ul> - -</div> -</div> -</div> </div> <div id="postamble" class="status"> <p class="author">Author: Matthieu Simonin</p> -<p class="date">Created: 2019-11-15 ven. 14:14</p> +<p class="date">Created: 2019-11-29 ven. 00:28</p> <p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p> </div> </body> diff --git a/index.org b/index.org index 777996da50565341ddd8ed47ba611b9e44dd874c..c3c9d6c65454d8fbaa1ebc5464e22d9f93f3b7fc 100644 --- a/index.org +++ b/index.org @@ -4,425 +4,6 @@ #+HTML_HEAD: <link rel="stylesheet" type="text/css" href="timeline.css" /> -#+MACRO: enoslib EnOSlib -#+MACRO: src_host https://gitlab.inria.fr/discovery/enoslib/blob/v4.8.1/enoslib/host.py#L8-14 -#+MACRO: doc_external_access https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000 -#+MACRO: src_provider https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra -#+MACRO: doc_provider https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html -#+MACRO: doc_tasks https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html -#+MACRO: doc_g5k_schema https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema -#+MACRO: doc_api https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html -#+MACRO: doc_services https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html - -* Foreword - -** Existing tools (Grid'5000) - - - {{{enoslib}}} falls under the **Experiment management tools** of the following - list: - https://www.grid5000.fr/w/Grid5000:Software - - - {{{enoslib}}} can target Grid'5000 but also other testbeds (Chameleon, local machines...) - - - {{{enoslib}}} provides high level constructs to help you with your experiments - -** EnOSlib quicktour - - - Documentation: https://discovery.gitlabpages.inria.fr/enoslib/index.html - - Source: https://gitlab.inria.fr/discovery/enoslib - - Reach us on: - + https://framateam.org/enoslib - + https://gitlab.inria.fr/discovery/enoslib/issues - -** Contributing - - *Before experimenting* - - - Tell us what your plans are: - + There might be already users doing similar thing - + There might be some missing/hidden pieces in the library you might need - - *While experimenting* - - - Write bug reports / ask questions - - Fix bugs / add your features - - *After experimenting* - - - Give your feedback - - Add yourself to the list: https://discovery.gitlabpages.inria.fr/enoslib/theyuseit.html - - -* Before you start - - #+begin_note - make sure you are familiar with the grid'5000 architecture. see section 1 & 2 of - https://www.grid5000.fr/w/Getting_Started. note that we won't do this tutorial - we'll prefer to use higher level tools for now. - #+end_note - -* Setup on Grid'5000 - - Connect to a Grid'5000 frontend of your choice (e.g rennes, nancy ...) - - - create a new directory to host all the scripts of the session - - bootstrap a new python3 virtualenv - - install {{{enoslib}}} and configure the access to the API - - #+BEGIN_SRC bash :noeval - $frontend: mkdir enoslib_seminar - $frontend: cd enoslib_seminar - $frontend: virtualenv --python=python3 venv - $frontend: source venv/bin/activate - $frontend(venv): pip install enoslib - $frontend(venv): echo ' - verify_ssl: False - ' > ~/.python-grid5000.yaml - #+END_SRC - -* Your first experiment on Grid'5000 - - Let's experiment with [[https://iperf.fr/][iperf3]]: a network bandwidth measuring tool. The goal is - to deploy a simple benchmark between two hosts. - - We'll also instrument the deployment in order to visualize in real-time the - network traffic between the hosts. Since this is super common, {{{enoslib}}} - exposes a /monitoring service/ that lets you deploy very quickly what is - needed. - -** First iteration - - We consider the following script - #+INCLUDE: exercices/iperf3.py src python - - #+BEGIN_question - How fast is the network between the nodes you have chosen ? - #+END_question - - #+BEGIN_note - Before moving to the next questions, you'll need to clean the reservation. - You can either uncomment the line ~provider.destroy()~ at the end of the script. - You can also do it manually using the low-level ~oarstat~ / ~oardel~ tools. - - #+BEGIN_SRC bash :noeval - # get you reservation id - $frontend: oarstat -u - # release the resources / kill the reservation - $frontend: oardel <the id of the reservation goes here> - #+END_SRC - #+END_note - - #+BEGIN_question - Can you adapt the script so that: - 1. The two nodes are in two different cluster in the same site ? - 2. The two nodes are in two different sites ? - #+END_question - - -** Let's observe in real-time what is happening - - #+BEGIN_note - Make sure you have cleaned your previous reservations. - #+END_note - - The following script installs a monitoring stack on your nodes. This is almost - the same script as before except the lines corresponding to the configuration - of the monitoring stack. - - #+INCLUDE: exercices/iperf3_monitoring.py src python - - Now, let's visualize the network traffic in real-time ! - #+BEGIN_note - Usually I follow this to access services running inside Grid'5000: - {{{doc_external_access}}}. - - - Today you can just create a tunnel like this (from your local machine). - - #+BEGIN_SRC bash :noeval - # Adapt the node names with the node where grafana (the UI) has been installed - # Replace <login> by your Grid'5000 login - $yourmachine: ssh -NL 3000:paravance-16.rennes.grid5000.fr:3000 <login>@access.grid5000.fr - - # point your browser to localhost:3000 - # username/mdp: admin/admin - #+END_SRC - - #+END_note - - Part of the experimenter work also consists in analysing the data. Here it - corresponds in writing the right request to monitor the traffic (check the - Fig. [[fig:iperf3]]). You should be able to visualize such a thing (after a bit - of point and clicks). - - #+CAPTION: iperf3 / monitoring - #+NAME: fig:iperf3 - #+ATTR_HTML: :width 100% :style border:1px solid black; - [[file:figs/iperf3.png][file:figs/iperf3.png]] - - -** Discussion - - So, far this seems (at least for me) very handy. But there might be some problems in our setup: - - we aren't isolated from the other users - - we aren't isolated from ourself in the sense that the monitoring stack generates its own - network traffic (yes, this is negligible in our case) - - Sometimes it's desirable to have the following setup (see Fig. [[fig:two_networks]]). - - #+CAPTION: nodes are using two network interfaces. - #+CAPTION: Monitoring traffic and benchmark traffic are separated. - #+NAME: fig:two_networks - [[file:figs/skydive_enoslib.png][file:figs/skydive_enoslib.png]] - -** A bit better approach - - Analyse/Understand the following script [[file:exercices/iperf3_better.py]] - Launch it. - - #+BEGIN_note - On Grid'5000, using the secondary interfaces requires to *deploy* the nodes: - an new OS will be installed on your nodes. This will give you full control on - the physical machine (root access). This might be longer to run the - experiment due to this deployment phase. - #+END_note - -** Ninja level - - Add the [[https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html#skydive][Skydive]] service to your deployment. - It should be accessible on the port ~8082~ of the analyzer node. You should - get something like Fig. [[fig:two_networks]]. - -** Some references - - - Services: {{{doc_services}}} - -* Providers: to replicate your experiment - - #+BEGIN_note - The resources that are used for your experiment are acquired through a - provider. Providers are a mean to decouple the infrastructure code (the code - that gets the resources) from the code that runs the experiment. Changing the - provider allows to replicate the experiment on another testbed. - #+END_note - - Originally it was used to iterate on the code locally (using the Vagrant - provider) and to only test on Grid'5000 when necessary. - - We now have couple of providers that you may picked or mixed. - -** iperf3 on virtual machines on Grid'5000 - - We'll adapt the initial iperf3 example to use virtual machines instead of - bare-metal machine. - - Note that: - - - The configuration object is different - - The experimentation logic is the same - - Some part have been rewritten using modules (see later in the dedicated section). - - #+INCLUDE: exercices/iperf3_vms.py src python - - Using module using the ~play_on~ context manager does not bring back the - results of the commands. Iperf3 let's you write the result of the command on - a file. We just need to scp the file back to our local machine using the - ~fetch~ module. - - -** References - - - Doc: {{{doc_provider}}} - - Sources: {{{src_provider}}} - - -* Variables in {{{enoslib}}} - - Learn how to get 2 nodes from Grid'5000 and start launching remote commands. - -** Discover the ~run~ command and its variants - - Before proceeding you can add this util function to your code. It is only - used to pretty print a python dictionnary. - #+INCLUDE: exercices/run.py :lines "35-39" src python - - And use the ~enoslib.api.run~ function - #+INCLUDE: exercices/run.py :lines "40-47" src python - - Or the ~enoslib.api.run_command~ function - #+INCLUDE: exercices/run.py :lines "48-56" src python - - #+BEGIN_note - ~enoslib.api.run~ is a specialisation of ~enoslib.api.run_command~. - The latter let's you use [[https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html][some fancy patterns]] to determine the list of hosts to run the command on. - - And yes, it uses Ansible behind the scene. - #+END_note - -** Advanced usages - - #+BEGIN_note - For all the remote interactions, {{{enoslib}}} relies on [[https://docs.ansible.com/ansible/latest/index.html][Ansible]]. Ansible - has it own variables management system. - For instance the task ~Gather Facts~ at the beginning of the previous tasks - gathers informations about all/some remote hosts and store them in the - Ansible management system. - #+END_note - - Let's see what Ansible is gathering about the hosts: - - #+INCLUDE: exercices/run.py :lines "58-65" src python - - #+BEGIN_note - {{{enoslib}}} sits in between two worlds: the Python world and the Ansible - world. One common need is to pass a variables from one world to another. - - ~enoslib.api.gather_facts~ is a way to get, in Python, the variables known - by Ansible about each host. - - ~extra_vars~ keyword argument of ~enoslib.api.run~ or ~enoslib.api.run_command~ will - pass variables from Python world to Ansible world (global variable) - - Injecting a key/value in a ~Host.extra~ attribute will make the variable ~key~ available to Ansible. - This makes the variables Host specific. - #+END_note - - The following inject a global variable in the Ansible world - #+INCLUDE: exercices/run.py :lines "65-71" src python - -** Ninja level - - The following is valid and inject in the ~client~ host a specific variable to - keep track of the server IP. - - #+INCLUDE: exercices/run.py :lines "73-81" src python - - #+BEGIN_note - Host level variables are interesting to introduce some dissymetry between - hosts while still using one single command to reach all of them. - #+END_note - - #+BEGIN_question - How to perform simultaneously the ping to the other machine in calling only - once ~run~ or ~run_command~ and using host level variables? - #+END_question - - #+BEGIN_question - We'd like to create 5 ~server~ machines and 5 ~client~ machines and start 5 - *parallel* streams of data using ~iperf3~. To answer this we'll need to learn - a bit more on how variables are handled in {{{enoslib}}}. - #+END_question - -** Putting all together - Access the full file: [[file:exercices/run.py]] - -** Some references - - - G5k configuration schema: {{{doc_g5k_schema}}} - - API Reference: {{{doc_api}}} - -* Modules: for safer remote actions - - In this section we'll discover the idiomatic way of managing resources on the - remote hosts. A resource can be anything: a user, a file, a line in a file, a - repo on Gitlab, a firewall rule ... - - -** Idempotency - - Let's assume you want to create a user (~foo~). With the ~run_command~ this would look like: - - #+BEGIN_SRC python :noeval - run_command("useradd -m foo", roles=role) - #+END_SRC - - The main issue with this code is that it is not *idempotent*. Running it once - will applied the effect (create the user). But, as soon as the user exist in - the system, this will raise an error. - -** One reason why idempotency is important - - Let's consider the following snippet (mispelling the second command is intentional) - #+BEGIN_SRC python :noeval - run_command("useradd -m foo", roles=role) - run_command("mkdirz plop") - #+END_SRC - Executing the above leads the system with the user ~foo~ created but the the - directory ~plop~ not created since the second command fails. - - So what you want to do is to fix the second command and re-run the snippet again. - But, you can't do that because ~useradd~ isn't idempotent. - -** Idempotency trick - - One easy solution is to protect your call to non idempotent commands with - some ad'hoc tricks - - Here it can look like this: - - #+BEGIN_SRC python :noeval - run_command("id foo || useradd -m foo", roles=role) - run_command("mkdir -p plop") - #+END_SRC - - *What's wrong with that* - - - The trick depends on the command - - Re-reading the code is more complex: the code focus on the **how** not the **what** - -** General idempotency - - The idiomatic solution is to use modules (inherited from the Ansible - Modules). The modules are specified in a *declarative* way and they ensure - *idempotency* for most of them. - - So rewriting the example with modules looks like: - #+BEGIN_SRC python :noeval - with play_on(roles=roles) as p: - p.user(name="foo", state="present", create_home="yes") - p.file(name="plop", state="directory") - #+END_SRC - - ~enoslib.api.play_on~ is the entry point to the module system. - - You can run this code as many times as you want without any error. You'll - eventually find one user ~foo~ and one directory ~plop~ in your target - systems. - - They are more than 2500 modules: https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html - - If you can't find what you want you must know that: - - Writing your own module is possible - - Falling back to the idempotency trick is reasonable - -* Tasks: to organize your experiment - - To discover the Task API, head to {{{doc_tasks}}}. - - The examples are written for Vagrant but may be changed to whatever provider you like/have. - - #+BEGIN_question - Adapt the ~iperf3~ example to provide a command line - - Either using G5k physical machines: - #+BEGIN_SRC bash - # deploy the dependencies of the experimentation using the G5k provider - myperf g5k - - # launch a performance measurement - # ideally exposes all the iperf3 client options there ;) - myperf bench -t 120 - - # Backup the reports / influxdb database - myperf backup - #+END_SRC - - # Destroy the ressources on Grid'5000 - myperf destroy - - - Either using the virtual machines on Grid'5000: - #+BEGIN_SRC bash - # deploy the dependencies of the experimentation using the G5k provider - myperf vm5k - - # Subsequent command line should be the same as above - # enjoy :) - #+END_SRC - #+END_question +- First tutorial: [[./tuto1/index.html]] +- Second tutorial: [[./tuto2/index.html]] diff --git a/tuto1/exercices/.#00_setup.sh b/tuto1/exercices/.#00_setup.sh new file mode 120000 index 0000000000000000000000000000000000000000..7c7e5b335f82fad0ead6fd3f20b98fa0071102f1 --- /dev/null +++ b/tuto1/exercices/.#00_setup.sh @@ -0,0 +1 @@ +msimonin@talouette.27523:1570544298 \ No newline at end of file diff --git a/exercices/iperf3.py b/tuto1/exercices/iperf3.py similarity index 100% rename from exercices/iperf3.py rename to tuto1/exercices/iperf3.py diff --git a/exercices/iperf3_5vms.py b/tuto1/exercices/iperf3_5vms.py similarity index 100% rename from exercices/iperf3_5vms.py rename to tuto1/exercices/iperf3_5vms.py diff --git a/exercices/iperf3_better.py b/tuto1/exercices/iperf3_better.py similarity index 100% rename from exercices/iperf3_better.py rename to tuto1/exercices/iperf3_better.py diff --git a/exercices/iperf3_monitoring.py b/tuto1/exercices/iperf3_monitoring.py similarity index 100% rename from exercices/iperf3_monitoring.py rename to tuto1/exercices/iperf3_monitoring.py diff --git a/exercices/iperf3_vms.py b/tuto1/exercices/iperf3_vms.py similarity index 100% rename from exercices/iperf3_vms.py rename to tuto1/exercices/iperf3_vms.py diff --git a/tuto1/exercices/iperfs_xvms.py b/tuto1/exercices/iperfs_xvms.py new file mode 100644 index 0000000000000000000000000000000000000000..65e478b9d9fc7d28220beb3aa78995704986501b --- /dev/null +++ b/tuto1/exercices/iperfs_xvms.py @@ -0,0 +1,59 @@ +from enoslib.api import play_on, wait_ssh, ensure_python3 +from enoslib.infra.enos_vmong5k.provider import VMonG5k +from enoslib.infra.enos_vmong5k.configuration import Configuration + +import logging +import os + +logging.basicConfig(level=logging.DEBUG) + +CLUSTER = "paravance" +NB_VMS = 4 + +def bench(nb_vms: int) -> None: + # claim the resources + conf = Configuration.from_settings(job_name="enoslib_tutorial_", gateway=True)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + number=nb_vms, + flavour="tiny")\ + .add_machine(roles=["client"], + cluster=CLUSTER, + number=nb_vms, + flavour="tiny")\ + .finalize() + + provider = VMonG5k(conf) + + roles, networks = provider.init() + wait_ssh(roles) + + + servers = roles["server"] + clients = roles["client"] + + for s, c in zip(servers, clients): + c.extra.update(target=s.address) + + # Install python3 and make it the default + ensure_python3(roles=roles) + + with play_on(roles=roles) as p: + 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", "tmux"], + state="present") + + with play_on(pattern_hosts="server", roles=roles) as p: + p.shell("tmux new-session -d 'exec netperf'") + + with play_on(pattern_hosts="client", roles=roles) as p: + p.shell("flent tcp_upload -p totals " + + "-l 60 " + + "-H {{ target }} " + + f"-t 'tcp_upload_{nb_vms}' " + + f"-o tcp_upload_{nb_vms}.png", display_name=f"Benchmarkings with {nb_vms} vms") + p.fetch(src=f"tcp_upload_{nb_vms}.png", dest=f"result_{nb_vms}") + +for nb_vms in [1, 4, 8, 16]: + bench(nb_vms) diff --git a/exercices/run.py b/tuto1/exercices/run.py similarity index 100% rename from exercices/run.py rename to tuto1/exercices/run.py diff --git a/tuto1/exercices/test.py b/tuto1/exercices/test.py new file mode 100644 index 0000000000000000000000000000000000000000..6d47d35a6a2de12792eb726e13bfe6287dbc7a7d --- /dev/null +++ b/tuto1/exercices/test.py @@ -0,0 +1,11 @@ +from enoslib.api import play_on +from enoslib.host import Host + +import logging + + +logging.basicConfig(level=logging.DEBUG) + +roles = {"all": [Host("localhost", extra={"ansible_connection": "local"})]} +with play_on(roles=roles) as p: + p.shell("ls {{ ansible_connection }}") diff --git a/figs/iperf3.png b/tuto1/figs/iperf3.png similarity index 100% rename from figs/iperf3.png rename to tuto1/figs/iperf3.png diff --git a/figs/skydive_enoslib.png b/tuto1/figs/skydive_enoslib.png similarity index 100% rename from figs/skydive_enoslib.png rename to tuto1/figs/skydive_enoslib.png diff --git a/tuto1/index.html b/tuto1/index.html new file mode 100644 index 0000000000000000000000000000000000000000..f69b6efaeed60d17525568a37ce78897011a2d49 --- /dev/null +++ b/tuto1/index.html @@ -0,0 +1,1287 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<head> +<!-- 2019-11-15 ven. 14:14 --> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1" /> +<title>Distributed experiments on Grid'5000 … and beyond !</title> +<meta name="generator" content="Org mode" /> +<meta name="author" content="Matthieu Simonin" /> +<style type="text/css"> + <!--/*--><![CDATA[/*><!--*/ + .title { text-align: center; + margin-bottom: .2em; } + .subtitle { text-align: center; + font-size: medium; + font-weight: bold; + margin-top:0; } + .todo { font-family: monospace; color: red; } + .done { font-family: monospace; color: green; } + .priority { font-family: monospace; color: orange; } + .tag { background-color: #eee; font-family: monospace; + padding: 2px; font-size: 80%; font-weight: normal; } + .timestamp { color: #bebebe; } + .timestamp-kwd { color: #5f9ea0; } + .org-right { margin-left: auto; margin-right: 0px; text-align: right; } + .org-left { margin-left: 0px; margin-right: auto; text-align: left; } + .org-center { margin-left: auto; margin-right: auto; text-align: center; } + .underline { text-decoration: underline; } + #postamble p, #preamble p { font-size: 90%; margin: .2em; } + p.verse { margin-left: 3%; } + pre { + border: 1px solid #ccc; + box-shadow: 3px 3px 3px #eee; + padding: 8pt; + font-family: monospace; + overflow: auto; + margin: 1.2em; + } + pre.src { + position: relative; + overflow: visible; + padding-top: 1.2em; + } + pre.src:before { + display: none; + position: absolute; + background-color: white; + top: -10px; + right: 10px; + padding: 3px; + border: 1px solid black; + } + pre.src:hover:before { display: inline;} + /* Languages per Org manual */ + pre.src-asymptote:before { content: 'Asymptote'; } + pre.src-awk:before { content: 'Awk'; } + pre.src-C:before { content: 'C'; } + /* pre.src-C++ doesn't work in CSS */ + pre.src-clojure:before { content: 'Clojure'; } + pre.src-css:before { content: 'CSS'; } + pre.src-D:before { content: 'D'; } + pre.src-ditaa:before { content: 'ditaa'; } + pre.src-dot:before { content: 'Graphviz'; } + pre.src-calc:before { content: 'Emacs Calc'; } + pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } + pre.src-fortran:before { content: 'Fortran'; } + pre.src-gnuplot:before { content: 'gnuplot'; } + pre.src-haskell:before { content: 'Haskell'; } + pre.src-hledger:before { content: 'hledger'; } + pre.src-java:before { content: 'Java'; } + pre.src-js:before { content: 'Javascript'; } + pre.src-latex:before { content: 'LaTeX'; } + pre.src-ledger:before { content: 'Ledger'; } + pre.src-lisp:before { content: 'Lisp'; } + pre.src-lilypond:before { content: 'Lilypond'; } + pre.src-lua:before { content: 'Lua'; } + pre.src-matlab:before { content: 'MATLAB'; } + pre.src-mscgen:before { content: 'Mscgen'; } + pre.src-ocaml:before { content: 'Objective Caml'; } + pre.src-octave:before { content: 'Octave'; } + pre.src-org:before { content: 'Org mode'; } + pre.src-oz:before { content: 'OZ'; } + pre.src-plantuml:before { content: 'Plantuml'; } + pre.src-processing:before { content: 'Processing.js'; } + pre.src-python:before { content: 'Python'; } + pre.src-R:before { content: 'R'; } + pre.src-ruby:before { content: 'Ruby'; } + pre.src-sass:before { content: 'Sass'; } + pre.src-scheme:before { content: 'Scheme'; } + pre.src-screen:before { content: 'Gnu Screen'; } + pre.src-sed:before { content: 'Sed'; } + pre.src-sh:before { content: 'shell'; } + pre.src-sql:before { content: 'SQL'; } + pre.src-sqlite:before { content: 'SQLite'; } + /* additional languages in org.el's org-babel-load-languages alist */ + pre.src-forth:before { content: 'Forth'; } + pre.src-io:before { content: 'IO'; } + pre.src-J:before { content: 'J'; } + pre.src-makefile:before { content: 'Makefile'; } + pre.src-maxima:before { content: 'Maxima'; } + pre.src-perl:before { content: 'Perl'; } + pre.src-picolisp:before { content: 'Pico Lisp'; } + pre.src-scala:before { content: 'Scala'; } + pre.src-shell:before { content: 'Shell Script'; } + pre.src-ebnf2ps:before { content: 'ebfn2ps'; } + /* additional language identifiers per "defun org-babel-execute" + in ob-*.el */ + pre.src-cpp:before { content: 'C++'; } + pre.src-abc:before { content: 'ABC'; } + pre.src-coq:before { content: 'Coq'; } + pre.src-groovy:before { content: 'Groovy'; } + /* additional language identifiers from org-babel-shell-names in + ob-shell.el: ob-shell is the only babel language using a lambda to put + the execution function name together. */ + pre.src-bash:before { content: 'bash'; } + pre.src-csh:before { content: 'csh'; } + pre.src-ash:before { content: 'ash'; } + pre.src-dash:before { content: 'dash'; } + pre.src-ksh:before { content: 'ksh'; } + pre.src-mksh:before { content: 'mksh'; } + pre.src-posh:before { content: 'posh'; } + /* Additional Emacs modes also supported by the LaTeX listings package */ + pre.src-ada:before { content: 'Ada'; } + pre.src-asm:before { content: 'Assembler'; } + pre.src-caml:before { content: 'Caml'; } + pre.src-delphi:before { content: 'Delphi'; } + pre.src-html:before { content: 'HTML'; } + pre.src-idl:before { content: 'IDL'; } + pre.src-mercury:before { content: 'Mercury'; } + pre.src-metapost:before { content: 'MetaPost'; } + pre.src-modula-2:before { content: 'Modula-2'; } + pre.src-pascal:before { content: 'Pascal'; } + pre.src-ps:before { content: 'PostScript'; } + pre.src-prolog:before { content: 'Prolog'; } + pre.src-simula:before { content: 'Simula'; } + pre.src-tcl:before { content: 'tcl'; } + pre.src-tex:before { content: 'TeX'; } + pre.src-plain-tex:before { content: 'Plain TeX'; } + pre.src-verilog:before { content: 'Verilog'; } + pre.src-vhdl:before { content: 'VHDL'; } + pre.src-xml:before { content: 'XML'; } + pre.src-nxml:before { content: 'XML'; } + /* add a generic configuration mode; LaTeX export needs an additional + (add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */ + pre.src-conf:before { content: 'Configuration File'; } + + table { border-collapse:collapse; } + caption.t-above { caption-side: top; } + caption.t-bottom { caption-side: bottom; } + td, th { vertical-align:top; } + th.org-right { text-align: center; } + th.org-left { text-align: center; } + th.org-center { text-align: center; } + td.org-right { text-align: right; } + td.org-left { text-align: left; } + td.org-center { text-align: center; } + dt { font-weight: bold; } + .footpara { display: inline; } + .footdef { margin-bottom: 1em; } + .figure { padding: 1em; } + .figure p { text-align: center; } + .inlinetask { + padding: 10px; + border: 2px solid gray; + margin: 10px; + background: #ffffcc; + } + #org-div-home-and-up + { text-align: right; font-size: 70%; white-space: nowrap; } + textarea { overflow-x: auto; } + .linenr { font-size: smaller } + .code-highlighted { background-color: #ffff00; } + .org-info-js_info-navigation { border-style: none; } + #org-info-js_console-label + { font-size: 10px; font-weight: bold; white-space: nowrap; } + .org-info-js_search-highlight + { background-color: #ffff00; color: #000000; font-weight: bold; } + .org-svg { width: 90%; } + /*]]>*/--> +</style> +<link rel="stylesheet" type="text/css" href="timeline.css" /> +<script type="text/javascript"> +/* +@licstart The following is the entire license notice for the +JavaScript code in this tag. + +Copyright (C) 2012-2018 Free Software Foundation, Inc. + +The JavaScript code in this tag is free software: you can +redistribute it and/or modify it under the terms of the GNU +General Public License (GNU GPL) as published by the Free Software +Foundation, either version 3 of the License, or (at your option) +any later version. The code is distributed WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + +As additional permission under GNU GPL version 3 section 7, you +may distribute non-source (e.g., minimized or compacted) forms of +that code without the copy of the GNU GPL normally required by +section 4, provided you include this license notice and a URL +through which recipients can access the Corresponding Source. + + +@licend The above is the entire license notice +for the JavaScript code in this tag. +*/ +<!--/*--><![CDATA[/*><!--*/ + function CodeHighlightOn(elem, id) + { + var target = document.getElementById(id); + if(null != target) { + elem.cacheClassElem = elem.className; + elem.cacheClassTarget = target.className; + target.className = "code-highlighted"; + elem.className = "code-highlighted"; + } + } + function CodeHighlightOff(elem, id) + { + var target = document.getElementById(id); + if(elem.cacheClassElem) + elem.className = elem.cacheClassElem; + if(elem.cacheClassTarget) + target.className = elem.cacheClassTarget; + } +/*]]>*///--> +</script> +</head> +<body> +<div id="content"> +<h1 class="title">Distributed experiments on Grid'5000 … and beyond !</h1> +<div id="table-of-contents"> +<h2>Table of Contents</h2> +<div id="text-table-of-contents"> +<ul> +<li><a href="#orgb1aa660">1. Foreword</a> +<ul> +<li><a href="#org33af631">1.1. Existing tools (Grid'5000)</a></li> +<li><a href="#org769cfcb">1.2. EnOSlib quicktour</a></li> +<li><a href="#org4a53df5">1.3. Contributing</a></li> +</ul> +</li> +<li><a href="#orgc7e91b9">2. Before you start</a></li> +<li><a href="#org782c78a">3. Setup on Grid'5000</a></li> +<li><a href="#org441c9dd">4. Your first experiment on Grid'5000</a> +<ul> +<li><a href="#orgf5c5450">4.1. First iteration</a></li> +<li><a href="#org993bcaf">4.2. Let's observe in real-time what is happening</a></li> +<li><a href="#org830e31f">4.3. Discussion</a></li> +<li><a href="#org5f35d8c">4.4. A bit better approach</a></li> +<li><a href="#orgdddf696">4.5. Ninja level</a></li> +<li><a href="#org1193557">4.6. Some references</a></li> +</ul> +</li> +<li><a href="#orgf965b96">5. Providers: to replicate your experiment</a> +<ul> +<li><a href="#orgad471c7">5.1. iperf3 on virtual machines on Grid'5000</a></li> +<li><a href="#org08faf88">5.2. References</a></li> +</ul> +</li> +<li><a href="#org56f9c08">6. Variables in EnOSlib</a> +<ul> +<li><a href="#org2281689">6.1. Discover the <code>run</code> command and its variants</a></li> +<li><a href="#orgd2c7291">6.2. Advanced usages</a></li> +<li><a href="#orgd78a6d8">6.3. Ninja level</a></li> +<li><a href="#orge739be4">6.4. Putting all together</a></li> +<li><a href="#org5dafa08">6.5. Some references</a></li> +</ul> +</li> +<li><a href="#orga167566">7. Modules: for safer remote actions</a> +<ul> +<li><a href="#orgc30170a">7.1. Idempotency</a></li> +<li><a href="#org334a244">7.2. One reason why idempotency is important</a></li> +<li><a href="#orgcf77118">7.3. Idempotency trick</a></li> +<li><a href="#orgcaa5aa0">7.4. General idempotency</a></li> +</ul> +</li> +<li><a href="#orgb39fe8f">8. Tasks: to organize your experiment</a></li> +</ul> +</div> +</div> + +<div id="outline-container-orgb1aa660" class="outline-2"> +<h2 id="orgb1aa660"><span class="section-number-2">1</span> Foreword</h2> +<div class="outline-text-2" id="text-1"> +</div> +<div id="outline-container-org33af631" class="outline-3"> +<h3 id="org33af631"><span class="section-number-3">1.1</span> Existing tools (Grid'5000)</h3> +<div class="outline-text-3" id="text-1-1"> +<ul class="org-ul"> +<li>EnOSlib falls under the <b><b>Experiment management tools</b></b> of the following +list: +<a href="https://www.grid5000.fr/w/Grid5000:Software">https://www.grid5000.fr/w/Grid5000:Software</a></li> + +<li>EnOSlib can target Grid'5000 but also other testbeds (Chameleon, local machines…)</li> + +<li>EnOSlib provides high level constructs to help you with your experiments</li> +</ul> +</div> +</div> + +<div id="outline-container-org769cfcb" class="outline-3"> +<h3 id="org769cfcb"><span class="section-number-3">1.2</span> EnOSlib quicktour</h3> +<div class="outline-text-3" id="text-1-2"> +<ul class="org-ul"> +<li>Documentation: <a href="https://discovery.gitlabpages.inria.fr/enoslib/index.html">https://discovery.gitlabpages.inria.fr/enoslib/index.html</a></li> +<li>Source: <a href="https://gitlab.inria.fr/discovery/enoslib">https://gitlab.inria.fr/discovery/enoslib</a></li> +<li>Reach us on: +<ul class="org-ul"> +<li><a href="https://framateam.org/enoslib">https://framateam.org/enoslib</a></li> +<li><a href="https://gitlab.inria.fr/discovery/enoslib/issues">https://gitlab.inria.fr/discovery/enoslib/issues</a></li> +</ul></li> +</ul> +</div> +</div> + +<div id="outline-container-org4a53df5" class="outline-3"> +<h3 id="org4a53df5"><span class="section-number-3">1.3</span> Contributing</h3> +<div class="outline-text-3" id="text-1-3"> +<p> +<b>Before experimenting</b> +</p> + +<ul class="org-ul"> +<li>Tell us what your plans are: +<ul class="org-ul"> +<li>There might be already users doing similar thing</li> +<li>There might be some missing/hidden pieces in the library you might need</li> +</ul></li> +</ul> + +<p> +<b>While experimenting</b> +</p> + +<ul class="org-ul"> +<li>Write bug reports / ask questions</li> +<li>Fix bugs / add your features</li> +</ul> + +<p> +<b>After experimenting</b> +</p> + +<ul class="org-ul"> +<li>Give your feedback</li> +<li>Add yourself to the list: <a href="https://discovery.gitlabpages.inria.fr/enoslib/theyuseit.html">https://discovery.gitlabpages.inria.fr/enoslib/theyuseit.html</a></li> +</ul> +</div> +</div> +</div> + + +<div id="outline-container-orgc7e91b9" class="outline-2"> +<h2 id="orgc7e91b9"><span class="section-number-2">2</span> Before you start</h2> +<div class="outline-text-2" id="text-2"> +<div class="note"> +<p> +make sure you are familiar with the grid'5000 architecture. see section 1 & 2 of +<a href="https://www.grid5000.fr/w/Getting_Started">https://www.grid5000.fr/w/Getting_Started</a>. note that we won't do this tutorial +we'll prefer to use higher level tools for now. +</p> + +</div> +</div> +</div> + +<div id="outline-container-org782c78a" class="outline-2"> +<h2 id="org782c78a"><span class="section-number-2">3</span> Setup on Grid'5000</h2> +<div class="outline-text-2" id="text-3"> +<p> +Connect to a Grid'5000 frontend of your choice (e.g rennes, nancy …) +</p> + +<ul class="org-ul"> +<li>create a new directory to host all the scripts of the session</li> +<li>bootstrap a new python3 virtualenv</li> +<li>install EnOSlib and configure the access to the API</li> +</ul> + +<div class="org-src-container"> +<pre class="src src-bash">$<span style="color: #7590db;">frontend</span>: mkdir enoslib_seminar +$<span style="color: #7590db;">frontend</span>: cd enoslib_seminar +$<span style="color: #7590db;">frontend</span>: virtualenv --python=python3 venv +$<span style="color: #7590db;">frontend</span>: source venv/bin/activate +$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: pip install enoslib +$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: echo <span style="color: #2d9574;">'</span> +<span style="color: #2d9574;">verify_ssl: False</span> +<span style="color: #2d9574;">'</span> > ~/.python-grid5000.yaml +</pre> +</div> +</div> +</div> + +<div id="outline-container-org441c9dd" class="outline-2"> +<h2 id="org441c9dd"><span class="section-number-2">4</span> Your first experiment on Grid'5000</h2> +<div class="outline-text-2" id="text-4"> +<p> +Let's experiment with <a href="https://iperf.fr/">iperf3</a>: a network bandwidth measuring tool. The goal is +to deploy a simple benchmark between two hosts. +</p> + +<p> +We'll also instrument the deployment in order to visualize in real-time the +network traffic between the hosts. Since this is super common, EnOSlib +exposes a <i>monitoring service</i> that lets you deploy very quickly what is +needed. +</p> +</div> + +<div id="outline-container-orgf5c5450" class="outline-3"> +<h3 id="orgf5c5450"><span class="section-number-3">4.1</span> First iteration</h3> +<div class="outline-text-3" id="text-4-1"> +<p> +We consider the following script +</p> +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.api <span style="color: #4f97d7; font-weight: bold;">import</span> run_command, wait_ssh +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.provider <span style="color: #4f97d7; font-weight: bold;">import</span> G5k +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.configuration <span style="color: #4f97d7; font-weight: bold;">import</span> Configuration, NetworkConfiguration +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.service <span style="color: #4f97d7; font-weight: bold;">import</span> Monitoring + +<span style="color: #4f97d7; font-weight: bold;">import</span> logging + + +<span style="color: #4f97d7; font-weight: bold;">def</span> <span style="color: #bc6ec5; font-weight: bold;">pprint</span><span style="color: #4f97d7;">(</span>d<span style="color: #4f97d7;">)</span>: + <span style="color: #2aa1ae;">"""Utils fonction to pretty print the results"""</span> + <span style="color: #4f97d7; font-weight: bold;">for</span> k, v <span style="color: #4f97d7; font-weight: bold;">in</span> d<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"ok"</span><span style="color: #4f97d7;">]</span>.items<span style="color: #4f97d7;">()</span>: + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"Result for {k}"</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"-"</span> * <span style="color: #a45bad;">70</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDOUT:"</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stdout"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDERR:"</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stderr"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> + + +logging.basicConfig<span style="color: #4f97d7;">(</span>level=logging.INFO<span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Some parameters.</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 1: that you don't need to be on rennes frontend to use nodes</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">from rennes</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 2: Adapt the site/cluster according to the availibility</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">see the Gantt in https://www.grid5000.fr/w/Status</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">SITE</span> = <span style="color: #2d9574;">"rennes"</span> +<span style="color: #7590db;">CLUSTER</span> = <span style="color: #2d9574;">"paravance"</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Configuration object describes the resource we want</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">here: 2 machines on the same cluster using the production network</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #7590db;">network</span> = NetworkConfiguration<span style="color: #4f97d7;">(</span><span style="color: #4f97d7;">id</span>=<span style="color: #2d9574;">"n1"</span>, + <span style="color: #4f97d7;">type</span>=<span style="color: #2d9574;">"prod"</span>, + roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"my_network"</span><span style="color: #bc6ec5;">]</span>, + site=SITE<span style="color: #4f97d7;">)</span> + +<span style="color: #7590db;">conf</span> = Configuration.from_settings<span style="color: #4f97d7;">(</span>job_name=<span style="color: #2d9574;">"enoslib_tutorial"</span>, + job_type=<span style="color: #2d9574;">"allow_classic_ssh"</span><span style="color: #4f97d7;">)</span>\ + .add_network_conf<span style="color: #4f97d7;">(</span>network<span style="color: #4f97d7;">)</span>\ + .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, + cluster=CLUSTER, + nodes=<span style="color: #a45bad;">1</span>, + primary_network=network<span style="color: #4f97d7;">)</span>\ + .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, + cluster=CLUSTER, + nodes=<span style="color: #a45bad;">1</span>, + primary_network=network<span style="color: #4f97d7;">)</span>\ + .finalize<span style="color: #4f97d7;">()</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Reserve the ressources corresponding to the configuration</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">you'll get two **physical machine** (not virtual)</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">the roles object is a dictionnary of the concrete compute resources</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">roles = {"server": [host1], "client": [host2] }</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">provider</span> = G5k<span style="color: #4f97d7;">(</span>conf<span style="color: #4f97d7;">)</span> +<span style="color: #7590db;">roles</span>, <span style="color: #7590db;">networks</span> = provider.init<span style="color: #4f97d7;">()</span> +wait_ssh<span style="color: #4f97d7;">(</span>roles<span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Below is the experimentation logic</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- It installs the bare minimum to run iperf3</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'server' is used to run a iperf3 server</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">started in the background (using tmux)</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'client' connects to that server and initiate a</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">transfer for 30s (duration variable)</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- Report is printed in stdout</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> +<span style="color: #7590db;">duration</span> = <span style="color: #a45bad;">30</span> +run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"apt update && apt install -y iperf3 tmux"</span>, roles=roles<span style="color: #4f97d7;">)</span> +run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"tmux new-session -d 'exec iperf3 -s'"</span>, pattern_hosts=<span style="color: #2d9574;">"server"</span>, roles=roles<span style="color: #4f97d7;">)</span> +<span style="color: #7590db;">result</span> = run_command<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t {duration}"</span>, pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> +pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Destroy the reservation, uncomment when needed</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">provider.destroy()</span> +</pre> +</div> + +<div class="question"> +<p> +How fast is the network between the nodes you have chosen ? +</p> + +</div> + +<div class="note"> +<p> +Before moving to the next questions, you'll need to clean the reservation. +You can either uncomment the line <code>provider.destroy()</code> at the end of the script. +You can also do it manually using the low-level <code>oarstat</code> / <code>oardel</code> tools. +</p> + +<div class="org-src-container"> +<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">get you reservation id</span> +$<span style="color: #7590db;">frontend</span>: oarstat -u +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">release the resources / kill the reservation</span> +$<span style="color: #7590db;">frontend</span>: oardel <the id of the reservation goes here> +</pre> +</div> + +</div> + +<div class="question"> +<p> +Can you adapt the script so that: +</p> +<ol class="org-ol"> +<li>The two nodes are in two different cluster in the same site ?</li> +<li>The two nodes are in two different sites ?</li> +</ol> + +</div> +</div> +</div> + + +<div id="outline-container-org993bcaf" class="outline-3"> +<h3 id="org993bcaf"><span class="section-number-3">4.2</span> Let's observe in real-time what is happening</h3> +<div class="outline-text-3" id="text-4-2"> +<div class="note"> +<p> +Make sure you have cleaned your previous reservations. +</p> + +</div> + +<p> +The following script installs a monitoring stack on your nodes. This is almost +the same script as before except the lines corresponding to the configuration +of the monitoring stack. +</p> + +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.api <span style="color: #4f97d7; font-weight: bold;">import</span> run_command, wait_ssh +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.provider <span style="color: #4f97d7; font-weight: bold;">import</span> G5k +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_g5k.configuration <span style="color: #4f97d7; font-weight: bold;">import</span> Configuration, NetworkConfiguration +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.service <span style="color: #4f97d7; font-weight: bold;">import</span> Monitoring + +<span style="color: #4f97d7; font-weight: bold;">import</span> logging + + +<span style="color: #4f97d7; font-weight: bold;">def</span> <span style="color: #bc6ec5; font-weight: bold;">pprint</span><span style="color: #4f97d7;">(</span>d<span style="color: #4f97d7;">)</span>: + <span style="color: #2aa1ae;">"""Utils fonction to pretty print the results"""</span> + <span style="color: #4f97d7; font-weight: bold;">for</span> k, v <span style="color: #4f97d7; font-weight: bold;">in</span> d<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"ok"</span><span style="color: #4f97d7;">]</span>.items<span style="color: #4f97d7;">()</span>: + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"Result for {k}"</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"-"</span> * <span style="color: #a45bad;">70</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDOUT:"</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stdout"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"STDERR:"</span><span style="color: #4f97d7;">)</span> + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>v.get<span style="color: #bc6ec5;">(</span><span style="color: #2d9574;">"stderr"</span>, <span style="color: #2d9574;">""</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> + + +logging.basicConfig<span style="color: #4f97d7;">(</span>level=logging.INFO<span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Some parameters.</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 1: that you don't need to be on rennes frontend to use nodes</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">from rennes</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Note 2: Adapt the site/cluster according to the availibility</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">see the Gantt in https://www.grid5000.fr/w/Status</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">SITE</span> = <span style="color: #2d9574;">"rennes"</span> +<span style="color: #7590db;">CLUSTER</span> = <span style="color: #2d9574;">"paravance"</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Configuration object describes the resource we want</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">here: 2 machines on the same cluster using the production network</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #7590db;">network</span> = NetworkConfiguration<span style="color: #4f97d7;">(</span><span style="color: #4f97d7;">id</span>=<span style="color: #2d9574;">"n1"</span>, + <span style="color: #4f97d7;">type</span>=<span style="color: #2d9574;">"prod"</span>, + roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"my_network"</span><span style="color: #bc6ec5;">]</span>, + site=SITE<span style="color: #4f97d7;">)</span> + +<span style="color: #7590db;">conf</span> = Configuration.from_settings<span style="color: #4f97d7;">(</span>job_name=<span style="color: #2d9574;">"enoslib_tutorial"</span>, + job_type=<span style="color: #2d9574;">"allow_classic_ssh"</span><span style="color: #4f97d7;">)</span>\ + .add_network_conf<span style="color: #4f97d7;">(</span>network<span style="color: #4f97d7;">)</span>\ + .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, + cluster=CLUSTER, + nodes=<span style="color: #a45bad;">1</span>, + primary_network=network<span style="color: #4f97d7;">)</span>\ + .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, + cluster=CLUSTER, + nodes=<span style="color: #a45bad;">1</span>, + primary_network=network<span style="color: #4f97d7;">)</span>\ + .finalize<span style="color: #4f97d7;">()</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Reserve the ressources corresponding to the configuration</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">you'll get two **physical machine** (not virtual)</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">the roles object is a dictionnary of the concrete compute resources</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">roles = {"server": [host1], "client": [host2] }</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">provider</span> = G5k<span style="color: #4f97d7;">(</span>conf<span style="color: #4f97d7;">)</span> +<span style="color: #7590db;">roles</span>, <span style="color: #7590db;">networks</span> = provider.init<span style="color: #4f97d7;">()</span> +wait_ssh<span style="color: #4f97d7;">(</span>roles<span style="color: #4f97d7;">)</span> + + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">This deploys a monitoring stack. It is composed of</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- some agents on each monitored nodes</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- one collector that collects the metrics from the agents</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- one UI to visualize</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">m</span> = Monitoring<span style="color: #4f97d7;">(</span>collector=roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, + agent=roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span> + roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, + ui=roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span><span style="color: #4f97d7;">)</span> +m.deploy<span style="color: #4f97d7;">()</span> + + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Below is the experimentation logic</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- It installs the bare minimum to run iperf3</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'server' is used to run a iperf3 server</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">started in the background (using tmux)</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- The machine with the role 'client' connects to that server and initiate a</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">transfer for 600s (duration variable)</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">- Report is printed in stdout</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> +<span style="color: #7590db;">duration</span> = <span style="color: #a45bad;">600</span> +run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"apt update && apt install -y iperf3 tmux"</span>, roles=roles<span style="color: #4f97d7;">)</span> +run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"tmux new-session -d 'exec iperf3 -s'"</span>, pattern_hosts=<span style="color: #2d9574;">"server"</span>, roles=roles<span style="color: #4f97d7;">)</span> +<span style="color: #7590db;">result</span> = run_command<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t {duration}"</span>, pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> +pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">## </span><span style="color: #2aa1ae; background-color: #292e34;">Destroy the reservation, uncomment when needed</span> +<span style="color: #2aa1ae; background-color: #292e34;">##</span> +<span style="color: #2aa1ae; background-color: #292e34;">######################################################################</span> + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">provider.destroy()</span> +</pre> +</div> + +<p> +Now, let's visualize the network traffic in real-time ! +</p> +<div class="note"> +<p> +Usually I follow this to access services running inside Grid'5000: +<a href="https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000">https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000</a>. +</p> + + +<p> +Today you can just create a tunnel like this (from your local machine). +</p> + +<div class="org-src-container"> +<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Adapt the node names with the node where grafana (the UI) has been installed</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Replace <login> by your Grid'5000 login</span> +$<span style="color: #7590db;">yourmachine</span>: ssh -NL <span style="color: #a45bad;">3000:paravance-16.rennes.grid5000.fr:3000</span> <login>@access.grid5000.fr + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">point your browser to localhost:3000</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">username/mdp: admin/admin</span> +</pre> +</div> + +</div> + +<p> +Part of the experimenter work also consists in analysing the data. Here it +corresponds in writing the right request to monitor the traffic (check the +Fig. <a href="#orga4a34ec">1</a>). You should be able to visualize such a thing (after a bit +of point and clicks). +</p> + + +<div id="orga4a34ec" class="figure"> +<p><a href="figs/iperf3.png" width="100%" style="border:1px solid black;"><img src="figs/iperf3.png" alt="iperf3.png" width="100%" style="border:1px solid black;" /></a> +</p> +<p><span class="figure-number">Figure 1: </span>iperf3 / monitoring</p> +</div> +</div> +</div> + + +<div id="outline-container-org830e31f" class="outline-3"> +<h3 id="org830e31f"><span class="section-number-3">4.3</span> Discussion</h3> +<div class="outline-text-3" id="text-4-3"> +<p> +So, far this seems (at least for me) very handy. But there might be some problems in our setup: +</p> +<ul class="org-ul"> +<li>we aren't isolated from the other users</li> +<li>we aren't isolated from ourself in the sense that the monitoring stack generates its own +network traffic (yes, this is negligible in our case)</li> +</ul> + +<p> +Sometimes it's desirable to have the following setup (see Fig. <a href="#orgf90cb97">2</a>). +</p> + + +<div id="orgf90cb97" class="figure"> +<p><a href="figs/skydive_enoslib.png"><img src="figs/skydive_enoslib.png" alt="skydive_enoslib.png" /></a> +</p> +<p><span class="figure-number">Figure 2: </span>nodes are using two network interfaces. Monitoring traffic and benchmark traffic are separated.</p> +</div> +</div> +</div> + +<div id="outline-container-org5f35d8c" class="outline-3"> +<h3 id="org5f35d8c"><span class="section-number-3">4.4</span> A bit better approach</h3> +<div class="outline-text-3" id="text-4-4"> +<p> +Analyse/Understand the following script <a href="exercices/iperf3_better.py">exercices/iperf3_better.py</a> +Launch it. +</p> + +<div class="note"> +<p> +On Grid'5000, using the secondary interfaces requires to <b>deploy</b> the nodes: +an new OS will be installed on your nodes. This will give you full control on +the physical machine (root access). This might be longer to run the +experiment due to this deployment phase. +</p> + +</div> +</div> +</div> + +<div id="outline-container-orgdddf696" class="outline-3"> +<h3 id="orgdddf696"><span class="section-number-3">4.5</span> Ninja level</h3> +<div class="outline-text-3" id="text-4-5"> +<p> +Add the <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html#skydive">Skydive</a> service to your deployment. +It should be accessible on the port <code>8082</code> of the analyzer node. You should +get something like Fig. <a href="#orgf90cb97">2</a>. +</p> +</div> +</div> + +<div id="outline-container-org1193557" class="outline-3"> +<h3 id="org1193557"><span class="section-number-3">4.6</span> Some references</h3> +<div class="outline-text-3" id="text-4-6"> +<ul class="org-ul"> +<li>Services: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html</a></li> +</ul> +</div> +</div> +</div> + +<div id="outline-container-orgf965b96" class="outline-2"> +<h2 id="orgf965b96"><span class="section-number-2">5</span> Providers: to replicate your experiment</h2> +<div class="outline-text-2" id="text-5"> +<div class="note"> +<p> +The resources that are used for your experiment are acquired through a +provider. Providers are a mean to decouple the infrastructure code (the code +that gets the resources) from the code that runs the experiment. Changing the +provider allows to replicate the experiment on another testbed. +</p> + +</div> + +<p> +Originally it was used to iterate on the code locally (using the Vagrant +provider) and to only test on Grid'5000 when necessary. +</p> + +<p> +We now have couple of providers that you may picked or mixed. +</p> +</div> + +<div id="outline-container-orgad471c7" class="outline-3"> +<h3 id="orgad471c7"><span class="section-number-3">5.1</span> iperf3 on virtual machines on Grid'5000</h3> +<div class="outline-text-3" id="text-5-1"> +<p> +We'll adapt the initial iperf3 example to use virtual machines instead of +bare-metal machine. +</p> + +<p> +Note that: +</p> + +<ul class="org-ul"> +<li>The configuration object is different</li> +<li>The experimentation logic is the same</li> +<li>Some part have been rewritten using modules (see later in the dedicated section).</li> +</ul> + +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.api <span style="color: #4f97d7; font-weight: bold;">import</span> play_on, wait_ssh +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_vmong5k.provider <span style="color: #4f97d7; font-weight: bold;">import</span> VMonG5k +<span style="color: #4f97d7; font-weight: bold;">from</span> enoslib.infra.enos_vmong5k.configuration <span style="color: #4f97d7; font-weight: bold;">import</span> Configuration + +<span style="color: #4f97d7; font-weight: bold;">import</span> logging +<span style="color: #4f97d7; font-weight: bold;">import</span> os + +logging.basicConfig<span style="color: #4f97d7;">(</span>level=logging.DEBUG<span style="color: #4f97d7;">)</span> + +<span style="color: #7590db;">CLUSTER</span> = <span style="color: #2d9574;">"paravance"</span> + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">path to the inventory</span> +<span style="color: #7590db;">inventory</span> = os.path.join<span style="color: #4f97d7;">(</span>os.getcwd<span style="color: #bc6ec5;">()</span>, <span style="color: #2d9574;">"hosts"</span><span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">claim the resources</span> +<span style="color: #7590db;">conf</span> = Configuration.from_settings<span style="color: #4f97d7;">(</span>job_name=<span style="color: #2d9574;">"enoslib_tutorial"</span>, gateway=<span style="color: #a45bad;">True</span><span style="color: #4f97d7;">)</span>\ + .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #bc6ec5;">]</span>, + cluster=CLUSTER, + number=<span style="color: #a45bad;">1</span>, + flavour=<span style="color: #2d9574;">"large"</span><span style="color: #4f97d7;">)</span>\ + .add_machine<span style="color: #4f97d7;">(</span>roles=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, + cluster=CLUSTER, + number=<span style="color: #a45bad;">1</span>, + flavour=<span style="color: #2d9574;">"medium"</span><span style="color: #4f97d7;">)</span>\ + .finalize<span style="color: #4f97d7;">()</span> + +<span style="color: #7590db;">provider</span> = VMonG5k<span style="color: #4f97d7;">(</span>conf<span style="color: #4f97d7;">)</span> + +<span style="color: #7590db;">roles</span>, <span style="color: #7590db;">networks</span> = provider.init<span style="color: #4f97d7;">()</span> +wait_ssh<span style="color: #4f97d7;">(</span>roles<span style="color: #4f97d7;">)</span> + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Below is the experimentation logic</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">It installs the bare minimum to run iperf3</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">The machine with the role 'server' is used to run a iperf3 server</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">started in the background in a tmux</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">The machine with the role 'client' connects to that server</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Report is printed in stdout</span> +<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> + +<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: + p.apt<span style="color: #4f97d7;">(</span>name=<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"iperf3"</span>, <span style="color: #2d9574;">"tmux"</span><span style="color: #bc6ec5;">]</span>, state=<span style="color: #2d9574;">"present"</span><span style="color: #4f97d7;">)</span> + +<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>pattern_hosts=<span style="color: #2d9574;">"server"</span>, roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: + p.shell<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"tmux new-session -d 'exec iperf3 -s'"</span><span style="color: #4f97d7;">)</span> + +<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: + p.shell<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t 30"</span><span style="color: #4f97d7;">)</span> + +<span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>pattern_hosts=<span style="color: #2d9574;">"client"</span>, roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: + p.shell<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"iperf3 -c {server.address} -t 30 --logfile iperf3.out"</span><span style="color: #4f97d7;">)</span> + p.fetch<span style="color: #4f97d7;">(</span>src=<span style="color: #2d9574;">"iperf3.out"</span>, dest=<span style="color: #2d9574;">"iperf3.out"</span><span style="color: #4f97d7;">)</span> +</pre> +</div> + +<p> +Using module using the <code>play_on</code> context manager does not bring back the +results of the commands. Iperf3 let's you write the result of the command on +a file. We just need to scp the file back to our local machine using the +<code>fetch</code> module. +</p> +</div> +</div> + + +<div id="outline-container-org08faf88" class="outline-3"> +<h3 id="org08faf88"><span class="section-number-3">5.2</span> References</h3> +<div class="outline-text-3" id="text-5-2"> +<ul class="org-ul"> +<li>Doc: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html</a></li> +<li>Sources: <a href="https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra">https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra</a></li> +</ul> +</div> +</div> +</div> + + +<div id="outline-container-org56f9c08" class="outline-2"> +<h2 id="org56f9c08"><span class="section-number-2">6</span> Variables in EnOSlib</h2> +<div class="outline-text-2" id="text-6"> +<p> +Learn how to get 2 nodes from Grid'5000 and start launching remote commands. +</p> +</div> + +<div id="outline-container-org2281689" class="outline-3"> +<h3 id="org2281689"><span class="section-number-3">6.1</span> Discover the <code>run</code> command and its variants</h3> +<div class="outline-text-3" id="text-6-1"> +<p> +Before proceeding you can add this util function to your code. It is only +used to pretty print a python dictionnary. +</p> +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">def</span> <span style="color: #bc6ec5; font-weight: bold;">pprint</span><span style="color: #4f97d7;">(</span>d<span style="color: #4f97d7;">)</span>: + <span style="color: #4f97d7; font-weight: bold;">import</span> json + <span style="color: #4f97d7; font-weight: bold;">print</span><span style="color: #4f97d7;">(</span>json.dumps<span style="color: #bc6ec5;">(</span>d, indent=<span style="color: #a45bad;">4</span><span style="color: #bc6ec5;">)</span><span style="color: #4f97d7;">)</span> +</pre> +</div> + +<p> +And use the <code>enoslib.api.run</code> function +</p> +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Using run</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> +<span style="color: #7590db;">result</span> = run<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"ping -c 5 {server.address}"</span>, roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span><span style="color: #4f97d7;">)</span> +pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> +</pre> +</div> + +<p> +Or the <code>enoslib.api.run_command</code> function +</p> +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Using run_command 1/2</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> +<span style="color: #7590db;">result</span> = run_command<span style="color: #4f97d7;">(</span>f<span style="color: #2d9574;">"ping -c 5 {server.address}"</span>, + pattern_hosts=<span style="color: #2d9574;">"client"</span>, + roles=roles<span style="color: #4f97d7;">)</span> +pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> +</pre> +</div> + +<div class="note"> +<p> +<code>enoslib.api.run</code> is a specialisation of <code>enoslib.api.run_command</code>. +The latter let's you use <a href="https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html">some fancy patterns</a> to determine the list of hosts to run the command on. +</p> + +<p> +And yes, it uses Ansible behind the scene. +</p> + +</div> +</div> +</div> + +<div id="outline-container-orgd2c7291" class="outline-3"> +<h3 id="orgd2c7291"><span class="section-number-3">6.2</span> Advanced usages</h3> +<div class="outline-text-3" id="text-6-2"> +<div class="note"> +<p> +For all the remote interactions, EnOSlib relies on <a href="https://docs.ansible.com/ansible/latest/index.html">Ansible</a>. Ansible +has it own variables management system. +For instance the task <code>Gather Facts</code> at the beginning of the previous tasks +gathers informations about all/some remote hosts and store them in the +Ansible management system. +</p> + +</div> + +<p> +Let's see what Ansible is gathering about the hosts: +</p> + +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Gather facts</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> +<span style="color: #7590db;">result</span> = gather_facts<span style="color: #4f97d7;">(</span>roles=roles<span style="color: #4f97d7;">)</span> +pprint<span style="color: #4f97d7;">(</span>result<span style="color: #4f97d7;">)</span> +</pre> +</div> + +<div class="note"> +<p> +EnOSlib sits in between two worlds: the Python world and the Ansible +world. One common need is to pass a variables from one world to another. +</p> +<ul class="org-ul"> +<li><code>enoslib.api.gather_facts</code> is a way to get, in Python, the variables known +by Ansible about each host.</li> +<li><code>extra_vars</code> keyword argument of <code>enoslib.api.run</code> or <code>enoslib.api.run_command</code> will +pass variables from Python world to Ansible world (global variable)</li> +<li>Injecting a key/value in a <code>Host.extra</code> attribute will make the variable <code>key</code> available to Ansible. +This makes the variables Host specific.</li> +</ul> + +</div> + +<p> +The following inject a global variable in the Ansible world +</p> +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Passing a variable to the Ansible World using a global level variable</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> +<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> +<span style="color: #7590db;">extra_vars</span>=<span style="color: #4f97d7;">{</span><span style="color: #2d9574;">"server_ip"</span>: server.address<span style="color: #4f97d7;">}</span> +<span style="color: #7590db;">result</span> = run<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"ping -c 5 {{ server_ip }}"</span>, roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span>, extra_vars=extra_vars<span style="color: #4f97d7;">)</span> +</pre> +</div> +</div> +</div> + +<div id="outline-container-orgd78a6d8" class="outline-3"> +<h3 id="orgd78a6d8"><span class="section-number-3">6.3</span> Ninja level</h3> +<div class="outline-text-3" id="text-6-3"> +<p> +The following is valid and inject in the <code>client</code> host a specific variable to +keep track of the server IP. +</p> + +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">---</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Passing a variable to the Ansible World using a host level variable</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">--------------------------------------------------------------------</span> +<span style="color: #7590db;">server</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"server"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> +<span style="color: #7590db;">client</span> = roles<span style="color: #4f97d7;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #4f97d7;">][</span><span style="color: #a45bad;">0</span><span style="color: #4f97d7;">]</span> +client.extra.update<span style="color: #4f97d7;">(</span>server_ip=server.address<span style="color: #4f97d7;">)</span> +<span style="color: #7590db;">result</span> = run<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"ping -c 5 {{ server_ip }}"</span>, roles<span style="color: #bc6ec5;">[</span><span style="color: #2d9574;">"client"</span><span style="color: #bc6ec5;">]</span><span style="color: #4f97d7;">)</span> +</pre> +</div> + +<div class="note"> +<p> +Host level variables are interesting to introduce some dissymetry between +hosts while still using one single command to reach all of them. +</p> + +</div> + +<div class="question"> +<p> +How to perform simultaneously the ping to the other machine in calling only +once <code>run</code> or <code>run_command</code> and using host level variables? +</p> + +</div> + +<div class="question"> +<p> +We'd like to create 5 <code>server</code> machines and 5 <code>client</code> machines and start 5 +<b>parallel</b> streams of data using <code>iperf3</code>. To answer this we'll need to learn +a bit more on how variables are handled in EnOSlib. +</p> + +</div> +</div> +</div> + +<div id="outline-container-orge739be4" class="outline-3"> +<h3 id="orge739be4"><span class="section-number-3">6.4</span> Putting all together</h3> +<div class="outline-text-3" id="text-6-4"> +<p> +Access the full file: <a href="exercices/run.py">exercices/run.py</a> +</p> +</div> +</div> + +<div id="outline-container-org5dafa08" class="outline-3"> +<h3 id="org5dafa08"><span class="section-number-3">6.5</span> Some references</h3> +<div class="outline-text-3" id="text-6-5"> +<ul class="org-ul"> +<li>G5k configuration schema: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema</a></li> +<li>API Reference: <a href="https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html">https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html</a></li> +</ul> +</div> +</div> +</div> + +<div id="outline-container-orga167566" class="outline-2"> +<h2 id="orga167566"><span class="section-number-2">7</span> Modules: for safer remote actions</h2> +<div class="outline-text-2" id="text-7"> +<p> +In this section we'll discover the idiomatic way of managing resources on the +remote hosts. A resource can be anything: a user, a file, a line in a file, a +repo on Gitlab, a firewall rule … +</p> +</div> + + +<div id="outline-container-orgc30170a" class="outline-3"> +<h3 id="orgc30170a"><span class="section-number-3">7.1</span> Idempotency</h3> +<div class="outline-text-3" id="text-7-1"> +<p> +Let's assume you want to create a user (<code>foo</code>). With the <code>run_command</code> this would look like: +</p> + +<div class="org-src-container"> +<pre class="src src-python">run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"useradd -m foo"</span>, roles=role<span style="color: #4f97d7;">)</span> +</pre> +</div> + +<p> +The main issue with this code is that it is not <b>idempotent</b>. Running it once +will applied the effect (create the user). But, as soon as the user exist in +the system, this will raise an error. +</p> +</div> +</div> + +<div id="outline-container-org334a244" class="outline-3"> +<h3 id="org334a244"><span class="section-number-3">7.2</span> One reason why idempotency is important</h3> +<div class="outline-text-3" id="text-7-2"> +<p> +Let's consider the following snippet (mispelling the second command is intentional) +</p> +<div class="org-src-container"> +<pre class="src src-python">run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"useradd -m foo"</span>, roles=role<span style="color: #4f97d7;">)</span> +run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"mkdirz plop"</span><span style="color: #4f97d7;">)</span> +</pre> +</div> +<p> +Executing the above leads the system with the user <code>foo</code> created but the the +directory <code>plop</code> not created since the second command fails. +</p> + +<p> +So what you want to do is to fix the second command and re-run the snippet again. +But, you can't do that because <code>useradd</code> isn't idempotent. +</p> +</div> +</div> + +<div id="outline-container-orgcf77118" class="outline-3"> +<h3 id="orgcf77118"><span class="section-number-3">7.3</span> Idempotency trick</h3> +<div class="outline-text-3" id="text-7-3"> +<p> +One easy solution is to protect your call to non idempotent commands with +some ad'hoc tricks +</p> + +<p> +Here it can look like this: +</p> + +<div class="org-src-container"> +<pre class="src src-python">run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"id foo || useradd -m foo"</span>, roles=role<span style="color: #4f97d7;">)</span> +run_command<span style="color: #4f97d7;">(</span><span style="color: #2d9574;">"mkdir -p plop"</span><span style="color: #4f97d7;">)</span> +</pre> +</div> + +<p> +<b>What's wrong with that</b> +</p> + +<ul class="org-ul"> +<li>The trick depends on the command</li> +<li>Re-reading the code is more complex: the code focus on the <b><b>how</b></b> not the <b><b>what</b></b></li> +</ul> +</div> +</div> + +<div id="outline-container-orgcaa5aa0" class="outline-3"> +<h3 id="orgcaa5aa0"><span class="section-number-3">7.4</span> General idempotency</h3> +<div class="outline-text-3" id="text-7-4"> +<p> +The idiomatic solution is to use modules (inherited from the Ansible +Modules). The modules are specified in a <b>declarative</b> way and they ensure +<b>idempotency</b> for most of them. +</p> + +<p> +So rewriting the example with modules looks like: +</p> +<div class="org-src-container"> +<pre class="src src-python"><span style="color: #4f97d7; font-weight: bold;">with</span> play_on<span style="color: #4f97d7;">(</span>roles=roles<span style="color: #4f97d7;">)</span> <span style="color: #4f97d7; font-weight: bold;">as</span> p: + p.user<span style="color: #4f97d7;">(</span>name=<span style="color: #2d9574;">"foo"</span>, state=<span style="color: #2d9574;">"present"</span>, create_home=<span style="color: #2d9574;">"yes"</span><span style="color: #4f97d7;">)</span> + p.<span style="color: #4f97d7;">file</span><span style="color: #4f97d7;">(</span>name=<span style="color: #2d9574;">"plop"</span>, state=<span style="color: #2d9574;">"directory"</span><span style="color: #4f97d7;">)</span> +</pre> +</div> + +<p> +<code>enoslib.api.play_on</code> is the entry point to the module system. +</p> + +<p> +You can run this code as many times as you want without any error. You'll +eventually find one user <code>foo</code> and one directory <code>plop</code> in your target +systems. +</p> + +<p> +They are more than 2500 modules: <a href="https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html">https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html</a> +</p> + +<p> +If you can't find what you want you must know that: +</p> +<ul class="org-ul"> +<li>Writing your own module is possible</li> +<li>Falling back to the idempotency trick is reasonable</li> +</ul> +</div> +</div> +</div> + +<div id="outline-container-orgb39fe8f" class="outline-2"> +<h2 id="orgb39fe8f"><span class="section-number-2">8</span> Tasks: to organize your experiment</h2> +<div class="outline-text-2" id="text-8"> +<p> +To discover the Task API, head to <a href="https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html">https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html</a>. +</p> + +<p> +The examples are written for Vagrant but may be changed to whatever provider you like/have. +</p> + +<div class="question"> +<p> +Adapt the <code>iperf3</code> example to provide a command line +</p> +<ul class="org-ul"> +<li><p> +Either using G5k physical machines: +</p> +<div class="org-src-container"> +<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">deploy the dependencies of the experimentation using the G5k provider</span> +myperf g5k + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">launch a performance measurement</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">ideally exposes all the iperf3 client options there ;)</span> +myperf bench -t <span style="color: #a45bad;">120</span> + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Backup the reports / influxdb database</span> +myperf backup +</pre> +</div> + +<p> +myperf destroy +</p></li> + +<li><p> +Either using the virtual machines on Grid'5000: +</p> +<div class="org-src-container"> +<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">deploy the dependencies of the experimentation using the G5k provider</span> +myperf vm5k + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Subsequent command line should be the same as above</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">enjoy :)</span> +</pre> +</div></li> +</ul> + +</div> +</div> +</div> +</div> +<div id="postamble" class="status"> +<p class="author">Author: Matthieu Simonin</p> +<p class="date">Created: 2019-11-15 ven. 14:14</p> +<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p> +</div> +</body> +</html> diff --git a/tuto1/index.org b/tuto1/index.org new file mode 100644 index 0000000000000000000000000000000000000000..777996da50565341ddd8ed47ba611b9e44dd874c --- /dev/null +++ b/tuto1/index.org @@ -0,0 +1,428 @@ +#+TITLE: Distributed experiments on Grid'5000 ... and beyond ! +#+DATE: +#+AUTHOR: Matthieu Simonin + +#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="timeline.css" /> + +#+MACRO: enoslib EnOSlib +#+MACRO: src_host https://gitlab.inria.fr/discovery/enoslib/blob/v4.8.1/enoslib/host.py#L8-14 +#+MACRO: doc_external_access https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000 +#+MACRO: src_provider https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra +#+MACRO: doc_provider https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html +#+MACRO: doc_tasks https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html +#+MACRO: doc_g5k_schema https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema +#+MACRO: doc_api https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html +#+MACRO: doc_services https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html + +* Foreword + +** Existing tools (Grid'5000) + + - {{{enoslib}}} falls under the **Experiment management tools** of the following + list: + https://www.grid5000.fr/w/Grid5000:Software + + - {{{enoslib}}} can target Grid'5000 but also other testbeds (Chameleon, local machines...) + + - {{{enoslib}}} provides high level constructs to help you with your experiments + +** EnOSlib quicktour + + - Documentation: https://discovery.gitlabpages.inria.fr/enoslib/index.html + - Source: https://gitlab.inria.fr/discovery/enoslib + - Reach us on: + + https://framateam.org/enoslib + + https://gitlab.inria.fr/discovery/enoslib/issues + +** Contributing + + *Before experimenting* + + - Tell us what your plans are: + + There might be already users doing similar thing + + There might be some missing/hidden pieces in the library you might need + + *While experimenting* + + - Write bug reports / ask questions + - Fix bugs / add your features + + *After experimenting* + + - Give your feedback + - Add yourself to the list: https://discovery.gitlabpages.inria.fr/enoslib/theyuseit.html + + +* Before you start + + #+begin_note + make sure you are familiar with the grid'5000 architecture. see section 1 & 2 of + https://www.grid5000.fr/w/Getting_Started. note that we won't do this tutorial + we'll prefer to use higher level tools for now. + #+end_note + +* Setup on Grid'5000 + + Connect to a Grid'5000 frontend of your choice (e.g rennes, nancy ...) + + - create a new directory to host all the scripts of the session + - bootstrap a new python3 virtualenv + - install {{{enoslib}}} and configure the access to the API + + #+BEGIN_SRC bash :noeval + $frontend: mkdir enoslib_seminar + $frontend: cd enoslib_seminar + $frontend: virtualenv --python=python3 venv + $frontend: source venv/bin/activate + $frontend(venv): pip install enoslib + $frontend(venv): echo ' + verify_ssl: False + ' > ~/.python-grid5000.yaml + #+END_SRC + +* Your first experiment on Grid'5000 + + Let's experiment with [[https://iperf.fr/][iperf3]]: a network bandwidth measuring tool. The goal is + to deploy a simple benchmark between two hosts. + + We'll also instrument the deployment in order to visualize in real-time the + network traffic between the hosts. Since this is super common, {{{enoslib}}} + exposes a /monitoring service/ that lets you deploy very quickly what is + needed. + +** First iteration + + We consider the following script + #+INCLUDE: exercices/iperf3.py src python + + #+BEGIN_question + How fast is the network between the nodes you have chosen ? + #+END_question + + #+BEGIN_note + Before moving to the next questions, you'll need to clean the reservation. + You can either uncomment the line ~provider.destroy()~ at the end of the script. + You can also do it manually using the low-level ~oarstat~ / ~oardel~ tools. + + #+BEGIN_SRC bash :noeval + # get you reservation id + $frontend: oarstat -u + # release the resources / kill the reservation + $frontend: oardel <the id of the reservation goes here> + #+END_SRC + #+END_note + + #+BEGIN_question + Can you adapt the script so that: + 1. The two nodes are in two different cluster in the same site ? + 2. The two nodes are in two different sites ? + #+END_question + + +** Let's observe in real-time what is happening + + #+BEGIN_note + Make sure you have cleaned your previous reservations. + #+END_note + + The following script installs a monitoring stack on your nodes. This is almost + the same script as before except the lines corresponding to the configuration + of the monitoring stack. + + #+INCLUDE: exercices/iperf3_monitoring.py src python + + Now, let's visualize the network traffic in real-time ! + #+BEGIN_note + Usually I follow this to access services running inside Grid'5000: + {{{doc_external_access}}}. + + + Today you can just create a tunnel like this (from your local machine). + + #+BEGIN_SRC bash :noeval + # Adapt the node names with the node where grafana (the UI) has been installed + # Replace <login> by your Grid'5000 login + $yourmachine: ssh -NL 3000:paravance-16.rennes.grid5000.fr:3000 <login>@access.grid5000.fr + + # point your browser to localhost:3000 + # username/mdp: admin/admin + #+END_SRC + + #+END_note + + Part of the experimenter work also consists in analysing the data. Here it + corresponds in writing the right request to monitor the traffic (check the + Fig. [[fig:iperf3]]). You should be able to visualize such a thing (after a bit + of point and clicks). + + #+CAPTION: iperf3 / monitoring + #+NAME: fig:iperf3 + #+ATTR_HTML: :width 100% :style border:1px solid black; + [[file:figs/iperf3.png][file:figs/iperf3.png]] + + +** Discussion + + So, far this seems (at least for me) very handy. But there might be some problems in our setup: + - we aren't isolated from the other users + - we aren't isolated from ourself in the sense that the monitoring stack generates its own + network traffic (yes, this is negligible in our case) + + Sometimes it's desirable to have the following setup (see Fig. [[fig:two_networks]]). + + #+CAPTION: nodes are using two network interfaces. + #+CAPTION: Monitoring traffic and benchmark traffic are separated. + #+NAME: fig:two_networks + [[file:figs/skydive_enoslib.png][file:figs/skydive_enoslib.png]] + +** A bit better approach + + Analyse/Understand the following script [[file:exercices/iperf3_better.py]] + Launch it. + + #+BEGIN_note + On Grid'5000, using the secondary interfaces requires to *deploy* the nodes: + an new OS will be installed on your nodes. This will give you full control on + the physical machine (root access). This might be longer to run the + experiment due to this deployment phase. + #+END_note + +** Ninja level + + Add the [[https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html#skydive][Skydive]] service to your deployment. + It should be accessible on the port ~8082~ of the analyzer node. You should + get something like Fig. [[fig:two_networks]]. + +** Some references + + - Services: {{{doc_services}}} + +* Providers: to replicate your experiment + + #+BEGIN_note + The resources that are used for your experiment are acquired through a + provider. Providers are a mean to decouple the infrastructure code (the code + that gets the resources) from the code that runs the experiment. Changing the + provider allows to replicate the experiment on another testbed. + #+END_note + + Originally it was used to iterate on the code locally (using the Vagrant + provider) and to only test on Grid'5000 when necessary. + + We now have couple of providers that you may picked or mixed. + +** iperf3 on virtual machines on Grid'5000 + + We'll adapt the initial iperf3 example to use virtual machines instead of + bare-metal machine. + + Note that: + + - The configuration object is different + - The experimentation logic is the same + - Some part have been rewritten using modules (see later in the dedicated section). + + #+INCLUDE: exercices/iperf3_vms.py src python + + Using module using the ~play_on~ context manager does not bring back the + results of the commands. Iperf3 let's you write the result of the command on + a file. We just need to scp the file back to our local machine using the + ~fetch~ module. + + +** References + + - Doc: {{{doc_provider}}} + - Sources: {{{src_provider}}} + + +* Variables in {{{enoslib}}} + + Learn how to get 2 nodes from Grid'5000 and start launching remote commands. + +** Discover the ~run~ command and its variants + + Before proceeding you can add this util function to your code. It is only + used to pretty print a python dictionnary. + #+INCLUDE: exercices/run.py :lines "35-39" src python + + And use the ~enoslib.api.run~ function + #+INCLUDE: exercices/run.py :lines "40-47" src python + + Or the ~enoslib.api.run_command~ function + #+INCLUDE: exercices/run.py :lines "48-56" src python + + #+BEGIN_note + ~enoslib.api.run~ is a specialisation of ~enoslib.api.run_command~. + The latter let's you use [[https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html][some fancy patterns]] to determine the list of hosts to run the command on. + + And yes, it uses Ansible behind the scene. + #+END_note + +** Advanced usages + + #+BEGIN_note + For all the remote interactions, {{{enoslib}}} relies on [[https://docs.ansible.com/ansible/latest/index.html][Ansible]]. Ansible + has it own variables management system. + For instance the task ~Gather Facts~ at the beginning of the previous tasks + gathers informations about all/some remote hosts and store them in the + Ansible management system. + #+END_note + + Let's see what Ansible is gathering about the hosts: + + #+INCLUDE: exercices/run.py :lines "58-65" src python + + #+BEGIN_note + {{{enoslib}}} sits in between two worlds: the Python world and the Ansible + world. One common need is to pass a variables from one world to another. + - ~enoslib.api.gather_facts~ is a way to get, in Python, the variables known + by Ansible about each host. + - ~extra_vars~ keyword argument of ~enoslib.api.run~ or ~enoslib.api.run_command~ will + pass variables from Python world to Ansible world (global variable) + - Injecting a key/value in a ~Host.extra~ attribute will make the variable ~key~ available to Ansible. + This makes the variables Host specific. + #+END_note + + The following inject a global variable in the Ansible world + #+INCLUDE: exercices/run.py :lines "65-71" src python + +** Ninja level + + The following is valid and inject in the ~client~ host a specific variable to + keep track of the server IP. + + #+INCLUDE: exercices/run.py :lines "73-81" src python + + #+BEGIN_note + Host level variables are interesting to introduce some dissymetry between + hosts while still using one single command to reach all of them. + #+END_note + + #+BEGIN_question + How to perform simultaneously the ping to the other machine in calling only + once ~run~ or ~run_command~ and using host level variables? + #+END_question + + #+BEGIN_question + We'd like to create 5 ~server~ machines and 5 ~client~ machines and start 5 + *parallel* streams of data using ~iperf3~. To answer this we'll need to learn + a bit more on how variables are handled in {{{enoslib}}}. + #+END_question + +** Putting all together + Access the full file: [[file:exercices/run.py]] + +** Some references + + - G5k configuration schema: {{{doc_g5k_schema}}} + - API Reference: {{{doc_api}}} + +* Modules: for safer remote actions + + In this section we'll discover the idiomatic way of managing resources on the + remote hosts. A resource can be anything: a user, a file, a line in a file, a + repo on Gitlab, a firewall rule ... + + +** Idempotency + + Let's assume you want to create a user (~foo~). With the ~run_command~ this would look like: + + #+BEGIN_SRC python :noeval + run_command("useradd -m foo", roles=role) + #+END_SRC + + The main issue with this code is that it is not *idempotent*. Running it once + will applied the effect (create the user). But, as soon as the user exist in + the system, this will raise an error. + +** One reason why idempotency is important + + Let's consider the following snippet (mispelling the second command is intentional) + #+BEGIN_SRC python :noeval + run_command("useradd -m foo", roles=role) + run_command("mkdirz plop") + #+END_SRC + Executing the above leads the system with the user ~foo~ created but the the + directory ~plop~ not created since the second command fails. + + So what you want to do is to fix the second command and re-run the snippet again. + But, you can't do that because ~useradd~ isn't idempotent. + +** Idempotency trick + + One easy solution is to protect your call to non idempotent commands with + some ad'hoc tricks + + Here it can look like this: + + #+BEGIN_SRC python :noeval + run_command("id foo || useradd -m foo", roles=role) + run_command("mkdir -p plop") + #+END_SRC + + *What's wrong with that* + + - The trick depends on the command + - Re-reading the code is more complex: the code focus on the **how** not the **what** + +** General idempotency + + The idiomatic solution is to use modules (inherited from the Ansible + Modules). The modules are specified in a *declarative* way and they ensure + *idempotency* for most of them. + + So rewriting the example with modules looks like: + #+BEGIN_SRC python :noeval + with play_on(roles=roles) as p: + p.user(name="foo", state="present", create_home="yes") + p.file(name="plop", state="directory") + #+END_SRC + + ~enoslib.api.play_on~ is the entry point to the module system. + + You can run this code as many times as you want without any error. You'll + eventually find one user ~foo~ and one directory ~plop~ in your target + systems. + + They are more than 2500 modules: https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html + + If you can't find what you want you must know that: + - Writing your own module is possible + - Falling back to the idempotency trick is reasonable + +* Tasks: to organize your experiment + + To discover the Task API, head to {{{doc_tasks}}}. + + The examples are written for Vagrant but may be changed to whatever provider you like/have. + + #+BEGIN_question + Adapt the ~iperf3~ example to provide a command line + - Either using G5k physical machines: + #+BEGIN_SRC bash + # deploy the dependencies of the experimentation using the G5k provider + myperf g5k + + # launch a performance measurement + # ideally exposes all the iperf3 client options there ;) + myperf bench -t 120 + + # Backup the reports / influxdb database + myperf backup + #+END_SRC + + # Destroy the ressources on Grid'5000 + myperf destroy + + - Either using the virtual machines on Grid'5000: + #+BEGIN_SRC bash + # deploy the dependencies of the experimentation using the G5k provider + myperf vm5k + + # Subsequent command line should be the same as above + # enjoy :) + #+END_SRC + #+END_question + diff --git a/tuto1/index.tex b/tuto1/index.tex new file mode 100644 index 0000000000000000000000000000000000000000..2525d89f1cfa5aa88c321dfeee22a0bd2c90ca34 --- /dev/null +++ b/tuto1/index.tex @@ -0,0 +1,569 @@ +% Created 2019-10-17 jeu. 01:05 +% Intended LaTeX compiler: pdflatex +\documentclass[11pt]{article} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{graphicx} +\usepackage{grffile} +\usepackage{longtable} +\usepackage{wrapfig} +\usepackage{rotating} +\usepackage[normalem]{ulem} +\usepackage{amsmath} +\usepackage{textcomp} +\usepackage{amssymb} +\usepackage{capt-of} +\usepackage{hyperref} +\author{Matthieu Simonin} +\date{} +\title{Distributed experiments on Grid'5000 \ldots{} and beyond !} +\hypersetup{ + pdfauthor={Matthieu Simonin}, + pdftitle={Distributed experiments on Grid'5000 \ldots{} and beyond !}, + pdfkeywords={}, + pdfsubject={}, + pdfcreator={Emacs 26.1 (Org mode 9.1.9)}, + pdflang={English}} +\begin{document} + +\maketitle +\tableofcontents + + +\section{Foreword} +\label{sec:org40b18b0} + +\section{Setup on Grid'5000} +\label{sec:orgec7b370} + +Connect to a Grid'5000 frontend of your choice. + +\begin{itemize} +\item create a new directory to host all the scripts of the session +\item bootstrap a new python3 virtualenv +\item install EnOSlib and configure the access to the API +\item you'll also want to have ipython and ipdb installed +\end{itemize} + +\begin{verbatim} +$frontend: mkdir enoslib_seminar +$frontend: cd enoslib_seminar +$frontend: virtualenv --python=python3 venv +$frontend: source venv/bin/activate +$frontend(venv): pip install enoslib ipython ipdb +$frontend(venv): echo ' +verify_ssl: False +' > ~/.python-grid5000.yaml + +\end{verbatim} + +\section{EnOSlib warmup on Grid'5000} +\label{sec:org54c7eac} + +Learn how to get 2 nodes from Grid'5000 and start launching commands. + +\subsection{Reserve 2 nodes} +\label{sec:org543c543} + +\begin{note} +With EnOSlib you first describe your resource requirements using an abstract +resource description. +Note that the network should be explictly stated. +\end{note} + +Write the following python script in a file \texttt{run.py}. If needed adapt the +\texttt{CLUSTER} and \texttt{SITE} variables. + +\begin{verbatim} +from enoslib.api import run, run_command, gather_facts +from enoslib.infra.enos_g5k.provider import G5k +from enoslib.infra.enos_g5k.configuration import Configuration, NetworkConfiguration + +import logging + + +logging.basicConfig(level=logging.INFO) + + +SITE = "rennes" +CLUSTER = "paravance" + +network = NetworkConfiguration(id="n1", + type="prod", + roles=["my_network"], + site=SITE) + +conf = Configuration.from_settings(job_name="enoslib_tutorial", + job_type="allow_classic_ssh")\ + .add_network_conf(network)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .add_machine(roles=["client"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .finalize() + +provider = G5k(conf) +roles, networks = provider.init() +\end{verbatim} + +For the sake of curiosity let's inspect the roles and networks data +structures using ipython. + +\begin{verbatim} +$frontend (venv): ipython +In [1]: run run.py +# ... +# ... +In [2]: roles +# ... +In [3]: networks +\end{verbatim} + +\begin{note} +The abstract resource description is concretized by the call to the +\texttt{provider.init} method. \texttt{roles} and \texttt{networks} contains the concrete machines +and networks given by Grid'5000. +Check the attributes of the Host data structure in the code: \url{https://gitlab.inria.fr/discovery/enoslib/blob/v4.8.1/enoslib/host.py\#L8-14} +\end{note} + +\subsection{Using the run command and its variants} +\label{sec:org4f0ed16} + +For this part you have two choices to run the examples: +\begin{itemize} +\item (prefered) append it in the previous file and re-run the file (yes this is safe to do so) +\item write the example in the previously open ipython console +\end{itemize} + +\subsubsection{Basics usages} +\label{sec:org7f40ca6} + +Before proceeding you can add this util function to your code. It only used +to pretty print a python dictionnary. +\begin{verbatim} +def pprint(d): + import json + print(json.dumps(d, indent=4)) +\end{verbatim} + +And use the \texttt{enoslib.api.run} function +\begin{verbatim} +server = roles["server"][0] +# --- +# Using run +# -------------------------------------------------------------------- +result = run(f"ping -c 5 {server.address}", roles["client"]) +pprint(result) +\end{verbatim} + +Or the \texttt{enoslib.api.run\_command} function +\begin{verbatim} +# --- +# Using run_command 1/2 +# -------------------------------------------------------------------- +result = run_command(f"ping -c 5 {server.address}", + pattern_hosts="client", + roles=roles) +pprint(result) +\end{verbatim} + +\begin{note} +\texttt{enoslib.api.run} is a specialisation of \texttt{enoslib.api.run\_command}. +The latter let's you use \href{https://docs.ansible.com/ansible/latest/user\_guide/intro\_patterns.html}{some fancy patterns} to determine the list of hosts to run the command on. + +And yes, it uses Ansible behind the scene. +\end{note} + +\subsubsection{Advanced usages} +\label{sec:org0e02fbb} + +\begin{note} +For all the remote interactions, EnOSlib relies on \href{https://docs.ansible.com/ansible/latest/index.html}{Ansible}. Ansible +has it own variables management system. +For instance the task \texttt{Gather Facts} at the beginning of the previous tasks +gathers informations about all/some remote hosts and store them in the +Ansible management system. +\end{note} + +Let's see what Ansible is gathering about the hosts: +\begin{verbatim} +# --- +# Gather facts +# -------------------------------------------------------------------- +result = gather_facts(roles=roles) +pprint(result) +\end{verbatim} +\begin{note} +\texttt{enoslib.api.gather\_facts} is a way to get, in python, the variables known +by Ansible about each host. +\end{note} + +\begin{note} +EnOSlib sits in between two worlds: the Python world and the Ansible +world. One common need is to pass a variables from one world to another. +\begin{itemize} +\item \texttt{enoslib.api.gather\_facts} is a way to get, in Python, the variables known +by Ansible about each host. +\item \texttt{extra\_vars} keyword argument of \texttt{enoslib.api.run} or \texttt{enoslib.api.run\_command} will +pass variables from Python world to Ansible world (global variable) +\item Injecting a key/value in a \texttt{Host.extra} attribute will make the variable \texttt{key} available to Ansible. +This makes the variables Host specific. +\end{itemize} +\end{note} + +The following inject a global variable in the Ansible world +\begin{verbatim} +# --- +# Passing a variable to the Ansible World using a global level variable +# -------------------------------------------------------------------- +server = roles["server"][0] +extra_vars={"server_ip": server.address} +result = run("ping -c 5 {{ server_ip }}", roles["client"], extra_vars=extra_vars) +\end{verbatim} + +\subsubsection{Ninja level} +\label{sec:org0edd362} + +The following is valid and inject in the client host a specific variable to +keep of the server IP. + +\begin{verbatim} +# --- +# Passing a variable to the Ansible World using a host level variable +# -------------------------------------------------------------------- +server = roles["server"][0] +client.extra.update(server_ip=server.address) +result = run("ping -c 5 {{ server_ip }}", roles["client"]) +\end{verbatim} + +\begin{note} +Host level variables are interesting to introduce some dissymetry between +hosts using the same intruction in your Python Code. +\end{note} + +\begin{question} +How to perform simultaneously the ping to the other machine in calling only +once \texttt{run} or \texttt{run\_command} and using host level variables? +\end{question} + +\subsubsection{All together} +\label{sec:org31aadd8} +Access the full file: \url{exercices/run.py} + +\subsubsection{Some references} +\label{sec:orga6331bb} + +\begin{itemize} +\item G5k configuration schema: \url{https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html\#g5k-schema} +\item API Reference: \url{https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html} +\end{itemize} +\section{Iperf3 playground} +\label{sec:org6a4417e} + +Let's experiment with \href{https://iperf.fr/}{iperf3}: a network bandwidth measuring tool. The goal is +to deploy a simple benchmark between two hosts. + +We'll also instrument the deployment in order to visualize in real-time the +network traffic between the hosts. Since this is super common, EnOSlib +exposes a \texttt{monitoring service} that lets you deploy very quickly what is +needed. + +\subsection{First attempt} +\label{sec:orgbfbe8c2} + +We adapt the previous example in the following script: +\begin{verbatim} +from enoslib.api import run_command, wait_ssh +from enoslib.infra.enos_g5k.provider import G5k +from enoslib.infra.enos_g5k.configuration import Configuration, NetworkConfiguration +from enoslib.service import Monitoring + +import logging + + +def pprint(d): + import json + print(json.dumps(d, indent=4)) + + +logging.basicConfig(level=logging.INFO) + + +SITE = "rennes" +CLUSTER = "paravance" + +network = NetworkConfiguration(id="n1", + type="prod", + roles=["my_network"], + site=SITE) + +conf = Configuration.from_settings(job_name="enoslib_tutorial", + job_type="allow_classic_ssh")\ + .add_network_conf(network)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .add_machine(roles=["client"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .finalize() + +provider = G5k(conf) +roles, networks = provider.init() +wait_ssh(roles) + +# This deploys a monitoring stack +m = Monitoring(collector=roles["server"], + agent=roles["server"] + roles["client"], + ui=roles["server"]) +m.deploy() + + +# Below is the experimentation logic +# It installs the bare minimum to run iperf3 +# The machine with the role 'server' is used to run a iperf3 server +# started in the background in a tmux +# The machine with the role 'client' connects to that server +# Report is printed in stdout +server = roles["server"][0] +run_command("apt update && apt install -y iperf3 tmux", roles=roles) +run_command("tmux new-session -d 'exec iperf3 -s'", pattern_hosts="server", roles=roles) +result = run_command(f"iperf3 -c {server.address} -t 30", pattern_hosts="client", roles=roles) +pprint(result) +\end{verbatim} + +Now, let's visualize the network traffic in real-time ! +\begin{note} +Usually I follow this to access services running inside Grid'5000: +\url{https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html\#accessing-http-services-inside-grid-5000} + +So to access the monitoring dashboard you need to connect using your browser +to the machine `server` on the port 3000. +\end{note} + +You should be able to visualize such a thing (after a bit of point and clicks). + +\href{./iperf3.png}{\begin{figure}[htbp] +\centering +\includegraphics[width=.9\linewidth]{./figs/iperf3.png} +\caption{\label{fig:orgd2b4ea5} +iperf3 / monitoring} +\end{figure}} + +\subsection{Discussion} +\label{sec:org8354e5d} + +\begin{itemize} +\item What's good is: \ldots{} +\item What's wrong is: \ldots{} +\end{itemize} + +\subsection{A better approach (maybe)} +\label{sec:org16d8bce} +Access the full file: \url{exercices/iperf3\_better.py} + +\section{{\bfseries\sffamily TODO} Partial wrap-up} +\label{sec:orgb35f46e} + +\begin{itemize} +\item Configuration for G5k +\begin{itemize} +\item non deploy / deploy +\item prod network / kavlan +\end{itemize} +\item Services +\end{itemize} + +\section{Modules: for safer remote actions} +\label{sec:org1ae5927} + +In this section we'll discover the idiomatic way of managing resources on the +remote hosts. A resource can be anything: a user, a file, a line in a file, a +repo on Gitlab, a firewall rule \ldots{} + + +\subsection{Idempotency} +\label{sec:org81e26b7} + +Let's assume you want to create a user (\texttt{foo}). With the \texttt{run\_command} this would look like: + +\begin{verbatim} +run_command("useradd -m foo", roles=role) +\end{verbatim} + +The main issue with this code is that it is not \textbf{idempotent}. Running it once +will applied the effect (create the user). But, as soon as the user exist in +the system, this will raise an error. + +\subsection{One reason why idempotency is important} +\label{sec:org53c14cb} + +Let's consider the following snippet (mispelling the second command is intentional) +\begin{verbatim} +run_command("useradd -m foo", roles=role) +run_command("mkdirz plop") +\end{verbatim} +Executing the above leads the system with the user \texttt{foo} created but the the +directory \texttt{plop} not created since the second command fails. + +So what you want to do is to fix the second command and re-run the snippet again. +But, you can't do that because \texttt{useradd} isn't idempotent. + +\subsection{Idempotency trick} +\label{sec:orgb15c5ef} + +One easy solution is to protect your call to non idempotent commands with +some ad'hoc tricks + +Here it can look like this: + +\begin{verbatim} +run_command("id foo || useradd -m foo", roles=role) +run_command("mkdir -p plop") +\end{verbatim} + +\textbf{What's wrong with that} + +\begin{itemize} +\item The trick depends on the command +\item Re-reading the code is more complex: the code focus on the \textbf{\textbf{how}} not the \textbf{\textbf{what}} +\end{itemize} + +\subsection{General idempotency} +\label{sec:org4061e60} + +The idiomatic solution is to use modules (inherited from the Ansible +Modules). The modules are specified in a \textbf{declarative} way and they ensure +\textbf{idempotency} for most of them. + +So rewriting the example with modules looks like: +\begin{verbatim} +with play_on(roles=roles) as p: + p.user(name="foo", state="present", create_home="yes") + p.file(name="plop", state="directory") +\end{verbatim} + +You can run this code as many times as you want without any error. You'll +eventually find one user \texttt{foo} and one directory \texttt{plop} in your target +systems. + + +They are more than 2500 modules: \url{https://docs.ansible.com/ansible/latest/modules/list\_of\_all\_modules.html} + +If you can't find what you want you must know that: +\begin{itemize} +\item Writing your own module is possible +\item Falling back to the idempotency trick is reasonable +\end{itemize} + +\section{Providers: to replicate your experiment} +\label{sec:orgfc2ec82} + +The resources that are used for your experiment are acquired through a +provider. Providers are a mean to decouple the infrastructure code (the code +that get the resources) from the code that runs the experiment. Changing the +provider allows to replicate the experiment on another testbed. + +Originally it was used to iterate on the code locally (using the Vagrant +provider) and to only test on Grid'5000 when necessary. + +We now have couple of providers that you may picked or mixed. + +\subsection{iperf3 on virtual machines on Grid'5000} +\label{sec:org162cac9} + +We'll adapt the initial iperf3 example to use virtual machines instead of +bare-metal machine. + +Note that: + +\begin{itemize} +\item The configuration object is different +\item The experimentation logic is the same (rewritten using modules when it applies) +\end{itemize} + +\begin{verbatim} +from enoslib.api import play_on, wait_ssh +from enoslib.infra.enos_vmong5k.provider import VMonG5k +from enoslib.infra.enos_vmong5k.configuration import Configuration + +import logging +import os + +logging.basicConfig(level=logging.DEBUG) + +CLUSTER = "paravance" + +# path to the inventory +inventory = os.path.join(os.getcwd(), "hosts") + +# claim the resources +conf = Configuration.from_settings(job_name="enoslib_tutorial", gateway=True)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + number=1, + flavour="large")\ + .add_machine(roles=["client"], + cluster=CLUSTER, + number=1, + flavour="medium")\ + .finalize() + +provider = VMonG5k(conf) + +roles, networks = provider.init() +wait_ssh(roles) + +# Below is the experimentation logic +# It installs the bare minimum to run iperf3 +# The machine with the role 'server' is used to run a iperf3 server +# started in the background in a tmux +# The machine with the role 'client' connects to that server +# Report is printed in stdout +server = roles["server"][0] + +with play_on(roles=roles) as p: + p.apt(name=["iperf3", "tmux"], state="present") + +with play_on(pattern_hosts="server", roles=roles) as p: + p.shell("tmux new-session -d 'exec iperf3 -s'") + +with play_on(pattern_hosts="client", roles=roles) as p: + p.shell(f"iperf3 -c {server.address} -t 30") + +with play_on(pattern_hosts="client", roles=roles) as p: + p.shell(f"iperf3 -c {server.address} -t 30 --logfile iperf3.out") + p.fetch(src="iperf3.out", dest="iperf3.out") +\end{verbatim} + +Using module using the \texttt{play\_on} context manager does not bring back the +results of the commands. Iperf3 let's you write the result of the command on +a file. We just need to scp the file back to our local machine using the +\texttt{fetch} module. + +\subsection{Ninja level} +\label{sec:orge484eb8} + +Creates 5 \texttt{server} machines and 5 \texttt{client} machines and start 5 \textbf{parallel} +streams of data using \texttt{iperf3}. + +\subsection{References} +\label{sec:org9c0017b} + +\begin{itemize} +\item Doc: \url{https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html} +\item Sources: \url{https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra} +\end{itemize} + +\section{Tasks: to organize your experiment} +\label{sec:org7f002c7} + +To discover the Task API, head to \url{https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html}. +The examples are written for Vagrant but may be changed to whatever provider you like/have. +\end{document} diff --git a/tuto1/timeline.css b/tuto1/timeline.css new file mode 100644 index 0000000000000000000000000000000000000000..268b0deaebe45d42a32888682272253296930629 --- /dev/null +++ b/tuto1/timeline.css @@ -0,0 +1,1035 @@ +@import url('https://fonts.googleapis.com/css?family=Crimson+Text:700|Fira+Sans:400,700'); +html { + font-family: sans-serif; + line-height: 1.15; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} +body { + margin: 0 +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +main, +menu, +nav, +section, +summary { + display: block +} +audio, +canvas, +progress, +video { + display: inline-block +} +audio:not([controls]) { + display: none; + height: 0 +} +progress { + vertical-align: baseline +} +[hidden], +template { + display: none +} +a { + background-color: transparent; + -webkit-text-decoration-skip: objects +} +a:active, +a:hover { + outline-width: 0 +} +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} +b, +strong { + font-weight: inherit; + font-weight: bolder +} +dfn { + font-style: italic +} +h1 { + font-size: 2em; + margin: .67em 0 +} +mark { + background-color: #ff0; + color: #000 +} +small { + font-size: 80% +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} +sub { + bottom: -.25em +} +sup { + top: -.5em +} +img { + border-style: none +} +svg:not(:root) { + overflow: hidden +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em +} +figure { + margin: 1em 40px +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} +button, +input, +optgroup, +select, +textarea { + font: inherit; + margin: 0 +} +optgroup { + font-weight: 700 +} +button, +input { + overflow: visible +} +button, +select { + text-transform: none +} +[type=reset], +[type=submit], +button, +html [type=button] { + -webkit-appearance: button +} +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner, +button::-moz-focus-inner { + border-style: none; + padding: 0 +} +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring, +button:-moz-focusring { + outline: 1px dotted ButtonText +} +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: .35em .625em .75em +} +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} +textarea { + overflow: auto +} +[type=checkbox], +[type=radio] { + box-sizing: border-box; + padding: 0 +} +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto +} +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} +[type=search]::-webkit-search-cancel-button, +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} +::-webkit-input-placeholder { + color: inherit; + opacity: .54 +} +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} +.org-bold { + font-weight: 700 +} +.org-bold-italic { + font-weight: 700; + font-style: italic +} +.org-buffer-menu-buffer { + font-weight: 700 +} +.org-builtin { + color: #483d8b +} +.org-button { + color: #3a5fcd; + text-decoration: underline +} +.org-calendar-month-header { + color: #00f +} +.org-calendar-today { + text-decoration: underline +} +.org-calendar-weekday-header { + color: #008b8b +} +.org-calendar-weekend-header { + color: #b22222 +} +.org-comint-highlight-input { + font-weight: 700 +} +.org-comint-highlight-prompt { + color: #0000cd +} +.org-comment, +.org-comment-delimiter { + color: #b22222 +} +.org-constant { + color: #008b8b +} +.org-diary { + color: red +} +.org-doc { + color: #8b2252 +} +.org-error { + color: red; + font-weight: 700 +} +.org-escape-glyph { + color: brown +} +.org-file-name-shadow { + color: #7f7f7f +} +.org-fringe { + background-color: #f2f2f2 +} +.org-function-name { + color: #00f +} +.org-glyphless-char { + font-size: 60% +} +.org-header-line { + color: #333; + background-color: #e5e5e5 +} +.org-help-argument-name { + font-style: italic +} +.org-highlight { + background-color: #b4eeb4 +} +.org-holiday { + background-color: pink +} +.org-info-header-node { + color: brown; + font-weight: 700; + font-style: italic +} +.org-info-header-xref { + color: #3a5fcd; + text-decoration: underline +} +.org-info-index-match { + background-color: #ff0 +} +.org-info-menu-header { + font-weight: 700 +} +.org-info-menu-star { + color: red +} +.org-info-node { + color: brown; + font-weight: 700; + font-style: italic +} +.org-info-title-1 { + font-size: 172%; + font-weight: 700 +} +.org-info-title-2 { + font-size: 144%; + font-weight: 700 +} +.org-info-title-3 { + font-size: 120%; + font-weight: 700 +} +.org-info-title-4 { + font-weight: 700 +} +.org-info-xref { + color: #3a5fcd; + text-decoration: underline +} +.org-italic { + font-style: italic +} +.org-keyword { + color: #a020f0 +} +.org-lazy-highlight { + background-color: #afeeee +} +.org-link { + color: #3a5fcd; + text-decoration: underline +} +.org-link-visited { + color: #8b008b; + text-decoration: underline +} +.org-makefile-makepp-perl { + background-color: #bfefff +} +.org-makefile-space { + background-color: #ff69b4 +} +.org-makefile-targets { + color: #00f +} +.org-match { + background-color: #ff0 +} +.org-next-error { + background-color: gtk_selection_bg_color +} +.org-nobreak-space { + color: brown; + text-decoration: underline +} +.org-org-agenda-calendar-event, +.org-org-agenda-calendar-sexp { + color: #000; + background-color: #fff +} +.org-org-agenda-clocking { + background-color: #ff0 +} +.org-org-agenda-column-dateline { + background-color: #e5e5e5 +} +.org-org-agenda-current-time { + color: #b8860b +} +.org-org-agenda-date { + color: #00f +} +.org-org-agenda-date-today { + color: #00f; + font-weight: 700; + font-style: italic +} +.org-org-agenda-date-weekend { + color: #00f; + font-weight: 700 +} +.org-org-agenda-diary { + color: #000; + background-color: #fff +} +.org-org-agenda-dimmed-todo { + color: #7f7f7f +} +.org-org-agenda-done { + color: #228b22 +} +.org-org-agenda-filter-category, +.org-org-agenda-filter-effort, +.org-org-agenda-filter-regexp, +.org-org-agenda-filter-tags { + color: #000; + background-color: #bfbfbf +} +.org-org-agenda-restriction-lock { + background-color: #eee +} +.org-org-agenda-structure { + color: #00f +} +.org-org-archived, +.org-org-block { + color: #7f7f7f +} +.org-org-block-begin-line, +.org-org-block-end-line { + color: #b22222 +} +.org-org-checkbox { + font-weight: 700 +} +.org-org-checkbox-statistics-done { + color: #228b22; + font-weight: 700 +} +.org-org-checkbox-statistics-todo { + color: red; + font-weight: 700 +} +.org-org-clock-overlay { + color: #000; + background-color: #d3d3d3 +} +.org-org-code { + color: #7f7f7f +} +.org-org-column, +.org-org-column-title { + background-color: #e5e5e5 +} +.org-org-column-title { + font-weight: 700; + text-decoration: underline +} +.org-org-date { + color: #a020f0; + text-decoration: underline +} +.org-org-date-selected { + color: red +} +.org-org-default { + color: #000; + background-color: #fff +} +.org-org-document-info { + color: #191970 +} +.org-org-document-info-keyword { + color: #7f7f7f +} +.org-org-document-title { + color: #191970; + font-weight: 700 +} +.org-org-done { + color: #228b22; + font-weight: 700 +} +.org-org-drawer { + color: #00f +} +.org-org-ellipsis { + color: #b8860b; + text-decoration: underline +} +.org-org-footnote { + color: #a020f0; + text-decoration: underline +} +.org-org-formula { + color: #b22222 +} +.org-org-headline-done { + color: #bc8f8f +} +.org-org-hide { + color: #fff +} +.org-org-latex-and-related { + color: #8b4513 +} +.org-org-level-1 { + color: #00f +} +.org-org-level-2 { + color: sienna +} +.org-org-level-3 { + color: #a020f0 +} +.org-org-level-4 { + color: #b22222 +} +.org-org-level-5 { + color: #228b22 +} +.org-org-level-6 { + color: #008b8b +} +.org-org-level-7 { + color: #483d8b +} +.org-org-level-8 { + color: #8b2252 +} +.org-org-link { + color: #3a5fcd; + text-decoration: underline +} +.org-org-list-dt { + font-weight: 700 +} +.org-org-macro { + color: #8b4513 +} +.org-org-meta-line { + color: #b22222 +} +.org-org-mode-line-clock { + color: #000; + background-color: #bfbfbf +} +.org-org-mode-line-clock-overrun { + color: #000; + background-color: red +} +.org-org-priority { + color: #a020f0 +} +.org-org-quote { + color: #7f7f7f +} +.org-org-scheduled { + color: #006400 +} +.org-org-scheduled-previously { + color: #b22222 +} +.org-org-scheduled-today { + color: #006400 +} +.org-org-sexp-date, +.org-org-special-keyword { + color: #a020f0 +} +.org-org-table { + color: #00f +} +.org-org-tag, +.org-org-tag-group { + font-weight: 700 +} +.org-org-target { + text-decoration: underline +} +.org-org-time-grid { + color: #b8860b +} +.org-org-todo { + color: red; + font-weight: 700 +} +.org-org-upcoming-deadline { + color: #b22222 +} +.org-org-verbatim, +.org-org-verse { + color: #7f7f7f +} +.org-org-warning { + color: red; + font-weight: 700 +} +.org-outline-1 { + color: #00f +} +.org-outline-2 { + color: sienna +} +.org-outline-3 { + color: #a020f0 +} +.org-outline-4 { + color: #b22222 +} +.org-outline-5 { + color: #228b22 +} +.org-outline-6 { + color: #008b8b +} +.org-outline-7 { + color: #483d8b +} +.org-outline-8 { + color: #8b2252 +} +.org-preprocessor { + color: #483d8b +} +.org-regexp-grouping-backslash, +.org-regexp-grouping-construct { + font-weight: 700 +} +.org-region { + background-color: gtk_selection_bg_color +} +.org-secondary-selection { + background-color: #ff0 +} +.org-shadow { + color: #7f7f7f +} +.org-show-paren-match { + background-color: #40e0d0 +} +.org-show-paren-mismatch { + color: #fff; + background-color: #a020f0 +} +.org-string { + color: #8b2252 +} +.org-success { + color: #228b22; + font-weight: 700 +} +.org-table-cell { + color: #e5e5e5; + background-color: #00f +} +.org-tooltip { + color: #000; + background-color: #ffffe0 +} +.org-trailing-whitespace { + background-color: red +} +.org-type { + color: #228b22 +} +.org-underline { + text-decoration: underline +} +.org-variable-name { + color: sienna +} +.org-warning { + color: #ff8c00; + font-weight: 700 +} +.org-warning-1 { + color: red; + font-weight: 700 +} +body { + width: 95%; + margin: 2%; + font: normal normal normal 16px/1.6em 'Fira Sans', sans-serif; + color: #333 +} +@media (min-width: 769px) { + body { + width: 700px; + margin-left: 5vw + } +} +::-moz-selection { + background: #d6edff +} +::selection { + background: #d6edff +} +.title { + margin: auto; + color: #000 +} +.subtitle, +.title { + text-align: center +} +.subtitle { + font-size: medium; + font-weight: 700 +} +.abstract { + margin: auto; + width: 80%; + font-style: italic +} +.abstract p:last-of-type:before { + content: " "; + white-space: pre +} +.status { + font-size: 90%; + margin: 2em auto +} +[class^=section-number-] { + margin-right: .5em +} +[id^=orgheadline] { + clear: both +} +#footnotes { + font-size: 90% +} +.footpara { + display: inline; + margin: .2em auto +} +.footdef { + margin-bottom: 1em +} +.footdef sup { + padding-right: .5em +} +a { + color: #527d9a; + text-decoration: none +} +a:hover { + color: #035; + border-bottom: 1px dotted +} +figure { + padding: 0; + margin: 0; + text-align: center +} +img { + max-width: 100%; + vertical-align: middle +} +.MathJax_Display { + font-size: 90%; + margin: 0!important; + width: 90%!important +} +h1, +h2, +h3, +h4, +h5, +h6 { + color: #a5573e; + line-height: 1.6em; + font-weight: bold; + font-family: 'Crimson Text', serif +} +h4, +h5, +h6 { + font-size: 1em +} +dt { + font-weight: 700 +} +table { + margin: auto; + border-top: 2px solid; + border-collapse: collapse +} +table, +thead { + border-bottom: 2px solid +} +table td+td, +table th+th { + border-left: 1px solid gray +} +table tr { + border-top: 1px solid #d3d3d3 +} +td, +th { + padding: 5px 10px; + vertical-align: middle +} +caption.t-above { + caption-side: top +} +caption.t-bottom { + caption-side: bottom +} +th.org-center, +th.org-left, +th.org-right { + text-align: center +} +td.org-right { + text-align: right +} +td.org-left { + text-align: left +} +td.org-center { + text-align: center +} +code { + padding: 2px 5px; + margin: auto 1px; + border: 1px solid #ddd; + border-radius: 3px; + background-clip: padding-box; + color: #333; + font-size: 80% +} +blockquote { + margin: 1em 2em; + padding-left: 1em; + border-left: 3px solid #ccc +} +kbd { + background-color: #f7f7f7; + font-size: 80%; + margin: 0 .1em; + padding: .1em .6em +} +.todo { + background-color: red; + padding: 2px +} +.done, +.todo { + color: #fff; + border-radius: 3px; + background-clip: padding-box; + font-size: 80%; + font-family: Lucida Console, monospace +} +.done { + background-color: green; + padding: 3px +} +.priority { + color: orange; + font-family: Lucida Console, monospace +} +#table-of-contents li { + clear: both +} +.tag { + font-family: Lucida Console, monospace; + font-size: 70%; + font-weight: 400 +} +.tag span { + padding: 0 5px; + float: right; + margin-right: 5px; + border: 1px solid #bbb; + border-radius: 3px; + background-clip: padding-box; + color: #333; + background-color: #eee; + line-height: 1.6 +} +.timestamp { + color: #bebebe; + font-size: 90% +} +.timestamp-kwd { + color: #5f9ea0 +} +.org-right { + margin-left: auto; + margin-right: 0; + text-align: right +} +.org-left { + margin-left: 0; + margin-right: auto; + text-align: left +} +.org-center { + margin-left: auto; + margin-right: auto; + text-align: center +} +.underline { + text-decoration: underline +} +#postamble p, +#preamble p { + font-size: 90%; + margin: .2em +} +p.verse { + margin-left: 3% +} +pre { + border: 1px solid #ccc; + box-shadow: 3px 3px 3px #eee; + font-family: Lucida Console, monospace; + margin: 1.2em; + padding: 8pt; + font-size: 80%; + background: #3f3f3f; + color: #dcdccc; +} +pre.src { + overflow: auto; + padding-top: 1.2em; + position: relative +} +pre.src:hover:before { + display: inline +} +pre.src-sh:before { + content: "sh" +} +pre.src-bash:before { + content: "bash" +} +pre.src-emacs-lisp:before { + content: "Emacs Lisp" +} +pre.src-R:before { + content: "R" +} +pre.src-org:before { + content: "Org" +} +pre.src-cpp:before { + content: "C++" +} +pre.src-c:before { + content: "C" +} +pre.src-html:before { + content: "HTML" +} +pre.src-javascript:before, +pre.src-js:before { + content: "Javascript" +} +pre.src-makefile:before { + content: "Makefile" +} +.inlinetask { + background: #ffc; + border: 2px solid gray; + margin: 10px; + padding: 10px +} +#org-div-home-and-up { + font-size: 70%; + text-align: right; + white-space: nowrap +} +.linenr { + font-size: 90% +} +.code-highlighted { + color: #FFFFE0; + background-color: #284F28; +} +#bibliography { + font-size: 90% +} +#bibliography table { + width: 100% +} +.creator { + display: block +} +@media (min-width: 769px) { + .creator { + display: inline; + float: right + } +} + +.org-src-container > label { + font-size: 75%; +} + +.note { + padding-left: 2em; + border: 1px dashed #00f; + position: relative; +} +.note:before { + display: block; + position: absolute; + left: 0px; + content: "i"; + background: #00f; + border-radius: 0.8em; + -moz-border-radius: 0.8em; + -webkit-border-radius: 0.8em; + color: #ffffff; + display: inline-block; + font-weight: bold; + line-height: 1.6em; + margin-right: 5px; + text-align: center; + width: 1.6em; +} + +@media screen and (min-width: 600px) { + h1 { + font-size: 2em; + } + h2 { + font-size: 1.5em; + } + h3 { + font-size: 1.3em; + } + h1,h2,h3 { + line-height: 1.4em; + } + h4,h5,h6 { + font-size: 1.1em; + } +} + +.question { + padding-left: 2em; + border: 1px dashed #0f0; + position: relative; +} +.question:before { + display: block; + position: absolute; + left: 0px; + content: "?"; + background: #00ff00; + border-radius: 0.8em; + -moz-border-radius: 0.8em; + -webkit-border-radius: 0.8em; + color: #ffffff; + display: inline-block; + font-weight: bold; + line-height: 1.6em; + margin-right: 5px; + text-align: center; + width: 1.6em; +} diff --git a/tuto2/figs/overleaf-v2-editor.png b/tuto2/figs/overleaf-v2-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..6343372093a8514b887e59b83d0f32d5569fb92f Binary files /dev/null and b/tuto2/figs/overleaf-v2-editor.png differ diff --git a/tuto2/figs/simple_compilation.png b/tuto2/figs/simple_compilation.png new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tuto2/figs/simple_compilation.svg b/tuto2/figs/simple_compilation.svg new file mode 100644 index 0000000000000000000000000000000000000000..f9a7a2c86d6e874bb4a948920002c9f9a983e769 --- /dev/null +++ b/tuto2/figs/simple_compilation.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="596px" height="452px" viewBox="-0.5 -0.5 596 452" content="<mxfile modified="2019-11-28T22:37:51.216Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0" etag="VcwF2yikf7-QsSzdrXRU" version="12.3.3" type="device" pages="1"><diagram id="f106602c-feb2-e66a-4537-3a34d633f6aa" name="Page-1">7ZpfU+o4FMA/DY86TdIGeASEXWe4i3t1xnufdmIbS6+l6aZBy376TUhKmxYFuSCMgs7QnJz8/52Tk5QWGszyPzhJp99YQOMWdIK8ha5aEAIXwpb6d4KFlrRdoAUhjwKjVApuo/+oETpGOo8CmlmKgrFYRKkt9FmSUF9YMsI5e7HVHllst5qSkDYEtz6Jm9L7KBBTIwW4W2b8SaNwapruwLbOeCD+U8jZPDHttSB6XH509owUdZmBZlMSsJeKCA1baMAZE/pplg9orOa2mDZdbvRK7qrfnCZimwI4H82ze5hfBQ/DX+DvdPjS/3mxWq1MLIoZoYGcIJNkXExZyBISD0tpfzlqqup1ZKrUGTOWSiGQwl9UiIVZbTIXTIqmYhabXNllvvihyl+CtlcIfi4Fq+RVbhrQqUU1dUN5NKOCciPUg1A9f3V2ioGyOffpW1MCkcGQ8JCKNycPrdZR2gdlskd8IUtyGhMRPdt9IQbUcKW3KnrDItlL6BijQp5hz5gUwp5dhe6ZKVUuuXyodKMULUF4DxTFDDyTeG4GMYgjalqrssLmIo4SOlgZp1qMR5aIAYsZX+og+TdSrfdDTgJVS5GXsETRlAnOnui6Ao9RHFfkxrxQPyDZdIXfM+UikvY8Jg80vmFZJCKWyLwHJgSbVRR6cRSqDKEg7ROT8mWHFEZVPNUADLsAFmkzaNUkyVI92scoV/3oS9tOVeYsD5WXvCQvmXvJqWbt2lf96cukfrK1fD2xBcKqszR/G+ImcKZAB1jcwK5xPS+lY8NGNK24tEK2jlCLrXeD5DoNYr68d2l/ee/SPnuXT+BdUBce2bsg9xjeZZ/OADnrZ/x3Tbzr2UsF8VYm3uOcLCpqqVLItm8Hgk5tVXWN+/UfyDn7j0/gP0Cnc2z/0T1ydFKLTTrusYMT9MoK7tsfddsfG3IgcHYZn8BluF332Aea5sm4BXEszJxaLOF/56zIuMiWs9+TCnL8eZkpn0L1fT+Uj87N5Ptdb1zUKLuoK9UqDVarC2yD5YGOO0INFA2f6/mJa7itONrIY4O8LCV+lIR3S494gWsAghqAoGlh3eVnDYehn8LLKc0Ngik3BPpsls4F/YcmobTePZCHiqNr4bJA8ywNAGqyB5zDweceCL6ryeD2bvJ9eAbv+OB5ng2e2276vA8HDx8IvNH1eHgm70TJA8WyH5E8bx15747Z6+GPFY0HPfUWSS1uzPynu2mUaPEoigslO+bXgBUvi6DWNtW7y3zCRSVtBXeN2NCFhluaR0IfCzrQM+nKnaVMlacClVhUEvUzwUnfgLrbv19x1wNbBbJg7zePKK6HLPg9UGNaj6txRGlU5G2qaE93L26nFqBg581+1fU96NbM8xB3NV7zrvdsv1/HfvGH2a/n2HjDzo72izdVdCL2C7r4A+wXN+9a9xP5DSbfbq7HvbvryV/n2O/0Yr9TOO7idXctn33vsHcOfSO869ZR2zgA3mXnWI6omKbVBaeUmTna++6Ct91d8Csvzw6wu9Rjp+6OmwvYVNGhL8LxuiukA9mUgaU0J+dsTrY5Vb1O1ab2/Zq52MVP6sSFgf07FIjhjhFbrSKEtnvbvT+jOl9SfOFDzgduQ249Squ/6Nr+kmJDRTubjEyWP2nW6uXvxtHwfw==</diagram></mxfile>"><defs/><g><path d="M 60 174 L 160 174 L 160 214 L 272.63 214" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 277.88 214 L 270.88 217.5 L 272.63 214 L 270.88 210.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 0 144 L 60 144 L 60 204 L 0 204 Z" fill="#ffffff" stroke="none" pointer-events="all"/><path d="M 37.98 189.84 C 37.27 189.84 36.7 190.29 36.7 190.84 L 36.7 192.75 C 36.7 193.31 37.27 193.75 37.98 193.75 L 48.95 193.75 C 49.66 193.75 50.23 193.31 50.23 192.75 L 50.23 190.84 C 50.23 190.29 49.66 189.84 48.95 189.84 Z M 9.51 187.84 L 50.51 187.84 C 51.46 187.84 52.17 188.54 52.17 189.38 L 52.17 194.18 C 52.17 195.01 51.46 195.71 50.51 195.71 L 9.51 195.71 C 8.54 195.71 7.84 195.01 7.84 194.18 L 7.84 189.38 C 7.84 188.54 8.54 187.84 9.51 187.84 Z M 9.51 186.05 C 7.63 186.05 6.05 187.52 6.05 189.38 L 6.05 194.18 C 6.05 196.03 7.63 197.5 9.51 197.5 L 50.51 197.5 C 52.38 197.5 53.96 196.03 53.96 194.18 L 53.96 189.38 C 53.96 187.52 52.38 186.05 50.51 186.05 Z M 11.64 156.14 L 48.34 156.14 L 48.34 178.44 L 11.64 178.44 Z M 10.77 154.35 C 10.77 154.35 10.27 154.5 10.27 154.5 C 10.27 154.5 10.17 154.58 10.13 154.61 C 10.1 154.65 10.07 154.69 10.05 154.71 C 9.96 154.83 9.94 154.9 9.92 154.95 C 9.88 155.05 9.88 155.1 9.87 155.15 C 9.86 155.24 9.85 155.29 9.85 155.37 L 9.85 179.21 C 9.85 179.28 9.86 179.34 9.87 179.43 C 9.88 179.48 9.88 179.52 9.92 179.62 C 9.94 179.67 9.96 179.75 10.05 179.86 C 10.07 179.89 10.1 179.93 10.13 179.96 C 10.17 180 10.27 180.07 10.27 180.07 C 10.27 180.08 10.77 180.23 10.77 180.23 L 49.21 180.23 C 49.22 180.23 49.71 180.07 49.71 180.07 C 49.71 180.07 49.82 180 49.85 179.96 C 49.89 179.93 49.91 179.89 49.94 179.86 C 50.03 179.75 50.04 179.67 50.07 179.62 C 50.11 179.52 50.11 179.48 50.12 179.43 C 50.13 179.34 50.13 179.28 50.13 179.21 L 50.13 155.37 C 50.13 155.29 50.13 155.24 50.12 155.15 C 50.11 155.1 50.11 155.05 50.07 154.95 C 50.04 154.9 50.03 154.83 49.94 154.71 C 49.91 154.69 49.89 154.65 49.85 154.61 C 49.82 154.58 49.71 154.5 49.71 154.5 C 49.71 154.5 49.22 154.35 49.21 154.35 Z M 8.65 152.29 L 51.35 152.29 C 51.81 152.29 52.22 152.69 52.22 153.29 L 52.22 181.24 C 52.22 181.84 51.81 182.23 51.35 182.23 L 8.65 182.23 C 8.2 182.23 7.79 181.84 7.79 181.24 L 7.79 153.29 C 7.79 152.69 8.2 152.29 8.65 152.29 Z M 8.65 150.5 C 7.16 150.5 6 151.79 6 153.29 L 6 181.24 C 6 182.73 7.16 184.02 8.65 184.02 L 51.35 184.02 C 52.84 184.02 54 182.73 54 181.24 L 54 153.29 C 54 151.79 52.84 150.5 51.35 150.5 Z" fill="#232f3e" stroke="none" pointer-events="all"/><g transform="translate(13.5,211.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Client</div></div></foreignObject><text x="16" y="12" fill="#232F3E" text-anchor="middle" font-size="12px" font-family="Helvetica">Client</text></switch></g><path d="M 60 276 L 160 276 L 160 214 L 272.63 214" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 277.88 214 L 270.88 217.5 L 272.63 214 L 270.88 210.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 0 246 L 60 246 L 60 306 L 0 306 Z" fill="#ffffff" stroke="none" pointer-events="all"/><path d="M 37.98 291.84 C 37.27 291.84 36.7 292.29 36.7 292.84 L 36.7 294.75 C 36.7 295.31 37.27 295.75 37.98 295.75 L 48.95 295.75 C 49.66 295.75 50.23 295.31 50.23 294.75 L 50.23 292.84 C 50.23 292.29 49.66 291.84 48.95 291.84 Z M 9.51 289.84 L 50.51 289.84 C 51.46 289.84 52.17 290.54 52.17 291.38 L 52.17 296.18 C 52.17 297.01 51.46 297.71 50.51 297.71 L 9.51 297.71 C 8.54 297.71 7.84 297.01 7.84 296.18 L 7.84 291.38 C 7.84 290.54 8.54 289.84 9.51 289.84 Z M 9.51 288.05 C 7.63 288.05 6.05 289.52 6.05 291.38 L 6.05 296.18 C 6.05 298.03 7.63 299.5 9.51 299.5 L 50.51 299.5 C 52.38 299.5 53.96 298.03 53.96 296.18 L 53.96 291.38 C 53.96 289.52 52.38 288.05 50.51 288.05 Z M 11.64 258.14 L 48.34 258.14 L 48.34 280.44 L 11.64 280.44 Z M 10.77 256.35 C 10.77 256.35 10.27 256.5 10.27 256.5 C 10.27 256.5 10.17 256.58 10.13 256.61 C 10.1 256.65 10.07 256.69 10.05 256.71 C 9.96 256.83 9.94 256.9 9.92 256.95 C 9.88 257.05 9.88 257.1 9.87 257.15 C 9.86 257.24 9.85 257.29 9.85 257.37 L 9.85 281.21 C 9.85 281.28 9.86 281.34 9.87 281.43 C 9.88 281.48 9.88 281.52 9.92 281.62 C 9.94 281.67 9.96 281.75 10.05 281.86 C 10.07 281.89 10.1 281.93 10.13 281.96 C 10.17 282 10.27 282.07 10.27 282.07 C 10.27 282.08 10.77 282.23 10.77 282.23 L 49.21 282.23 C 49.22 282.23 49.71 282.07 49.71 282.07 C 49.71 282.07 49.82 282 49.85 281.96 C 49.89 281.93 49.91 281.89 49.94 281.86 C 50.03 281.75 50.04 281.67 50.07 281.62 C 50.11 281.52 50.11 281.48 50.12 281.43 C 50.13 281.34 50.13 281.28 50.13 281.21 L 50.13 257.37 C 50.13 257.29 50.13 257.24 50.12 257.15 C 50.11 257.1 50.11 257.05 50.07 256.95 C 50.04 256.9 50.03 256.83 49.94 256.71 C 49.91 256.69 49.89 256.65 49.85 256.61 C 49.82 256.58 49.71 256.5 49.71 256.5 C 49.71 256.5 49.22 256.35 49.21 256.35 Z M 8.65 254.29 L 51.35 254.29 C 51.81 254.29 52.22 254.69 52.22 255.29 L 52.22 283.24 C 52.22 283.84 51.81 284.23 51.35 284.23 L 8.65 284.23 C 8.2 284.23 7.79 283.84 7.79 283.24 L 7.79 255.29 C 7.79 254.69 8.2 254.29 8.65 254.29 Z M 8.65 252.5 C 7.16 252.5 6 253.79 6 255.29 L 6 283.24 C 6 284.73 7.16 286.02 8.65 286.02 L 51.35 286.02 C 52.84 286.02 54 284.73 54 283.24 L 54 255.29 C 54 253.79 52.84 252.5 51.35 252.5 Z" fill="#232f3e" stroke="none" pointer-events="all"/><g transform="translate(13.5,313.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Client</div></div></foreignObject><text x="16" y="12" fill="#232F3E" text-anchor="middle" font-size="12px" font-family="Helvetica">Client</text></switch></g><path d="M 60 72 L 314 72 L 314 173.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 314 178.88 L 310.5 171.88 L 314 173.63 L 317.5 171.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 0 42 L 60 42 L 60 102 L 0 102 Z" fill="#ffffff" stroke="none" pointer-events="all"/><path d="M 37.98 87.84 C 37.27 87.84 36.7 88.29 36.7 88.84 L 36.7 90.75 C 36.7 91.31 37.27 91.75 37.98 91.75 L 48.95 91.75 C 49.66 91.75 50.23 91.31 50.23 90.75 L 50.23 88.84 C 50.23 88.29 49.66 87.84 48.95 87.84 Z M 9.51 85.84 L 50.51 85.84 C 51.46 85.84 52.17 86.54 52.17 87.38 L 52.17 92.18 C 52.17 93.01 51.46 93.71 50.51 93.71 L 9.51 93.71 C 8.54 93.71 7.84 93.01 7.84 92.18 L 7.84 87.38 C 7.84 86.54 8.54 85.84 9.51 85.84 Z M 9.51 84.05 C 7.63 84.05 6.05 85.52 6.05 87.38 L 6.05 92.18 C 6.05 94.03 7.63 95.5 9.51 95.5 L 50.51 95.5 C 52.38 95.5 53.96 94.03 53.96 92.18 L 53.96 87.38 C 53.96 85.52 52.38 84.05 50.51 84.05 Z M 11.64 54.14 L 48.34 54.14 L 48.34 76.44 L 11.64 76.44 Z M 10.77 52.35 C 10.77 52.35 10.27 52.5 10.27 52.5 C 10.27 52.5 10.17 52.58 10.13 52.61 C 10.1 52.65 10.07 52.69 10.05 52.71 C 9.96 52.83 9.94 52.9 9.92 52.95 C 9.88 53.05 9.88 53.1 9.87 53.15 C 9.86 53.24 9.85 53.29 9.85 53.37 L 9.85 77.21 C 9.85 77.28 9.86 77.34 9.87 77.43 C 9.88 77.48 9.88 77.52 9.92 77.62 C 9.94 77.67 9.96 77.75 10.05 77.86 C 10.07 77.89 10.1 77.93 10.13 77.96 C 10.17 78 10.27 78.07 10.27 78.07 C 10.27 78.08 10.77 78.23 10.77 78.23 L 49.21 78.23 C 49.22 78.23 49.71 78.07 49.71 78.07 C 49.71 78.07 49.82 78 49.85 77.96 C 49.89 77.93 49.91 77.89 49.94 77.86 C 50.03 77.75 50.04 77.67 50.07 77.62 C 50.11 77.52 50.11 77.48 50.12 77.43 C 50.13 77.34 50.13 77.28 50.13 77.21 L 50.13 53.37 C 50.13 53.29 50.13 53.24 50.12 53.15 C 50.11 53.1 50.11 53.05 50.07 52.95 C 50.04 52.9 50.03 52.83 49.94 52.71 C 49.91 52.69 49.89 52.65 49.85 52.61 C 49.82 52.58 49.71 52.5 49.71 52.5 C 49.71 52.5 49.22 52.35 49.21 52.35 Z M 8.65 50.29 L 51.35 50.29 C 51.81 50.29 52.22 50.69 52.22 51.29 L 52.22 79.24 C 52.22 79.84 51.81 80.23 51.35 80.23 L 8.65 80.23 C 8.2 80.23 7.79 79.84 7.79 79.24 L 7.79 51.29 C 7.79 50.69 8.2 50.29 8.65 50.29 Z M 8.65 48.5 C 7.16 48.5 6 49.79 6 51.29 L 6 79.24 C 6 80.73 7.16 82.02 8.65 82.02 L 51.35 82.02 C 52.84 82.02 54 80.73 54 79.24 L 54 51.29 C 54 49.79 52.84 48.5 51.35 48.5 Z" fill="#232f3e" stroke="none" pointer-events="all"/><g transform="translate(13.5,109.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Client</div></div></foreignObject><text x="16" y="12" fill="#232F3E" text-anchor="middle" font-size="12px" font-family="Helvetica">Client</text></switch></g><path d="M 60 378 L 314 378 L 314 257.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 314 252.12 L 317.5 259.12 L 314 257.37 L 310.5 259.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 0 348 L 60 348 L 60 408 L 0 408 Z" fill="#ffffff" stroke="none" pointer-events="all"/><path d="M 37.98 393.84 C 37.27 393.84 36.7 394.29 36.7 394.84 L 36.7 396.75 C 36.7 397.31 37.27 397.75 37.98 397.75 L 48.95 397.75 C 49.66 397.75 50.23 397.31 50.23 396.75 L 50.23 394.84 C 50.23 394.29 49.66 393.84 48.95 393.84 Z M 9.51 391.84 L 50.51 391.84 C 51.46 391.84 52.17 392.54 52.17 393.38 L 52.17 398.18 C 52.17 399.01 51.46 399.71 50.51 399.71 L 9.51 399.71 C 8.54 399.71 7.84 399.01 7.84 398.18 L 7.84 393.38 C 7.84 392.54 8.54 391.84 9.51 391.84 Z M 9.51 390.05 C 7.63 390.05 6.05 391.52 6.05 393.38 L 6.05 398.18 C 6.05 400.03 7.63 401.5 9.51 401.5 L 50.51 401.5 C 52.38 401.5 53.96 400.03 53.96 398.18 L 53.96 393.38 C 53.96 391.52 52.38 390.05 50.51 390.05 Z M 11.64 360.14 L 48.34 360.14 L 48.34 382.44 L 11.64 382.44 Z M 10.77 358.35 C 10.77 358.35 10.27 358.5 10.27 358.5 C 10.27 358.5 10.17 358.58 10.13 358.61 C 10.1 358.65 10.07 358.69 10.05 358.71 C 9.96 358.83 9.94 358.9 9.92 358.95 C 9.88 359.05 9.88 359.1 9.87 359.15 C 9.86 359.24 9.85 359.29 9.85 359.37 L 9.85 383.21 C 9.85 383.28 9.86 383.34 9.87 383.43 C 9.88 383.48 9.88 383.52 9.92 383.62 C 9.94 383.67 9.96 383.75 10.05 383.86 C 10.07 383.89 10.1 383.93 10.13 383.96 C 10.17 384 10.27 384.07 10.27 384.07 C 10.27 384.08 10.77 384.23 10.77 384.23 L 49.21 384.23 C 49.22 384.23 49.71 384.07 49.71 384.07 C 49.71 384.07 49.82 384 49.85 383.96 C 49.89 383.93 49.91 383.89 49.94 383.86 C 50.03 383.75 50.04 383.67 50.07 383.62 C 50.11 383.52 50.11 383.48 50.12 383.43 C 50.13 383.34 50.13 383.28 50.13 383.21 L 50.13 359.37 C 50.13 359.29 50.13 359.24 50.12 359.15 C 50.11 359.1 50.11 359.05 50.07 358.95 C 50.04 358.9 50.03 358.83 49.94 358.71 C 49.91 358.69 49.89 358.65 49.85 358.61 C 49.82 358.58 49.71 358.5 49.71 358.5 C 49.71 358.5 49.22 358.35 49.21 358.35 Z M 8.65 356.29 L 51.35 356.29 C 51.81 356.29 52.22 356.69 52.22 357.29 L 52.22 385.24 C 52.22 385.84 51.81 386.23 51.35 386.23 L 8.65 386.23 C 8.2 386.23 7.79 385.84 7.79 385.24 L 7.79 357.29 C 7.79 356.69 8.2 356.29 8.65 356.29 Z M 8.65 354.5 C 7.16 354.5 6 355.79 6 357.29 L 6 385.24 C 6 386.73 7.16 388.02 8.65 388.02 L 51.35 388.02 C 52.84 388.02 54 386.73 54 385.24 L 54 357.29 C 54 355.79 52.84 354.5 51.35 354.5 Z" fill="#232f3e" stroke="none" pointer-events="all"/><g transform="translate(13.5,415.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Client</div></div></foreignObject><text x="16" y="12" fill="#232F3E" text-anchor="middle" font-size="12px" font-family="Helvetica">Client</text></switch></g><path d="M 300.57 248 C 298.05 248 295.63 246.61 294.37 244 L 279.55 217.33 C 278.21 215 278.42 212.29 279.55 210.35 L 294.5 183.68 C 295.76 181.25 298.02 180 300.32 180 L 330.33 180 C 332.57 180 334.78 181.13 336.1 183.4 L 350.99 210.06 C 352.79 212.87 352.17 215.87 351.16 217.54 L 336.34 244.04 C 335.35 246.19 333.11 248 330.23 248 Z" fill="#5184f3" stroke="none" pointer-events="all"/><path d="M 327.76 248 L 301.23 220.98 L 305.47 219.2 L 301.15 215.01 L 305.25 213.23 L 301.27 209.15 L 306.99 206.13 L 310.91 199.29 L 315.1 203.89 L 316.48 199.4 L 321.33 204.61 L 322.16 199.33 L 346.92 225.11 L 336.34 244.04 C 335.36 246.19 333.11 248 330.23 248 Z" fill-opacity="0.07" fill="#000000" stroke="none" pointer-events="all"/><rect x="278.21" y="180" width="0" height="0" fill="none" stroke="none" pointer-events="all"/><path d="M 319.51 217.25 C 319.51 217.94 319.35 218.24 318.51 218.2 L 312.21 218.2 C 311.65 218.2 311.27 217.86 311.27 217.26 L 311.27 210.71 C 311.27 210.23 311.49 209.8 312.03 209.8 L 318.77 209.8 C 319.2 209.8 319.51 210.04 319.51 210.45 Z M 323.4 222.24 L 323.4 205.82 L 307.43 205.82 L 307.43 222.24 Z M 311.11 224.93 L 311.11 227.87 C 311.11 228.39 310.81 228.84 310.44 228.84 L 309.19 228.84 C 308.82 228.84 308.5 228.48 308.5 228.07 L 308.5 224.93 L 305.68 224.93 C 305.22 224.93 304.91 224.6 304.91 224.11 L 304.91 221.19 L 301.91 221.19 C 301.44 221.19 301.15 220.88 301.15 220.35 L 301.15 219.33 C 301.15 218.75 301.47 218.49 301.92 218.49 L 304.91 218.49 L 304.91 215.28 L 301.71 215.28 C 301.35 215.28 301.15 214.93 301.15 214.5 L 301.15 213.48 C 301.15 213.03 301.31 212.76 301.74 212.76 L 304.91 212.76 L 304.91 209.49 L 301.88 209.49 C 301.55 209.49 301.15 209.15 301.15 208.72 L 301.15 207.55 C 301.15 207.1 301.36 206.82 301.72 206.82 L 304.91 206.82 L 304.91 203.73 C 304.91 203.36 305.13 203.04 305.54 203.04 L 308.5 203.04 L 308.5 199.89 C 308.5 199.44 308.75 199.16 309.07 199.16 L 310.53 199.16 C 310.91 199.16 311.11 199.4 311.11 199.92 L 311.11 203.04 L 314.23 203.04 L 314.23 199.83 C 314.23 199.4 314.46 199.16 314.94 199.16 L 316.03 199.16 C 316.33 199.16 316.64 199.48 316.64 199.8 L 316.64 203.04 L 319.91 203.04 L 319.91 199.81 C 319.91 199.46 320.23 199.16 320.72 199.16 L 321.76 199.16 C 322.06 199.16 322.35 199.41 322.35 199.77 L 322.35 203.04 L 325.34 203.04 C 325.97 203.04 326.1 203.49 326.1 203.98 L 326.1 206.82 L 328.94 206.82 C 329.49 206.82 329.85 207.21 329.85 207.69 L 329.85 208.92 C 329.85 209.19 329.6 209.49 329.2 209.49 L 326.1 209.49 L 326.1 212.76 L 329.34 212.76 C 329.72 212.76 329.85 213.02 329.85 213.33 L 329.85 214.55 C 329.85 214.96 329.66 215.28 329.23 215.28 L 326.1 215.28 L 326.1 218.49 L 328.88 218.49 C 329.5 218.49 329.85 218.72 329.85 219.29 L 329.85 220.56 C 329.85 220.84 329.59 221.19 329.24 221.19 L 326.1 221.19 L 326.1 223.85 C 326.1 224.52 325.81 224.93 324.96 224.93 L 322.35 224.93 L 322.35 227.87 C 322.35 228.44 322.14 228.84 321.55 228.84 L 320.37 228.84 C 320.09 228.84 319.92 228.56 319.91 228.21 L 319.91 224.93 L 316.64 224.93 L 316.64 228.07 C 316.64 228.42 316.49 228.84 315.97 228.84 L 314.71 228.84 C 314.41 228.84 314.23 228.41 314.23 228.07 L 314.23 224.93 Z" fill="#ffffff" stroke="none" pointer-events="all"/><g transform="translate(249.5,264.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="132" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(153, 153, 153); line-height: 1.2; vertical-align: top; white-space: nowrap; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><font style="font-size: 20px">WEB PORTAL</font></div></div></foreignObject><text x="66" y="17" fill="#999999" text-anchor="middle" font-size="11px" font-family="Helvetica" font-weight="bold"><font style="font-size: 20px">WEB PORTAL</font></text></switch></g><path d="M 510.57 412 C 508.05 412 505.63 410.61 504.37 408 L 489.55 381.33 C 488.21 379 488.42 376.29 489.55 374.35 L 504.5 347.68 C 505.76 345.25 508.02 344 510.32 344 L 540.33 344 C 542.57 344 544.78 345.13 546.1 347.4 L 560.99 374.06 C 562.79 376.87 562.17 379.87 561.16 381.54 L 546.34 408.04 C 545.35 410.19 543.11 412 540.23 412 Z" fill="#5184f3" stroke="none" pointer-events="all"/><path d="M 537.76 412 L 511.23 384.98 L 515.47 383.2 L 511.15 379.01 L 515.25 377.23 L 511.27 373.15 L 516.99 370.13 L 520.91 363.29 L 525.1 367.89 L 526.48 363.4 L 531.33 368.61 L 532.16 363.33 L 556.92 389.11 L 546.34 408.04 C 545.36 410.19 543.11 412 540.23 412 Z" fill-opacity="0.07" fill="#000000" stroke="none" pointer-events="all"/><rect x="488.21" y="344" width="0" height="0" fill="none" stroke="none" pointer-events="all"/><path d="M 529.51 381.25 C 529.51 381.94 529.35 382.24 528.51 382.2 L 522.21 382.2 C 521.65 382.2 521.27 381.86 521.27 381.26 L 521.27 374.71 C 521.27 374.23 521.49 373.8 522.03 373.8 L 528.77 373.8 C 529.2 373.8 529.51 374.04 529.51 374.45 Z M 533.4 386.24 L 533.4 369.82 L 517.43 369.82 L 517.43 386.24 Z M 521.11 388.93 L 521.11 391.87 C 521.11 392.39 520.81 392.84 520.44 392.84 L 519.19 392.84 C 518.82 392.84 518.5 392.48 518.5 392.07 L 518.5 388.93 L 515.68 388.93 C 515.22 388.93 514.91 388.6 514.91 388.11 L 514.91 385.19 L 511.91 385.19 C 511.44 385.19 511.15 384.88 511.15 384.35 L 511.15 383.33 C 511.15 382.75 511.47 382.49 511.92 382.49 L 514.91 382.49 L 514.91 379.28 L 511.71 379.28 C 511.35 379.28 511.15 378.93 511.15 378.5 L 511.15 377.48 C 511.15 377.03 511.31 376.76 511.74 376.76 L 514.91 376.76 L 514.91 373.49 L 511.88 373.49 C 511.55 373.49 511.15 373.15 511.15 372.72 L 511.15 371.55 C 511.15 371.1 511.36 370.82 511.72 370.82 L 514.91 370.82 L 514.91 367.73 C 514.91 367.36 515.13 367.04 515.54 367.04 L 518.5 367.04 L 518.5 363.89 C 518.5 363.44 518.75 363.16 519.07 363.16 L 520.53 363.16 C 520.91 363.16 521.11 363.4 521.11 363.92 L 521.11 367.04 L 524.23 367.04 L 524.23 363.83 C 524.23 363.4 524.46 363.16 524.94 363.16 L 526.03 363.16 C 526.33 363.16 526.64 363.48 526.64 363.8 L 526.64 367.04 L 529.91 367.04 L 529.91 363.81 C 529.91 363.46 530.23 363.16 530.72 363.16 L 531.76 363.16 C 532.06 363.16 532.35 363.41 532.35 363.77 L 532.35 367.04 L 535.34 367.04 C 535.97 367.04 536.1 367.49 536.1 367.98 L 536.1 370.82 L 538.94 370.82 C 539.49 370.82 539.85 371.21 539.85 371.69 L 539.85 372.92 C 539.85 373.19 539.6 373.49 539.2 373.49 L 536.1 373.49 L 536.1 376.76 L 539.34 376.76 C 539.72 376.76 539.85 377.02 539.85 377.33 L 539.85 378.55 C 539.85 378.96 539.66 379.28 539.23 379.28 L 536.1 379.28 L 536.1 382.49 L 538.88 382.49 C 539.5 382.49 539.85 382.72 539.85 383.29 L 539.85 384.56 C 539.85 384.84 539.59 385.19 539.24 385.19 L 536.1 385.19 L 536.1 387.85 C 536.1 388.52 535.81 388.93 534.96 388.93 L 532.35 388.93 L 532.35 391.87 C 532.35 392.44 532.14 392.84 531.55 392.84 L 530.37 392.84 C 530.09 392.84 529.92 392.56 529.91 392.21 L 529.91 388.93 L 526.64 388.93 L 526.64 392.07 C 526.64 392.42 526.49 392.84 525.97 392.84 L 524.71 392.84 C 524.41 392.84 524.23 392.41 524.23 392.07 L 524.23 388.93 Z" fill="#ffffff" stroke="none" pointer-events="all"/><g transform="translate(469.5,428.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="112" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(153, 153, 153); line-height: 1.2; vertical-align: top; white-space: nowrap; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><font style="font-size: 20px">DOCSTORE</font></div></div></foreignObject><text x="56" y="17" fill="#999999" text-anchor="middle" font-size="11px" font-family="Helvetica" font-weight="bold"><font style="font-size: 20px">DOCSTORE</font></text></switch></g><path d="M 510.57 84 C 508.05 84 505.63 82.61 504.37 80 L 489.55 53.33 C 488.21 51 488.42 48.29 489.55 46.35 L 504.5 19.68 C 505.76 17.25 508.02 16 510.32 16 L 540.33 16 C 542.57 16 544.78 17.13 546.1 19.4 L 560.99 46.06 C 562.79 48.87 562.17 51.87 561.16 53.54 L 546.34 80.04 C 545.35 82.19 543.11 84 540.23 84 Z" fill="#5184f3" stroke="none" pointer-events="all"/><path d="M 537.76 84 L 511.23 56.98 L 515.47 55.2 L 511.15 51.01 L 515.25 49.23 L 511.27 45.15 L 516.99 42.13 L 520.91 35.29 L 525.1 39.89 L 526.48 35.4 L 531.33 40.61 L 532.16 35.33 L 556.92 61.11 L 546.34 80.04 C 545.36 82.19 543.11 84 540.23 84 Z" fill-opacity="0.07" fill="#000000" stroke="none" pointer-events="all"/><rect x="488.21" y="16" width="0" height="0" fill="none" stroke="none" pointer-events="all"/><path d="M 529.51 53.25 C 529.51 53.94 529.35 54.24 528.51 54.2 L 522.21 54.2 C 521.65 54.2 521.27 53.86 521.27 53.26 L 521.27 46.71 C 521.27 46.23 521.49 45.8 522.03 45.8 L 528.77 45.8 C 529.2 45.8 529.51 46.04 529.51 46.45 Z M 533.4 58.24 L 533.4 41.82 L 517.43 41.82 L 517.43 58.24 Z M 521.11 60.93 L 521.11 63.87 C 521.11 64.39 520.81 64.84 520.44 64.84 L 519.19 64.84 C 518.82 64.84 518.5 64.48 518.5 64.07 L 518.5 60.93 L 515.68 60.93 C 515.22 60.93 514.91 60.6 514.91 60.11 L 514.91 57.19 L 511.91 57.19 C 511.44 57.19 511.15 56.88 511.15 56.35 L 511.15 55.33 C 511.15 54.75 511.47 54.49 511.92 54.49 L 514.91 54.49 L 514.91 51.28 L 511.71 51.28 C 511.35 51.28 511.15 50.93 511.15 50.5 L 511.15 49.48 C 511.15 49.03 511.31 48.76 511.74 48.76 L 514.91 48.76 L 514.91 45.49 L 511.88 45.49 C 511.55 45.49 511.15 45.15 511.15 44.72 L 511.15 43.55 C 511.15 43.1 511.36 42.82 511.72 42.82 L 514.91 42.82 L 514.91 39.73 C 514.91 39.36 515.13 39.04 515.54 39.04 L 518.5 39.04 L 518.5 35.89 C 518.5 35.44 518.75 35.16 519.07 35.16 L 520.53 35.16 C 520.91 35.16 521.11 35.4 521.11 35.92 L 521.11 39.04 L 524.23 39.04 L 524.23 35.83 C 524.23 35.4 524.46 35.16 524.94 35.16 L 526.03 35.16 C 526.33 35.16 526.64 35.48 526.64 35.8 L 526.64 39.04 L 529.91 39.04 L 529.91 35.81 C 529.91 35.46 530.23 35.16 530.72 35.16 L 531.76 35.16 C 532.06 35.16 532.35 35.41 532.35 35.77 L 532.35 39.04 L 535.34 39.04 C 535.97 39.04 536.1 39.49 536.1 39.98 L 536.1 42.82 L 538.94 42.82 C 539.49 42.82 539.85 43.21 539.85 43.69 L 539.85 44.92 C 539.85 45.19 539.6 45.49 539.2 45.49 L 536.1 45.49 L 536.1 48.76 L 539.34 48.76 C 539.72 48.76 539.85 49.02 539.85 49.33 L 539.85 50.55 C 539.85 50.96 539.66 51.28 539.23 51.28 L 536.1 51.28 L 536.1 54.49 L 538.88 54.49 C 539.5 54.49 539.85 54.72 539.85 55.29 L 539.85 56.56 C 539.85 56.84 539.59 57.19 539.24 57.19 L 536.1 57.19 L 536.1 59.85 C 536.1 60.52 535.81 60.93 534.96 60.93 L 532.35 60.93 L 532.35 63.87 C 532.35 64.44 532.14 64.84 531.55 64.84 L 530.37 64.84 C 530.09 64.84 529.92 64.56 529.91 64.21 L 529.91 60.93 L 526.64 60.93 L 526.64 64.07 C 526.64 64.42 526.49 64.84 525.97 64.84 L 524.71 64.84 C 524.41 64.84 524.23 64.41 524.23 64.07 L 524.23 60.93 Z" fill="#ffffff" stroke="none" pointer-events="all"/><g transform="translate(469.5,100.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="111" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(153, 153, 153); line-height: 1.2; vertical-align: top; white-space: nowrap; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><font style="font-size: 20px">FILESTORE</font></div></div></foreignObject><text x="56" y="17" fill="#999999" text-anchor="middle" font-size="11px" font-family="Helvetica" font-weight="bold"><font style="font-size: 20px">FILESTORE</font></text></switch></g><path d="M 352 214 L 399 214 L 399 378 L 480.76 378" fill="none" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 486.76 378 L 480.76 380 L 480.76 376 Z" fill="#4284f3" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 352 214 L 399 214 L 399 50 L 480.76 50" fill="none" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 486.76 50 L 480.76 52 L 480.76 48 Z" fill="#4284f3" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 510.57 248 C 508.05 248 505.63 246.61 504.37 244 L 489.55 217.33 C 488.21 215 488.42 212.29 489.55 210.35 L 504.5 183.68 C 505.76 181.25 508.02 180 510.32 180 L 540.33 180 C 542.57 180 544.78 181.13 546.1 183.4 L 560.99 210.06 C 562.79 212.87 562.17 215.87 561.16 217.54 L 546.34 244.04 C 545.35 246.19 543.11 248 540.23 248 Z" fill="#5184f3" stroke="none" pointer-events="all"/><path d="M 537.76 248 L 511.23 220.98 L 515.47 219.2 L 511.15 215.01 L 515.25 213.23 L 511.27 209.15 L 516.99 206.13 L 520.91 199.29 L 525.1 203.89 L 526.48 199.4 L 531.33 204.61 L 532.16 199.33 L 556.92 225.11 L 546.34 244.04 C 545.36 246.19 543.11 248 540.23 248 Z" fill-opacity="0.07" fill="#000000" stroke="none" pointer-events="all"/><rect x="488.21" y="180" width="0" height="0" fill="none" stroke="none" pointer-events="all"/><path d="M 529.51 217.25 C 529.51 217.94 529.35 218.24 528.51 218.2 L 522.21 218.2 C 521.65 218.2 521.27 217.86 521.27 217.26 L 521.27 210.71 C 521.27 210.23 521.49 209.8 522.03 209.8 L 528.77 209.8 C 529.2 209.8 529.51 210.04 529.51 210.45 Z M 533.4 222.24 L 533.4 205.82 L 517.43 205.82 L 517.43 222.24 Z M 521.11 224.93 L 521.11 227.87 C 521.11 228.39 520.81 228.84 520.44 228.84 L 519.19 228.84 C 518.82 228.84 518.5 228.48 518.5 228.07 L 518.5 224.93 L 515.68 224.93 C 515.22 224.93 514.91 224.6 514.91 224.11 L 514.91 221.19 L 511.91 221.19 C 511.44 221.19 511.15 220.88 511.15 220.35 L 511.15 219.33 C 511.15 218.75 511.47 218.49 511.92 218.49 L 514.91 218.49 L 514.91 215.28 L 511.71 215.28 C 511.35 215.28 511.15 214.93 511.15 214.5 L 511.15 213.48 C 511.15 213.03 511.31 212.76 511.74 212.76 L 514.91 212.76 L 514.91 209.49 L 511.88 209.49 C 511.55 209.49 511.15 209.15 511.15 208.72 L 511.15 207.55 C 511.15 207.1 511.36 206.82 511.72 206.82 L 514.91 206.82 L 514.91 203.73 C 514.91 203.36 515.13 203.04 515.54 203.04 L 518.5 203.04 L 518.5 199.89 C 518.5 199.44 518.75 199.16 519.07 199.16 L 520.53 199.16 C 520.91 199.16 521.11 199.4 521.11 199.92 L 521.11 203.04 L 524.23 203.04 L 524.23 199.83 C 524.23 199.4 524.46 199.16 524.94 199.16 L 526.03 199.16 C 526.33 199.16 526.64 199.48 526.64 199.8 L 526.64 203.04 L 529.91 203.04 L 529.91 199.81 C 529.91 199.46 530.23 199.16 530.72 199.16 L 531.76 199.16 C 532.06 199.16 532.35 199.41 532.35 199.77 L 532.35 203.04 L 535.34 203.04 C 535.97 203.04 536.1 203.49 536.1 203.98 L 536.1 206.82 L 538.94 206.82 C 539.49 206.82 539.85 207.21 539.85 207.69 L 539.85 208.92 C 539.85 209.19 539.6 209.49 539.2 209.49 L 536.1 209.49 L 536.1 212.76 L 539.34 212.76 C 539.72 212.76 539.85 213.02 539.85 213.33 L 539.85 214.55 C 539.85 214.96 539.66 215.28 539.23 215.28 L 536.1 215.28 L 536.1 218.49 L 538.88 218.49 C 539.5 218.49 539.85 218.72 539.85 219.29 L 539.85 220.56 C 539.85 220.84 539.59 221.19 539.24 221.19 L 536.1 221.19 L 536.1 223.85 C 536.1 224.52 535.81 224.93 534.96 224.93 L 532.35 224.93 L 532.35 227.87 C 532.35 228.44 532.14 228.84 531.55 228.84 L 530.37 228.84 C 530.09 228.84 529.92 228.56 529.91 228.21 L 529.91 224.93 L 526.64 224.93 L 526.64 228.07 C 526.64 228.42 526.49 228.84 525.97 228.84 L 524.71 228.84 C 524.41 228.84 524.23 228.41 524.23 228.07 L 524.23 224.93 Z" fill="#ffffff" stroke="none" pointer-events="all"/><g transform="translate(455.5,264.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="139" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(153, 153, 153); line-height: 1.2; vertical-align: top; white-space: nowrap; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><font style="font-size: 20px">COMPILATION</font></div></div></foreignObject><text x="70" y="17" fill="#999999" text-anchor="middle" font-size="11px" font-family="Helvetica" font-weight="bold"><font style="font-size: 20px">COMPILATION</font></text></switch></g><path d="M 526 84 L 526 171.76" fill="none" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 526 177.76 L 524 171.76 L 528 171.76 Z" fill="#4284f3" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 526 256.24 L 526 344" fill="none" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 526 250.24 L 528 256.24 L 524 256.24 Z" fill="#4284f3" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 352 214 L 480.76 214" fill="none" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 486.76 214 L 480.76 216 L 480.76 212 Z" fill="#4284f3" stroke="#4284f3" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/></g></svg> \ No newline at end of file diff --git a/tuto2/index.html b/tuto2/index.html new file mode 100644 index 0000000000000000000000000000000000000000..daed5641157b88a5364aa4f48bca9bc3ef1a9364 --- /dev/null +++ b/tuto2/index.html @@ -0,0 +1,444 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<head> +<!-- 2019-11-29 ven. 00:21 --> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1" /> +<title>Distributed experiments on Grid'5000 … and beyond !</title> +<meta name="generator" content="Org mode" /> +<meta name="author" content="Matthieu Simonin" /> +<style type="text/css"> + <!--/*--><![CDATA[/*><!--*/ + .title { text-align: center; + margin-bottom: .2em; } + .subtitle { text-align: center; + font-size: medium; + font-weight: bold; + margin-top:0; } + .todo { font-family: monospace; color: red; } + .done { font-family: monospace; color: green; } + .priority { font-family: monospace; color: orange; } + .tag { background-color: #eee; font-family: monospace; + padding: 2px; font-size: 80%; font-weight: normal; } + .timestamp { color: #bebebe; } + .timestamp-kwd { color: #5f9ea0; } + .org-right { margin-left: auto; margin-right: 0px; text-align: right; } + .org-left { margin-left: 0px; margin-right: auto; text-align: left; } + .org-center { margin-left: auto; margin-right: auto; text-align: center; } + .underline { text-decoration: underline; } + #postamble p, #preamble p { font-size: 90%; margin: .2em; } + p.verse { margin-left: 3%; } + pre { + border: 1px solid #ccc; + box-shadow: 3px 3px 3px #eee; + padding: 8pt; + font-family: monospace; + overflow: auto; + margin: 1.2em; + } + pre.src { + position: relative; + overflow: visible; + padding-top: 1.2em; + } + pre.src:before { + display: none; + position: absolute; + background-color: white; + top: -10px; + right: 10px; + padding: 3px; + border: 1px solid black; + } + pre.src:hover:before { display: inline;} + /* Languages per Org manual */ + pre.src-asymptote:before { content: 'Asymptote'; } + pre.src-awk:before { content: 'Awk'; } + pre.src-C:before { content: 'C'; } + /* pre.src-C++ doesn't work in CSS */ + pre.src-clojure:before { content: 'Clojure'; } + pre.src-css:before { content: 'CSS'; } + pre.src-D:before { content: 'D'; } + pre.src-ditaa:before { content: 'ditaa'; } + pre.src-dot:before { content: 'Graphviz'; } + pre.src-calc:before { content: 'Emacs Calc'; } + pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } + pre.src-fortran:before { content: 'Fortran'; } + pre.src-gnuplot:before { content: 'gnuplot'; } + pre.src-haskell:before { content: 'Haskell'; } + pre.src-hledger:before { content: 'hledger'; } + pre.src-java:before { content: 'Java'; } + pre.src-js:before { content: 'Javascript'; } + pre.src-latex:before { content: 'LaTeX'; } + pre.src-ledger:before { content: 'Ledger'; } + pre.src-lisp:before { content: 'Lisp'; } + pre.src-lilypond:before { content: 'Lilypond'; } + pre.src-lua:before { content: 'Lua'; } + pre.src-matlab:before { content: 'MATLAB'; } + pre.src-mscgen:before { content: 'Mscgen'; } + pre.src-ocaml:before { content: 'Objective Caml'; } + pre.src-octave:before { content: 'Octave'; } + pre.src-org:before { content: 'Org mode'; } + pre.src-oz:before { content: 'OZ'; } + pre.src-plantuml:before { content: 'Plantuml'; } + pre.src-processing:before { content: 'Processing.js'; } + pre.src-python:before { content: 'Python'; } + pre.src-R:before { content: 'R'; } + pre.src-ruby:before { content: 'Ruby'; } + pre.src-sass:before { content: 'Sass'; } + pre.src-scheme:before { content: 'Scheme'; } + pre.src-screen:before { content: 'Gnu Screen'; } + pre.src-sed:before { content: 'Sed'; } + pre.src-sh:before { content: 'shell'; } + pre.src-sql:before { content: 'SQL'; } + pre.src-sqlite:before { content: 'SQLite'; } + /* additional languages in org.el's org-babel-load-languages alist */ + pre.src-forth:before { content: 'Forth'; } + pre.src-io:before { content: 'IO'; } + pre.src-J:before { content: 'J'; } + pre.src-makefile:before { content: 'Makefile'; } + pre.src-maxima:before { content: 'Maxima'; } + pre.src-perl:before { content: 'Perl'; } + pre.src-picolisp:before { content: 'Pico Lisp'; } + pre.src-scala:before { content: 'Scala'; } + pre.src-shell:before { content: 'Shell Script'; } + pre.src-ebnf2ps:before { content: 'ebfn2ps'; } + /* additional language identifiers per "defun org-babel-execute" + in ob-*.el */ + pre.src-cpp:before { content: 'C++'; } + pre.src-abc:before { content: 'ABC'; } + pre.src-coq:before { content: 'Coq'; } + pre.src-groovy:before { content: 'Groovy'; } + /* additional language identifiers from org-babel-shell-names in + ob-shell.el: ob-shell is the only babel language using a lambda to put + the execution function name together. */ + pre.src-bash:before { content: 'bash'; } + pre.src-csh:before { content: 'csh'; } + pre.src-ash:before { content: 'ash'; } + pre.src-dash:before { content: 'dash'; } + pre.src-ksh:before { content: 'ksh'; } + pre.src-mksh:before { content: 'mksh'; } + pre.src-posh:before { content: 'posh'; } + /* Additional Emacs modes also supported by the LaTeX listings package */ + pre.src-ada:before { content: 'Ada'; } + pre.src-asm:before { content: 'Assembler'; } + pre.src-caml:before { content: 'Caml'; } + pre.src-delphi:before { content: 'Delphi'; } + pre.src-html:before { content: 'HTML'; } + pre.src-idl:before { content: 'IDL'; } + pre.src-mercury:before { content: 'Mercury'; } + pre.src-metapost:before { content: 'MetaPost'; } + pre.src-modula-2:before { content: 'Modula-2'; } + pre.src-pascal:before { content: 'Pascal'; } + pre.src-ps:before { content: 'PostScript'; } + pre.src-prolog:before { content: 'Prolog'; } + pre.src-simula:before { content: 'Simula'; } + pre.src-tcl:before { content: 'tcl'; } + pre.src-tex:before { content: 'TeX'; } + pre.src-plain-tex:before { content: 'Plain TeX'; } + pre.src-verilog:before { content: 'Verilog'; } + pre.src-vhdl:before { content: 'VHDL'; } + pre.src-xml:before { content: 'XML'; } + pre.src-nxml:before { content: 'XML'; } + /* add a generic configuration mode; LaTeX export needs an additional + (add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */ + pre.src-conf:before { content: 'Configuration File'; } + + table { border-collapse:collapse; } + caption.t-above { caption-side: top; } + caption.t-bottom { caption-side: bottom; } + td, th { vertical-align:top; } + th.org-right { text-align: center; } + th.org-left { text-align: center; } + th.org-center { text-align: center; } + td.org-right { text-align: right; } + td.org-left { text-align: left; } + td.org-center { text-align: center; } + dt { font-weight: bold; } + .footpara { display: inline; } + .footdef { margin-bottom: 1em; } + .figure { padding: 1em; } + .figure p { text-align: center; } + .inlinetask { + padding: 10px; + border: 2px solid gray; + margin: 10px; + background: #ffffcc; + } + #org-div-home-and-up + { text-align: right; font-size: 70%; white-space: nowrap; } + textarea { overflow-x: auto; } + .linenr { font-size: smaller } + .code-highlighted { background-color: #ffff00; } + .org-info-js_info-navigation { border-style: none; } + #org-info-js_console-label + { font-size: 10px; font-weight: bold; white-space: nowrap; } + .org-info-js_search-highlight + { background-color: #ffff00; color: #000000; font-weight: bold; } + .org-svg { width: 90%; } + /*]]>*/--> +</style> +<link rel="stylesheet" type="text/css" href="timeline.css" /> +<script type="text/javascript"> +/* +@licstart The following is the entire license notice for the +JavaScript code in this tag. + +Copyright (C) 2012-2018 Free Software Foundation, Inc. + +The JavaScript code in this tag is free software: you can +redistribute it and/or modify it under the terms of the GNU +General Public License (GNU GPL) as published by the Free Software +Foundation, either version 3 of the License, or (at your option) +any later version. The code is distributed WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + +As additional permission under GNU GPL version 3 section 7, you +may distribute non-source (e.g., minimized or compacted) forms of +that code without the copy of the GNU GPL normally required by +section 4, provided you include this license notice and a URL +through which recipients can access the Corresponding Source. + + +@licend The above is the entire license notice +for the JavaScript code in this tag. +*/ +<!--/*--><![CDATA[/*><!--*/ + function CodeHighlightOn(elem, id) + { + var target = document.getElementById(id); + if(null != target) { + elem.cacheClassElem = elem.className; + elem.cacheClassTarget = target.className; + target.className = "code-highlighted"; + elem.className = "code-highlighted"; + } + } + function CodeHighlightOff(elem, id) + { + var target = document.getElementById(id); + if(elem.cacheClassElem) + elem.className = elem.cacheClassElem; + if(elem.cacheClassTarget) + target.className = elem.cacheClassTarget; + } +/*]]>*///--> +</script> +</head> +<body> +<div id="content"> +<h1 class="title">Distributed experiments on Grid'5000 … and beyond !</h1> +<div id="table-of-contents"> +<h2>Table of Contents</h2> +<div id="text-table-of-contents"> +<ul> +<li><a href="#orgc38ce74">1. Benchmarking a real application</a></li> +<li><a href="#org3f66fb1">2. Before you start</a> +<ul> +<li><a href="#orga16f997">2.1. Grid'5000 stuffs</a></li> +<li><a href="#orgfd9377b">2.2. Setup on Grid'5000</a></li> +</ul> +</li> +<li><a href="#orgc0bc76f">3. Deployment time !</a> +<ul> +<li><a href="#org1df303b">3.1. Deploy it</a></li> +<li><a href="#org8a7d969">3.2. Access it</a></li> +</ul> +</li> +</ul> +</div> +</div> + + +<div id="outline-container-orgc38ce74" class="outline-2"> +<h2 id="orgc38ce74"><span class="section-number-2">1</span> Benchmarking a real application</h2> +<div class="outline-text-2" id="text-1"> +<p> +In this tutorial we'll cover some aspects of evaluating the performance of a +real application. We'll work with <code>overleaf</code>. <code>overleaf</code> is a collaborative +text editor that uses Latex to produce pdf files. Figure <a href="#org3218667">1</a> +is an overview of the editing part of the software. +</p> + + +<div id="org3218667" class="figure"> +<p><a href="./figs/overleaf-v2-editor.png"><img src="./figs/overleaf-v2-editor.png" alt="overleaf-v2-editor.png" /></a> +</p> +<p><span class="figure-number">Figure 1: </span>Overview of <code>overleaf</code> editor: on the left users can collaboratively edit the document. On the right the document is rendered.</p> +</div> + + +<p> +Here is the plan: +</p> + +<ul class="org-ul"> +<li><b><b>Deployment</b></b> You'll first deploy our own <code>overleaf</code> instance (we don't want to use +the official/commercial instance).</li> +<li><b><b>Load generation</b></b> You'll generate compilation of different projects programatically.</li> +<li><b><b>Observation</b></b> You'll observe the effect of the load in the running system through various metrics.</li> +<li><b><b>Feedback</b></b> You'll formulate some hypothesis on the load characteristics and the observed effects on the system.</li> +</ul> +</div> +</div> + +<div id="outline-container-org3f66fb1" class="outline-2"> +<h2 id="org3f66fb1"><span class="section-number-2">2</span> Before you start</h2> +<div class="outline-text-2" id="text-2"> +<p> +Make sure you are ok with the following. +</p> +</div> + +<div id="outline-container-orga16f997" class="outline-3"> +<h3 id="orga16f997"><span class="section-number-3">2.1</span> Grid'5000 stuffs</h3> +<div class="outline-text-3" id="text-2-1"> +<div class="note"> +<p> +Make sure you are familiar with the Grid'5000 architecture. see section 1 & 2 of +<a href="https://www.grid5000.fr/w/Getting_Started">https://www.grid5000.fr/w/Getting_Started</a>. note that we won't do this tutorial +we'll prefer to use higher level tools for now. +</p> + +</div> +</div> +</div> + + +<div id="outline-container-orgfd9377b" class="outline-3"> +<h3 id="orgfd9377b"><span class="section-number-3">2.2</span> Setup on Grid'5000</h3> +<div class="outline-text-3" id="text-2-2"> +<p> +Connect to a Grid'5000 frontend of your choice (e.g rennes, nancy …) +</p> + +<ul class="org-ul"> +<li>create a new directory to host all the scripts of the session</li> +<li>bootstrap a new python3 virtualenv</li> +<li>install EnOSlib and configure the access to the API</li> +</ul> + +<div class="org-src-container"> +<pre class="src src-bash">$<span style="color: #7590db;">frontend</span>: cp -r ~msimonin/public/ccs-g5k-tuto2 . +$<span style="color: #7590db;">frontend</span>: cd ccs-g5k-tuto2 +$<span style="color: #7590db;">frontend</span>: virtualenv --python=python3 venv +$<span style="color: #7590db;">frontend</span>: source venv/bin/activate +$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: pip install -r requirements.txt +$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: echo <span style="color: #2d9574;">'</span> +<span style="color: #2d9574;">verify_ssl: False</span> +<span style="color: #2d9574;">'</span> > ~/.python-grid5000.yaml +</pre> +</div> +</div> +</div> +</div> + +<div id="outline-container-orgc0bc76f" class="outline-2"> +<h2 id="orgc0bc76f"><span class="section-number-2">3</span> Deployment time !</h2> +<div class="outline-text-2" id="text-3"> +<p> +Figure <a href="#org4b6858a">2</a> represents a simplified view of what we'll deploy. In blue +some services of <code>overleaf</code> are represented. First the Web portal is the entry +point to all the user requests. The three other services in the picture are +involved when compiling a document. The compilation service interacts with the +filestore (where the files of the image of the projects are stored) and the +docstore (where the text of the project is stored). There are other service +involved to provide the chat feature, history feature, real-time interaction +… but we'll focus ony on the compilation process. +</p> + + +<div id="org4b6858a" class="figure"> +<p><a href="./figs/simple_compilation.png"><object type="image/svg+xml" data="./figs/simple_compilation.svg" class="org-svg"> +Sorry, your browser does not support SVG.</object></a> +</p> +<p><span class="figure-number">Figure 2: </span>Simplified architecture of the system under study (blue) and the generated users (black). Overleaf is composed of a dozen services ony four are represented here.</p> +</div> + +<div class="note"> +<p> +After the deployment you'll be able to: +</p> +<ul class="org-ul"> +<li>access the web portal and play with your own overleaf instance</li> +<li>access the web portal of your friends and collaborate on a document +(that's not the main objective of the tutorial but that's fun)</li> +</ul> + +</div> +</div> + +<div id="outline-container-org1df303b" class="outline-3"> +<h3 id="org1df303b"><span class="section-number-3">3.1</span> Deploy it</h3> +<div class="outline-text-3" id="text-3-1"> +<div class="org-src-container"> +<pre class="src src-bash">$<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: python overleaf.py deploy --cluster=paravance +</pre> +</div> + +<div class="note"> +<ul class="org-ul"> +<li>You can change the cluster name with any cluster on Grid'5000: see <a href="https://www.grid5000.fr/w/Hardware">https://www.grid5000.fr/w/Hardware</a></li> +<li>This can take several minutes…</li> +</ul> + +</div> +</div> +</div> + +<div id="outline-container-org8a7d969" class="outline-3"> +<h3 id="org8a7d969"><span class="section-number-3">3.2</span> Access it</h3> +<div class="outline-text-3" id="text-3-2"> +<p> +To know where your services is installed you can run: +</p> + +<div class="org-src-container"> +<pre class="src src-bash"> $<span style="color: #7590db;">frontend</span><span style="color: #4f97d7;">(</span>venv<span style="color: #4f97d7;">)</span>: python overleaf.py describe + + <span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Possible outputp</span> +╒══════════════════════╤════════════╤════════╕ +│ Name │ Address │ Port │ +╞══════════════════════╪════════════╪════════╡ +│ Web portal │ <span style="color: #a45bad;">10.144.0.2</span> │ <span style="color: #a45bad;">3000</span> │ +├──────────────────────┼────────────┼────────┤ +│ Monitoring portal │ <span style="color: #a45bad;">10.144.0.2</span> │ <span style="color: #a45bad;">2000</span> │ +├──────────────────────┼────────────┼────────┤ +│ Benchmark portal │ <span style="color: #a45bad;">10.144.0.3</span> │ <span style="color: #a45bad;">8089</span> │ +├──────────────────────┼────────────┼────────┤ +│ Compilation machines │ <span style="color: #a45bad;">10.144.0.4</span> │ - │ +╘══════════════════════╧════════════╧════════╛ +</pre> +</div> + +<div class="note"> +<p> +To access the web portal you can create a tunnel from your local machine to +the machine running the web portal as follows +</p> + +<div class="org-src-container"> +<pre class="src src-bash"><span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Adapt the node names with the node where the portal has been installed</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">Replace <login> by your Grid'5000 login</span> +$<span style="color: #7590db;">yourmachine</span>: ssh -NL <span style="color: #a45bad;">3000:10.144.0.2:3000</span> <login>@access.grid5000.fr + +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">point your browser to localhost:3000</span> +<span style="color: #2aa1ae; background-color: #292e34;"># </span><span style="color: #2aa1ae; background-color: #292e34;">username/mdp: toto@toto.com / toto4242</span> +</pre> +</div> + +</div> +</div> +</div> +</div> +</div> +<div id="postamble" class="status"> +<p class="author">Author: Matthieu Simonin</p> +<p class="date">Created: 2019-11-29 ven. 00:21</p> +<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p> +</div> +</body> +</html> diff --git a/tuto2/index.org b/tuto2/index.org new file mode 100644 index 0000000000000000000000000000000000000000..f915d1e07a48fc6fd0e05922ea0b8fe845d5a6f1 --- /dev/null +++ b/tuto2/index.org @@ -0,0 +1,147 @@ +#+TITLE: Distributed experiments on Grid'5000 ... and beyond ! +#+DATE: +#+AUTHOR: Matthieu Simonin + +#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="timeline.css" /> + +#+MACRO: enoslib EnOSlib +#+MACRO: src_host https://gitlab.inria.fr/discovery/enoslib/blob/v4.8.1/enoslib/host.py#L8-14 +#+MACRO: doc_external_access https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html#accessing-http-services-inside-grid-5000 +#+MACRO: src_provider https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra +#+MACRO: doc_provider https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html +#+MACRO: doc_tasks https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html +#+MACRO: doc_g5k_schema https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html#g5k-schema +#+MACRO: doc_api https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html +#+MACRO: doc_services https://discovery.gitlabpages.inria.fr/enoslib/apidoc/service.html + + +* Benchmarking a real application + + In this tutorial we'll cover some aspects of evaluating the performance of a + real application. We'll work with ~overleaf~. ~overleaf~ is a collaborative + text editor that uses Latex to produce pdf files. Figure [[overleaf]] + is an overview of the editing part of the software. + + #+NAME: overleaf + #+CAPTION: Overview of ~overleaf~ editor: on the left users can collaboratively edit the document. + #+CAPTION: On the right the document is rendered. + [[file:./figs/overleaf-v2-editor.png][file:./figs/overleaf-v2-editor.png]] + + + Here is the plan: + + - **Deployment** You'll first deploy our own ~overleaf~ instance (we don't want to use + the official/commercial instance). + - **Load generation** You'll generate compilation of different projects programatically. + - **Observation** You'll observe the effect of the load in the running system through various metrics. + - **Feedback** You'll formulate some hypothesis on the load characteristics and the observed effects on the system. + +* Before you start + + Make sure you are ok with the following. + +** Grid'5000 stuffs + + #+begin_note + Make sure you are familiar with the Grid'5000 architecture. see section 1 & 2 of + https://www.grid5000.fr/w/Getting_Started. note that we won't do this tutorial + we'll prefer to use higher level tools for now. + #+end_note + + +** Setup on Grid'5000 + + Connect to a Grid'5000 frontend of your choice (e.g rennes, nancy ...) + + - create a new directory to host all the scripts of the session + - bootstrap a new python3 virtualenv + - install {{{enoslib}}} and configure the access to the API + + #+BEGIN_SRC bash :noeval + $frontend: cp -r ~msimonin/public/ccs-g5k-tuto2 . + $frontend: cd ccs-g5k-tuto2 + $frontend: virtualenv --python=python3 venv + $frontend: source venv/bin/activate + $frontend(venv): pip install -r requirements.txt + $frontend(venv): echo ' + verify_ssl: False + ' > ~/.python-grid5000.yaml + #+END_SRC + +* Deployment time ! + + Figure [[architecture]] represents a simplified view of what we'll deploy. In blue + some services of ~overleaf~ are represented. First the Web portal is the entry + point to all the user requests. The three other services in the picture are + involved when compiling a document. The compilation service interacts with the + filestore (where the files of the image of the projects are stored) and the + docstore (where the text of the project is stored). There are other service + involved to provide the chat feature, history feature, real-time interaction + ... but we'll focus ony on the compilation process. + + #+NAME: architecture + #+CAPTION: Simplified architecture of the system under study (blue) and the generated users (black). + #+CAPTION: Overleaf is composed of a dozen services ony four are represented here. + [[file:./figs/simple_compilation.png][file:./figs/simple_compilation.svg]] + + #+begin_note + After the deployment you'll be able to: + - access the web portal and play with your own overleaf instance + - access the web portal of your friends and collaborate on a document + (that's not the main objective of the tutorial but that's fun) + #+end_note + +** Deploy it + + #+BEGIN_SRC bash :noeval + $frontend(venv): python overleaf.py deploy --cluster=paravance + #+END_SRC + + #+begin_note + - You can change the cluster name with any cluster on Grid'5000: see https://www.grid5000.fr/w/Hardware + - This can take several minutes... + #+end_note + +** Access it + + To know where your services is installed you can run: + + #+BEGIN_SRC bash :noeval + $frontend(venv): python overleaf.py describe + + # Possible outputp +╒══════════════════════╤════════════╤════════╕ +│ Name │ Address │ Port │ +╞══════════════════════╪════════════╪════════╡ +│ Web portal │ 10.144.0.2 │ 3000 │ +├──────────────────────┼────────────┼────────┤ +│ Monitoring portal │ 10.144.0.2 │ 2000 │ +├──────────────────────┼────────────┼────────┤ +│ Benchmark portal │ 10.144.0.3 │ 8089 │ +├──────────────────────┼────────────┼────────┤ +│ Compilation machines │ 10.144.0.4 │ - │ +╘══════════════════════╧════════════╧════════╛ + #+END_SRC + + #+BEGIN_note + To access the web portal you can create a tunnel from your local machine to + the machine running the web portal as follows + + #+BEGIN_SRC bash :noeval + # Adapt the node names with the node where the portal has been installed + # Replace <login> by your Grid'5000 login + $yourmachine: ssh -NL 3000:10.144.0.2:3000 <login>@access.grid5000.fr + + # point your browser to localhost:3000 + # username/mdp: toto@toto.com / toto4242 + #+END_SRC + #+END_note + + +* Benchmark the system + +** Deploy the benchmark nodes +** Prepare the monitoring stack +** Observations + +* Scale the system diff --git a/tuto2/index.tex b/tuto2/index.tex new file mode 100644 index 0000000000000000000000000000000000000000..2525d89f1cfa5aa88c321dfeee22a0bd2c90ca34 --- /dev/null +++ b/tuto2/index.tex @@ -0,0 +1,569 @@ +% Created 2019-10-17 jeu. 01:05 +% Intended LaTeX compiler: pdflatex +\documentclass[11pt]{article} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{graphicx} +\usepackage{grffile} +\usepackage{longtable} +\usepackage{wrapfig} +\usepackage{rotating} +\usepackage[normalem]{ulem} +\usepackage{amsmath} +\usepackage{textcomp} +\usepackage{amssymb} +\usepackage{capt-of} +\usepackage{hyperref} +\author{Matthieu Simonin} +\date{} +\title{Distributed experiments on Grid'5000 \ldots{} and beyond !} +\hypersetup{ + pdfauthor={Matthieu Simonin}, + pdftitle={Distributed experiments on Grid'5000 \ldots{} and beyond !}, + pdfkeywords={}, + pdfsubject={}, + pdfcreator={Emacs 26.1 (Org mode 9.1.9)}, + pdflang={English}} +\begin{document} + +\maketitle +\tableofcontents + + +\section{Foreword} +\label{sec:org40b18b0} + +\section{Setup on Grid'5000} +\label{sec:orgec7b370} + +Connect to a Grid'5000 frontend of your choice. + +\begin{itemize} +\item create a new directory to host all the scripts of the session +\item bootstrap a new python3 virtualenv +\item install EnOSlib and configure the access to the API +\item you'll also want to have ipython and ipdb installed +\end{itemize} + +\begin{verbatim} +$frontend: mkdir enoslib_seminar +$frontend: cd enoslib_seminar +$frontend: virtualenv --python=python3 venv +$frontend: source venv/bin/activate +$frontend(venv): pip install enoslib ipython ipdb +$frontend(venv): echo ' +verify_ssl: False +' > ~/.python-grid5000.yaml + +\end{verbatim} + +\section{EnOSlib warmup on Grid'5000} +\label{sec:org54c7eac} + +Learn how to get 2 nodes from Grid'5000 and start launching commands. + +\subsection{Reserve 2 nodes} +\label{sec:org543c543} + +\begin{note} +With EnOSlib you first describe your resource requirements using an abstract +resource description. +Note that the network should be explictly stated. +\end{note} + +Write the following python script in a file \texttt{run.py}. If needed adapt the +\texttt{CLUSTER} and \texttt{SITE} variables. + +\begin{verbatim} +from enoslib.api import run, run_command, gather_facts +from enoslib.infra.enos_g5k.provider import G5k +from enoslib.infra.enos_g5k.configuration import Configuration, NetworkConfiguration + +import logging + + +logging.basicConfig(level=logging.INFO) + + +SITE = "rennes" +CLUSTER = "paravance" + +network = NetworkConfiguration(id="n1", + type="prod", + roles=["my_network"], + site=SITE) + +conf = Configuration.from_settings(job_name="enoslib_tutorial", + job_type="allow_classic_ssh")\ + .add_network_conf(network)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .add_machine(roles=["client"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .finalize() + +provider = G5k(conf) +roles, networks = provider.init() +\end{verbatim} + +For the sake of curiosity let's inspect the roles and networks data +structures using ipython. + +\begin{verbatim} +$frontend (venv): ipython +In [1]: run run.py +# ... +# ... +In [2]: roles +# ... +In [3]: networks +\end{verbatim} + +\begin{note} +The abstract resource description is concretized by the call to the +\texttt{provider.init} method. \texttt{roles} and \texttt{networks} contains the concrete machines +and networks given by Grid'5000. +Check the attributes of the Host data structure in the code: \url{https://gitlab.inria.fr/discovery/enoslib/blob/v4.8.1/enoslib/host.py\#L8-14} +\end{note} + +\subsection{Using the run command and its variants} +\label{sec:org4f0ed16} + +For this part you have two choices to run the examples: +\begin{itemize} +\item (prefered) append it in the previous file and re-run the file (yes this is safe to do so) +\item write the example in the previously open ipython console +\end{itemize} + +\subsubsection{Basics usages} +\label{sec:org7f40ca6} + +Before proceeding you can add this util function to your code. It only used +to pretty print a python dictionnary. +\begin{verbatim} +def pprint(d): + import json + print(json.dumps(d, indent=4)) +\end{verbatim} + +And use the \texttt{enoslib.api.run} function +\begin{verbatim} +server = roles["server"][0] +# --- +# Using run +# -------------------------------------------------------------------- +result = run(f"ping -c 5 {server.address}", roles["client"]) +pprint(result) +\end{verbatim} + +Or the \texttt{enoslib.api.run\_command} function +\begin{verbatim} +# --- +# Using run_command 1/2 +# -------------------------------------------------------------------- +result = run_command(f"ping -c 5 {server.address}", + pattern_hosts="client", + roles=roles) +pprint(result) +\end{verbatim} + +\begin{note} +\texttt{enoslib.api.run} is a specialisation of \texttt{enoslib.api.run\_command}. +The latter let's you use \href{https://docs.ansible.com/ansible/latest/user\_guide/intro\_patterns.html}{some fancy patterns} to determine the list of hosts to run the command on. + +And yes, it uses Ansible behind the scene. +\end{note} + +\subsubsection{Advanced usages} +\label{sec:org0e02fbb} + +\begin{note} +For all the remote interactions, EnOSlib relies on \href{https://docs.ansible.com/ansible/latest/index.html}{Ansible}. Ansible +has it own variables management system. +For instance the task \texttt{Gather Facts} at the beginning of the previous tasks +gathers informations about all/some remote hosts and store them in the +Ansible management system. +\end{note} + +Let's see what Ansible is gathering about the hosts: +\begin{verbatim} +# --- +# Gather facts +# -------------------------------------------------------------------- +result = gather_facts(roles=roles) +pprint(result) +\end{verbatim} +\begin{note} +\texttt{enoslib.api.gather\_facts} is a way to get, in python, the variables known +by Ansible about each host. +\end{note} + +\begin{note} +EnOSlib sits in between two worlds: the Python world and the Ansible +world. One common need is to pass a variables from one world to another. +\begin{itemize} +\item \texttt{enoslib.api.gather\_facts} is a way to get, in Python, the variables known +by Ansible about each host. +\item \texttt{extra\_vars} keyword argument of \texttt{enoslib.api.run} or \texttt{enoslib.api.run\_command} will +pass variables from Python world to Ansible world (global variable) +\item Injecting a key/value in a \texttt{Host.extra} attribute will make the variable \texttt{key} available to Ansible. +This makes the variables Host specific. +\end{itemize} +\end{note} + +The following inject a global variable in the Ansible world +\begin{verbatim} +# --- +# Passing a variable to the Ansible World using a global level variable +# -------------------------------------------------------------------- +server = roles["server"][0] +extra_vars={"server_ip": server.address} +result = run("ping -c 5 {{ server_ip }}", roles["client"], extra_vars=extra_vars) +\end{verbatim} + +\subsubsection{Ninja level} +\label{sec:org0edd362} + +The following is valid and inject in the client host a specific variable to +keep of the server IP. + +\begin{verbatim} +# --- +# Passing a variable to the Ansible World using a host level variable +# -------------------------------------------------------------------- +server = roles["server"][0] +client.extra.update(server_ip=server.address) +result = run("ping -c 5 {{ server_ip }}", roles["client"]) +\end{verbatim} + +\begin{note} +Host level variables are interesting to introduce some dissymetry between +hosts using the same intruction in your Python Code. +\end{note} + +\begin{question} +How to perform simultaneously the ping to the other machine in calling only +once \texttt{run} or \texttt{run\_command} and using host level variables? +\end{question} + +\subsubsection{All together} +\label{sec:org31aadd8} +Access the full file: \url{exercices/run.py} + +\subsubsection{Some references} +\label{sec:orga6331bb} + +\begin{itemize} +\item G5k configuration schema: \url{https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html\#g5k-schema} +\item API Reference: \url{https://discovery.gitlabpages.inria.fr/enoslib/apidoc/api.html} +\end{itemize} +\section{Iperf3 playground} +\label{sec:org6a4417e} + +Let's experiment with \href{https://iperf.fr/}{iperf3}: a network bandwidth measuring tool. The goal is +to deploy a simple benchmark between two hosts. + +We'll also instrument the deployment in order to visualize in real-time the +network traffic between the hosts. Since this is super common, EnOSlib +exposes a \texttt{monitoring service} that lets you deploy very quickly what is +needed. + +\subsection{First attempt} +\label{sec:orgbfbe8c2} + +We adapt the previous example in the following script: +\begin{verbatim} +from enoslib.api import run_command, wait_ssh +from enoslib.infra.enos_g5k.provider import G5k +from enoslib.infra.enos_g5k.configuration import Configuration, NetworkConfiguration +from enoslib.service import Monitoring + +import logging + + +def pprint(d): + import json + print(json.dumps(d, indent=4)) + + +logging.basicConfig(level=logging.INFO) + + +SITE = "rennes" +CLUSTER = "paravance" + +network = NetworkConfiguration(id="n1", + type="prod", + roles=["my_network"], + site=SITE) + +conf = Configuration.from_settings(job_name="enoslib_tutorial", + job_type="allow_classic_ssh")\ + .add_network_conf(network)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .add_machine(roles=["client"], + cluster=CLUSTER, + nodes=1, + primary_network=network)\ + .finalize() + +provider = G5k(conf) +roles, networks = provider.init() +wait_ssh(roles) + +# This deploys a monitoring stack +m = Monitoring(collector=roles["server"], + agent=roles["server"] + roles["client"], + ui=roles["server"]) +m.deploy() + + +# Below is the experimentation logic +# It installs the bare minimum to run iperf3 +# The machine with the role 'server' is used to run a iperf3 server +# started in the background in a tmux +# The machine with the role 'client' connects to that server +# Report is printed in stdout +server = roles["server"][0] +run_command("apt update && apt install -y iperf3 tmux", roles=roles) +run_command("tmux new-session -d 'exec iperf3 -s'", pattern_hosts="server", roles=roles) +result = run_command(f"iperf3 -c {server.address} -t 30", pattern_hosts="client", roles=roles) +pprint(result) +\end{verbatim} + +Now, let's visualize the network traffic in real-time ! +\begin{note} +Usually I follow this to access services running inside Grid'5000: +\url{https://discovery.gitlabpages.inria.fr/enoslib/tutorials/grid5000.html\#accessing-http-services-inside-grid-5000} + +So to access the monitoring dashboard you need to connect using your browser +to the machine `server` on the port 3000. +\end{note} + +You should be able to visualize such a thing (after a bit of point and clicks). + +\href{./iperf3.png}{\begin{figure}[htbp] +\centering +\includegraphics[width=.9\linewidth]{./figs/iperf3.png} +\caption{\label{fig:orgd2b4ea5} +iperf3 / monitoring} +\end{figure}} + +\subsection{Discussion} +\label{sec:org8354e5d} + +\begin{itemize} +\item What's good is: \ldots{} +\item What's wrong is: \ldots{} +\end{itemize} + +\subsection{A better approach (maybe)} +\label{sec:org16d8bce} +Access the full file: \url{exercices/iperf3\_better.py} + +\section{{\bfseries\sffamily TODO} Partial wrap-up} +\label{sec:orgb35f46e} + +\begin{itemize} +\item Configuration for G5k +\begin{itemize} +\item non deploy / deploy +\item prod network / kavlan +\end{itemize} +\item Services +\end{itemize} + +\section{Modules: for safer remote actions} +\label{sec:org1ae5927} + +In this section we'll discover the idiomatic way of managing resources on the +remote hosts. A resource can be anything: a user, a file, a line in a file, a +repo on Gitlab, a firewall rule \ldots{} + + +\subsection{Idempotency} +\label{sec:org81e26b7} + +Let's assume you want to create a user (\texttt{foo}). With the \texttt{run\_command} this would look like: + +\begin{verbatim} +run_command("useradd -m foo", roles=role) +\end{verbatim} + +The main issue with this code is that it is not \textbf{idempotent}. Running it once +will applied the effect (create the user). But, as soon as the user exist in +the system, this will raise an error. + +\subsection{One reason why idempotency is important} +\label{sec:org53c14cb} + +Let's consider the following snippet (mispelling the second command is intentional) +\begin{verbatim} +run_command("useradd -m foo", roles=role) +run_command("mkdirz plop") +\end{verbatim} +Executing the above leads the system with the user \texttt{foo} created but the the +directory \texttt{plop} not created since the second command fails. + +So what you want to do is to fix the second command and re-run the snippet again. +But, you can't do that because \texttt{useradd} isn't idempotent. + +\subsection{Idempotency trick} +\label{sec:orgb15c5ef} + +One easy solution is to protect your call to non idempotent commands with +some ad'hoc tricks + +Here it can look like this: + +\begin{verbatim} +run_command("id foo || useradd -m foo", roles=role) +run_command("mkdir -p plop") +\end{verbatim} + +\textbf{What's wrong with that} + +\begin{itemize} +\item The trick depends on the command +\item Re-reading the code is more complex: the code focus on the \textbf{\textbf{how}} not the \textbf{\textbf{what}} +\end{itemize} + +\subsection{General idempotency} +\label{sec:org4061e60} + +The idiomatic solution is to use modules (inherited from the Ansible +Modules). The modules are specified in a \textbf{declarative} way and they ensure +\textbf{idempotency} for most of them. + +So rewriting the example with modules looks like: +\begin{verbatim} +with play_on(roles=roles) as p: + p.user(name="foo", state="present", create_home="yes") + p.file(name="plop", state="directory") +\end{verbatim} + +You can run this code as many times as you want without any error. You'll +eventually find one user \texttt{foo} and one directory \texttt{plop} in your target +systems. + + +They are more than 2500 modules: \url{https://docs.ansible.com/ansible/latest/modules/list\_of\_all\_modules.html} + +If you can't find what you want you must know that: +\begin{itemize} +\item Writing your own module is possible +\item Falling back to the idempotency trick is reasonable +\end{itemize} + +\section{Providers: to replicate your experiment} +\label{sec:orgfc2ec82} + +The resources that are used for your experiment are acquired through a +provider. Providers are a mean to decouple the infrastructure code (the code +that get the resources) from the code that runs the experiment. Changing the +provider allows to replicate the experiment on another testbed. + +Originally it was used to iterate on the code locally (using the Vagrant +provider) and to only test on Grid'5000 when necessary. + +We now have couple of providers that you may picked or mixed. + +\subsection{iperf3 on virtual machines on Grid'5000} +\label{sec:org162cac9} + +We'll adapt the initial iperf3 example to use virtual machines instead of +bare-metal machine. + +Note that: + +\begin{itemize} +\item The configuration object is different +\item The experimentation logic is the same (rewritten using modules when it applies) +\end{itemize} + +\begin{verbatim} +from enoslib.api import play_on, wait_ssh +from enoslib.infra.enos_vmong5k.provider import VMonG5k +from enoslib.infra.enos_vmong5k.configuration import Configuration + +import logging +import os + +logging.basicConfig(level=logging.DEBUG) + +CLUSTER = "paravance" + +# path to the inventory +inventory = os.path.join(os.getcwd(), "hosts") + +# claim the resources +conf = Configuration.from_settings(job_name="enoslib_tutorial", gateway=True)\ + .add_machine(roles=["server"], + cluster=CLUSTER, + number=1, + flavour="large")\ + .add_machine(roles=["client"], + cluster=CLUSTER, + number=1, + flavour="medium")\ + .finalize() + +provider = VMonG5k(conf) + +roles, networks = provider.init() +wait_ssh(roles) + +# Below is the experimentation logic +# It installs the bare minimum to run iperf3 +# The machine with the role 'server' is used to run a iperf3 server +# started in the background in a tmux +# The machine with the role 'client' connects to that server +# Report is printed in stdout +server = roles["server"][0] + +with play_on(roles=roles) as p: + p.apt(name=["iperf3", "tmux"], state="present") + +with play_on(pattern_hosts="server", roles=roles) as p: + p.shell("tmux new-session -d 'exec iperf3 -s'") + +with play_on(pattern_hosts="client", roles=roles) as p: + p.shell(f"iperf3 -c {server.address} -t 30") + +with play_on(pattern_hosts="client", roles=roles) as p: + p.shell(f"iperf3 -c {server.address} -t 30 --logfile iperf3.out") + p.fetch(src="iperf3.out", dest="iperf3.out") +\end{verbatim} + +Using module using the \texttt{play\_on} context manager does not bring back the +results of the commands. Iperf3 let's you write the result of the command on +a file. We just need to scp the file back to our local machine using the +\texttt{fetch} module. + +\subsection{Ninja level} +\label{sec:orge484eb8} + +Creates 5 \texttt{server} machines and 5 \texttt{client} machines and start 5 \textbf{parallel} +streams of data using \texttt{iperf3}. + +\subsection{References} +\label{sec:org9c0017b} + +\begin{itemize} +\item Doc: \url{https://discovery.gitlabpages.inria.fr/enoslib/apidoc/infra.html} +\item Sources: \url{https://gitlab.inria.fr/discovery/enoslib/tree/v4.8.1/enoslib/infra} +\end{itemize} + +\section{Tasks: to organize your experiment} +\label{sec:org7f002c7} + +To discover the Task API, head to \url{https://discovery.gitlabpages.inria.fr/enoslib/tutorials/using-tasks.html}. +The examples are written for Vagrant but may be changed to whatever provider you like/have. +\end{document} diff --git a/tuto2/timeline.css b/tuto2/timeline.css new file mode 100644 index 0000000000000000000000000000000000000000..268b0deaebe45d42a32888682272253296930629 --- /dev/null +++ b/tuto2/timeline.css @@ -0,0 +1,1035 @@ +@import url('https://fonts.googleapis.com/css?family=Crimson+Text:700|Fira+Sans:400,700'); +html { + font-family: sans-serif; + line-height: 1.15; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} +body { + margin: 0 +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +main, +menu, +nav, +section, +summary { + display: block +} +audio, +canvas, +progress, +video { + display: inline-block +} +audio:not([controls]) { + display: none; + height: 0 +} +progress { + vertical-align: baseline +} +[hidden], +template { + display: none +} +a { + background-color: transparent; + -webkit-text-decoration-skip: objects +} +a:active, +a:hover { + outline-width: 0 +} +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} +b, +strong { + font-weight: inherit; + font-weight: bolder +} +dfn { + font-style: italic +} +h1 { + font-size: 2em; + margin: .67em 0 +} +mark { + background-color: #ff0; + color: #000 +} +small { + font-size: 80% +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} +sub { + bottom: -.25em +} +sup { + top: -.5em +} +img { + border-style: none +} +svg:not(:root) { + overflow: hidden +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em +} +figure { + margin: 1em 40px +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} +button, +input, +optgroup, +select, +textarea { + font: inherit; + margin: 0 +} +optgroup { + font-weight: 700 +} +button, +input { + overflow: visible +} +button, +select { + text-transform: none +} +[type=reset], +[type=submit], +button, +html [type=button] { + -webkit-appearance: button +} +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner, +button::-moz-focus-inner { + border-style: none; + padding: 0 +} +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring, +button:-moz-focusring { + outline: 1px dotted ButtonText +} +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: .35em .625em .75em +} +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} +textarea { + overflow: auto +} +[type=checkbox], +[type=radio] { + box-sizing: border-box; + padding: 0 +} +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto +} +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} +[type=search]::-webkit-search-cancel-button, +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} +::-webkit-input-placeholder { + color: inherit; + opacity: .54 +} +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} +.org-bold { + font-weight: 700 +} +.org-bold-italic { + font-weight: 700; + font-style: italic +} +.org-buffer-menu-buffer { + font-weight: 700 +} +.org-builtin { + color: #483d8b +} +.org-button { + color: #3a5fcd; + text-decoration: underline +} +.org-calendar-month-header { + color: #00f +} +.org-calendar-today { + text-decoration: underline +} +.org-calendar-weekday-header { + color: #008b8b +} +.org-calendar-weekend-header { + color: #b22222 +} +.org-comint-highlight-input { + font-weight: 700 +} +.org-comint-highlight-prompt { + color: #0000cd +} +.org-comment, +.org-comment-delimiter { + color: #b22222 +} +.org-constant { + color: #008b8b +} +.org-diary { + color: red +} +.org-doc { + color: #8b2252 +} +.org-error { + color: red; + font-weight: 700 +} +.org-escape-glyph { + color: brown +} +.org-file-name-shadow { + color: #7f7f7f +} +.org-fringe { + background-color: #f2f2f2 +} +.org-function-name { + color: #00f +} +.org-glyphless-char { + font-size: 60% +} +.org-header-line { + color: #333; + background-color: #e5e5e5 +} +.org-help-argument-name { + font-style: italic +} +.org-highlight { + background-color: #b4eeb4 +} +.org-holiday { + background-color: pink +} +.org-info-header-node { + color: brown; + font-weight: 700; + font-style: italic +} +.org-info-header-xref { + color: #3a5fcd; + text-decoration: underline +} +.org-info-index-match { + background-color: #ff0 +} +.org-info-menu-header { + font-weight: 700 +} +.org-info-menu-star { + color: red +} +.org-info-node { + color: brown; + font-weight: 700; + font-style: italic +} +.org-info-title-1 { + font-size: 172%; + font-weight: 700 +} +.org-info-title-2 { + font-size: 144%; + font-weight: 700 +} +.org-info-title-3 { + font-size: 120%; + font-weight: 700 +} +.org-info-title-4 { + font-weight: 700 +} +.org-info-xref { + color: #3a5fcd; + text-decoration: underline +} +.org-italic { + font-style: italic +} +.org-keyword { + color: #a020f0 +} +.org-lazy-highlight { + background-color: #afeeee +} +.org-link { + color: #3a5fcd; + text-decoration: underline +} +.org-link-visited { + color: #8b008b; + text-decoration: underline +} +.org-makefile-makepp-perl { + background-color: #bfefff +} +.org-makefile-space { + background-color: #ff69b4 +} +.org-makefile-targets { + color: #00f +} +.org-match { + background-color: #ff0 +} +.org-next-error { + background-color: gtk_selection_bg_color +} +.org-nobreak-space { + color: brown; + text-decoration: underline +} +.org-org-agenda-calendar-event, +.org-org-agenda-calendar-sexp { + color: #000; + background-color: #fff +} +.org-org-agenda-clocking { + background-color: #ff0 +} +.org-org-agenda-column-dateline { + background-color: #e5e5e5 +} +.org-org-agenda-current-time { + color: #b8860b +} +.org-org-agenda-date { + color: #00f +} +.org-org-agenda-date-today { + color: #00f; + font-weight: 700; + font-style: italic +} +.org-org-agenda-date-weekend { + color: #00f; + font-weight: 700 +} +.org-org-agenda-diary { + color: #000; + background-color: #fff +} +.org-org-agenda-dimmed-todo { + color: #7f7f7f +} +.org-org-agenda-done { + color: #228b22 +} +.org-org-agenda-filter-category, +.org-org-agenda-filter-effort, +.org-org-agenda-filter-regexp, +.org-org-agenda-filter-tags { + color: #000; + background-color: #bfbfbf +} +.org-org-agenda-restriction-lock { + background-color: #eee +} +.org-org-agenda-structure { + color: #00f +} +.org-org-archived, +.org-org-block { + color: #7f7f7f +} +.org-org-block-begin-line, +.org-org-block-end-line { + color: #b22222 +} +.org-org-checkbox { + font-weight: 700 +} +.org-org-checkbox-statistics-done { + color: #228b22; + font-weight: 700 +} +.org-org-checkbox-statistics-todo { + color: red; + font-weight: 700 +} +.org-org-clock-overlay { + color: #000; + background-color: #d3d3d3 +} +.org-org-code { + color: #7f7f7f +} +.org-org-column, +.org-org-column-title { + background-color: #e5e5e5 +} +.org-org-column-title { + font-weight: 700; + text-decoration: underline +} +.org-org-date { + color: #a020f0; + text-decoration: underline +} +.org-org-date-selected { + color: red +} +.org-org-default { + color: #000; + background-color: #fff +} +.org-org-document-info { + color: #191970 +} +.org-org-document-info-keyword { + color: #7f7f7f +} +.org-org-document-title { + color: #191970; + font-weight: 700 +} +.org-org-done { + color: #228b22; + font-weight: 700 +} +.org-org-drawer { + color: #00f +} +.org-org-ellipsis { + color: #b8860b; + text-decoration: underline +} +.org-org-footnote { + color: #a020f0; + text-decoration: underline +} +.org-org-formula { + color: #b22222 +} +.org-org-headline-done { + color: #bc8f8f +} +.org-org-hide { + color: #fff +} +.org-org-latex-and-related { + color: #8b4513 +} +.org-org-level-1 { + color: #00f +} +.org-org-level-2 { + color: sienna +} +.org-org-level-3 { + color: #a020f0 +} +.org-org-level-4 { + color: #b22222 +} +.org-org-level-5 { + color: #228b22 +} +.org-org-level-6 { + color: #008b8b +} +.org-org-level-7 { + color: #483d8b +} +.org-org-level-8 { + color: #8b2252 +} +.org-org-link { + color: #3a5fcd; + text-decoration: underline +} +.org-org-list-dt { + font-weight: 700 +} +.org-org-macro { + color: #8b4513 +} +.org-org-meta-line { + color: #b22222 +} +.org-org-mode-line-clock { + color: #000; + background-color: #bfbfbf +} +.org-org-mode-line-clock-overrun { + color: #000; + background-color: red +} +.org-org-priority { + color: #a020f0 +} +.org-org-quote { + color: #7f7f7f +} +.org-org-scheduled { + color: #006400 +} +.org-org-scheduled-previously { + color: #b22222 +} +.org-org-scheduled-today { + color: #006400 +} +.org-org-sexp-date, +.org-org-special-keyword { + color: #a020f0 +} +.org-org-table { + color: #00f +} +.org-org-tag, +.org-org-tag-group { + font-weight: 700 +} +.org-org-target { + text-decoration: underline +} +.org-org-time-grid { + color: #b8860b +} +.org-org-todo { + color: red; + font-weight: 700 +} +.org-org-upcoming-deadline { + color: #b22222 +} +.org-org-verbatim, +.org-org-verse { + color: #7f7f7f +} +.org-org-warning { + color: red; + font-weight: 700 +} +.org-outline-1 { + color: #00f +} +.org-outline-2 { + color: sienna +} +.org-outline-3 { + color: #a020f0 +} +.org-outline-4 { + color: #b22222 +} +.org-outline-5 { + color: #228b22 +} +.org-outline-6 { + color: #008b8b +} +.org-outline-7 { + color: #483d8b +} +.org-outline-8 { + color: #8b2252 +} +.org-preprocessor { + color: #483d8b +} +.org-regexp-grouping-backslash, +.org-regexp-grouping-construct { + font-weight: 700 +} +.org-region { + background-color: gtk_selection_bg_color +} +.org-secondary-selection { + background-color: #ff0 +} +.org-shadow { + color: #7f7f7f +} +.org-show-paren-match { + background-color: #40e0d0 +} +.org-show-paren-mismatch { + color: #fff; + background-color: #a020f0 +} +.org-string { + color: #8b2252 +} +.org-success { + color: #228b22; + font-weight: 700 +} +.org-table-cell { + color: #e5e5e5; + background-color: #00f +} +.org-tooltip { + color: #000; + background-color: #ffffe0 +} +.org-trailing-whitespace { + background-color: red +} +.org-type { + color: #228b22 +} +.org-underline { + text-decoration: underline +} +.org-variable-name { + color: sienna +} +.org-warning { + color: #ff8c00; + font-weight: 700 +} +.org-warning-1 { + color: red; + font-weight: 700 +} +body { + width: 95%; + margin: 2%; + font: normal normal normal 16px/1.6em 'Fira Sans', sans-serif; + color: #333 +} +@media (min-width: 769px) { + body { + width: 700px; + margin-left: 5vw + } +} +::-moz-selection { + background: #d6edff +} +::selection { + background: #d6edff +} +.title { + margin: auto; + color: #000 +} +.subtitle, +.title { + text-align: center +} +.subtitle { + font-size: medium; + font-weight: 700 +} +.abstract { + margin: auto; + width: 80%; + font-style: italic +} +.abstract p:last-of-type:before { + content: " "; + white-space: pre +} +.status { + font-size: 90%; + margin: 2em auto +} +[class^=section-number-] { + margin-right: .5em +} +[id^=orgheadline] { + clear: both +} +#footnotes { + font-size: 90% +} +.footpara { + display: inline; + margin: .2em auto +} +.footdef { + margin-bottom: 1em +} +.footdef sup { + padding-right: .5em +} +a { + color: #527d9a; + text-decoration: none +} +a:hover { + color: #035; + border-bottom: 1px dotted +} +figure { + padding: 0; + margin: 0; + text-align: center +} +img { + max-width: 100%; + vertical-align: middle +} +.MathJax_Display { + font-size: 90%; + margin: 0!important; + width: 90%!important +} +h1, +h2, +h3, +h4, +h5, +h6 { + color: #a5573e; + line-height: 1.6em; + font-weight: bold; + font-family: 'Crimson Text', serif +} +h4, +h5, +h6 { + font-size: 1em +} +dt { + font-weight: 700 +} +table { + margin: auto; + border-top: 2px solid; + border-collapse: collapse +} +table, +thead { + border-bottom: 2px solid +} +table td+td, +table th+th { + border-left: 1px solid gray +} +table tr { + border-top: 1px solid #d3d3d3 +} +td, +th { + padding: 5px 10px; + vertical-align: middle +} +caption.t-above { + caption-side: top +} +caption.t-bottom { + caption-side: bottom +} +th.org-center, +th.org-left, +th.org-right { + text-align: center +} +td.org-right { + text-align: right +} +td.org-left { + text-align: left +} +td.org-center { + text-align: center +} +code { + padding: 2px 5px; + margin: auto 1px; + border: 1px solid #ddd; + border-radius: 3px; + background-clip: padding-box; + color: #333; + font-size: 80% +} +blockquote { + margin: 1em 2em; + padding-left: 1em; + border-left: 3px solid #ccc +} +kbd { + background-color: #f7f7f7; + font-size: 80%; + margin: 0 .1em; + padding: .1em .6em +} +.todo { + background-color: red; + padding: 2px +} +.done, +.todo { + color: #fff; + border-radius: 3px; + background-clip: padding-box; + font-size: 80%; + font-family: Lucida Console, monospace +} +.done { + background-color: green; + padding: 3px +} +.priority { + color: orange; + font-family: Lucida Console, monospace +} +#table-of-contents li { + clear: both +} +.tag { + font-family: Lucida Console, monospace; + font-size: 70%; + font-weight: 400 +} +.tag span { + padding: 0 5px; + float: right; + margin-right: 5px; + border: 1px solid #bbb; + border-radius: 3px; + background-clip: padding-box; + color: #333; + background-color: #eee; + line-height: 1.6 +} +.timestamp { + color: #bebebe; + font-size: 90% +} +.timestamp-kwd { + color: #5f9ea0 +} +.org-right { + margin-left: auto; + margin-right: 0; + text-align: right +} +.org-left { + margin-left: 0; + margin-right: auto; + text-align: left +} +.org-center { + margin-left: auto; + margin-right: auto; + text-align: center +} +.underline { + text-decoration: underline +} +#postamble p, +#preamble p { + font-size: 90%; + margin: .2em +} +p.verse { + margin-left: 3% +} +pre { + border: 1px solid #ccc; + box-shadow: 3px 3px 3px #eee; + font-family: Lucida Console, monospace; + margin: 1.2em; + padding: 8pt; + font-size: 80%; + background: #3f3f3f; + color: #dcdccc; +} +pre.src { + overflow: auto; + padding-top: 1.2em; + position: relative +} +pre.src:hover:before { + display: inline +} +pre.src-sh:before { + content: "sh" +} +pre.src-bash:before { + content: "bash" +} +pre.src-emacs-lisp:before { + content: "Emacs Lisp" +} +pre.src-R:before { + content: "R" +} +pre.src-org:before { + content: "Org" +} +pre.src-cpp:before { + content: "C++" +} +pre.src-c:before { + content: "C" +} +pre.src-html:before { + content: "HTML" +} +pre.src-javascript:before, +pre.src-js:before { + content: "Javascript" +} +pre.src-makefile:before { + content: "Makefile" +} +.inlinetask { + background: #ffc; + border: 2px solid gray; + margin: 10px; + padding: 10px +} +#org-div-home-and-up { + font-size: 70%; + text-align: right; + white-space: nowrap +} +.linenr { + font-size: 90% +} +.code-highlighted { + color: #FFFFE0; + background-color: #284F28; +} +#bibliography { + font-size: 90% +} +#bibliography table { + width: 100% +} +.creator { + display: block +} +@media (min-width: 769px) { + .creator { + display: inline; + float: right + } +} + +.org-src-container > label { + font-size: 75%; +} + +.note { + padding-left: 2em; + border: 1px dashed #00f; + position: relative; +} +.note:before { + display: block; + position: absolute; + left: 0px; + content: "i"; + background: #00f; + border-radius: 0.8em; + -moz-border-radius: 0.8em; + -webkit-border-radius: 0.8em; + color: #ffffff; + display: inline-block; + font-weight: bold; + line-height: 1.6em; + margin-right: 5px; + text-align: center; + width: 1.6em; +} + +@media screen and (min-width: 600px) { + h1 { + font-size: 2em; + } + h2 { + font-size: 1.5em; + } + h3 { + font-size: 1.3em; + } + h1,h2,h3 { + line-height: 1.4em; + } + h4,h5,h6 { + font-size: 1.1em; + } +} + +.question { + padding-left: 2em; + border: 1px dashed #0f0; + position: relative; +} +.question:before { + display: block; + position: absolute; + left: 0px; + content: "?"; + background: #00ff00; + border-radius: 0.8em; + -moz-border-radius: 0.8em; + -webkit-border-radius: 0.8em; + color: #ffffff; + display: inline-block; + font-weight: bold; + line-height: 1.6em; + margin-right: 5px; + text-align: center; + width: 1.6em; +}