Beispiel #1
0
async def compute_pipeline_details(
        complete_dag: nx.DiGraph, pipeline_dag: nx.DiGraph,
        comp_tasks: List[CompTaskAtDB]) -> PipelineDetails:
    try:
        # FIXME: this problem of cyclic graphs for control loops create all kinds of issues that must be fixed
        # first pass, traversing in topological order to correctly get the dependencies, set the nodes states
        await _set_computational_nodes_states(complete_dag)
    except nx.NetworkXUnfeasible:
        # not acyclic
        pass
    return PipelineDetails(
        adjacency_list=nx.to_dict_of_lists(pipeline_dag),
        node_states={
            node_id: NodeState(
                modified=node_data.get(kNODE_MODIFIED_STATE, False),
                dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE,
                                           set()),
                currentStatus=next(
                    (task.state
                     for task in comp_tasks if str(task.node_id) == node_id),
                    RunningState.UNKNOWN,
                ),
            )
            for node_id, node_data in complete_dag.nodes.data()
            if _is_node_computational(node_data.get("key", ""))
        },
    )
def fake_workbench_computational_pipeline_details(
    fake_workbench_computational_adjacency_file: Path,
    fake_workbench_node_states_file: Path,
) -> PipelineDetails:
    adjacency_list = json.loads(fake_workbench_computational_adjacency_file.read_text())
    node_states = json.loads(fake_workbench_node_states_file.read_text())
    return PipelineDetails.parse_obj(
        {"adjacency_list": adjacency_list, "node_states": node_states}
    )
async def assert_computation_task_out_obj(
    client: httpx.AsyncClient,
    task_out: ComputationTaskGet,
    *,
    project: ProjectAtDB,
    exp_task_state: RunningState,
    exp_pipeline_details: PipelineDetails,
):
    assert task_out.id == project.uuid
    assert task_out.state == exp_task_state
    assert task_out.url == f"{client.base_url}/v2/computations/{project.uuid}"
    assert task_out.stop_url == (
        f"{client.base_url}/v2/computations/{project.uuid}:stop"
        if exp_task_state in [RunningState.PUBLISHED, RunningState.PENDING
                              ] else None)
    # check pipeline details contents
    assert task_out.pipeline_details.dict() == exp_pipeline_details.dict()
 def _convert_to_pipeline_details(
     project: ProjectAtDB,
     exp_pipeline_adj_list: Dict[int, List[str]],
     exp_node_states: Dict[int, Dict[str, Any]],
 ) -> PipelineDetails:
     workbench_node_uuids = list(project.workbench.keys())
     converted_adj_list: Dict[NodeID, Dict[NodeID, List[NodeID]]] = {}
     for node_key, next_nodes in exp_pipeline_adj_list.items():
         converted_adj_list[NodeID(workbench_node_uuids[node_key])] = [
             NodeID(workbench_node_uuids[n]) for n in next_nodes
         ]
     converted_node_states: Dict[NodeID, NodeState] = {
         NodeID(workbench_node_uuids[n]): NodeState(
             modified=s["modified"],
             dependencies={
                 workbench_node_uuids[dep_n]
                 for dep_n in s["dependencies"]
             },
             currentStatus=s.get("currentStatus", RunningState.NOT_STARTED),
         )
         for n, s in exp_node_states.items()
     }
     return PipelineDetails(adjacency_list=converted_adj_list,
                            node_states=converted_node_states)
