diff --git a/README.org b/README.org index 726ba721ef61fc2f59e990ce480bf5ab8cd1a58c..f1dfa85f0665aa98202106123eac4c028c8f7222 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 ' diff --git a/README.rst b/README.rst index cef81d9dec0b2e463cc1113d55f170596063d4c1..f28e73f13182237f28dbab7d0ff427cf843908ba 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() diff --git a/grid5000/__init__.py b/grid5000/__init__.py index 8850e50ae4fcfdab24ed3fe8b86831efd50b6976..0dff2ec7440fca07252bc575cc0b88f02e4aaa4f 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 diff --git a/grid5000/cli.py b/grid5000/cli.py index 1f5a9966ab54fdf5fbe458dd9910564120a2d736..c297f69ed0697594f43b6b73b6145eba9e63bce8 100644 --- a/grid5000/cli.py +++ b/grid5000/cli.py @@ -20,24 +20,30 @@ 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) - IPython.embed(header=motd) + 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, colors="neutral") def auth(user: str):