From 0539aed2fe8b94d6b84e6e5fa4eb48d5d6633bc7 Mon Sep 17 00:00:00 2001 From: Baptiste Jonglez <baptiste.jonglez@inria.fr> Date: Fri, 6 May 2022 11:48:34 +0200 Subject: [PATCH 1/5] Load system CA bundle by default to solve SSL errors inside Grid'5000 --- grid5000/__init__.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/grid5000/__init__.py b/grid5000/__init__.py index 8850e50..0dff2ec 100644 --- a/grid5000/__init__.py +++ b/grid5000/__init__.py @@ -19,6 +19,7 @@ from .__version__ import __version__ logger = logging.getLogger(__name__) DEFAULT_BASE_URL = "https://api.grid5000.fr/stable" +DEFAULT_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt" USER_AGENT = "python-grid5000 %s" % __version__ @@ -61,7 +62,7 @@ class Grid5000(object): uri=DEFAULT_BASE_URL, username=None, password=None, - verify_ssl=True, + verify_ssl=None, timeout=None, session=None, sslcert=None, @@ -74,6 +75,18 @@ class Grid5000(object): self.username = username self.password = password self.verify_ssl = verify_ssl + if self.verify_ssl is None: + # By default, requests ignores trusted CA from the system + # (it uses certifi instead). + # On Grid'5000 frontend and nodes, it is necessary to use the + # system CA bundle, because it includes the root Grid'5000 CA + # allowing to validate the internal API certificate. + ca_bundle = Path(DEFAULT_CA_BUNDLE) + if ca_bundle.exists(): + self.verify_ssl = ca_bundle + else: + # As a last resort, use certifi + self.verify_ssl = True self.client_ssl = False self.client_cert = None -- GitLab From f32b4d762d0e3fd7f5b3a6219e4e2bb1dacbbeb2 Mon Sep 17 00:00:00 2001 From: Baptiste Jonglez <baptiste.jonglez@inria.fr> Date: Fri, 6 May 2022 11:33:12 +0200 Subject: [PATCH 2/5] cli: allow to run even if config file is missing --- grid5000/cli.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/grid5000/cli.py b/grid5000/cli.py index 1f5a996..454983f 100644 --- a/grid5000/cli.py +++ b/grid5000/cli.py @@ -20,23 +20,29 @@ MOTD = r""" \____/_/ /_/\__,_/ /_____/\____/\____/\____/ -* Configuration loaded from %s -* A new client (variable gk) has been created for the user %s +""" + +MOTD_END = """\ * Start exploring the API through the gk variable # Example: Get all available sites $) gk.sites.list() """ - def main(): - path = pathlib.Path(CONF_PATH) - if not path.exists(): - print("Configuration file %s is missing" % CONF_PATH) - return - gk = Grid5000.from_yaml(CONF_PATH) - motd = MOTD % (CONF_PATH, gk.username) + motd = MOTD + if path.exists(): + gk = Grid5000.from_yaml(CONF_PATH) + motd += "* Configuration loaded from %s\n" % CONF_PATH + else: + gk = Grid5000() + motd += "* Warning: configuration file %s is missing, authentication might not work\n" % CONF_PATH + if gk.username: + motd += "* A new client (variable gk) has been created for the user %s\n" % gk.username + else: + motd += "* A new client (variable gk) has been created\n" + motd += MOTD_END IPython.embed(header=motd) -- GitLab From 76a92b096f68923ce7132058bced43f65f4904fa Mon Sep 17 00:00:00 2001 From: Baptiste Jonglez <baptiste.jonglez@inria.fr> Date: Fri, 6 May 2022 11:33:33 +0200 Subject: [PATCH 3/5] cli: enable shiny IPython colors --- grid5000/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grid5000/cli.py b/grid5000/cli.py index 454983f..c297f69 100644 --- a/grid5000/cli.py +++ b/grid5000/cli.py @@ -43,7 +43,7 @@ def main(): else: motd += "* A new client (variable gk) has been created\n" motd += MOTD_END - IPython.embed(header=motd) + IPython.embed(header=motd, colors="neutral") def auth(user: str): -- GitLab From 82bed9ed0653101e72e24b69d3a25ba8c4456211 Mon Sep 17 00:00:00 2001 From: Baptiste Jonglez <baptiste.jonglez@inria.fr> Date: Mon, 23 May 2022 11:48:27 +0200 Subject: [PATCH 4/5] Update note about authentication on frontends --- README.org | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.org b/README.org index 726ba72..f1dfa85 100644 --- a/README.org +++ b/README.org @@ -83,9 +83,10 @@ password: MYPASSWORD ' > ~/.python-grid5000.yaml #+END_EXAMPLE -- When accessing the API from a Grid'5000 frontend, providing the username and - password is optionnal. Nevertheless you'll need to deal with SSL verification - by specifying the path to the certificate to use: +- When accessing the API from a Grid'5000 frontend, providing the username + and password is optionnal. Authentication should work out-of-the-box; if + it fails, try updating python-grid5000, or specify the path to the + certificate to use: #+BEGIN_EXAMPLE echo ' -- GitLab From e7de8a209b75f8038895d338505f872f004671ce Mon Sep 17 00:00:00 2001 From: Baptiste Jonglez <baptiste.jonglez@inria.fr> Date: Mon, 23 May 2022 13:10:26 +0200 Subject: [PATCH 5/5] Regenerate ReST README --- README.rst | 90 +++++++++++++----------------------------------------- 1 file changed, 21 insertions(+), 69 deletions(-) diff --git a/README.rst b/README.rst index cef81d9..f28e73f 100644 --- a/README.rst +++ b/README.rst @@ -88,9 +88,10 @@ conform with the Grid5000 API models (with an ’s’!) password: MYPASSWORD ' > ~/.python-grid5000.yaml -- When accessing the API from a Grid’5000 frontend, providing the username and - password is optionnal. Nevertheless you’ll need to deal with SSL verification - by specifying the path to the certificate to use: +- When accessing the API from a Grid’5000 frontend, providing the username + and password is optionnal. Authentication should work out-of-the-box; if + it fails, try updating python-grid5000, or specify the path to the + certificate to use: :: @@ -614,51 +615,7 @@ connected to other testbeds for experiments involving wide area layer2 networks. 5.8 Metrics API ~~~~~~~~~~~~~~~ -5.8.1 Get the timeseries corresponding to a job -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Credits to ``lturpin``. - -.. code:: python - - import logging - import os - - from grid5000 import Grid5000 - - - logging.basicConfig(level=logging.DEBUG) - - - def get_job_consumption(job_id, gk, site): - metrics = gk.sites[site].metrics - job = gk.sites[site].jobs[job_id] - # nodes as list : "cluster-number.site.grid5000.fr" - nodes_dom = job.assigned_nodes - # nodes as list : "cluster-number" - nodes = map(lambda node_dom: node_dom.split('.')[0], nodes_dom) - # nodes as string : "cluster-number,cluster-number,..." - nodes_str = ','.join(nodes) - - start = job.started_at - end = job.stopped_at - kwargs = { - "only": nodes_str, - "resolution": 1, - "from": start, - "to": end - } - timeseries = metrics["power"].timeseries.list(**kwargs) - return timeseries - - - conf_file = os.path.join(os.environ.get("HOME"), ".python-grid5000.yaml") - gk = Grid5000.from_yaml(conf_file) - - timeseries = get_job_consumption("1092446", gk, "lyon") - print(timeseries) - -5.8.2 Get some timeseries (and plot them) +5.8.1 Get some timeseries (and plot them) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For this example you’ll need ``matplotlib``, ``seaborn`` and ``pandas``. @@ -675,45 +632,40 @@ For this example you’ll need ``matplotlib``, ``seaborn`` and ``pandas``. import seaborn as sns import time - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.INFO) conf_file = os.path.join(os.environ.get("HOME"), ".python-grid5000.yaml") gk = Grid5000.from_yaml(conf_file) - metrics = gk.sites["lyon"].metrics + metrics = gk.sites["lyon"].clusters["nova"].metrics print("--- available metrics") - print(metrics.list()) - - print("---- power metric") - print(metrics["power"]) + print(metrics) print("----- a timeserie") now = time.time() + # NOTE that you can pass a job_id here kwargs = { - "only": "nova-1,nova-2,nova-3", - "resolution": 1, - "from": int(now - 600), - "to": int(now) + "nodes": "nova-1,nova-2,nova-3", + "metrics": "wattmetre_power_watt", + "start_time": int(now - 600), } - timeseries = metrics["power"].timeseries.list(**kwargs) + metrics = gk.sites["lyon"].metrics.list(**kwargs) # let's visualize this df = pd.DataFrame() - for timeserie in timeseries: - print(timeserie) - timestamp = timeserie.timestamps - value = timeserie.values - measurement = timeserie.uid + for metric in metrics: + timestamp = metric.timestamp + value = metric.value + device_id = metric.device_id df = pd.concat([df, pd.DataFrame({ - "timestamp": timestamp, - "value": value, - "measurement": [measurement]*len(timestamp) + "timestamp": [timestamp], + "value": [value], + "device_id": [device_id] })]) - sns.relplot(data=df, x="timestamp", y="value", - hue="measurement", + hue="device_id", kind="line") plt.show() -- GitLab