async def test_nodeports_integration(
    # pylint: disable=too-many-arguments
    minimal_configuration: None,
    cleanup_services_and_networks: None,
    update_project_workbench_with_comp_tasks: Callable,
    async_client: httpx.AsyncClient,
    db_manager: DBManager,
    user_db: Dict,
    current_study: ProjectAtDB,
    services_endpoint: Dict[str, URL],
    workbench_dynamic_services: Dict[str, Node],
    services_node_uuids: ServicesNodeUUIDs,
    fake_dy_success: Dict[str, Any],
    fake_dy_published: Dict[str, Any],
    temp_dir: Path,
    mocker: MockerFixture,
) -> None:
    """
    Creates a new project with where the following connections
    are defined: `sleeper:1.0.0` ->
    `dy-static-file-server-dynamic-sidecar:2.0.0` ->
    `dy-static-file-server-dynamic-sidecar-compose-spec:2.0.0`.

    Both `dy-static-file-server-*` services are able to map the
    inputs of the service to the outputs. Both services also
    generate an internal state which is to be persisted
    between runs.

    Execution steps:
    1. start all the dynamic services and make sure they are running
    2. run the computational pipeline & trigger port retrievals
    3. check that the outputs of the `sleeper` are the same as the
        outputs of the `dy-static-file-server-dynamic-sidecar-compose-spec``
    4. fetch the "state" via `docker/aioboto` for both dynamic services
    5. start the dynamic-services and fetch the "state" via
        `storage-data_manager API/aioboto` for both dynamic services
    6. start the dynamic-services again, fetch the "state" via
        `docker/aioboto` for both dynamic services
    7. finally check that all states for both dynamic services match

    NOTE: when the services are started using S3 as a backend
    for saving the state, the state files are recovered via
    `aioboto` instead of `docker` or `storage-data_manager API`.
    """

    # STEP 1

    dynamic_services_urls: Dict[
        str, str] = await _wait_for_dynamic_services_to_be_running(
            director_v2_client=async_client,
            user_id=user_db["id"],
            workbench_dynamic_services=workbench_dynamic_services,
            current_study=current_study,
        )

    # STEP 2

    response = await create_pipeline(
        async_client,
        project=current_study,
        user_id=user_db["id"],
        start_pipeline=True,
        expected_response_status_code=status.HTTP_201_CREATED,
    )
    task_out = ComputationTaskGet.parse_obj(response.json())

    # check the contents is correct: a pipeline that just started gets PUBLISHED
    await assert_computation_task_out_obj(
        async_client,
        task_out,
        project=current_study,
        exp_task_state=RunningState.PUBLISHED,
        exp_pipeline_details=PipelineDetails.parse_obj(fake_dy_published),
    )

    # wait for the computation to start
    await assert_and_wait_for_pipeline_status(
        async_client,
        task_out.url,
        user_db["id"],
        current_study.uuid,
        wait_for_states=[RunningState.STARTED],
    )

    # wait for the computation to finish (either by failing, success or abort)
    task_out = await assert_and_wait_for_pipeline_status(
        async_client, task_out.url, user_db["id"], current_study.uuid)

    await assert_computation_task_out_obj(
        async_client,
        task_out,
        project=current_study,
        exp_task_state=RunningState.SUCCESS,
        exp_pipeline_details=PipelineDetails.parse_obj(fake_dy_success),
    )

    update_project_workbench_with_comp_tasks(str(current_study.uuid))

    # Trigger inputs pulling & outputs pushing on dynamic services

    # Since there is no webserver monitoring postgres notifications
    # trigger the call manually

    # dump logs form started containers before retrieve
    await _print_dynamic_sidecars_containers_logs_and_get_containers(
        dynamic_services_urls)

    await _assert_retrieve_completed(
        director_v2_client=async_client,
        service_uuid=services_node_uuids.dy,
        dynamic_services_urls=dynamic_services_urls,
    )

    await _assert_retrieve_completed(
        director_v2_client=async_client,
        service_uuid=services_node_uuids.dy_compose_spec,
        dynamic_services_urls=dynamic_services_urls,
    )

    # STEP 3
    # pull data via nodeports

    # storage config.py resolves env vars at import time, unlike newer settingslib
    # configuration. patching the module with the correct url
    mocker.patch(
        "simcore_sdk.node_ports_common.config.STORAGE_ENDPOINT",
        str(services_endpoint["storage"]).replace("http://", ""),
    )

    mapped_nodeports_values = await _get_mapped_nodeports_values(
        user_db["id"], str(current_study.uuid), current_study.workbench,
        db_manager)
    await _assert_port_values(mapped_nodeports_values, services_node_uuids)

    # STEP 4

    # pylint: disable=protected-access
    app_settings: AppSettings = async_client._transport.app.state.settings
    r_clone_settings: RCloneSettings = (
        app_settings.DYNAMIC_SERVICES.DYNAMIC_SIDECAR.
        DYNAMIC_SIDECAR_R_CLONE_SETTINGS)

    if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED:
        await sleep_for(
            WAIT_FOR_R_CLONE_VOLUME_TO_SYNC_DATA,
            "Waiting for rclone to sync data from the docker volume",
        )

    dy_path_volume_before = (await _fetch_data_via_aioboto(
        r_clone_settings=r_clone_settings,
        dir_tag="dy",
        temp_dir=temp_dir,
        node_id=services_node_uuids.dy,
        project_id=current_study.uuid,
    ) if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED else await
                             _fetch_data_from_container(
                                 dir_tag="dy",
                                 service_uuid=services_node_uuids.dy,
                                 temp_dir=temp_dir))
    dy_compose_spec_path_volume_before = (
        await _fetch_data_via_aioboto(
            r_clone_settings=r_clone_settings,
            dir_tag="dy_compose_spec",
            temp_dir=temp_dir,
            node_id=services_node_uuids.dy_compose_spec,
            project_id=current_study.uuid,
        ) if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED else await
        _fetch_data_from_container(
            dir_tag="dy_compose_spec",
            service_uuid=services_node_uuids.dy_compose_spec,
            temp_dir=temp_dir,
        ))

    # STEP 5

    # stop the services to make sure the data is saved to storage
    await asyncio.gather(*(assert_stop_service(
        director_v2_client=async_client,
        service_uuid=service_uuid,
    ) for service_uuid in workbench_dynamic_services))

    await _wait_for_dy_services_to_fully_stop(async_client)

    if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED:
        await sleep_for(
            WAIT_FOR_R_CLONE_VOLUME_TO_SYNC_DATA,
            "Waiting for rclone to sync data from the docker volume",
        )

    dy_path_data_manager_before = (await _fetch_data_via_aioboto(
        r_clone_settings=r_clone_settings,
        dir_tag="dy",
        temp_dir=temp_dir,
        node_id=services_node_uuids.dy,
        project_id=current_study.uuid,
    ) if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED else await
                                   _fetch_data_via_data_manager(
                                       dir_tag="dy",
                                       user_id=user_db["id"],
                                       project_id=str(current_study.uuid),
                                       service_uuid=services_node_uuids.dy,
                                       temp_dir=temp_dir,
                                   ))

    dy_compose_spec_path_data_manager_before = (
        await _fetch_data_via_aioboto(
            r_clone_settings=r_clone_settings,
            dir_tag="dy_compose_spec",
            temp_dir=temp_dir,
            node_id=services_node_uuids.dy_compose_spec,
            project_id=current_study.uuid,
        ) if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED else await
        _fetch_data_via_data_manager(
            dir_tag="dy_compose_spec",
            user_id=user_db["id"],
            project_id=str(current_study.uuid),
            service_uuid=services_node_uuids.dy_compose_spec,
            temp_dir=temp_dir,
        ))

    # STEP 6

    await _wait_for_dynamic_services_to_be_running(
        director_v2_client=async_client,
        user_id=user_db["id"],
        workbench_dynamic_services=workbench_dynamic_services,
        current_study=current_study,
    )

    dy_path_volume_after = (await _fetch_data_via_aioboto(
        r_clone_settings=r_clone_settings,
        dir_tag="dy",
        temp_dir=temp_dir,
        node_id=services_node_uuids.dy,
        project_id=current_study.uuid,
    ) if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED else await
                            _fetch_data_from_container(
                                dir_tag="dy",
                                service_uuid=services_node_uuids.dy,
                                temp_dir=temp_dir))
    dy_compose_spec_path_volume_after = (
        await _fetch_data_via_aioboto(
            r_clone_settings=r_clone_settings,
            dir_tag="dy_compose_spec",
            temp_dir=temp_dir,
            node_id=services_node_uuids.dy_compose_spec,
            project_id=current_study.uuid,
        ) if app_settings.DIRECTOR_V2_DEV_FEATURES_ENABLED else await
        _fetch_data_from_container(
            dir_tag="dy_compose_spec",
            service_uuid=services_node_uuids.dy_compose_spec,
            temp_dir=temp_dir,
        ))

    # STEP 7

    _assert_same_set(
        _get_file_hashes_in_path(dy_path_volume_before),
        _get_file_hashes_in_path(dy_path_data_manager_before),
        _get_file_hashes_in_path(dy_path_volume_after),
    )

    _assert_same_set(
        _get_file_hashes_in_path(dy_compose_spec_path_volume_before),
        _get_file_hashes_in_path(dy_compose_spec_path_data_manager_before),
        _get_file_hashes_in_path(dy_compose_spec_path_volume_after),
    )