From 060ced3406bc60e0c8afaae14b13e9271a791ed6 Mon Sep 17 00:00:00 2001 From: Nikita Beletskii <2nikita.b@gmail.com> Date: Tue, 23 Apr 2024 17:03:39 -0400 Subject: [PATCH 1/5] add latest Nomad and Python to tests (#165) * add latest Nomad and Python to tests * upgrade pylint --- .github/workflows/lint.yml | 4 ++-- .github/workflows/main.yml | 10 +++++----- .github/workflows/publish.yml | 8 ++++---- requirements-lint.txt | 2 +- setup.py | 1 + 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 095b56c..cb822a0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python 3 uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Install Dependencies shell: bash @@ -29,4 +29,4 @@ jobs: shell: bash run: | pylint nomad/ - black --check -l 120 -t py311 nomad/ + black --check -l 120 -t py312 nomad/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d49f39..9bcdfe9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,12 +22,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.11'] # the oldest and newest support versions - nomad-version: ['1.0.18', '1.1.18', '1.2.15', '1.3.14', '1.4.9', '1.5.5'] + python-version: ['3.7', '3.12'] # the oldest and newest support versions + nomad-version: ['1.2.16', '1.3.16', '1.4.14', '1.5.17', '1.6.10', '1.7.7'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Setup Nomad ${{ matrix.nomad-version }} @@ -60,4 +60,4 @@ jobs: run: | ./run_tests.sh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 \ No newline at end of file + uses: codecov/codecov-action@v4 \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9fcc9ed..b40fa75 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,11 +9,11 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.11 - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 - name: Install Dependencies shell: bash run: pip install build diff --git a/requirements-lint.txt b/requirements-lint.txt index 2855e0a..5d0c3b3 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,2 +1,2 @@ -pylint==2.15.10 +pylint==3.1.0 black==24.4.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 1baaee4..9c97309 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,7 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ], keywords='nomad hashicorp client', ) From 79ae9d9a42d0b24b35f0df936eca53e42137b0d5 Mon Sep 17 00:00:00 2001 From: aas47 <179645738+aas47@users.noreply.github.com> Date: Thu, 30 Jan 2025 10:21:10 -0500 Subject: [PATCH 2/5] feat: Update dispatch jobs to support `templateprefix` and `idempotencytoken` (#171) * Update `dispatch_jobs` to support `idempotency_token` and `IdPrefixTemplate` * bump action versions * black formatting --- .github/workflows/lint.yml | 6 ++--- .github/workflows/main.yml | 10 +++++--- nomad/api/job.py | 18 ++++++++++++-- tests/test_job.py | 51 +++++++++++++++----------------------- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cb822a0..ee8f1b9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,12 +12,12 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Install Dependencies shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9bcdfe9..52f78cb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,8 +22,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.12'] # the oldest and newest support versions - nomad-version: ['1.2.16', '1.3.16', '1.4.14', '1.5.17', '1.6.10', '1.7.7'] + python-version: ['3.8', '3.13'] # the oldest and newest support versions + nomad-version: ['1.4.14', '1.5.17', '1.6.10', '1.7.7', '1.8.4', '1.9.5'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -41,7 +41,9 @@ jobs: curl -L -o /tmp/nomad_${NOMAD_VERSION}_linux_amd64.zip https://releases.hashicorp.com/nomad/${NOMAD_VERSION}/nomad_${NOMAD_VERSION}_linux_amd64.zip echo "unzip nomad" - unzip -d /usr/local/bin/ /tmp/nomad_${NOMAD_VERSION}_linux_amd64.zip + unzip -o -d /usr/local/bin/ /tmp/nomad_${NOMAD_VERSION}_linux_amd64.zip + chmod +x /usr/local/bin/nomad + /usr/local/bin/nomad version - name: Install Dependencies shell: bash run: | @@ -60,4 +62,4 @@ jobs: run: | ./run_tests.sh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 \ No newline at end of file + uses: codecov/codecov-action@v5 diff --git a/nomad/api/job.py b/nomad/api/job.py index b8beba8..5ba2502 100644 --- a/nomad/api/job.py +++ b/nomad/api/job.py @@ -245,7 +245,14 @@ def periodic_job(self, id_): """ return self.request(id_, "periodic", "force", method="post").json() - def dispatch_job(self, id_, payload=None, meta=None): + def dispatch_job( + self, + id_, + payload=None, + meta=None, + id_prefix_template=None, + idempotency_token=None, + ): # pylint: disable=too-many-arguments """Dispatches a new instance of a parameterized job. https://www.nomadproject.io/docs/http/job.html @@ -254,12 +261,19 @@ def dispatch_job(self, id_, payload=None, meta=None): - id_ - payload - meta + - id_prefix_template + - idempotency_token returns: dict raises: - nomad.api.exceptions.BaseNomadException - nomad.api.exceptions.URLNotFoundNomadException """ - dispatch_json = {"Meta": meta, "Payload": payload} + dispatch_json = { + "Meta": meta, + "Payload": payload, + "idempotency_token": idempotency_token, + "IdPrefixTemplate": id_prefix_template, + } return self.request(id_, "dispatch", json=dispatch_json, method="post").json() def revert_job(self, id_, version, enforce_prior_version=None): diff --git a/tests/test_job.py b/tests/test_job.py index 486a101..ae1eb36 100644 --- a/tests/test_job.py +++ b/tests/test_job.py @@ -125,56 +125,57 @@ def test_delete_job(nomad_setup): @flaky(max_runs=5, min_passes=1) -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 5, 3), reason="Nomad dispatch not supported" -) def test_dispatch_job(nomad_setup): with open("example_batch_parameterized.json") as fh: job = json.loads(fh.read()) nomad_setup.job.register_job("example-batch", job) try: - nomad_setup.job.dispatch_job("example-batch", meta={"time": "500"}) + nomad_setup.job.dispatch_job("example-batch", meta={"time": "500"}, id_prefix_template="run1") except (exceptions.URLNotFoundNomadException, exceptions.BaseNomadException) as e: print(e.nomad_resp.text) raise e assert "example-batch" in nomad_setup.job -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 5, 3), reason="Nomad dispatch not supported" -) +@flaky(max_runs=5, min_passes=1) +def test_dispatch_job_idempotency(nomad_setup): + with open("example_batch_parameterized.json") as fh: + job = json.loads(fh.read()) + nomad_setup.job.register_job("example-batch-idempotent", job) + + # First dispatch should succeed + try: + nomad_setup.job.dispatch_job("example-batch-idempotent", meta={"time": "500"}, id_prefix_template="run1", idempotency_token="737ae8cd-f237-43a5-8fad-0e6a3f94ad55") + except (exceptions.URLNotFoundNomadException, exceptions.BaseNomadException) as e: + print(e.nomad_resp.text) + raise e + assert "example-batch-idempotent" in nomad_setup.job + + # Second dispatch with the same idempotency token should fail + with pytest.raises(exceptions.BaseNomadException): + nomad_setup.job.dispatch_job("example-batch-idempotent", meta={"time": "500"}, id_prefix_template="run2", idempotency_token="737ae8cd-f237-43a5-8fad-0e6a3f94ad55") + + def test_summary_job(nomad_setup): j = nomad_setup.job["example"] assert "JobID" in nomad_setup.job.get_summary(j["ID"]) -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 4, 0), reason="Not supported in version" -) def test_plan_job(nomad_setup): with open("example.json") as fh: job = json.loads(fh.read()) assert "Index" in nomad_setup.job.plan_job(nomad_setup.job["example"]["ID"], job) -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_versions_job(nomad_setup): assert "Versions" in nomad_setup.job.get_versions("example") -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_versions_job_missing(nomad_setup): with pytest.raises(nomad.api.exceptions.URLNotFoundNomadException): assert "Versions" in nomad_setup.job.get_versions("example1") -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_get_job_deployments(nomad_setup): assert "JobID" in nomad_setup.job.get_deployments("example")[0] assert isinstance(nomad_setup.job.get_deployments("example"), list) @@ -182,36 +183,24 @@ def test_get_job_deployments(nomad_setup): assert "example" == nomad_setup.job.get_deployments("example")[0]["JobID"] -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_get_job_deployment(nomad_setup): assert "JobID" in nomad_setup.job.get_deployment("example") assert isinstance(nomad_setup.job.get_deployment("example"), dict) assert "example" == nomad_setup.job.get_deployment("example")["JobID"] -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_get_summary(nomad_setup): assert "JobID" in nomad_setup.job.get_summary("example") assert isinstance(nomad_setup.job.get_summary("example"), dict) assert "example" == nomad_setup.job.get_summary("example")["JobID"] -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_revert_job(nomad_setup): current_job_version = nomad_setup.job.get_deployment("example")["JobVersion"] prior_job_version = current_job_version - 1 nomad_setup.job.revert_job("example", prior_job_version, current_job_version) -@pytest.mark.skipif( - tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 6, 0), reason="Not supported in version" -) def test_stable_job(nomad_setup): current_job_version = nomad_setup.job.get_deployment("example")["JobVersion"] nomad_setup.job.stable_job("example", current_job_version, True) From 1befcb46eefb7b0bc3718f879f1ab599c156d0d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 10:36:46 -0500 Subject: [PATCH 3/5] Bump requests from 2.31.0 to 2.32.2 (#172) Bumps [requests](https://github.com/psf/requests) from 2.31.0 to 2.32.2. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.31.0...v2.32.2) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c24336..6e42168 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests==2.31.0 +requests==2.32.2 From ff1f1db58fc6cc21270f14ce451377ee8c2b4a99 Mon Sep 17 00:00:00 2001 From: Matias <15092815+Parselon@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:16:57 -0300 Subject: [PATCH 4/5] Feature: signal allocations (#169) * Add signal allocation method to requester * Add tests for signal allocation * Add documentation * Update signal docstring --- docs/api/allocations.md | 23 +++++++++++++++++++++ nomad/api/client.py | 16 +++++++++++++++ tests/test_client.py | 45 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/docs/api/allocations.md b/docs/api/allocations.md index 4f525af..1e6ac67 100644 --- a/docs/api/allocations.md +++ b/docs/api/allocations.md @@ -18,3 +18,26 @@ allocations = my_nomad.allocations.get_allocations() for allocation in allocations: print (allocation) ``` + +### Signal allocation + +This endpoint sends a signal to an allocation or task. + +https://developer.hashicorp.com/nomad/api-docs/allocations#signal-allocation + +Example: + +``` +import signal +import nomad + +my_nomad = nomad.Nomad(host='192.168.33.10') + +alloc_id = nomad_setup.allocations.get_allocations()[0]["ID"] + +# Send signal to an allocation +my_nomad.client.allocation.signal_allocation(alloc_id, signal.SIGUSR1.name) + +# Send signal to a task in allocation +my_nomad.client.allocation.signal_allocation(alloc_id, signal.SIGUSR1.name, task="my_task") +``` diff --git a/nomad/api/client.py b/nomad/api/client.py index ebf5991..9f4374c 100644 --- a/nomad/api/client.py +++ b/nomad/api/client.py @@ -304,6 +304,22 @@ def restart_allocation(self, id_): """ return self.request(id_, "restart", method="post").json() + def signal_allocation(self, id_, signal, task=None): + """Send a signal to an allocation or task. + https://www.nomadproject.io/api-docs/allocations#signal-allocation + arguments: + - id_ + - signal (str) + optional_arguments: + - task: (str) Optional, if omitted, the signal will be sent to all tasks in the allocation. + returns: dict + raises: + - nomad.api.exceptions.BaseNomadException + - nomad.api.exceptions.URLNotFoundNomadException + """ + payload = {"Signal": signal, "Task": task} + return self.request(id_, "signal", json=payload, method="post").json() + class gc_allocation(Requester): """ diff --git a/tests/test_client.py b/tests/test_client.py index 84bd076..5330869 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,5 +1,6 @@ import pytest import json +import signal import time import os @@ -8,6 +9,21 @@ from flaky import flaky +def get_running_allocation(nomad_setup): + max_iterations = 6 + for _ in range(max_iterations): + try: + return next( + alloc + for alloc in nomad_setup.allocations.get_allocations() + if alloc["ClientStatus"] == "running" + ) + except StopIteration: + # No alloc running + time.sleep(5) + raise ValueError("No allocations running") + + # integration tests requires nomad Vagrant VM or Binary running def test_register_job(nomad_setup): @@ -17,7 +33,6 @@ def test_register_job(nomad_setup): assert "example" in nomad_setup.job max_iterations = 6 - while nomad_setup.job["example"]["Status"] != "running": time.sleep(5) if max_iterations == 0: @@ -70,11 +85,37 @@ def test_read_allocation_stats(nomad_setup): f = nomad_setup.client.allocation.read_allocation_stats(a) +@pytest.mark.skipif( + tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 9, 1), reason="Not supported in version" +) +def test_signal_allocation(nomad_setup): + alloc_id = get_running_allocation(nomad_setup)["ID"] + nomad_setup.client.allocation.signal_allocation(alloc_id, signal.SIGUSR1.name) + + +@pytest.mark.skipif( + tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 9, 1), reason="Not supported in version" +) +def test_signal_allocation_task(nomad_setup): + allocation = get_running_allocation(nomad_setup) + alloc_id = allocation["ID"] + task = list(allocation["TaskStates"].keys())[0] + nomad_setup.client.allocation.signal_allocation(alloc_id, signal.SIGUSR1.name, task) + + +@pytest.mark.skipif( + tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 9, 1), reason="Not supported in version" +) +def test_signal_allocation_invalid_signal(nomad_setup): + alloc_id = get_running_allocation(nomad_setup)["ID"] + with pytest.raises(nomad.api.exceptions.BaseNomadException, match="invalid signal"): + nomad_setup.client.allocation.signal_allocation(alloc_id, "INVALID-SIGNAL") + + @pytest.mark.skipif( tuple(int(i) for i in os.environ.get("NOMAD_VERSION").split(".")) < (0, 8, 1), reason="Not supported in version" ) def test_gc_all_allocations(nomad_setup): - node_id = nomad_setup.nodes.get_nodes()[0]["ID"] nomad_setup.client.gc_all_allocations.garbage_collect(node_id) nomad_setup.client.gc_all_allocations.garbage_collect() From db31eaadd02cc562c4f0fb55da06ab2ac800665b Mon Sep 17 00:00:00 2001 From: Nikita Beletskii <2nikita.b@gmail.com> Date: Tue, 4 Feb 2025 13:29:43 -0500 Subject: [PATCH 5/5] update version (#173) --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9c97309..c44d07b 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setuptools.setup( name='python-nomad', - version='2.0.1', + version='2.1.0', install_requires=['requests'], packages=['nomad', 'nomad.api'], url='http://github.com/jrxfive/python-nomad', @@ -26,6 +26,7 @@ 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], keywords='nomad hashicorp client', )