Repository: mlflow/mlflow

Issue:
[BUG] Resuming runs with mlflow.start_run(run_id=) does not set state properly.
Thank you for submitting an issue. Please refer to our [issue policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md) for additional information about bug reports. For help with debugging your code, please refer to [Stack Overflow](https://stackoverflow.com/questions/tagged/mlflow).

**Please fill in this bug report template to ensure a timely and thorough response.**

### Willingness to contribute
The MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix for this bug to the MLflow code base?

- [ ] Yes. I can contribute a fix for this bug independently.
- [X] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.
- [ ] No. I cannot contribute a bug fix at this time.

### System information
- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**: No
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Linux Ubuntu 20.04.1 LTS
- **MLflow installed from (source or binary)**: binary `pip install mlflow` in conda env
- **MLflow version (run ``mlflow --version``)**: mlflow, version 1.10.0
- **Python version**: Python 3.8.3
- **Exact command to reproduce**: See code to reproduce section.

### Describe the problem
Setting `run_id` in `mlflow.start_run(run_id=)` doe no behave as advertised.
According to the [start_run documentation](https://mlflow.org/docs/latest/python_api/mlflow.html#mlflow.start_run), setting the `run_id` should set the status to running and it does not.

Relevant line from the doc:
"run_id – If specified, get the run with the specified UUID and log parameters and metrics under that run. The run’s end time is unset and its status is set to running"

### Code to reproduce issue
```python
import mlflow

# Start run and print status (should be Running)
run = mlflow.start_run()
run_id = run.info.run_id
print(f"Should be RUNNING = {run.info.status}")

# Simulate failure and print status (should be FAILED)
mlflow.end_run(status="FAILED")
print(f"Should be FAILED = {mlflow.get_run(run_id).info.status}")

# Resume failed run from id
resumed_run = mlflow.start_run(run_id)
# Print status of resumed run (should be RUNNING)
print(f"Should be RUNNING = {resumed_run.info.status}")
```

### Other info / logs
Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached.


### What component(s), interfaces, languages, and integrations does this bug affect?
Components 
- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [X] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: Local serving, model deployment tools, spark UDFs
- [ ] `area/server-infra`: MLflow server, JavaScript dev server
- [X] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface 
- [ ] `area/uiux`: Front-end, user experience, JavaScript, plotting
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language 
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations



Discussion:

Resolving patch:
diff --git a/mlflow/tracking/fluent.py b/mlflow/tracking/fluent.py
--- a/mlflow/tracking/fluent.py
+++ b/mlflow/tracking/fluent.py
@@ -14,7 +14,7 @@
 from mlflow.entities.lifecycle_stage import LifecycleStage
 from mlflow.exceptions import MlflowException
 from mlflow.tracking.client import MlflowClient
-from mlflow.tracking import artifact_utils
+from mlflow.tracking import artifact_utils, _get_store
 from mlflow.tracking.context import registry as context_registry
 from mlflow.store.tracking import SEARCH_MAX_RESULTS_DEFAULT
 from mlflow.utils import env
@@ -82,6 +82,8 @@ def start_run(run_id=None, experiment_id=None, run_name=None, nested=False):
     ``start_run`` attempts to resume a run with the specified run ID and
     other parameters are ignored. ``run_id`` takes precedence over ``MLFLOW_RUN_ID``.
 
+    If resuming an existing run, the run status is set to ``RunStatus.RUNNING``.
+
     MLflow sets a variety of default tags on the run, as defined in
     :ref:`MLflow system tags <system_tags>`.
 
@@ -140,6 +142,12 @@ def start_run(run_id=None, experiment_id=None, run_name=None, nested=False):
                 "Cannot start run with ID {} because it is in the "
                 "deleted state.".format(existing_run_id)
             )
+        # Use previous end_time because a value is required for update_run_info
+        end_time = active_run_obj.info.end_time
+        _get_store().update_run_info(
+            existing_run_id, run_status=RunStatus.RUNNING, end_time=end_time
+        )
+        active_run_obj = MlflowClient().get_run(existing_run_id)
     else:
         if len(_active_run_stack) > 0:
             parent_run_id = _active_run_stack[-1].info.run_id


Test patch:
diff --git a/tests/tracking/test_fluent.py b/tests/tracking/test_fluent.py
--- a/tests/tracking/test_fluent.py
+++ b/tests/tracking/test_fluent.py
@@ -387,12 +387,13 @@ def test_start_run_existing_run(empty_active_run_stack):
     mock_run.info.lifecycle_stage = LifecycleStage.ACTIVE
 
     run_id = uuid.uuid4().hex
+    mock_get_store = mock.patch("mlflow.tracking.fluent._get_store")
 
-    with mock.patch.object(MlflowClient, "get_run", return_value=mock_run):
+    with mock_get_store, mock.patch.object(MlflowClient, "get_run", return_value=mock_run):
         active_run = start_run(run_id)
 
         assert is_from_run(active_run, mock_run)
-        MlflowClient.get_run.assert_called_once_with(run_id)
+        MlflowClient.get_run.assert_called_with(run_id)
 
 
 def test_start_run_existing_run_from_environment(empty_active_run_stack):
@@ -401,12 +402,15 @@ def test_start_run_existing_run_from_environment(empty_active_run_stack):
 
     run_id = uuid.uuid4().hex
     env_patch = mock.patch.dict("os.environ", {_RUN_ID_ENV_VAR: run_id})
+    mock_get_store = mock.patch("mlflow.tracking.fluent._get_store")
 
-    with env_patch, mock.patch.object(MlflowClient, "get_run", return_value=mock_run):
+    with env_patch, mock_get_store, mock.patch.object(
+        MlflowClient, "get_run", return_value=mock_run
+    ):
         active_run = start_run()
 
         assert is_from_run(active_run, mock_run)
-        MlflowClient.get_run.assert_called_once_with(run_id)
+        MlflowClient.get_run.assert_called_with(run_id)
 
 
 def test_start_run_existing_run_from_environment_with_set_environment(empty_active_run_stack):
@@ -433,6 +437,26 @@ def test_start_run_existing_run_deleted(empty_active_run_stack):
             start_run(run_id)
 
 
+def test_start_existing_run_status(empty_active_run_stack):
+    run_id = mlflow.start_run().info.run_id
+    mlflow.end_run()
+    assert MlflowClient().get_run(run_id).info.status == RunStatus.to_string(RunStatus.FINISHED)
+    restarted_run = mlflow.start_run(run_id)
+    assert restarted_run.info.status == RunStatus.to_string(RunStatus.RUNNING)
+
+
+def test_start_existing_run_end_time(empty_active_run_stack):
+    run_id = mlflow.start_run().info.run_id
+    mlflow.end_run()
+    run_obj_info = MlflowClient().get_run(run_id).info
+    old_end = run_obj_info.end_time
+    assert run_obj_info.status == RunStatus.to_string(RunStatus.FINISHED)
+    mlflow.start_run(run_id)
+    mlflow.end_run()
+    run_obj_info = MlflowClient().get_run(run_id).info
+    assert run_obj_info.end_time > old_end
+
+
 def test_get_run():
     run_id = uuid.uuid4().hex
     mock_run = mock.Mock()