From 2cf4dc58147121b8f8011ff503000b6a270cc226 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 13:44:50 +0100 Subject: [PATCH 01/30] Init --- e2e/e2e-bare-auth/certificate.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/e2e-bare-auth/certificate.conf b/e2e/e2e-bare-auth/certificate.conf index ea97fcbb700d..55cd1bbedd39 100644 --- a/e2e/e2e-bare-auth/certificate.conf +++ b/e2e/e2e-bare-auth/certificate.conf @@ -18,3 +18,4 @@ subjectAltName = @alt_names DNS.1 = localhost IP.1 = ::1 IP.2 = 127.0.0.1 +IP.3 = 0.0.0.0 \ No newline at end of file From c3569a1ee08b141dc9f9bd606d09f97a58c2736c Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 13:45:00 +0100 Subject: [PATCH 02/30] Init --- e2e/test_superexec.sh | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 e2e/test_superexec.sh diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh new file mode 100644 index 000000000000..c4b87e48279b --- /dev/null +++ b/e2e/test_superexec.sh @@ -0,0 +1,113 @@ +#!/bin/bash +set -e + +# Set connectivity parameters +case "$1" in + secure) + ./generate.sh + server_arg='--ssl-ca-certfile ../certificates/ca.crt + --ssl-certfile ../certificates/server.pem + --ssl-keyfile ../certificates/server.key' + client_arg='--root-certificates ../certificates/ca.crt' + # For $superexec_arg, note special ordering of single- and double-quotes + superexec_arg='--executor-config 'root-certificates=\"../certificates/ca.crt\"'' + superexec_arg="$server_arg $superexec_arg" + ;; + insecure) + server_arg="--insecure" + client_arg=$server_arg + superexec_arg=$server_arg + ;; +esac + +# Set authentication parameters +case "$2" in + client-auth) + server_auth="--auth-list-public-keys ../keys/client_public_keys.csv + --auth-superlink-private-key ../keys/server_credentials + --auth-superlink-public-key ../keys/server_credentials.pub" + clnt_auth_1="--auth-supernode-private-key ../keys/client_credentials_1 + --auth-supernode-public-key ../keys/client_credentials_1.pub" + clnt_auth_2="--auth-supernode-private-key ../keys/client_credentials_2 + --auth-supernode-public-key ../keys/client_credentials_2.pub" + server_address="127.0.0.1:9092" + ;; + *) + server_auth="" + clnt_auth_1="" + clnt_auth_2="" + server_address="127.0.0.1:9092" + ;; +esac + +# Set engine +case "$3" in + deployment-engine) + superexec_engine_arg="--executor flwr.superexec.deployment:executor" + ;; + simulation-engine) + superexec_engine_arg="--executor flwr.superexec.simulation:executor + --executor-config 'num-supernodes=10'" + ;; +esac + + +# Create and install Flower app +flwr new e2e-tmp-test --framework numpy --username flwrlabs +cd e2e-tmp-test +pip install -e . --no-deps + +# Check if the first argument is 'insecure' +if [ "$1" == "insecure" ]; then + # If $1 is 'insecure', append the first line + echo -e $"\n[tool.flwr.federations.superexec]\naddress = \"127.0.0.1:9093\"\ninsecure = true" >> pyproject.toml +else + # Otherwise, append the second line + echo -e $"\n[tool.flwr.federations.superexec]\naddress = \"127.0.0.1:9093\"\nroot-certificates = \"../certificates/ca.crt\"" >> pyproject.toml +fi + +timeout 2m flower-superlink $server_arg $server_auth & +sl_pid=$! +sleep 2 + +timeout 2m flower-supernode ./ $client_arg \ + --superlink $server_address $clnt_auth_1 \ + --node-config "partition-id=0 num-partitions=2" & +cl1_pid=$! +sleep 2 + +timeout 2m flower-supernode ./ $client_arg \ + --superlink $server_address $clnt_auth_2 \ + --node-config "partition-id=1 num-partitions=2" & +cl2_pid=$! +sleep 2 + +timeout 1m flower-superexec $superexec_arg $superexec_engine_arg 2>&1 | tee flwr_output.log & +se_pid=$(pgrep -f "flower-superexec") +sleep 2 + +timeout 1m flwr run --run-config num-server-rounds=1 ../e2e-tmp-test superexec + +# Initialize a flag to track if training is successful +found_success=false +timeout=120 # Timeout after 120 seconds +elapsed=0 + +# Check for "Success" in a loop with a timeout +while [ "$found_success" = false ] && [ $elapsed -lt $timeout ]; do + if grep -q "Run finished" flwr_output.log; then + echo "Training worked correctly!" + found_success=true + kill $cl1_pid; kill $cl2_pid; sleep 1; kill $sl_pid; kill $se_pid; + else + echo "Waiting for training ... ($elapsed seconds elapsed)" + fi + # Sleep for a short period and increment the elapsed time + sleep 2 + elapsed=$((elapsed + 2)) +done + +if [ "$found_success" = false ]; then + echo "Training had an issue and timed out." + kill $cl1_pid; kill $cl2_pid; kill $sl_pid; kill $se_pid; +fi From 17d9d3725498133f24b0cd9e0720a2ac794555bf Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 14:05:05 +0100 Subject: [PATCH 03/30] Init change to e2e --- .github/workflows/e2e.yml | 386 +++++++++++++++++++++----------------- 1 file changed, 211 insertions(+), 175 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index aba3726017fd..530da6fb8632 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -51,77 +51,20 @@ jobs: short_sha: ${{ steps.upload.outputs.SHORT_SHA }} dir: ${{ steps.upload.outputs.DIR }} - frameworks: + superexec: runs-on: ubuntu-22.04 timeout-minutes: 10 needs: wheel - # Using approach described here: - # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs strategy: matrix: - include: - - directory: e2e-bare - e2e: e2e_bare - - - directory: e2e-bare-https - e2e: e2e_bare_https - - - directory: e2e-bare-auth - e2e: e2e_bare_auth - - - directory: e2e-jax - e2e: e2e_jax - - - directory: e2e-pytorch - e2e: e2e_pytorch - dataset: | - from torchvision.datasets import CIFAR10 - CIFAR10('./data', download=True) - - - directory: e2e-tensorflow - e2e: e2e_tensorflow - dataset: | - import tensorflow as tf - tf.keras.datasets.cifar10.load_data() - - - directory: e2e-opacus - e2e: e2e_opacus - dataset: | - from torchvision.datasets import CIFAR10 - CIFAR10('./data', download=True) - - - directory: e2e-pytorch-lightning - e2e: e2e_pytorch_lightning - dataset: | - from torchvision.datasets import MNIST - MNIST('./data', download=True) - - - directory: e2e-scikit-learn - e2e: e2e_scikit_learn - dataset: | - import openml - openml.datasets.get_dataset(554) - - - directory: e2e-fastai - e2e: e2e_fastai - dataset: | - from fastai.vision.all import untar_data, URLs - untar_data(URLs.MNIST) - - - directory: e2e-pandas - e2e: e2e_pandas - dataset: | - from pathlib import Path - from sklearn.datasets import load_iris - Path('data').mkdir(exist_ok=True) - load_iris(as_frame=True)['data'].to_csv('./data/client.csv') - - name: Framework / ${{ matrix.directory }} - + directory: [e2e-bare-auth] + connection: [secure, insecure] + engine: [deployment-engine, simulation-engine] + authentication: [""] + name: SuperExec / ${{ matrix.directory }} defaults: run: working-directory: e2e/${{ matrix.directory }} - steps: - uses: actions/checkout@v4 - name: Set up Python @@ -129,127 +72,220 @@ jobs: with: python-version: 3.9 - name: Install build tools - run: | - python -m pip install -U pip==23.3.1 + run: python -m pip install -U pip==23.3.1 shell: bash - # Using approach described here for Python location caching: - # https://blog.allenai.org/python-caching-in-github-actions-e9452698e98d - - name: Cache Python location - id: cache-restore-python - uses: actions/cache/restore@v4 - with: - path: ${{ env.pythonLocation }} - key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} - - name: Install dependencies - run: python -m pip install --upgrade . - name: Install Flower wheel from artifact store if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - - name: Download dataset - if: ${{ matrix.dataset }} - run: python -c "${{ matrix.dataset }}" - - name: Run edge client test - if: ${{ matrix.directory != 'e2e-bare-auth' }} - working-directory: e2e/${{ matrix.directory }}/${{ matrix.e2e }} - run: ./../../test_legacy.sh "${{ matrix.directory }}" - - name: Run virtual client test - if: ${{ matrix.directory != 'e2e-bare-auth' }} - run: python simulation.py - - name: Run simulation engine test - if: ${{ matrix.directory == 'e2e-pytorch' || matrix.directory == 'e2e-tensorflow'}} - run: python simulation_next.py - - name: Run driver test - if: ${{ matrix.directory != 'e2e-bare-auth' }} - run: ./../test_superlink.sh "${{ matrix.directory }}" - - name: Run driver test with REST - if: ${{ matrix.directory == 'e2e-bare' }} - run: ./../test_superlink.sh bare rest - - name: Run driver test with SQLite database - if: ${{ matrix.directory == 'e2e-bare' }} - run: ./../test_superlink.sh bare sqlite - - name: Run driver test with client authentication - if: ${{ matrix.directory == 'e2e-bare-auth' }} - run: ./../test_superlink.sh "${{ matrix.directory }}" client-auth - - name: Run reconnection test with SQLite database - if: ${{ matrix.directory == 'e2e-bare' }} - run: ./../test_reconnection.sh sqlite - - name: Cache save Python location - id: cache-save-python - uses: actions/cache/save@v4 - if: ${{ github.ref_name == 'main' && !steps.cache-restore-python.outputs.cache-hit }} - with: - path: ${{ env.pythonLocation }} - key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} + - name: | + Run SuperExec test / + ${{ matrix.directory }} / + ${{ matrix.connection }} / + ${{ matrix.authentication }} / + ${{ matrix.engine }} + working-directory: e2e/${{ matrix.directory }} + run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" - strategies: - runs-on: ubuntu-22.04 - timeout-minutes: 10 - needs: wheel - strategy: - matrix: - strat: ["FedMedian", "FedTrimmedAvg", "QFedAvg", "FaultTolerantFedAvg", "FedAvgM", "FedAdam", "FedAdagrad", "FedYogi"] + # frameworks: + # runs-on: ubuntu-22.04 + # timeout-minutes: 10 + # needs: wheel + # # Using approach described here: + # # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + # strategy: + # matrix: + # include: + # - directory: e2e-bare + # e2e: e2e_bare - name: Strategy / ${{ matrix.strat }} + # - directory: e2e-bare-https + # e2e: e2e_bare_https - defaults: - run: - working-directory: e2e/strategies + # - directory: e2e-bare-auth + # e2e: e2e_bare_auth - steps: - - uses: actions/checkout@v4 - - name: Bootstrap - uses: ./.github/actions/bootstrap - - name: Install dependencies - run: | - python -m poetry install - - name: Install Flower wheel from artifact store - if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - - name: Cache Datasets - uses: actions/cache@v4 - with: - path: "~/.keras" - key: keras-datasets - - name: Download Datasets - run: | - python -c "import tensorflow as tf; tf.keras.datasets.mnist.load_data()" - - name: Test strategies - run: | - python test.py "${{ matrix.strat }}" + # - directory: e2e-jax + # e2e: e2e_jax - templates: - runs-on: ubuntu-22.04 - timeout-minutes: 10 - needs: wheel - strategy: - matrix: - framework: ["numpy", "pytorch", "tensorflow", "jax", "sklearn"] + # - directory: e2e-pytorch + # e2e: e2e_pytorch + # dataset: | + # from torchvision.datasets import CIFAR10 + # CIFAR10('./data', download=True) - name: Template / ${{ matrix.framework }} + # - directory: e2e-tensorflow + # e2e: e2e_tensorflow + # dataset: | + # import tensorflow as tf + # tf.keras.datasets.cifar10.load_data() - steps: - - uses: actions/checkout@v4 - - name: Bootstrap - uses: ./.github/actions/bootstrap - - name: Install Flower from repo - if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} - run: | - python -m pip install . - - name: Install Flower wheel from artifact store - if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - - name: Create project and install it - run: | - flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci - cd tmp-${{ matrix.framework }} - pip install . - - name: Run project - run: | - cd tmp-${{ matrix.framework }} - flwr run --run-config num-server-rounds=1 2>&1 | tee flwr_output.log - if grep -q "ERROR" flwr_output.log; then - exit 1 - fi + # - directory: e2e-opacus + # e2e: e2e_opacus + # dataset: | + # from torchvision.datasets import CIFAR10 + # CIFAR10('./data', download=True) + + # - directory: e2e-pytorch-lightning + # e2e: e2e_pytorch_lightning + # dataset: | + # from torchvision.datasets import MNIST + # MNIST('./data', download=True) + + # - directory: e2e-scikit-learn + # e2e: e2e_scikit_learn + # dataset: | + # import openml + # openml.datasets.get_dataset(554) + + # - directory: e2e-fastai + # e2e: e2e_fastai + # dataset: | + # from fastai.vision.all import untar_data, URLs + # untar_data(URLs.MNIST) + + # - directory: e2e-pandas + # e2e: e2e_pandas + # dataset: | + # from pathlib import Path + # from sklearn.datasets import load_iris + # Path('data').mkdir(exist_ok=True) + # load_iris(as_frame=True)['data'].to_csv('./data/client.csv') + + # name: Framework / ${{ matrix.directory }} + + # defaults: + # run: + # working-directory: e2e/${{ matrix.directory }} + + # steps: + # - uses: actions/checkout@v4 + # - name: Set up Python + # uses: actions/setup-python@v5 + # with: + # python-version: 3.9 + # - name: Install build tools + # run: | + # python -m pip install -U pip==23.3.1 + # shell: bash + # # Using approach described here for Python location caching: + # # https://blog.allenai.org/python-caching-in-github-actions-e9452698e98d + # - name: Cache Python location + # id: cache-restore-python + # uses: actions/cache/restore@v4 + # with: + # path: ${{ env.pythonLocation }} + # key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} + # - name: Install dependencies + # run: python -m pip install --upgrade . + # - name: Install Flower wheel from artifact store + # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + # run: | + # python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + # - name: Download dataset + # if: ${{ matrix.dataset }} + # run: python -c "${{ matrix.dataset }}" + # - name: Run edge client test + # if: ${{ matrix.directory != 'e2e-bare-auth' }} + # working-directory: e2e/${{ matrix.directory }}/${{ matrix.e2e }} + # run: ./../../test_legacy.sh "${{ matrix.directory }}" + # - name: Run virtual client test + # if: ${{ matrix.directory != 'e2e-bare-auth' }} + # run: python simulation.py + # - name: Run simulation engine test + # if: ${{ matrix.directory == 'e2e-pytorch' || matrix.directory == 'e2e-tensorflow'}} + # run: python simulation_next.py + # - name: Run driver test + # if: ${{ matrix.directory != 'e2e-bare-auth' }} + # run: ./../test_superlink.sh "${{ matrix.directory }}" + # - name: Run driver test with REST + # if: ${{ matrix.directory == 'e2e-bare' }} + # run: ./../test_superlink.sh bare rest + # - name: Run driver test with SQLite database + # if: ${{ matrix.directory == 'e2e-bare' }} + # run: ./../test_superlink.sh bare sqlite + # - name: Run driver test with client authentication + # if: ${{ matrix.directory == 'e2e-bare-auth' }} + # run: ./../test_superlink.sh "${{ matrix.directory }}" client-auth + # - name: Run reconnection test with SQLite database + # if: ${{ matrix.directory == 'e2e-bare' }} + # run: ./../test_reconnection.sh sqlite + # - name: Cache save Python location + # id: cache-save-python + # uses: actions/cache/save@v4 + # if: ${{ github.ref_name == 'main' && !steps.cache-restore-python.outputs.cache-hit }} + # with: + # path: ${{ env.pythonLocation }} + # key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} + + # strategies: + # runs-on: ubuntu-22.04 + # timeout-minutes: 10 + # needs: wheel + # strategy: + # matrix: + # strat: ["FedMedian", "FedTrimmedAvg", "QFedAvg", "FaultTolerantFedAvg", "FedAvgM", "FedAdam", "FedAdagrad", "FedYogi"] + + # name: Strategy / ${{ matrix.strat }} + + # defaults: + # run: + # working-directory: e2e/strategies + + # steps: + # - uses: actions/checkout@v4 + # - name: Bootstrap + # uses: ./.github/actions/bootstrap + # - name: Install dependencies + # run: | + # python -m poetry install + # - name: Install Flower wheel from artifact store + # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + # run: | + # python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + # - name: Cache Datasets + # uses: actions/cache@v4 + # with: + # path: "~/.keras" + # key: keras-datasets + # - name: Download Datasets + # run: | + # python -c "import tensorflow as tf; tf.keras.datasets.mnist.load_data()" + # - name: Test strategies + # run: | + # python test.py "${{ matrix.strat }}" + + # templates: + # runs-on: ubuntu-22.04 + # timeout-minutes: 10 + # needs: wheel + # strategy: + # matrix: + # framework: ["numpy", "pytorch", "tensorflow", "jax", "sklearn"] + + # name: Template / ${{ matrix.framework }} + + # steps: + # - uses: actions/checkout@v4 + # - name: Bootstrap + # uses: ./.github/actions/bootstrap + # - name: Install Flower from repo + # if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} + # run: | + # python -m pip install . + # - name: Install Flower wheel from artifact store + # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + # run: | + # python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + # - name: Create project and install it + # run: | + # flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci + # cd tmp-${{ matrix.framework }} + # pip install . + # - name: Run project + # run: | + # cd tmp-${{ matrix.framework }} + # flwr run --run-config num-server-rounds=1 2>&1 | tee flwr_output.log + # if grep -q "ERROR" flwr_output.log; then + # exit 1 + # fi From afbd9474b38bfa14000756d7cd89ed8708b2c60e Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 14:09:27 +0100 Subject: [PATCH 04/30] Change permissions, update name in CI --- .github/workflows/e2e.yml | 7 ++++++- e2e/test_superexec.sh | 0 2 files changed, 6 insertions(+), 1 deletion(-) mode change 100644 => 100755 e2e/test_superexec.sh diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 530da6fb8632..52ef0cbe14ba 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -61,7 +61,12 @@ jobs: connection: [secure, insecure] engine: [deployment-engine, simulation-engine] authentication: [""] - name: SuperExec / ${{ matrix.directory }} + name: | + SuperExec / + ${{ matrix.directory }} / + ${{ matrix.connection }} / + ${{ matrix.authentication }} / + ${{ matrix.engine }} defaults: run: working-directory: e2e/${{ matrix.directory }} diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh old mode 100644 new mode 100755 From 8e2429b8bf3046a8eef56a5a376c82d5a8c34556 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 14:26:45 +0100 Subject: [PATCH 05/30] Change double quotes to single quotes to preserve literal values --- e2e/test_superexec.sh | 68 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index c4b87e48279b..ffc6d2c69717 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -3,51 +3,51 @@ set -e # Set connectivity parameters case "$1" in - secure) - ./generate.sh - server_arg='--ssl-ca-certfile ../certificates/ca.crt - --ssl-certfile ../certificates/server.pem - --ssl-keyfile ../certificates/server.key' - client_arg='--root-certificates ../certificates/ca.crt' - # For $superexec_arg, note special ordering of single- and double-quotes - superexec_arg='--executor-config 'root-certificates=\"../certificates/ca.crt\"'' - superexec_arg="$server_arg $superexec_arg" - ;; - insecure) - server_arg="--insecure" - client_arg=$server_arg - superexec_arg=$server_arg + secure) + ./generate.sh + server_arg='--ssl-ca-certfile ../certificates/ca.crt + --ssl-certfile ../certificates/server.pem + --ssl-keyfile ../certificates/server.key' + client_arg='--root-certificates ../certificates/ca.crt' + # For $superexec_arg, note special ordering of single- and double-quotes + superexec_arg='--executor-config 'root-certificates=\"../certificates/ca.crt\"'' + superexec_arg="$server_arg $superexec_arg" + ;; + insecure) + server_arg='--insecure' + client_arg=$server_arg + superexec_arg=$server_arg ;; esac # Set authentication parameters case "$2" in - client-auth) - server_auth="--auth-list-public-keys ../keys/client_public_keys.csv - --auth-superlink-private-key ../keys/server_credentials - --auth-superlink-public-key ../keys/server_credentials.pub" - clnt_auth_1="--auth-supernode-private-key ../keys/client_credentials_1 - --auth-supernode-public-key ../keys/client_credentials_1.pub" - clnt_auth_2="--auth-supernode-private-key ../keys/client_credentials_2 - --auth-supernode-public-key ../keys/client_credentials_2.pub" - server_address="127.0.0.1:9092" - ;; - *) - server_auth="" - clnt_auth_1="" - clnt_auth_2="" - server_address="127.0.0.1:9092" + client-auth) + server_auth='--auth-list-public-keys ../keys/client_public_keys.csv + --auth-superlink-private-key ../keys/server_credentials + --auth-superlink-public-key ../keys/server_credentials.pub' + clnt_auth_1='--auth-supernode-private-key ../keys/client_credentials_1 + --auth-supernode-public-key ../keys/client_credentials_1.pub' + clnt_auth_2='--auth-supernode-private-key ../keys/client_credentials_2 + --auth-supernode-public-key ../keys/client_credentials_2.pub' + server_address='127.0.0.1:9092' + ;; + *) + server_auth='' + clnt_auth_1='' + clnt_auth_2='' + server_address='127.0.0.1:9092' ;; esac # Set engine case "$3" in - deployment-engine) - superexec_engine_arg="--executor flwr.superexec.deployment:executor" + deployment-engine) + superexec_engine_arg='--executor flwr.superexec.deployment:executor' ;; - simulation-engine) - superexec_engine_arg="--executor flwr.superexec.simulation:executor - --executor-config 'num-supernodes=10'" + simulation-engine) + superexec_engine_arg='--executor flwr.superexec.simulation:executor + --executor-config 'num-supernodes=10'' ;; esac From a55de8cae2d5b044ab9ed81ccc88f04183ec8945 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 14:48:31 +0100 Subject: [PATCH 06/30] Add optional extras when installing wheel from artifact store --- .github/workflows/e2e.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 52ef0cbe14ba..11bdd85f5a3f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -82,7 +82,11 @@ jobs: - name: Install Flower wheel from artifact store if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then + python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}[simulation] + else + python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + fi - name: | Run SuperExec test / ${{ matrix.directory }} / From 77284b30d222f6476af93fe6e53fb9dad32ec4fe Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 14:59:05 +0100 Subject: [PATCH 07/30] Modify installation from Flower wheel --- .github/workflows/e2e.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 11bdd85f5a3f..658ff8aa1a86 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -79,13 +79,14 @@ jobs: - name: Install build tools run: python -m pip install -U pip==23.3.1 shell: bash - - name: Install Flower wheel from artifact store + - name: Download and install Flower wheel from artifact store if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | + wget https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} -O flwr-tmp-package.whl if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}[simulation] + python -m pip install 'flwr-tmp-package.whl[simulation]' else - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + python -m pip install flwr-tmp-package.whl fi - name: | Run SuperExec test / From b4cf87e07b829e0c601fc8d4c663488bddd3cd31 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 15:04:28 +0100 Subject: [PATCH 08/30] Conform to pypi wheel name convention --- .github/workflows/e2e.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 658ff8aa1a86..dfa2d6bed94f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -82,11 +82,12 @@ jobs: - name: Download and install Flower wheel from artifact store if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - wget https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} -O flwr-tmp-package.whl + wget https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + WHEEL_FILE=$(basename https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}) if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then - python -m pip install 'flwr-tmp-package.whl[simulation]' + python -m pip install "${WHEEL_FILE}[simulation]" else - python -m pip install flwr-tmp-package.whl + python -m pip install "${WHEEL_FILE}" fi - name: | Run SuperExec test / From 6f132a54e59ad5acce6d751855fd36be7483abab Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 15:07:31 +0100 Subject: [PATCH 09/30] Refactor CI --- .github/workflows/e2e.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index dfa2d6bed94f..27cb0a53fc83 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -82,8 +82,12 @@ jobs: - name: Download and install Flower wheel from artifact store if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - wget https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - WHEEL_FILE=$(basename https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}) + # Define base URL for wheel file + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + # Download wheel file + wget "${WHEEL_URL}" + # Extract the original filename from the URL + WHEEL_FILE=$(basename "${WHEEL_URL}") if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then python -m pip install "${WHEEL_FILE}[simulation]" else From 61be6b92072cf920be1447c1cc3b74ec0dfd46df Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 15:10:31 +0100 Subject: [PATCH 10/30] Change to no-auth --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 27cb0a53fc83..c066a342a0a4 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -60,7 +60,7 @@ jobs: directory: [e2e-bare-auth] connection: [secure, insecure] engine: [deployment-engine, simulation-engine] - authentication: [""] + authentication: [no-auth] name: | SuperExec / ${{ matrix.directory }} / From 4a78dd7de55e765813537b85708b7c95323fc12b Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 17 Sep 2024 15:59:05 +0100 Subject: [PATCH 11/30] Small fixes --- e2e/test_superexec.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index ffc6d2c69717..5d5edf69f9e0 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -26,16 +26,16 @@ case "$2" in server_auth='--auth-list-public-keys ../keys/client_public_keys.csv --auth-superlink-private-key ../keys/server_credentials --auth-superlink-public-key ../keys/server_credentials.pub' - clnt_auth_1='--auth-supernode-private-key ../keys/client_credentials_1 - --auth-supernode-public-key ../keys/client_credentials_1.pub' - clnt_auth_2='--auth-supernode-private-key ../keys/client_credentials_2 - --auth-supernode-public-key ../keys/client_credentials_2.pub' + client_auth_1='--auth-supernode-private-key ../keys/client_credentials_1 + --auth-supernode-public-key ../keys/client_credentials_1.pub' + client_auth_2='--auth-supernode-private-key ../keys/client_credentials_2 + --auth-supernode-public-key ../keys/client_credentials_2.pub' server_address='127.0.0.1:9092' ;; *) server_auth='' - clnt_auth_1='' - clnt_auth_2='' + client_auth_1='' + client_auth_2='' server_address='127.0.0.1:9092' ;; esac @@ -71,18 +71,18 @@ sl_pid=$! sleep 2 timeout 2m flower-supernode ./ $client_arg \ - --superlink $server_address $clnt_auth_1 \ + --superlink $server_address $client_auth_1 \ --node-config "partition-id=0 num-partitions=2" & cl1_pid=$! sleep 2 timeout 2m flower-supernode ./ $client_arg \ - --superlink $server_address $clnt_auth_2 \ + --superlink $server_address $client_auth_2 \ --node-config "partition-id=1 num-partitions=2" & cl2_pid=$! sleep 2 -timeout 1m flower-superexec $superexec_arg $superexec_engine_arg 2>&1 | tee flwr_output.log & +timeout 2m flower-superexec $superexec_arg $superexec_engine_arg 2>&1 | tee flwr_output.log & se_pid=$(pgrep -f "flower-superexec") sleep 2 From d369ead6280134794931167b503c88fed1956040 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 08:08:10 +0100 Subject: [PATCH 12/30] Remove flwr from pyproject.toml --- e2e/test_superexec.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index 5d5edf69f9e0..fffe92243d80 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -55,6 +55,15 @@ esac # Create and install Flower app flwr new e2e-tmp-test --framework numpy --username flwrlabs cd e2e-tmp-test +# Remove flwr dependency from `pyproject.toml`. Seems necessary so that it does +# not override the wheel dependency +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS (Darwin) system + sed -i '' '/flwr\[simulation\]/d' pyproject.toml +else + # Non-macOS system (Linux) + sed -i '/flwr\[simulation\]/d' pyproject.toml +fi pip install -e . --no-deps # Check if the first argument is 'insecure' From 27bb83c9f4f102a1c2caebaad5c3c6e244afc083 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 08:10:05 +0100 Subject: [PATCH 13/30] Add new line --- e2e/e2e-bare-auth/certificate.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/e2e-bare-auth/certificate.conf b/e2e/e2e-bare-auth/certificate.conf index 55cd1bbedd39..04a2ed388174 100644 --- a/e2e/e2e-bare-auth/certificate.conf +++ b/e2e/e2e-bare-auth/certificate.conf @@ -18,4 +18,4 @@ subjectAltName = @alt_names DNS.1 = localhost IP.1 = ::1 IP.2 = 127.0.0.1 -IP.3 = 0.0.0.0 \ No newline at end of file +IP.3 = 0.0.0.0 From 71957a718c8123b4d14725015f54c3a163315075 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 08:14:19 +0100 Subject: [PATCH 14/30] Test yaml concat --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c066a342a0a4..bccc9e97cf4b 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -93,7 +93,7 @@ jobs: else python -m pip install "${WHEEL_FILE}" fi - - name: | + - name: > Run SuperExec test / ${{ matrix.directory }} / ${{ matrix.connection }} / From 5a721b7a24341a0bd5b502295b787743a7376882 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 08:39:37 +0100 Subject: [PATCH 15/30] Add Python matrix --- .github/workflows/e2e.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index bccc9e97cf4b..ed25f8a7ee60 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -57,12 +57,14 @@ jobs: needs: wheel strategy: matrix: + python-version: ["3.9", "3.10", "3.11"] directory: [e2e-bare-auth] connection: [secure, insecure] engine: [deployment-engine, simulation-engine] authentication: [no-auth] name: | SuperExec / + Python ${{ matrix.python-version }} / ${{ matrix.directory }} / ${{ matrix.connection }} / ${{ matrix.authentication }} / @@ -72,10 +74,10 @@ jobs: working-directory: e2e/${{ matrix.directory }} steps: - uses: actions/checkout@v4 - - name: Set up Python + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: ${{ matrix.python-version }} - name: Install build tools run: python -m pip install -U pip==23.3.1 shell: bash From 6d623b6959ee2ee766d11ed544ca072b4b164311 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 08:50:33 +0100 Subject: [PATCH 16/30] Minor fixes --- .github/workflows/e2e.yml | 6 +----- e2e/test_superexec.sh | 16 ++++++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ed25f8a7ee60..e74a26c2fed1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -64,11 +64,8 @@ jobs: authentication: [no-auth] name: | SuperExec / - Python ${{ matrix.python-version }} / ${{ matrix.directory }} / - ${{ matrix.connection }} / - ${{ matrix.authentication }} / - ${{ matrix.engine }} + Python ${{ matrix.python-version }} defaults: run: working-directory: e2e/${{ matrix.directory }} @@ -97,7 +94,6 @@ jobs: fi - name: > Run SuperExec test / - ${{ matrix.directory }} / ${{ matrix.connection }} / ${{ matrix.authentication }} / ${{ matrix.engine }} diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index fffe92243d80..2eabce97f2d8 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -75,23 +75,23 @@ else echo -e $"\n[tool.flwr.federations.superexec]\naddress = \"127.0.0.1:9093\"\nroot-certificates = \"../certificates/ca.crt\"" >> pyproject.toml fi -timeout 2m flower-superlink $server_arg $server_auth & +timeout 2m flower-superlink "$server_arg" "$server_auth" & sl_pid=$! sleep 2 -timeout 2m flower-supernode ./ $client_arg \ - --superlink $server_address $client_auth_1 \ +timeout 2m flower-supernode ./ "$client_arg" \ + --superlink $server_address "$client_auth_1" \ --node-config "partition-id=0 num-partitions=2" & cl1_pid=$! sleep 2 -timeout 2m flower-supernode ./ $client_arg \ - --superlink $server_address $client_auth_2 \ +timeout 2m flower-supernode ./ "$client_arg" \ + --superlink $server_address "$client_auth_2" \ --node-config "partition-id=1 num-partitions=2" & cl2_pid=$! sleep 2 -timeout 2m flower-superexec $superexec_arg $superexec_engine_arg 2>&1 | tee flwr_output.log & +timeout 2m flower-superexec "$superexec_arg" "$superexec_engine_arg" 2>&1 | tee flwr_output.log & se_pid=$(pgrep -f "flower-superexec") sleep 2 @@ -107,7 +107,7 @@ while [ "$found_success" = false ] && [ $elapsed -lt $timeout ]; do if grep -q "Run finished" flwr_output.log; then echo "Training worked correctly!" found_success=true - kill $cl1_pid; kill $cl2_pid; sleep 1; kill $sl_pid; kill $se_pid; + kill $cl1_pid; kill $cl2_pid; sleep 1; kill $sl_pid; kill "$se_pid"; else echo "Waiting for training ... ($elapsed seconds elapsed)" fi @@ -118,5 +118,5 @@ done if [ "$found_success" = false ]; then echo "Training had an issue and timed out." - kill $cl1_pid; kill $cl2_pid; kill $sl_pid; kill $se_pid; + kill $cl1_pid; kill $cl2_pid; kill $sl_pid; kill "$se_pid"; fi From 4ba33a4efa405f79f4f13eec426a9ffd07ac04aa Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 10:08:26 +0100 Subject: [PATCH 17/30] Tweaks --- e2e/test_superexec.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index 2eabce97f2d8..2212b0a88a4f 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -75,23 +75,23 @@ else echo -e $"\n[tool.flwr.federations.superexec]\naddress = \"127.0.0.1:9093\"\nroot-certificates = \"../certificates/ca.crt\"" >> pyproject.toml fi -timeout 2m flower-superlink "$server_arg" "$server_auth" & +timeout 2m flower-superlink $server_arg $server_auth & sl_pid=$! sleep 2 -timeout 2m flower-supernode ./ "$client_arg" \ - --superlink $server_address "$client_auth_1" \ +timeout 2m flower-supernode ./ $client_arg \ + --superlink $server_address $client_auth_1 \ --node-config "partition-id=0 num-partitions=2" & cl1_pid=$! sleep 2 -timeout 2m flower-supernode ./ "$client_arg" \ - --superlink $server_address "$client_auth_2" \ +timeout 2m flower-supernode ./ $client_arg \ + --superlink $server_address $client_auth_2 \ --node-config "partition-id=1 num-partitions=2" & cl2_pid=$! sleep 2 -timeout 2m flower-superexec "$superexec_arg" "$superexec_engine_arg" 2>&1 | tee flwr_output.log & +timeout 2m flower-superexec $superexec_arg $superexec_engine_arg 2>&1 | tee flwr_output.log & se_pid=$(pgrep -f "flower-superexec") sleep 2 From e247de07e51935600752c214ecfada78acc947ee Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 10:15:17 +0100 Subject: [PATCH 18/30] Fix --- e2e/test_superexec.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index 2212b0a88a4f..fffe92243d80 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -107,7 +107,7 @@ while [ "$found_success" = false ] && [ $elapsed -lt $timeout ]; do if grep -q "Run finished" flwr_output.log; then echo "Training worked correctly!" found_success=true - kill $cl1_pid; kill $cl2_pid; sleep 1; kill $sl_pid; kill "$se_pid"; + kill $cl1_pid; kill $cl2_pid; sleep 1; kill $sl_pid; kill $se_pid; else echo "Waiting for training ... ($elapsed seconds elapsed)" fi @@ -118,5 +118,5 @@ done if [ "$found_success" = false ]; then echo "Training had an issue and timed out." - kill $cl1_pid; kill $cl2_pid; kill $sl_pid; kill "$se_pid"; + kill $cl1_pid; kill $cl2_pid; kill $sl_pid; kill $se_pid; fi From 3323c798c750634ff065ba82e63b3237f103e2ef Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 10:20:25 +0100 Subject: [PATCH 19/30] Update name --- .github/workflows/e2e.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e74a26c2fed1..41732fbd6428 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -64,8 +64,10 @@ jobs: authentication: [no-auth] name: | SuperExec / - ${{ matrix.directory }} / - Python ${{ matrix.python-version }} + Python ${{ matrix.python-version }} / + ${{ matrix.connection }} / + ${{ matrix.authentication }} / + ${{ matrix.engine }} defaults: run: working-directory: e2e/${{ matrix.directory }} From 2d4ed8b82847e6d54d6f149f12e1e8fd6a4824b6 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 11:32:15 +0100 Subject: [PATCH 20/30] Skip node_id check for GetFabRequest --- e2e/test_superexec.sh | 4 ++-- src/py/flwr/client/grpc_rere_client/client_interceptor.py | 3 +++ .../server/superlink/fleet/grpc_rere/server_interceptor.py | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/e2e/test_superexec.sh b/e2e/test_superexec.sh index fffe92243d80..ae79128c6ac1 100755 --- a/e2e/test_superexec.sh +++ b/e2e/test_superexec.sh @@ -81,13 +81,13 @@ sleep 2 timeout 2m flower-supernode ./ $client_arg \ --superlink $server_address $client_auth_1 \ - --node-config "partition-id=0 num-partitions=2" & + --node-config "partition-id=0 num-partitions=2" --max-retries 0 & cl1_pid=$! sleep 2 timeout 2m flower-supernode ./ $client_arg \ --superlink $server_address $client_auth_2 \ - --node-config "partition-id=1 num-partitions=2" & + --node-config "partition-id=1 num-partitions=2" --max-retries 0 & cl2_pid=$! sleep 2 diff --git a/src/py/flwr/client/grpc_rere_client/client_interceptor.py b/src/py/flwr/client/grpc_rere_client/client_interceptor.py index 653e384aff96..041860957db7 100644 --- a/src/py/flwr/client/grpc_rere_client/client_interceptor.py +++ b/src/py/flwr/client/grpc_rere_client/client_interceptor.py @@ -31,6 +31,7 @@ generate_shared_key, public_key_to_bytes, ) +from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611 from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, DeleteNodeRequest, @@ -50,6 +51,7 @@ PushTaskResRequest, GetRunRequest, PingRequest, + GetFabRequest, ] @@ -126,6 +128,7 @@ def intercept_unary_unary( PushTaskResRequest, GetRunRequest, PingRequest, + GetFabRequest, ), ): if self.shared_secret is None: diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py index d836a74bef2e..17d2c7406528 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py @@ -30,6 +30,7 @@ generate_shared_key, verify_hmac, ) +from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611 from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, CreateNodeResponse, @@ -173,6 +174,7 @@ def _verify_node_id( PushTaskResRequest, GetRunRequest, PingRequest, + GetFabRequest, ], ) -> bool: if node_id is None: @@ -183,6 +185,8 @@ def _verify_node_id( return request.task_res_list[0].task.producer.node_id == node_id if isinstance(request, GetRunRequest): return node_id in self.state.get_nodes(request.run_id) + if isinstance(request, GetFabRequest): + return True return request.node.node_id == node_id def _verify_hmac( From 752d31c3c4b63c902f03e4ffaa07090036335b05 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 11:35:31 +0100 Subject: [PATCH 21/30] Add auth check --- .github/workflows/e2e.yml | 407 +++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 202 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 41732fbd6428..8e5cda914dcc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -62,6 +62,9 @@ jobs: connection: [secure, insecure] engine: [deployment-engine, simulation-engine] authentication: [no-auth] + include: + - connection: secure + authentication: client-auth name: | SuperExec / Python ${{ matrix.python-version }} / @@ -102,205 +105,205 @@ jobs: working-directory: e2e/${{ matrix.directory }} run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" - # frameworks: - # runs-on: ubuntu-22.04 - # timeout-minutes: 10 - # needs: wheel - # # Using approach described here: - # # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs - # strategy: - # matrix: - # include: - # - directory: e2e-bare - # e2e: e2e_bare - - # - directory: e2e-bare-https - # e2e: e2e_bare_https - - # - directory: e2e-bare-auth - # e2e: e2e_bare_auth - - # - directory: e2e-jax - # e2e: e2e_jax - - # - directory: e2e-pytorch - # e2e: e2e_pytorch - # dataset: | - # from torchvision.datasets import CIFAR10 - # CIFAR10('./data', download=True) - - # - directory: e2e-tensorflow - # e2e: e2e_tensorflow - # dataset: | - # import tensorflow as tf - # tf.keras.datasets.cifar10.load_data() - - # - directory: e2e-opacus - # e2e: e2e_opacus - # dataset: | - # from torchvision.datasets import CIFAR10 - # CIFAR10('./data', download=True) - - # - directory: e2e-pytorch-lightning - # e2e: e2e_pytorch_lightning - # dataset: | - # from torchvision.datasets import MNIST - # MNIST('./data', download=True) - - # - directory: e2e-scikit-learn - # e2e: e2e_scikit_learn - # dataset: | - # import openml - # openml.datasets.get_dataset(554) - - # - directory: e2e-fastai - # e2e: e2e_fastai - # dataset: | - # from fastai.vision.all import untar_data, URLs - # untar_data(URLs.MNIST) - - # - directory: e2e-pandas - # e2e: e2e_pandas - # dataset: | - # from pathlib import Path - # from sklearn.datasets import load_iris - # Path('data').mkdir(exist_ok=True) - # load_iris(as_frame=True)['data'].to_csv('./data/client.csv') - - # name: Framework / ${{ matrix.directory }} - - # defaults: - # run: - # working-directory: e2e/${{ matrix.directory }} - - # steps: - # - uses: actions/checkout@v4 - # - name: Set up Python - # uses: actions/setup-python@v5 - # with: - # python-version: 3.9 - # - name: Install build tools - # run: | - # python -m pip install -U pip==23.3.1 - # shell: bash - # # Using approach described here for Python location caching: - # # https://blog.allenai.org/python-caching-in-github-actions-e9452698e98d - # - name: Cache Python location - # id: cache-restore-python - # uses: actions/cache/restore@v4 - # with: - # path: ${{ env.pythonLocation }} - # key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} - # - name: Install dependencies - # run: python -m pip install --upgrade . - # - name: Install Flower wheel from artifact store - # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - # run: | - # python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - # - name: Download dataset - # if: ${{ matrix.dataset }} - # run: python -c "${{ matrix.dataset }}" - # - name: Run edge client test - # if: ${{ matrix.directory != 'e2e-bare-auth' }} - # working-directory: e2e/${{ matrix.directory }}/${{ matrix.e2e }} - # run: ./../../test_legacy.sh "${{ matrix.directory }}" - # - name: Run virtual client test - # if: ${{ matrix.directory != 'e2e-bare-auth' }} - # run: python simulation.py - # - name: Run simulation engine test - # if: ${{ matrix.directory == 'e2e-pytorch' || matrix.directory == 'e2e-tensorflow'}} - # run: python simulation_next.py - # - name: Run driver test - # if: ${{ matrix.directory != 'e2e-bare-auth' }} - # run: ./../test_superlink.sh "${{ matrix.directory }}" - # - name: Run driver test with REST - # if: ${{ matrix.directory == 'e2e-bare' }} - # run: ./../test_superlink.sh bare rest - # - name: Run driver test with SQLite database - # if: ${{ matrix.directory == 'e2e-bare' }} - # run: ./../test_superlink.sh bare sqlite - # - name: Run driver test with client authentication - # if: ${{ matrix.directory == 'e2e-bare-auth' }} - # run: ./../test_superlink.sh "${{ matrix.directory }}" client-auth - # - name: Run reconnection test with SQLite database - # if: ${{ matrix.directory == 'e2e-bare' }} - # run: ./../test_reconnection.sh sqlite - # - name: Cache save Python location - # id: cache-save-python - # uses: actions/cache/save@v4 - # if: ${{ github.ref_name == 'main' && !steps.cache-restore-python.outputs.cache-hit }} - # with: - # path: ${{ env.pythonLocation }} - # key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} - - # strategies: - # runs-on: ubuntu-22.04 - # timeout-minutes: 10 - # needs: wheel - # strategy: - # matrix: - # strat: ["FedMedian", "FedTrimmedAvg", "QFedAvg", "FaultTolerantFedAvg", "FedAvgM", "FedAdam", "FedAdagrad", "FedYogi"] - - # name: Strategy / ${{ matrix.strat }} - - # defaults: - # run: - # working-directory: e2e/strategies - - # steps: - # - uses: actions/checkout@v4 - # - name: Bootstrap - # uses: ./.github/actions/bootstrap - # - name: Install dependencies - # run: | - # python -m poetry install - # - name: Install Flower wheel from artifact store - # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - # run: | - # python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - # - name: Cache Datasets - # uses: actions/cache@v4 - # with: - # path: "~/.keras" - # key: keras-datasets - # - name: Download Datasets - # run: | - # python -c "import tensorflow as tf; tf.keras.datasets.mnist.load_data()" - # - name: Test strategies - # run: | - # python test.py "${{ matrix.strat }}" - - # templates: - # runs-on: ubuntu-22.04 - # timeout-minutes: 10 - # needs: wheel - # strategy: - # matrix: - # framework: ["numpy", "pytorch", "tensorflow", "jax", "sklearn"] - - # name: Template / ${{ matrix.framework }} - - # steps: - # - uses: actions/checkout@v4 - # - name: Bootstrap - # uses: ./.github/actions/bootstrap - # - name: Install Flower from repo - # if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} - # run: | - # python -m pip install . - # - name: Install Flower wheel from artifact store - # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - # run: | - # python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} - # - name: Create project and install it - # run: | - # flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci - # cd tmp-${{ matrix.framework }} - # pip install . - # - name: Run project - # run: | - # cd tmp-${{ matrix.framework }} - # flwr run --run-config num-server-rounds=1 2>&1 | tee flwr_output.log - # if grep -q "ERROR" flwr_output.log; then - # exit 1 - # fi +# frameworks: +# runs-on: ubuntu-22.04 +# timeout-minutes: 10 +# needs: wheel +# # Using approach described here: +# # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs +# strategy: +# matrix: +# include: +# - directory: e2e-bare +# e2e: e2e_bare +# +# - directory: e2e-bare-https +# e2e: e2e_bare_https +# +# - directory: e2e-bare-auth +# e2e: e2e_bare_auth +# +# - directory: e2e-jax +# e2e: e2e_jax +# +# - directory: e2e-pytorch +# e2e: e2e_pytorch +# dataset: | +# from torchvision.datasets import CIFAR10 +# CIFAR10('./data', download=True) +# +# - directory: e2e-tensorflow +# e2e: e2e_tensorflow +# dataset: | +# import tensorflow as tf +# tf.keras.datasets.cifar10.load_data() +# +# - directory: e2e-opacus +# e2e: e2e_opacus +# dataset: | +# from torchvision.datasets import CIFAR10 +# CIFAR10('./data', download=True) +# +# - directory: e2e-pytorch-lightning +# e2e: e2e_pytorch_lightning +# dataset: | +# from torchvision.datasets import MNIST +# MNIST('./data', download=True) +# +# - directory: e2e-scikit-learn +# e2e: e2e_scikit_learn +# dataset: | +# import openml +# openml.datasets.get_dataset(554) +# +# - directory: e2e-fastai +# e2e: e2e_fastai +# dataset: | +# from fastai.vision.all import untar_data, URLs +# untar_data(URLs.MNIST) +# +# - directory: e2e-pandas +# e2e: e2e_pandas +# dataset: | +# from pathlib import Path +# from sklearn.datasets import load_iris +# Path('data').mkdir(exist_ok=True) +# load_iris(as_frame=True)['data'].to_csv('./data/client.csv') +# +# name: Framework / ${{ matrix.directory }} +# +# defaults: +# run: +# working-directory: e2e/${{ matrix.directory }} +# +# steps: +# - uses: actions/checkout@v4 +# - name: Set up Python +# uses: actions/setup-python@v5 +# with: +# python-version: 3.9 +# - name: Install build tools +# run: | +# python -m pip install -U pip==23.3.1 +# shell: bash +# # Using approach described here for Python location caching: +# # https://blog.allenai.org/python-caching-in-github-actions-e9452698e98d +# - name: Cache Python location +# id: cache-restore-python +# uses: actions/cache/restore@v4 +# with: +# path: ${{ env.pythonLocation }} +# key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} +# - name: Install dependencies +# run: python -m pip install --upgrade . +# - name: Install Flower wheel from artifact store +# if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} +# run: | +# python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} +# - name: Download dataset +# if: ${{ matrix.dataset }} +# run: python -c "${{ matrix.dataset }}" +# - name: Run edge client test +# if: ${{ matrix.directory != 'e2e-bare-auth' }} +# working-directory: e2e/${{ matrix.directory }}/${{ matrix.e2e }} +# run: ./../../test_legacy.sh "${{ matrix.directory }}" +# - name: Run virtual client test +# if: ${{ matrix.directory != 'e2e-bare-auth' }} +# run: python simulation.py +# - name: Run simulation engine test +# if: ${{ matrix.directory == 'e2e-pytorch' || matrix.directory == 'e2e-tensorflow'}} +# run: python simulation_next.py +# - name: Run driver test +# if: ${{ matrix.directory != 'e2e-bare-auth' }} +# run: ./../test_superlink.sh "${{ matrix.directory }}" +# - name: Run driver test with REST +# if: ${{ matrix.directory == 'e2e-bare' }} +# run: ./../test_superlink.sh bare rest +# - name: Run driver test with SQLite database +# if: ${{ matrix.directory == 'e2e-bare' }} +# run: ./../test_superlink.sh bare sqlite +# - name: Run driver test with client authentication +# if: ${{ matrix.directory == 'e2e-bare-auth' }} +# run: ./../test_superlink.sh "${{ matrix.directory }}" client-auth +# - name: Run reconnection test with SQLite database +# if: ${{ matrix.directory == 'e2e-bare' }} +# run: ./../test_reconnection.sh sqlite +# - name: Cache save Python location +# id: cache-save-python +# uses: actions/cache/save@v4 +# if: ${{ github.ref_name == 'main' && !steps.cache-restore-python.outputs.cache-hit }} +# with: +# path: ${{ env.pythonLocation }} +# key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} +# +# strategies: +# runs-on: ubuntu-22.04 +# timeout-minutes: 10 +# needs: wheel +# strategy: +# matrix: +# strat: ["FedMedian", "FedTrimmedAvg", "QFedAvg", "FaultTolerantFedAvg", "FedAvgM", "FedAdam", "FedAdagrad", "FedYogi"] +# +# name: Strategy / ${{ matrix.strat }} +# +# defaults: +# run: +# working-directory: e2e/strategies +# +# steps: +# - uses: actions/checkout@v4 +# - name: Bootstrap +# uses: ./.github/actions/bootstrap +# - name: Install dependencies +# run: | +# python -m poetry install +# - name: Install Flower wheel from artifact store +# if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} +# run: | +# python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} +# - name: Cache Datasets +# uses: actions/cache@v4 +# with: +# path: "~/.keras" +# key: keras-datasets +# - name: Download Datasets +# run: | +# python -c "import tensorflow as tf; tf.keras.datasets.mnist.load_data()" +# - name: Test strategies +# run: | +# python test.py "${{ matrix.strat }}" +# +# templates: +# runs-on: ubuntu-22.04 +# timeout-minutes: 10 +# needs: wheel +# strategy: +# matrix: +# framework: ["numpy", "pytorch", "tensorflow", "jax", "sklearn"] +# +# name: Template / ${{ matrix.framework }} +# +# steps: +# - uses: actions/checkout@v4 +# - name: Bootstrap +# uses: ./.github/actions/bootstrap +# - name: Install Flower from repo +# if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} +# run: | +# python -m pip install . +# - name: Install Flower wheel from artifact store +# if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} +# run: | +# python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} +# - name: Create project and install it +# run: | +# flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci +# cd tmp-${{ matrix.framework }} +# pip install . +# - name: Run project +# run: | +# cd tmp-${{ matrix.framework }} +# flwr run --run-config num-server-rounds=1 2>&1 | tee flwr_output.log +# if grep -q "ERROR" flwr_output.log; then +# exit 1 +# fi From 58172e718a9741580fbe37ca4fc1930b1a7ade60 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 11:58:41 +0100 Subject: [PATCH 22/30] Intermediate --- .github/workflows/e2e.yml | 66 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8e5cda914dcc..4187b5e9fcb6 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -71,39 +71,39 @@ jobs: ${{ matrix.connection }} / ${{ matrix.authentication }} / ${{ matrix.engine }} - defaults: - run: - working-directory: e2e/${{ matrix.directory }} - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install build tools - run: python -m pip install -U pip==23.3.1 - shell: bash - - name: Download and install Flower wheel from artifact store - if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - run: | - # Define base URL for wheel file - WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" - # Download wheel file - wget "${WHEEL_URL}" - # Extract the original filename from the URL - WHEEL_FILE=$(basename "${WHEEL_URL}") - if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then - python -m pip install "${WHEEL_FILE}[simulation]" - else - python -m pip install "${WHEEL_FILE}" - fi - - name: > - Run SuperExec test / - ${{ matrix.connection }} / - ${{ matrix.authentication }} / - ${{ matrix.engine }} - working-directory: e2e/${{ matrix.directory }} - run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" + # defaults: + # run: + # working-directory: e2e/${{ matrix.directory }} + # steps: + # - uses: actions/checkout@v4 + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v5 + # with: + # python-version: ${{ matrix.python-version }} + # - name: Install build tools + # run: python -m pip install -U pip==23.3.1 + # shell: bash + # - name: Download and install Flower wheel from artifact store + # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + # run: | + # # Define base URL for wheel file + # WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + # # Download wheel file + # wget "${WHEEL_URL}" + # # Extract the original filename from the URL + # WHEEL_FILE=$(basename "${WHEEL_URL}") + # if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then + # python -m pip install "${WHEEL_FILE}[simulation]" + # else + # python -m pip install "${WHEEL_FILE}" + # fi + # - name: > + # Run SuperExec test / + # ${{ matrix.connection }} / + # ${{ matrix.authentication }} / + # ${{ matrix.engine }} + # working-directory: e2e/${{ matrix.directory }} + # run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" # frameworks: # runs-on: ubuntu-22.04 From 7f3b5a97bdd7918a5a2bb9d7e9ddb5dba3e811e0 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 12:21:47 +0100 Subject: [PATCH 23/30] Fix matrix strategy --- .github/workflows/e2e.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4187b5e9fcb6..491335729964 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -61,9 +61,9 @@ jobs: directory: [e2e-bare-auth] connection: [secure, insecure] engine: [deployment-engine, simulation-engine] - authentication: [no-auth] - include: - - connection: secure + authentication: [no-auth, client-auth] + exclude: + - connection: insecure authentication: client-auth name: | SuperExec / @@ -71,18 +71,18 @@ jobs: ${{ matrix.connection }} / ${{ matrix.authentication }} / ${{ matrix.engine }} - # defaults: - # run: - # working-directory: e2e/${{ matrix.directory }} - # steps: - # - uses: actions/checkout@v4 - # - name: Set up Python ${{ matrix.python-version }} - # uses: actions/setup-python@v5 - # with: - # python-version: ${{ matrix.python-version }} - # - name: Install build tools - # run: python -m pip install -U pip==23.3.1 - # shell: bash + defaults: + run: + working-directory: e2e/${{ matrix.directory }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install build tools + run: python -m pip install -U pip==23.3.1 + shell: bash # - name: Download and install Flower wheel from artifact store # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} # run: | From 0456652af8bc91bb44d8a480274d0afe6f709713 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 12:24:39 +0100 Subject: [PATCH 24/30] Expand e2e --- .github/workflows/e2e.yml | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 491335729964..6bfe1744aaa5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -83,27 +83,27 @@ jobs: - name: Install build tools run: python -m pip install -U pip==23.3.1 shell: bash - # - name: Download and install Flower wheel from artifact store - # if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} - # run: | - # # Define base URL for wheel file - # WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" - # # Download wheel file - # wget "${WHEEL_URL}" - # # Extract the original filename from the URL - # WHEEL_FILE=$(basename "${WHEEL_URL}") - # if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then - # python -m pip install "${WHEEL_FILE}[simulation]" - # else - # python -m pip install "${WHEEL_FILE}" - # fi - # - name: > - # Run SuperExec test / - # ${{ matrix.connection }} / - # ${{ matrix.authentication }} / - # ${{ matrix.engine }} - # working-directory: e2e/${{ matrix.directory }} - # run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" + - name: Download and install Flower wheel from artifact store + if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + run: | + # Define base URL for wheel file + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + # Download wheel file + wget "${WHEEL_URL}" + # Extract the original filename from the URL + WHEEL_FILE=$(basename "${WHEEL_URL}") + if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then + python -m pip install "${WHEEL_FILE}[simulation]" + else + python -m pip install "${WHEEL_FILE}" + fi + - name: > + Run SuperExec test / + ${{ matrix.connection }} / + ${{ matrix.authentication }} / + ${{ matrix.engine }} + working-directory: e2e/${{ matrix.directory }} + run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" # frameworks: # runs-on: ubuntu-22.04 From 9401cc67086f60e9bcbac5507353662462b9ad5b Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 12:28:42 +0100 Subject: [PATCH 25/30] Full e2e --- .github/workflows/e2e.yml | 404 +++++++++++++++++++------------------- 1 file changed, 202 insertions(+), 202 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 6bfe1744aaa5..c218f40d8df2 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -105,205 +105,205 @@ jobs: working-directory: e2e/${{ matrix.directory }} run: ./../test_superexec.sh "${{ matrix.connection }}" "${{ matrix.authentication}}" "${{ matrix.engine }}" -# frameworks: -# runs-on: ubuntu-22.04 -# timeout-minutes: 10 -# needs: wheel -# # Using approach described here: -# # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs -# strategy: -# matrix: -# include: -# - directory: e2e-bare -# e2e: e2e_bare -# -# - directory: e2e-bare-https -# e2e: e2e_bare_https -# -# - directory: e2e-bare-auth -# e2e: e2e_bare_auth -# -# - directory: e2e-jax -# e2e: e2e_jax -# -# - directory: e2e-pytorch -# e2e: e2e_pytorch -# dataset: | -# from torchvision.datasets import CIFAR10 -# CIFAR10('./data', download=True) -# -# - directory: e2e-tensorflow -# e2e: e2e_tensorflow -# dataset: | -# import tensorflow as tf -# tf.keras.datasets.cifar10.load_data() -# -# - directory: e2e-opacus -# e2e: e2e_opacus -# dataset: | -# from torchvision.datasets import CIFAR10 -# CIFAR10('./data', download=True) -# -# - directory: e2e-pytorch-lightning -# e2e: e2e_pytorch_lightning -# dataset: | -# from torchvision.datasets import MNIST -# MNIST('./data', download=True) -# -# - directory: e2e-scikit-learn -# e2e: e2e_scikit_learn -# dataset: | -# import openml -# openml.datasets.get_dataset(554) -# -# - directory: e2e-fastai -# e2e: e2e_fastai -# dataset: | -# from fastai.vision.all import untar_data, URLs -# untar_data(URLs.MNIST) -# -# - directory: e2e-pandas -# e2e: e2e_pandas -# dataset: | -# from pathlib import Path -# from sklearn.datasets import load_iris -# Path('data').mkdir(exist_ok=True) -# load_iris(as_frame=True)['data'].to_csv('./data/client.csv') -# -# name: Framework / ${{ matrix.directory }} -# -# defaults: -# run: -# working-directory: e2e/${{ matrix.directory }} -# -# steps: -# - uses: actions/checkout@v4 -# - name: Set up Python -# uses: actions/setup-python@v5 -# with: -# python-version: 3.9 -# - name: Install build tools -# run: | -# python -m pip install -U pip==23.3.1 -# shell: bash -# # Using approach described here for Python location caching: -# # https://blog.allenai.org/python-caching-in-github-actions-e9452698e98d -# - name: Cache Python location -# id: cache-restore-python -# uses: actions/cache/restore@v4 -# with: -# path: ${{ env.pythonLocation }} -# key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} -# - name: Install dependencies -# run: python -m pip install --upgrade . -# - name: Install Flower wheel from artifact store -# if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} -# run: | -# python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} -# - name: Download dataset -# if: ${{ matrix.dataset }} -# run: python -c "${{ matrix.dataset }}" -# - name: Run edge client test -# if: ${{ matrix.directory != 'e2e-bare-auth' }} -# working-directory: e2e/${{ matrix.directory }}/${{ matrix.e2e }} -# run: ./../../test_legacy.sh "${{ matrix.directory }}" -# - name: Run virtual client test -# if: ${{ matrix.directory != 'e2e-bare-auth' }} -# run: python simulation.py -# - name: Run simulation engine test -# if: ${{ matrix.directory == 'e2e-pytorch' || matrix.directory == 'e2e-tensorflow'}} -# run: python simulation_next.py -# - name: Run driver test -# if: ${{ matrix.directory != 'e2e-bare-auth' }} -# run: ./../test_superlink.sh "${{ matrix.directory }}" -# - name: Run driver test with REST -# if: ${{ matrix.directory == 'e2e-bare' }} -# run: ./../test_superlink.sh bare rest -# - name: Run driver test with SQLite database -# if: ${{ matrix.directory == 'e2e-bare' }} -# run: ./../test_superlink.sh bare sqlite -# - name: Run driver test with client authentication -# if: ${{ matrix.directory == 'e2e-bare-auth' }} -# run: ./../test_superlink.sh "${{ matrix.directory }}" client-auth -# - name: Run reconnection test with SQLite database -# if: ${{ matrix.directory == 'e2e-bare' }} -# run: ./../test_reconnection.sh sqlite -# - name: Cache save Python location -# id: cache-save-python -# uses: actions/cache/save@v4 -# if: ${{ github.ref_name == 'main' && !steps.cache-restore-python.outputs.cache-hit }} -# with: -# path: ${{ env.pythonLocation }} -# key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} -# -# strategies: -# runs-on: ubuntu-22.04 -# timeout-minutes: 10 -# needs: wheel -# strategy: -# matrix: -# strat: ["FedMedian", "FedTrimmedAvg", "QFedAvg", "FaultTolerantFedAvg", "FedAvgM", "FedAdam", "FedAdagrad", "FedYogi"] -# -# name: Strategy / ${{ matrix.strat }} -# -# defaults: -# run: -# working-directory: e2e/strategies -# -# steps: -# - uses: actions/checkout@v4 -# - name: Bootstrap -# uses: ./.github/actions/bootstrap -# - name: Install dependencies -# run: | -# python -m poetry install -# - name: Install Flower wheel from artifact store -# if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} -# run: | -# python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} -# - name: Cache Datasets -# uses: actions/cache@v4 -# with: -# path: "~/.keras" -# key: keras-datasets -# - name: Download Datasets -# run: | -# python -c "import tensorflow as tf; tf.keras.datasets.mnist.load_data()" -# - name: Test strategies -# run: | -# python test.py "${{ matrix.strat }}" -# -# templates: -# runs-on: ubuntu-22.04 -# timeout-minutes: 10 -# needs: wheel -# strategy: -# matrix: -# framework: ["numpy", "pytorch", "tensorflow", "jax", "sklearn"] -# -# name: Template / ${{ matrix.framework }} -# -# steps: -# - uses: actions/checkout@v4 -# - name: Bootstrap -# uses: ./.github/actions/bootstrap -# - name: Install Flower from repo -# if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} -# run: | -# python -m pip install . -# - name: Install Flower wheel from artifact store -# if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} -# run: | -# python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} -# - name: Create project and install it -# run: | -# flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci -# cd tmp-${{ matrix.framework }} -# pip install . -# - name: Run project -# run: | -# cd tmp-${{ matrix.framework }} -# flwr run --run-config num-server-rounds=1 2>&1 | tee flwr_output.log -# if grep -q "ERROR" flwr_output.log; then -# exit 1 -# fi + frameworks: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + needs: wheel + # Using approach described here: + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + strategy: + matrix: + include: + - directory: e2e-bare + e2e: e2e_bare + + - directory: e2e-bare-https + e2e: e2e_bare_https + + - directory: e2e-bare-auth + e2e: e2e_bare_auth + + - directory: e2e-jax + e2e: e2e_jax + + - directory: e2e-pytorch + e2e: e2e_pytorch + dataset: | + from torchvision.datasets import CIFAR10 + CIFAR10('./data', download=True) + + - directory: e2e-tensorflow + e2e: e2e_tensorflow + dataset: | + import tensorflow as tf + tf.keras.datasets.cifar10.load_data() + + - directory: e2e-opacus + e2e: e2e_opacus + dataset: | + from torchvision.datasets import CIFAR10 + CIFAR10('./data', download=True) + + - directory: e2e-pytorch-lightning + e2e: e2e_pytorch_lightning + dataset: | + from torchvision.datasets import MNIST + MNIST('./data', download=True) + + - directory: e2e-scikit-learn + e2e: e2e_scikit_learn + dataset: | + import openml + openml.datasets.get_dataset(554) + + - directory: e2e-fastai + e2e: e2e_fastai + dataset: | + from fastai.vision.all import untar_data, URLs + untar_data(URLs.MNIST) + + - directory: e2e-pandas + e2e: e2e_pandas + dataset: | + from pathlib import Path + from sklearn.datasets import load_iris + Path('data').mkdir(exist_ok=True) + load_iris(as_frame=True)['data'].to_csv('./data/client.csv') + + name: Framework / ${{ matrix.directory }} + + defaults: + run: + working-directory: e2e/${{ matrix.directory }} + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + - name: Install build tools + run: | + python -m pip install -U pip==23.3.1 + shell: bash + # Using approach described here for Python location caching: + # https://blog.allenai.org/python-caching-in-github-actions-e9452698e98d + - name: Cache Python location + id: cache-restore-python + uses: actions/cache/restore@v4 + with: + path: ${{ env.pythonLocation }} + key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} + - name: Install dependencies + run: python -m pip install --upgrade . + - name: Install Flower wheel from artifact store + if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + run: | + python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + - name: Download dataset + if: ${{ matrix.dataset }} + run: python -c "${{ matrix.dataset }}" + - name: Run edge client test + if: ${{ matrix.directory != 'e2e-bare-auth' }} + working-directory: e2e/${{ matrix.directory }}/${{ matrix.e2e }} + run: ./../../test_legacy.sh "${{ matrix.directory }}" + - name: Run virtual client test + if: ${{ matrix.directory != 'e2e-bare-auth' }} + run: python simulation.py + - name: Run simulation engine test + if: ${{ matrix.directory == 'e2e-pytorch' || matrix.directory == 'e2e-tensorflow'}} + run: python simulation_next.py + - name: Run driver test + if: ${{ matrix.directory != 'e2e-bare-auth' }} + run: ./../test_superlink.sh "${{ matrix.directory }}" + - name: Run driver test with REST + if: ${{ matrix.directory == 'e2e-bare' }} + run: ./../test_superlink.sh bare rest + - name: Run driver test with SQLite database + if: ${{ matrix.directory == 'e2e-bare' }} + run: ./../test_superlink.sh bare sqlite + - name: Run driver test with client authentication + if: ${{ matrix.directory == 'e2e-bare-auth' }} + run: ./../test_superlink.sh "${{ matrix.directory }}" client-auth + - name: Run reconnection test with SQLite database + if: ${{ matrix.directory == 'e2e-bare' }} + run: ./../test_reconnection.sh sqlite + - name: Cache save Python location + id: cache-save-python + uses: actions/cache/save@v4 + if: ${{ github.ref_name == 'main' && !steps.cache-restore-python.outputs.cache-hit }} + with: + path: ${{ env.pythonLocation }} + key: pythonloc-${{ runner.os }}-${{ matrix.directory }}-${{ env.pythonLocation }}-${{ hashFiles(format('./e2e/{0}/pyproject.toml', matrix.directory)) }} + + strategies: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + needs: wheel + strategy: + matrix: + strat: ["FedMedian", "FedTrimmedAvg", "QFedAvg", "FaultTolerantFedAvg", "FedAvgM", "FedAdam", "FedAdagrad", "FedYogi"] + + name: Strategy / ${{ matrix.strat }} + + defaults: + run: + working-directory: e2e/strategies + + steps: + - uses: actions/checkout@v4 + - name: Bootstrap + uses: ./.github/actions/bootstrap + - name: Install dependencies + run: | + python -m poetry install + - name: Install Flower wheel from artifact store + if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + run: | + python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + - name: Cache Datasets + uses: actions/cache@v4 + with: + path: "~/.keras" + key: keras-datasets + - name: Download Datasets + run: | + python -c "import tensorflow as tf; tf.keras.datasets.mnist.load_data()" + - name: Test strategies + run: | + python test.py "${{ matrix.strat }}" + + templates: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + needs: wheel + strategy: + matrix: + framework: ["numpy", "pytorch", "tensorflow", "jax", "sklearn"] + + name: Template / ${{ matrix.framework }} + + steps: + - uses: actions/checkout@v4 + - name: Bootstrap + uses: ./.github/actions/bootstrap + - name: Install Flower from repo + if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} + run: | + python -m pip install . + - name: Install Flower wheel from artifact store + if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + run: | + python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + - name: Create project and install it + run: | + flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci + cd tmp-${{ matrix.framework }} + pip install . + - name: Run project + run: | + cd tmp-${{ matrix.framework }} + flwr run --run-config num-server-rounds=1 2>&1 | tee flwr_output.log + if grep -q "ERROR" flwr_output.log; then + exit 1 + fi From db3a633e04087438a5c60e9cebe2869c1e42cec6 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Thu, 19 Sep 2024 14:58:40 +0100 Subject: [PATCH 26/30] Use bootstrap --- .github/workflows/e2e.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c218f40d8df2..1b479a7ecf19 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -76,26 +76,20 @@ jobs: working-directory: e2e/${{ matrix.directory }} steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - name: Bootstrap + uses: ./.github/actions/bootstrap with: python-version: ${{ matrix.python-version }} - - name: Install build tools - run: python -m pip install -U pip==23.3.1 - shell: bash + poetry-skip: 'true' - name: Download and install Flower wheel from artifact store if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | # Define base URL for wheel file WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" - # Download wheel file - wget "${WHEEL_URL}" - # Extract the original filename from the URL - WHEEL_FILE=$(basename "${WHEEL_URL}") if [[ "${{ matrix.engine }}" == "simulation-engine" ]]; then - python -m pip install "${WHEEL_FILE}[simulation]" + python -m pip install "flwr[simulation] @ ${WHEEL_URL}" else - python -m pip install "${WHEEL_FILE}" + python -m pip install "${WHEEL_URL}" fi - name: > Run SuperExec test / From 8f6d804ff92d4266d25d77bf967fa0d3e5068bf2 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Thu, 19 Sep 2024 15:04:27 +0100 Subject: [PATCH 27/30] Revert patch --- src/py/flwr/client/grpc_rere_client/client_interceptor.py | 3 --- .../server/superlink/fleet/grpc_rere/server_interceptor.py | 4 ---- 2 files changed, 7 deletions(-) diff --git a/src/py/flwr/client/grpc_rere_client/client_interceptor.py b/src/py/flwr/client/grpc_rere_client/client_interceptor.py index 041860957db7..653e384aff96 100644 --- a/src/py/flwr/client/grpc_rere_client/client_interceptor.py +++ b/src/py/flwr/client/grpc_rere_client/client_interceptor.py @@ -31,7 +31,6 @@ generate_shared_key, public_key_to_bytes, ) -from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611 from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, DeleteNodeRequest, @@ -51,7 +50,6 @@ PushTaskResRequest, GetRunRequest, PingRequest, - GetFabRequest, ] @@ -128,7 +126,6 @@ def intercept_unary_unary( PushTaskResRequest, GetRunRequest, PingRequest, - GetFabRequest, ), ): if self.shared_secret is None: diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py index 17d2c7406528..d836a74bef2e 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py @@ -30,7 +30,6 @@ generate_shared_key, verify_hmac, ) -from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611 from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, CreateNodeResponse, @@ -174,7 +173,6 @@ def _verify_node_id( PushTaskResRequest, GetRunRequest, PingRequest, - GetFabRequest, ], ) -> bool: if node_id is None: @@ -185,8 +183,6 @@ def _verify_node_id( return request.task_res_list[0].task.producer.node_id == node_id if isinstance(request, GetRunRequest): return node_id in self.state.get_nodes(request.run_id) - if isinstance(request, GetFabRequest): - return True return request.node.node_id == node_id def _verify_hmac( From 9379bbe8768321dd884a94e6e7cfc6968ed0f88e Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Thu, 19 Sep 2024 17:04:04 +0100 Subject: [PATCH 28/30] Add fail slow --- .github/workflows/e2e.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 1b479a7ecf19..a7c099d8101f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -56,6 +56,7 @@ jobs: timeout-minutes: 10 needs: wheel strategy: + fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11"] directory: [e2e-bare-auth] From 405ed9c3a36c23dca84f8ba2c43ac7dceaa6278c Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 19 Sep 2024 16:12:59 +0100 Subject: [PATCH 29/30] feat(framework) Add minimal `Control` service (#4239) --- src/proto/flwr/proto/control.proto | 25 ++++++++++ src/py/flwr/proto/control_pb2.py | 27 +++++++++++ src/py/flwr/proto/control_pb2.pyi | 7 +++ src/py/flwr/proto/control_pb2_grpc.py | 67 ++++++++++++++++++++++++++ src/py/flwr/proto/control_pb2_grpc.pyi | 27 +++++++++++ src/py/flwr_tool/protoc_test.py | 2 +- 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/proto/flwr/proto/control.proto create mode 100644 src/py/flwr/proto/control_pb2.py create mode 100644 src/py/flwr/proto/control_pb2.pyi create mode 100644 src/py/flwr/proto/control_pb2_grpc.py create mode 100644 src/py/flwr/proto/control_pb2_grpc.pyi diff --git a/src/proto/flwr/proto/control.proto b/src/proto/flwr/proto/control.proto new file mode 100644 index 000000000000..4e0867bbf9e4 --- /dev/null +++ b/src/proto/flwr/proto/control.proto @@ -0,0 +1,25 @@ +// Copyright 2024 Flower Labs GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================== + +syntax = "proto3"; + +package flwr.proto; + +import "flwr/proto/driver.proto"; + +service Control { + // Request to create a new run + rpc CreateRun(CreateRunRequest) returns (CreateRunResponse) {} +} diff --git a/src/py/flwr/proto/control_pb2.py b/src/py/flwr/proto/control_pb2.py new file mode 100644 index 000000000000..d206e75cb388 --- /dev/null +++ b/src/py/flwr/proto/control_pb2.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: flwr/proto/control.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from flwr.proto import driver_pb2 as flwr_dot_proto_dot_driver__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x17\x66lwr/proto/driver.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.control_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_CONTROL']._serialized_start=65 + _globals['_CONTROL']._serialized_end=150 +# @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/control_pb2.pyi b/src/py/flwr/proto/control_pb2.pyi new file mode 100644 index 000000000000..e08fa11c2caa --- /dev/null +++ b/src/py/flwr/proto/control_pb2.pyi @@ -0,0 +1,7 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import google.protobuf.descriptor + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor diff --git a/src/py/flwr/proto/control_pb2_grpc.py b/src/py/flwr/proto/control_pb2_grpc.py new file mode 100644 index 000000000000..9c671be88a47 --- /dev/null +++ b/src/py/flwr/proto/control_pb2_grpc.py @@ -0,0 +1,67 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from flwr.proto import driver_pb2 as flwr_dot_proto_dot_driver__pb2 + + +class ControlStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.CreateRun = channel.unary_unary( + '/flwr.proto.Control/CreateRun', + request_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + ) + + +class ControlServicer(object): + """Missing associated documentation comment in .proto file.""" + + def CreateRun(self, request, context): + """Request to create a new run + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ControlServicer_to_server(servicer, server): + rpc_method_handlers = { + 'CreateRun': grpc.unary_unary_rpc_method_handler( + servicer.CreateRun, + request_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'flwr.proto.Control', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class Control(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def CreateRun(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/CreateRun', + flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, + flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/control_pb2_grpc.pyi b/src/py/flwr/proto/control_pb2_grpc.pyi new file mode 100644 index 000000000000..f4613fa0e2f3 --- /dev/null +++ b/src/py/flwr/proto/control_pb2_grpc.pyi @@ -0,0 +1,27 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import abc +import flwr.proto.driver_pb2 +import grpc + +class ControlStub: + def __init__(self, channel: grpc.Channel) -> None: ... + CreateRun: grpc.UnaryUnaryMultiCallable[ + flwr.proto.driver_pb2.CreateRunRequest, + flwr.proto.driver_pb2.CreateRunResponse] + """Request to create a new run""" + + +class ControlServicer(metaclass=abc.ABCMeta): + @abc.abstractmethod + def CreateRun(self, + request: flwr.proto.driver_pb2.CreateRunRequest, + context: grpc.ServicerContext, + ) -> flwr.proto.driver_pb2.CreateRunResponse: + """Request to create a new run""" + pass + + +def add_ControlServicer_to_server(servicer: ControlServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr_tool/protoc_test.py b/src/py/flwr_tool/protoc_test.py index 6f9127304f25..f0784a4498d2 100644 --- a/src/py/flwr_tool/protoc_test.py +++ b/src/py/flwr_tool/protoc_test.py @@ -28,4 +28,4 @@ def test_directories() -> None: def test_proto_file_count() -> None: """Test if the correct number of proto files were captured by the glob.""" - assert len(PROTO_FILES) == 13 + assert len(PROTO_FILES) == 14 From c1175917f02fe38bf55e773e51bcf8b3e9085cfa Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 19 Sep 2024 16:50:56 +0100 Subject: [PATCH 30/30] feat(framework) Move run-related request/response to `run.proto` (#4240) --- src/proto/flwr/proto/control.proto | 2 +- src/proto/flwr/proto/driver.proto | 10 ---- src/proto/flwr/proto/run.proto | 12 +++++ src/py/flwr/proto/control_pb2.py | 8 +-- src/py/flwr/proto/control_pb2_grpc.py | 14 ++--- src/py/flwr/proto/control_pb2_grpc.pyi | 10 ++-- src/py/flwr/proto/driver_pb2.py | 39 ++++++-------- src/py/flwr/proto/driver_pb2.pyi | 52 ------------------- src/py/flwr/proto/driver_pb2_grpc.py | 12 ++--- src/py/flwr/proto/driver_pb2_grpc.pyi | 8 +-- src/py/flwr/proto/run_pb2.py | 27 ++++++---- src/py/flwr/proto/run_pb2.pyi | 52 +++++++++++++++++++ src/py/flwr/server/run_serverapp.py | 4 +- .../superlink/driver/driver_servicer.py | 4 +- src/py/flwr/superexec/deployment.py | 2 +- 15 files changed, 129 insertions(+), 127 deletions(-) diff --git a/src/proto/flwr/proto/control.proto b/src/proto/flwr/proto/control.proto index 4e0867bbf9e4..9747c2e3ea11 100644 --- a/src/proto/flwr/proto/control.proto +++ b/src/proto/flwr/proto/control.proto @@ -17,7 +17,7 @@ syntax = "proto3"; package flwr.proto; -import "flwr/proto/driver.proto"; +import "flwr/proto/run.proto"; service Control { // Request to create a new run diff --git a/src/proto/flwr/proto/driver.proto b/src/proto/flwr/proto/driver.proto index c7ae7dcf30f0..e26003862a76 100644 --- a/src/proto/flwr/proto/driver.proto +++ b/src/proto/flwr/proto/driver.proto @@ -21,7 +21,6 @@ import "flwr/proto/node.proto"; import "flwr/proto/task.proto"; import "flwr/proto/run.proto"; import "flwr/proto/fab.proto"; -import "flwr/proto/transport.proto"; service Driver { // Request run_id @@ -43,15 +42,6 @@ service Driver { rpc GetFab(GetFabRequest) returns (GetFabResponse) {} } -// CreateRun -message CreateRunRequest { - string fab_id = 1; - string fab_version = 2; - map override_config = 3; - Fab fab = 4; -} -message CreateRunResponse { uint64 run_id = 1; } - // GetNodes messages message GetNodesRequest { uint64 run_id = 1; } message GetNodesResponse { repeated Node nodes = 1; } diff --git a/src/proto/flwr/proto/run.proto b/src/proto/flwr/proto/run.proto index fc3294f7a583..ada72610e182 100644 --- a/src/proto/flwr/proto/run.proto +++ b/src/proto/flwr/proto/run.proto @@ -17,6 +17,7 @@ syntax = "proto3"; package flwr.proto; +import "flwr/proto/fab.proto"; import "flwr/proto/transport.proto"; message Run { @@ -26,5 +27,16 @@ message Run { map override_config = 4; string fab_hash = 5; } + +// CreateRun +message CreateRunRequest { + string fab_id = 1; + string fab_version = 2; + map override_config = 3; + Fab fab = 4; +} +message CreateRunResponse { uint64 run_id = 1; } + +// GetRun message GetRunRequest { uint64 run_id = 1; } message GetRunResponse { Run run = 1; } diff --git a/src/py/flwr/proto/control_pb2.py b/src/py/flwr/proto/control_pb2.py index d206e75cb388..2b8776509d32 100644 --- a/src/py/flwr/proto/control_pb2.py +++ b/src/py/flwr/proto/control_pb2.py @@ -12,16 +12,16 @@ _sym_db = _symbol_database.Default() -from flwr.proto import driver_pb2 as flwr_dot_proto_dot_driver__pb2 +from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x17\x66lwr/proto/driver.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/run.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.control_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_CONTROL']._serialized_start=65 - _globals['_CONTROL']._serialized_end=150 + _globals['_CONTROL']._serialized_start=62 + _globals['_CONTROL']._serialized_end=147 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/control_pb2_grpc.py b/src/py/flwr/proto/control_pb2_grpc.py index 9c671be88a47..987b9d8d7433 100644 --- a/src/py/flwr/proto/control_pb2_grpc.py +++ b/src/py/flwr/proto/control_pb2_grpc.py @@ -2,7 +2,7 @@ """Client and server classes corresponding to protobuf-defined services.""" import grpc -from flwr.proto import driver_pb2 as flwr_dot_proto_dot_driver__pb2 +from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 class ControlStub(object): @@ -16,8 +16,8 @@ def __init__(self, channel): """ self.CreateRun = channel.unary_unary( '/flwr.proto.Control/CreateRun', - request_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - response_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + request_serializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, ) @@ -36,8 +36,8 @@ def add_ControlServicer_to_server(servicer, server): rpc_method_handlers = { 'CreateRun': grpc.unary_unary_rpc_method_handler( servicer.CreateRun, - request_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.FromString, - response_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.SerializeToString, + request_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -61,7 +61,7 @@ def CreateRun(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/CreateRun', - flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/control_pb2_grpc.pyi b/src/py/flwr/proto/control_pb2_grpc.pyi index f4613fa0e2f3..c38b7f15d125 100644 --- a/src/py/flwr/proto/control_pb2_grpc.pyi +++ b/src/py/flwr/proto/control_pb2_grpc.pyi @@ -3,23 +3,23 @@ isort:skip_file """ import abc -import flwr.proto.driver_pb2 +import flwr.proto.run_pb2 import grpc class ControlStub: def __init__(self, channel: grpc.Channel) -> None: ... CreateRun: grpc.UnaryUnaryMultiCallable[ - flwr.proto.driver_pb2.CreateRunRequest, - flwr.proto.driver_pb2.CreateRunResponse] + flwr.proto.run_pb2.CreateRunRequest, + flwr.proto.run_pb2.CreateRunResponse] """Request to create a new run""" class ControlServicer(metaclass=abc.ABCMeta): @abc.abstractmethod def CreateRun(self, - request: flwr.proto.driver_pb2.CreateRunRequest, + request: flwr.proto.run_pb2.CreateRunRequest, context: grpc.ServicerContext, - ) -> flwr.proto.driver_pb2.CreateRunResponse: + ) -> flwr.proto.run_pb2.CreateRunResponse: """Request to create a new run""" pass diff --git a/src/py/flwr/proto/driver_pb2.py b/src/py/flwr/proto/driver_pb2.py index 1322660cfac1..d294b03be5af 100644 --- a/src/py/flwr/proto/driver_pb2.py +++ b/src/py/flwr/proto/driver_pb2.py @@ -16,36 +16,27 @@ from flwr.proto import task_pb2 as flwr_dot_proto_dot_task__pb2 from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2 -from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xeb\x01\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\x12I\n\x0foverride_config\x18\x03 \x03(\x0b\x32\x30.flwr.proto.CreateRunRequest.OverrideConfigEntry\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\xc7\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\xc7\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.driver_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_CREATERUNREQUEST']._serialized_start=158 - _globals['_CREATERUNREQUEST']._serialized_end=393 - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=320 - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=393 - _globals['_CREATERUNRESPONSE']._serialized_start=395 - _globals['_CREATERUNRESPONSE']._serialized_end=430 - _globals['_GETNODESREQUEST']._serialized_start=432 - _globals['_GETNODESREQUEST']._serialized_end=465 - _globals['_GETNODESRESPONSE']._serialized_start=467 - _globals['_GETNODESRESPONSE']._serialized_end=518 - _globals['_PUSHTASKINSREQUEST']._serialized_start=520 - _globals['_PUSHTASKINSREQUEST']._serialized_end=584 - _globals['_PUSHTASKINSRESPONSE']._serialized_start=586 - _globals['_PUSHTASKINSRESPONSE']._serialized_end=625 - _globals['_PULLTASKRESREQUEST']._serialized_start=627 - _globals['_PULLTASKRESREQUEST']._serialized_end=697 - _globals['_PULLTASKRESRESPONSE']._serialized_start=699 - _globals['_PULLTASKRESRESPONSE']._serialized_end=764 - _globals['_DRIVER']._serialized_start=767 - _globals['_DRIVER']._serialized_end=1222 + _globals['_GETNODESREQUEST']._serialized_start=129 + _globals['_GETNODESREQUEST']._serialized_end=162 + _globals['_GETNODESRESPONSE']._serialized_start=164 + _globals['_GETNODESRESPONSE']._serialized_end=215 + _globals['_PUSHTASKINSREQUEST']._serialized_start=217 + _globals['_PUSHTASKINSREQUEST']._serialized_end=281 + _globals['_PUSHTASKINSRESPONSE']._serialized_start=283 + _globals['_PUSHTASKINSRESPONSE']._serialized_end=322 + _globals['_PULLTASKRESREQUEST']._serialized_start=324 + _globals['_PULLTASKRESREQUEST']._serialized_end=394 + _globals['_PULLTASKRESRESPONSE']._serialized_start=396 + _globals['_PULLTASKRESRESPONSE']._serialized_end=461 + _globals['_DRIVER']._serialized_start=464 + _globals['_DRIVER']._serialized_end=919 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/driver_pb2.pyi b/src/py/flwr/proto/driver_pb2.pyi index d025e00474eb..77ceb496d70c 100644 --- a/src/py/flwr/proto/driver_pb2.pyi +++ b/src/py/flwr/proto/driver_pb2.pyi @@ -3,10 +3,8 @@ isort:skip_file """ import builtins -import flwr.proto.fab_pb2 import flwr.proto.node_pb2 import flwr.proto.task_pb2 -import flwr.proto.transport_pb2 import google.protobuf.descriptor import google.protobuf.internal.containers import google.protobuf.message @@ -15,56 +13,6 @@ import typing_extensions DESCRIPTOR: google.protobuf.descriptor.FileDescriptor -class CreateRunRequest(google.protobuf.message.Message): - """CreateRun""" - DESCRIPTOR: google.protobuf.descriptor.Descriptor - class OverrideConfigEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: typing.Text - @property - def value(self) -> flwr.proto.transport_pb2.Scalar: ... - def __init__(self, - *, - key: typing.Text = ..., - value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... - - FAB_ID_FIELD_NUMBER: builtins.int - FAB_VERSION_FIELD_NUMBER: builtins.int - OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int - FAB_FIELD_NUMBER: builtins.int - fab_id: typing.Text - fab_version: typing.Text - @property - def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... - @property - def fab(self) -> flwr.proto.fab_pb2.Fab: ... - def __init__(self, - *, - fab_id: typing.Text = ..., - fab_version: typing.Text = ..., - override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., - fab: typing.Optional[flwr.proto.fab_pb2.Fab] = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["fab",b"fab"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["fab",b"fab","fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config"]) -> None: ... -global___CreateRunRequest = CreateRunRequest - -class CreateRunResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - RUN_ID_FIELD_NUMBER: builtins.int - run_id: builtins.int - def __init__(self, - *, - run_id: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... -global___CreateRunResponse = CreateRunResponse - class GetNodesRequest(google.protobuf.message.Message): """GetNodes messages""" DESCRIPTOR: google.protobuf.descriptor.Descriptor diff --git a/src/py/flwr/proto/driver_pb2_grpc.py b/src/py/flwr/proto/driver_pb2_grpc.py index 6745bc7af62a..91e9fd8b9bdd 100644 --- a/src/py/flwr/proto/driver_pb2_grpc.py +++ b/src/py/flwr/proto/driver_pb2_grpc.py @@ -18,8 +18,8 @@ def __init__(self, channel): """ self.CreateRun = channel.unary_unary( '/flwr.proto.Driver/CreateRun', - request_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - response_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + request_serializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, ) self.GetNodes = channel.unary_unary( '/flwr.proto.Driver/GetNodes', @@ -98,8 +98,8 @@ def add_DriverServicer_to_server(servicer, server): rpc_method_handlers = { 'CreateRun': grpc.unary_unary_rpc_method_handler( servicer.CreateRun, - request_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.FromString, - response_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.SerializeToString, + request_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.SerializeToString, ), 'GetNodes': grpc.unary_unary_rpc_method_handler( servicer.GetNodes, @@ -148,8 +148,8 @@ def CreateRun(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/flwr.proto.Driver/CreateRun', - flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/driver_pb2_grpc.pyi b/src/py/flwr/proto/driver_pb2_grpc.pyi index 7f9fd0acbd82..8f665301073d 100644 --- a/src/py/flwr/proto/driver_pb2_grpc.pyi +++ b/src/py/flwr/proto/driver_pb2_grpc.pyi @@ -11,8 +11,8 @@ import grpc class DriverStub: def __init__(self, channel: grpc.Channel) -> None: ... CreateRun: grpc.UnaryUnaryMultiCallable[ - flwr.proto.driver_pb2.CreateRunRequest, - flwr.proto.driver_pb2.CreateRunResponse] + flwr.proto.run_pb2.CreateRunRequest, + flwr.proto.run_pb2.CreateRunResponse] """Request run_id""" GetNodes: grpc.UnaryUnaryMultiCallable[ @@ -44,9 +44,9 @@ class DriverStub: class DriverServicer(metaclass=abc.ABCMeta): @abc.abstractmethod def CreateRun(self, - request: flwr.proto.driver_pb2.CreateRunRequest, + request: flwr.proto.run_pb2.CreateRunRequest, context: grpc.ServicerContext, - ) -> flwr.proto.driver_pb2.CreateRunResponse: + ) -> flwr.proto.run_pb2.CreateRunResponse: """Request run_id""" pass diff --git a/src/py/flwr/proto/run_pb2.py b/src/py/flwr/proto/run_pb2.py index 13fc43f90f8c..99ca4df5c44c 100644 --- a/src/py/flwr/proto/run_pb2.py +++ b/src/py/flwr/proto/run_pb2.py @@ -12,10 +12,11 @@ _sym_db = _symbol_database.Default() +from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2 from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\xeb\x01\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\x12I\n\x0foverride_config\x18\x03 \x03(\x0b\x32\x30.flwr.proto.CreateRunRequest.OverrideConfigEntry\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -24,12 +25,20 @@ DESCRIPTOR._options = None _globals['_RUN_OVERRIDECONFIGENTRY']._options = None _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_RUN']._serialized_start=65 - _globals['_RUN']._serialized_end=278 - _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=205 - _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=278 - _globals['_GETRUNREQUEST']._serialized_start=280 - _globals['_GETRUNREQUEST']._serialized_end=311 - _globals['_GETRUNRESPONSE']._serialized_start=313 - _globals['_GETRUNRESPONSE']._serialized_end=359 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_RUN']._serialized_start=87 + _globals['_RUN']._serialized_end=300 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=227 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=300 + _globals['_CREATERUNREQUEST']._serialized_start=303 + _globals['_CREATERUNREQUEST']._serialized_end=538 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=227 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=300 + _globals['_CREATERUNRESPONSE']._serialized_start=540 + _globals['_CREATERUNRESPONSE']._serialized_end=575 + _globals['_GETRUNREQUEST']._serialized_start=577 + _globals['_GETRUNREQUEST']._serialized_end=608 + _globals['_GETRUNRESPONSE']._serialized_start=610 + _globals['_GETRUNRESPONSE']._serialized_end=656 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/run_pb2.pyi b/src/py/flwr/proto/run_pb2.pyi index e65feee9c518..26b69e7eed27 100644 --- a/src/py/flwr/proto/run_pb2.pyi +++ b/src/py/flwr/proto/run_pb2.pyi @@ -3,6 +3,7 @@ isort:skip_file """ import builtins +import flwr.proto.fab_pb2 import flwr.proto.transport_pb2 import google.protobuf.descriptor import google.protobuf.internal.containers @@ -51,7 +52,58 @@ class Run(google.protobuf.message.Message): def ClearField(self, field_name: typing_extensions.Literal["fab_hash",b"fab_hash","fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config","run_id",b"run_id"]) -> None: ... global___Run = Run +class CreateRunRequest(google.protobuf.message.Message): + """CreateRun""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + class OverrideConfigEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: typing.Text + @property + def value(self) -> flwr.proto.transport_pb2.Scalar: ... + def __init__(self, + *, + key: typing.Text = ..., + value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... + + FAB_ID_FIELD_NUMBER: builtins.int + FAB_VERSION_FIELD_NUMBER: builtins.int + OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int + FAB_FIELD_NUMBER: builtins.int + fab_id: typing.Text + fab_version: typing.Text + @property + def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... + @property + def fab(self) -> flwr.proto.fab_pb2.Fab: ... + def __init__(self, + *, + fab_id: typing.Text = ..., + fab_version: typing.Text = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., + fab: typing.Optional[flwr.proto.fab_pb2.Fab] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["fab",b"fab"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["fab",b"fab","fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config"]) -> None: ... +global___CreateRunRequest = CreateRunRequest + +class CreateRunResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_ID_FIELD_NUMBER: builtins.int + run_id: builtins.int + def __init__(self, + *, + run_id: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... +global___CreateRunResponse = CreateRunResponse + class GetRunRequest(google.protobuf.message.Message): + """GetRun""" DESCRIPTOR: google.protobuf.descriptor.Descriptor RUN_ID_FIELD_NUMBER: builtins.int run_id: builtins.int diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index c0628db04360..a9ec05fe90e0 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -35,11 +35,11 @@ from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app from flwr.common.typing import UserConfig -from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 +from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611 +from flwr.proto.run_pb2 import ( # pylint: disable=E0611 CreateRunRequest, CreateRunResponse, ) -from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611 from .driver import Driver from .driver.grpc_driver import GrpcDriver diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index 4d7d6cb6ce89..3cafb3e71f7c 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -32,8 +32,6 @@ from flwr.common.typing import Fab from flwr.proto import driver_pb2_grpc # pylint: disable=E0611 from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 - CreateRunRequest, - CreateRunResponse, GetNodesRequest, GetNodesResponse, PullTaskResRequest, @@ -44,6 +42,8 @@ from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611 from flwr.proto.node_pb2 import Node # pylint: disable=E0611 from flwr.proto.run_pb2 import ( # pylint: disable=E0611 + CreateRunRequest, + CreateRunResponse, GetRunRequest, GetRunResponse, Run, diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index 55e519cf5f27..d60870995d39 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -28,8 +28,8 @@ from flwr.common.logger import log from flwr.common.serde import fab_to_proto, user_config_to_proto from flwr.common.typing import Fab, UserConfig -from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 from flwr.proto.driver_pb2_grpc import DriverStub +from flwr.proto.run_pb2 import CreateRunRequest # pylint: disable=E0611 from .executor import Executor, RunTracker