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)