Repository: dask/distributed

Issue:
Persist-call ignores assigned worker resources
When ```.persist(resources={...})``` is called on a Dask DataFrame, the specified resources will be ignored for scheduling. In particular, no task in the corresponding dashboard pages will outline the resources. In my setup, the following example triggers the issue (using python 3.6 and distributed 1.28.1):

```python
import pandas as pd

import dask.dataframe as dd
from dask.distributed import Client


def test_resources():
    client = Client(n_workers=1, resources={"MyRes": 1000})

    test_input_frame = dd.from_pandas(pd.DataFrame({"A": [1, 2], "B": [3, 4]}), npartitions=1)
    test_output_frame = test_input_frame.apply(lambda row: row.sum(), axis=1)

    output = test_output_frame.persist(resources={"MyRes": 1})
    print(output)

    # The task pages of the dashboard do not display the resource dependencies.
    input("Press any key to exit...")
    client.close()


if __name__ == '__main__':
    test_resources()
```

I quickly ran the debugger on the Dask distributed code and discovered that in ```client.Client._graph_to_futures(...)```, ```self._expand_resources(...)``` returns a dict where the keys are tuples identifying the task. When I manually convert the keys to their string representation, i.e. call:
```python
resources = {tokey(k): v for k,v in resources.items()}
```
after ```_expand_resources(...)```, the task dashboard will display the associated resources. (But I don't know whether converting the keys will have any other side effects)


Discussion:
Sorry for the lack of response here. Can you try your example (which was great btw) on distributed master? This may be fixed. Locally, I see

<img width="953" alt="Screen Shot 2019-05-28 at 6 35 29 AM" src="https://user-images.githubusercontent.com/1312546/58475154-cec62f80-8112-11e9-9ca7-dc96d6c43985.png">

@Spiegel0, have you had a chance to restest this on `master`? Please let us know if there is a way to help.

Resolving patch:
diff --git a/distributed/client.py b/distributed/client.py
--- a/distributed/client.py
+++ b/distributed/client.py
@@ -2351,6 +2351,7 @@ def _graph_to_futures(
                 resources = self._expand_resources(
                     resources, all_keys=itertools.chain(dsk, keys)
                 )
+                resources = {tokey(k): v for k, v in resources.items()}
 
             if retries:
                 retries = self._expand_retries(


Test patch:
diff --git a/distributed/tests/test_resources.py b/distributed/tests/test_resources.py
--- a/distributed/tests/test_resources.py
+++ b/distributed/tests/test_resources.py
@@ -202,6 +202,24 @@ def test_persist_tuple(c, s, a, b):
     assert not b.data
 
 
+@gen_cluster(client=True)
+def test_resources_str(c, s, a, b):
+    pd = pytest.importorskip("pandas")
+    dd = pytest.importorskip("dask.dataframe")
+
+    yield a.set_resources(MyRes=1)
+
+    x = dd.from_pandas(pd.DataFrame({"A": [1, 2], "B": [3, 4]}), npartitions=1)
+    y = x.apply(lambda row: row.sum(), axis=1, meta=(None, "int64"))
+    yy = y.persist(resources={"MyRes": 1})
+    yield wait(yy)
+
+    ts_first = s.tasks[tokey(y.__dask_keys__()[0])]
+    assert ts_first.resource_restrictions == {"MyRes": 1}
+    ts_last = s.tasks[tokey(y.__dask_keys__()[-1])]
+    assert ts_last.resource_restrictions == {"MyRes": 1}
+
+
 @gen_cluster(
     client=True,
     ncores=[