Repository: AgnostiqHQ/covalent
Pull request #1601 (2023-04-25 22:00:47)

dispatch when covalent server is not available
Fixes https://github.com/AgnostiqHQ/covalent/issues/1260

<!--
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Increment the version number in the /VERSION file. If this is a bugfix, increment the patch version (the rightmost number), for example 0.18.2 becomes 0.18.3. If it's a new feature, increment the minor version (the middle number), for example 0.18.2 becomes 0.19.0.
⚠️ Add a note to /CHANGELOG.md with your version number and the date, summarizing the changes.
⚠️ If your pull request fixes an open issue, please link to the issue.
⚠️ Rebase the latest changes from the develop branch. Assuming origin points to https://github.com/AgnostiqHQ/covalent, you should run git rebase origin/develop.
-->

- [x] I have added the tests to cover my changes.
- [x] I have updated the documentation and CHANGELOG accordingly.
- [x] I have read the CONTRIBUTING document.


madhur-tandon: This should be ready to merge as well, @santoshkumarradha @kessler-frost @AlejandroEsquivel 
codecov[bot]: ## [Codecov](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1601?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) Report
> Merging [#1601](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1601?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) (18c8571) into [develop](https://codecov.io/gh/AgnostiqHQ/covalent/commit/c72ab6a2ed091bbca1d4847fbe6df6e8be4d2d8f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) (c72ab6a) will **increase** coverage by `1.24%`.
> The diff coverage is `77.77%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1601/graphs/tree.svg?width=650&height=150&src=pr&token=YGHCB3DE4P&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ)](https://codecov.io/gh/AgnostiqHQ/covalent/pull/1601?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ)

```diff
@@             Coverage Diff             @@
##           develop    #1601      +/-   ##
===========================================
+ Coverage    85.91%   87.16%   +1.24%     
===========================================
  Files          159       83      -76     
  Lines         6646     4962    -1684     
  Branches       145        0     -145     
===========================================
- Hits          5710     4325    -1385     
+ Misses         834      637     -197     
+ Partials       102        0     -102     
```

| Flag | Coverage Δ | | *Carryforward flag |
|---|---|---|---|
| Dispatcher | `87.28% <ø> (ø)` | | Carriedforward from [c72ab6a](https://codecov.io/gh/AgnostiqHQ/covalent/commit/c72ab6a2ed091bbca1d4847fbe6df6e8be4d2d8f?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) |
| Functional_Tests | `?` | | |
| SDK | `87.08% <77.77%> (+0.44%)` | :arrow_up: | |
| UI_Frontend | `?` | | |

*This pull request uses carry forward flags. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AgnostiqHQ) to find out more.

</details>
kessler-frost: @madhur-tandon Again, thanks for the PR! Seems like the diff coverage isn't >=95%. These two lines (line 149, 150) in `covalent/_dispatcher_plugins/local.py` seem to be the culprit here:

```python
                r.raise_for_status()
                lattice_dispatch_id = r.content.decode("utf-8").strip().replace('"', "")
```

Once these are covered as well, i.e taking care of the `try` block, the diff coverage should be sufficient for passing the check.
madhur-tandon: @kessler-frost last commit is to make `license/cla` work
madhur-tandon: Feel free to merge @kessler-frost!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d915e052..5629ac56 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 
 - Raise error on dispatching a non-lattice
+- Helpful message when trying to dispatch a workflow when covalent server is not available
 
 ### Authors
 
@@ -47,7 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Sankalp Sanand <sankalp@agnostiq.ai>
 - Co-authored-by: kessler-frost <ssanand@hawk.iit.edu>
 
-
 ### Tests
 
 - Move QA scripts from QA repo to Covalent functional tests.
diff --git a/covalent/_dispatcher_plugins/local.py b/covalent/_dispatcher_plugins/local.py
index 63d9707c..e14a119a 100644
--- a/covalent/_dispatcher_plugins/local.py
+++ b/covalent/_dispatcher_plugins/local.py
@@ -146,12 +146,17 @@ class LocalDispatcher(BaseDispatcher):
 
             submit_dispatch_url = f"{dispatcher_addr}/api/submit"
 
-            r = requests.post(
-                submit_dispatch_url, data=json_lattice, params={"disable_run": disable_run}
-            )
-            r.raise_for_status()
-
-            lattice_dispatch_id = r.content.decode("utf-8").strip().replace('"', "")
+            lattice_dispatch_id = None
+            try:
+                r = requests.post(
+                    submit_dispatch_url, data=json_lattice, params={"disable_run": disable_run}
+                )
+                r.raise_for_status()
+                lattice_dispatch_id = r.content.decode("utf-8").strip().replace('"', "")
+            except requests.exceptions.ConnectionError as e:
+                message = f"The Covalent dispatcher server is not running at {dispatcher_addr}. You must start Covalent (with `covalent start`) before dispatching your workflow."
+                app_log.error(message)
+                raise ConnectionError(message) from e
 
             if not disable_run or triggers_data is None:
                 return lattice_dispatch_id


Test patch:
diff --git a/tests/covalent_tests/dispatcher_plugins/local_test.py b/tests/covalent_tests/dispatcher_plugins/local_test.py
index 7ffec6b9..f0790688 100644
--- a/tests/covalent_tests/dispatcher_plugins/local_test.py
+++ b/tests/covalent_tests/dispatcher_plugins/local_test.py
@@ -24,6 +24,8 @@
 from unittest.mock import MagicMock
 
 import pytest
+from requests import Response
+from requests.exceptions import HTTPError
 
 import covalent as ct
 from covalent._dispatcher_plugins.local import LocalDispatcher, get_redispatch_request_body
@@ -119,3 +121,60 @@ def test_dispatching_a_non_lattice():
         TypeError, match="Dispatcher expected a Lattice, received <class 'function'> instead."
     ):
         LocalDispatcher.dispatch(workflow)(1, 2)
+
+
+def test_dispatch_when_no_server_is_running():
+    """test dispatching a lattice when no server is running"""
+
+    # the test suite is using another port, thus, with the dummy address below
+    # the covalent server is not running in some sense.
+    dummy_dispatcher_addr = "http://localhost:12345"
+
+    @ct.electron
+    def task(a, b, c):
+        return a + b + c
+
+    @ct.lattice
+    def workflow(a, b):
+        return task(a, b, c=4)
+
+    with pytest.raises(
+        ConnectionError,
+        match=f"The Covalent dispatcher server is not running at {dummy_dispatcher_addr}.",
+    ):
+        LocalDispatcher.dispatch(workflow, dispatcher_addr=dummy_dispatcher_addr)(1, 2)
+
+
+def test_dispatcher_submit_api(mocker):
+    """test dispatching a lattice with submit api"""
+
+    @ct.electron
+    def task(a, b, c):
+        return a + b + c
+
+    @ct.lattice
+    def workflow(a, b):
+        return task(a, b, c=4)
+
+    # test when api raises an implicit error
+    r = Response()
+    r.status_code = 404
+    r.url = "http://dummy"
+    r.reason = "dummy reason"
+
+    mocker.patch("covalent._dispatcher_plugins.local.requests.post", return_value=r)
+
+    with pytest.raises(HTTPError, match="404 Client Error: dummy reason for url: http://dummy"):
+        dispatch_id = LocalDispatcher.dispatch(workflow)(1, 2)
+        assert dispatch_id is None
+
+    # test when api doesn't raise an implicit error
+    r = Response()
+    r.status_code = 201
+    r.url = "http://dummy"
+    r._content = b"abcde"
+
+    mocker.patch("covalent._dispatcher_plugins.local.requests.post", return_value=r)
+
+    dispatch_id = LocalDispatcher.dispatch(workflow)(1, 2)
+    assert dispatch_id == "abcde"