From 9728b7408aa1cbb2634c5aef94e7748bccaac159 Mon Sep 17 00:00:00 2001 From: Willem Pienaar <6728866+woop@users.noreply.github.com> Date: Mon, 3 Aug 2020 00:21:09 +0800 Subject: [PATCH] Backport CI housekeeping to 0.6 (#923) * Backport housekeeping of Feast CI, docker compose, and notebooks (#916) * Add caching to core and serving builds * Fix flaky e2e tests * Simplify docker-compose setup * Add kafka broker to docker compose test * Rename batch to historical * Update troubleshooting.md * Fix casing for FEAST_HISTORICAL_SERVING_URL * Remove f-string prefix * Fix missing key in docker compose setup * Fix documentation typos * Add jupyter to load test * Add docker compose back to load-test * Clean up FEAST_VERSION in load test * Fix artifact output for load test * Revert e2e test order * Add missing makefile commands from backport * Remove missing tests --- .github/workflows/code_standards.yaml | 42 ----- .github/workflows/complete.yml | 111 ++++++++++++++ .github/workflows/docker_compose_tests.yml | 15 -- .github/workflows/master_only.yml | 60 ++++++++ .github/workflows/unit_tests.yml | 45 ------ Makefile | 11 +- docs/administration/troubleshooting.md | 18 +-- .../deploying-feast/docker-compose.md | 4 +- docs/installation/docker-compose.md | 2 +- docs/installation/gke.md | 2 +- docs/user-guide/feature-retrieval.md | 2 +- examples/basic/basic.ipynb | 14 +- ... Prediction (with Feast and XGBoost).ipynb | 4 +- .../feast-jupyter/templates/deployment.yaml | 2 +- .../tests/test-feast-batch-serving.yaml | 4 +- infra/docker-compose/.env.sample | 25 +-- infra/docker-compose/core/core.yml | 5 +- infra/docker-compose/docker-compose.batch.yml | 27 ---- infra/docker-compose/docker-compose.dev.yml | 11 +- .../docker-compose/docker-compose.online.yml | 27 ---- infra/docker-compose/docker-compose.yml | 55 +++++-- ...tch-serving.yml => historical-serving.yml} | 5 +- infra/docker/core/Dockerfile | 27 +++- infra/docker/jupyter/Dockerfile | 4 +- infra/docker/serving/Dockerfile | 27 +++- infra/scripts/test-docker-compose.sh | 11 +- infra/scripts/test-load.sh | 109 +++++++++++++ tests/e2e/conftest.py | 1 + tests/e2e/redis/basic-ingest-redis-serving.py | 143 +++++++++++++----- 29 files changed, 530 insertions(+), 283 deletions(-) delete mode 100644 .github/workflows/code_standards.yaml create mode 100644 .github/workflows/complete.yml delete mode 100644 .github/workflows/docker_compose_tests.yml create mode 100644 .github/workflows/master_only.yml delete mode 100644 .github/workflows/unit_tests.yml delete mode 100644 infra/docker-compose/docker-compose.batch.yml delete mode 100644 infra/docker-compose/docker-compose.online.yml rename infra/docker-compose/serving/{batch-serving.yml => historical-serving.yml} (87%) create mode 100755 infra/scripts/test-load.sh diff --git a/.github/workflows/code_standards.yaml b/.github/workflows/code_standards.yaml deleted file mode 100644 index 92ff8effee..0000000000 --- a/.github/workflows/code_standards.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: code standards - -on: [push, pull_request] - -jobs: - lint-java: - container: gcr.io/kf-feast/feast-ci:latest - runs-on: [ubuntu-latest] - steps: - - uses: actions/checkout@v2 - - name: lint java - run: make lint-java - - lint-python: - container: gcr.io/kf-feast/feast-ci:latest - runs-on: [ubuntu-latest] - steps: - - uses: actions/checkout@v2 - - name: install dependencies - run: make install-python-ci-dependencies - - name: compile protos - run: make compile-protos-python - - name: lint python - run: make lint-python - - lint-go: - container: gcr.io/kf-feast/feast-ci:latest - runs-on: [ubuntu-latest] - steps: - - uses: actions/checkout@v2 - - name: install dependencies - run: make install-go-ci-dependencies - - name: lint go - run: make lint-go - - lint-versions: - container: gcr.io/kf-feast/feast-ci:latest - runs-on: [ubuntu-latest] - steps: - - uses: actions/checkout@v2 - - name: install dependencies - run: make lint-versions \ No newline at end of file diff --git a/.github/workflows/complete.yml b/.github/workflows/complete.yml new file mode 100644 index 0000000000..4cf6fdbbf4 --- /dev/null +++ b/.github/workflows/complete.yml @@ -0,0 +1,111 @@ +name: complete + +on: [push, pull_request] + +jobs: + build-push-docker-images: + runs-on: [self-hosted] + strategy: + matrix: + component: [core, serving, jupyter] + env: + GITHUB_PR_SHA: ${{ github.event.pull_request.head.sha }} + REGISTRY: gcr.io/kf-feast + MAVEN_CACHE: gs://feast-templocation-kf-feast/.m2.2019-10-24.tar + steps: + - uses: actions/checkout@v2 + - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master + with: + version: '290.0.1' + export_default_credentials: true + - run: gcloud auth configure-docker --quiet + - name: Get m2 cache + run: | + infra/scripts/download-maven-cache.sh \ + --archive-uri ${MAVEN_CACHE} \ + --output-dir . + - name: Build image + run: make build-${{ matrix.component }}-docker REGISTRY=${REGISTRY} VERSION=${GITHUB_SHA} + - name: Push image + run: | + docker push ${REGISTRY}/feast-${{ matrix.component }}:${GITHUB_SHA} + if [ -n "${GITHUB_PR_SHA}" ]; then + docker tag ${REGISTRY}/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_PR_SHA} + docker push ${REGISTRY}/feast-${{ matrix.component }}:${GITHUB_PR_SHA} + fi + + lint-java: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: Lint java + run: make lint-java + + lint-python: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: make install-python-ci-dependencies + - name: Compile protos + run: make compile-protos-python + - name: Lint python + run: make lint-python + + lint-go: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: make install-go-ci-dependencies + - name: Lint go + run: make lint-go + + lint-versions: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: install dependencies + run: make lint-versions + + unit-test-java: + runs-on: ubuntu-latest + container: gcr.io/kf-feast/feast-ci:latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Test java + run: make test-java-with-coverage + - uses: actions/upload-artifact@v2 + with: + name: java-coverage-report + path: ${{ github.workspace }}/docs/coverage/java/target/site/jacoco-aggregate/ + + unit-test-python: + runs-on: ubuntu-latest + container: gcr.io/kf-feast/feast-ci:latest + steps: + - uses: actions/checkout@v2 + - name: Install python + run: make install-python + - name: Test python + run: make test-python + + unit-test-go: + runs-on: ubuntu-latest + container: gcr.io/kf-feast/feast-ci:latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: make compile-protos-go + - name: Test go + run: make test-go \ No newline at end of file diff --git a/.github/workflows/docker_compose_tests.yml b/.github/workflows/docker_compose_tests.yml deleted file mode 100644 index 2984ebc37b..0000000000 --- a/.github/workflows/docker_compose_tests.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: docker compose tests - -on: - push: - branches: - - master - -jobs: - basic-redis-e2e-tests-docker-compose: - runs-on: ubuntu-latest - name: basic redis e2e tests on docker compose - steps: - - uses: actions/checkout@v2 - - name: test docker compose - run: ./infra/scripts/test-docker-compose.sh diff --git a/.github/workflows/master_only.yml b/.github/workflows/master_only.yml new file mode 100644 index 0000000000..b24b5d85ac --- /dev/null +++ b/.github/workflows/master_only.yml @@ -0,0 +1,60 @@ +name: master only + +on: + push: + branches: master + tags: + - 'v*.*.*' + +jobs: + build-docker-images: + runs-on: [self-hosted] + strategy: + matrix: + component: [core, serving, jupyter, ci] + env: + MAVEN_CACHE: gs://feast-templocation-kf-feast/.m2.2019-10-24.tar + steps: + - uses: actions/checkout@v2 + - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master + with: + version: '290.0.1' + export_default_credentials: true + - run: gcloud auth configure-docker --quiet + - name: Get m2 cache + run: | + infra/scripts/download-maven-cache.sh \ + --archive-uri ${MAVEN_CACHE} \ + --output-dir . + - name: Build image + run: make build-${{ matrix.component }}-docker REGISTRY=gcr.io/kf-feast VERSION=${GITHUB_SHA} + - name: Push image + run: make push-${{ matrix.component }}-docker REGISTRY=gcr.io/kf-feast VERSION=${GITHUB_SHA} + - name: Push image to feast dev + run: | + if [ ${GITHUB_REF#refs/*/} == "master" ]; then + docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:dev + docker push gcr.io/kf-feast/feast-${{ matrix.component }}:dev + fi + - name: Get version + run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} + - name: Push versioned release + run: | + # Build and push semver tagged commits + rx='^v[0-9]+?\.[0-9]+?\.[0-9]+?$' + if [[ "${RELEASE_VERSION}" =~ $rx ]]; then + VERSION_WITHOUT_PREFIX=${RELEASE_VERSION:1} + + docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:${VERSION_WITHOUT_PREFIX} + docker push gcr.io/kf-feast/feast-${{ matrix.component }}:${VERSION_WITHOUT_PREFIX} + + # Also update "latest" image if tagged commit is pushed to stable branch + HIGHEST_SEMVER_TAG=$(git tag -l --sort -version:refname | head -n 1) + echo "Only push to latest tag if tag is the highest semver version $HIGHEST_SEMVER_TAG" + + if [ "${VERSION_WITHOUT_PREFIX}" == "${HIGHEST_SEMVER_TAG:1}" ] + then + docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:latest + docker push gcr.io/kf-feast/feast-${{ matrix.component }}:latest + fi + fi diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml deleted file mode 100644 index f71788a90b..0000000000 --- a/.github/workflows/unit_tests.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: unit tests - -on: [push, pull_request] - -jobs: - unit-test-java: - runs-on: ubuntu-latest - container: gcr.io/kf-feast/feast-ci:latest - name: unit test java - steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: test java - run: make test-java-with-coverage - - uses: actions/upload-artifact@v2 - with: - name: java-coverage-report - path: ${{ github.workspace }}/docs/coverage/java/target/site/jacoco-aggregate/ - - unit-test-python: - runs-on: ubuntu-latest - container: gcr.io/kf-feast/feast-ci:latest - name: unit test python - steps: - - uses: actions/checkout@v2 - - name: install python - run: make install-python - - name: test python - run: make test-python - - unit-test-go: - runs-on: ubuntu-latest - container: gcr.io/kf-feast/feast-ci:latest - name: unit test go - steps: - - uses: actions/checkout@v2 - - name: install dependencies - run: make compile-protos-go - - name: test go - run: make test-go diff --git a/Makefile b/Makefile index 27c5c9954f..76430bb38f 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,9 @@ push-serving-docker: push-ci-docker: docker push $(REGISTRY)/feast-ci:latest +push-jupyter-docker: + docker push $(REGISTRY)/feast-jupyter:$(VERSION) + build-core-docker: docker build -t $(REGISTRY)/feast-core:$(VERSION) -f infra/docker/core/Dockerfile . @@ -135,6 +138,9 @@ build-serving-docker: build-ci-docker: docker build -t $(REGISTRY)/feast-ci:latest -f infra/docker/ci/Dockerfile . +build-jupyter-docker: + docker build -t $(REGISTRY)/feast-jupyter:$(VERSION) -f infra/docker/jupyter/Dockerfile . + # Documentation install-dependencies-proto-docs: @@ -175,4 +181,7 @@ build-html: clean-html # Versions lint-versions: - ./infra/scripts/validate-version-consistency.sh \ No newline at end of file + ./infra/scripts/validate-version-consistency.sh + +test-load: + ./infra/scripts/test-load.sh $(GIT_SHA) \ No newline at end of file diff --git a/docs/administration/troubleshooting.md b/docs/administration/troubleshooting.md index e293945acc..e4c946fa0a 100644 --- a/docs/administration/troubleshooting.md +++ b/docs/administration/troubleshooting.md @@ -15,8 +15,8 @@ docker ps ```text CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d7447205bced jupyter/datascience-notebook:latest "tini -g -- start-no…" 2 minutes ago Up 2 minutes 0.0.0.0:8888->8888/tcp feast_jupyter_1 -8e49dbe81b92 gcr.io/kf-feast/feast-serving:latest "java -Xms1024m -Xmx…" 2 minutes ago Up 5 seconds 0.0.0.0:6567->6567/tcp feast_batch-serving_1 -b859494bd33a gcr.io/kf-feast/feast-serving:latest "java -jar /opt/feas…" 2 minutes ago Up About a minute 0.0.0.0:6566->6566/tcp feast_online-serving_1 +8e49dbe81b92 gcr.io/kf-feast/feast-serving:latest "java -Xms1024m -Xmx…" 2 minutes ago Up 5 seconds 0.0.0.0:6567->6567/tcp feast_historical_serving_1 +b859494bd33a gcr.io/kf-feast/feast-serving:latest "java -jar /opt/feas…" 2 minutes ago Up About a minute 0.0.0.0:6566->6566/tcp feast_online_serving_1 5c4962811767 gcr.io/kf-feast/feast-core:latest "java -jar /opt/feas…" 2 minutes ago Up 2 minutes 0.0.0.0:6565->6565/tcp feast_core_1 1ba7239e0ae0 confluentinc/cp-kafka:5.2.1 "/etc/confluent/dock…" 2 minutes ago Up 2 minutes 0.0.0.0:9092->9092/tcp, 0.0.0.0:9094->9094/tcp feast_kafka_1 e2779672735c confluentinc/cp-zookeeper:5.2.1 "/etc/confluent/dock…" 2 minutes ago Up 2 minutes 2181/tcp, 2888/tcp, 3888/tcp feast_zookeeper_1 @@ -59,8 +59,8 @@ You will probably need to connect using the hostnames of services and standard F ```bash export FEAST_CORE_URL=core:6565 -export FEAST_ONLINE_SERVING_URL=online-serving:6566 -export FEAST_BATCH_SERVING_URL=batch-serving:6567 +export FEAST_ONLINE_SERVING_URL=online_serving:6566 +export FEAST_HISTORICAL_SERVING_URL=historical_serving:6567 ``` ### **Docker Compose \(from outside the docker cluster\)** @@ -70,7 +70,7 @@ You will probably need to connect using `localhost` and standard ports: ```bash export FEAST_CORE_URL=localhost:6565 export FEAST_ONLINE_SERVING_URL=localhost:6566 -export FEAST_BATCH_SERVING_URL=localhost:6567 +export FEAST_HISTORICAL_SERVING_URL=localhost:6567 ``` ### **Google Kubernetes Engine \(GKE\)** @@ -81,7 +81,7 @@ You will need to find the external IP of one of the nodes as well as the NodePor export FEAST_IP=$(kubectl describe nodes | grep ExternalIP | awk '{print $2}' | head -n 1) export FEAST_CORE_URL=${FEAST_IP}:32090 export FEAST_ONLINE_SERVING_URL=${FEAST_IP}:32091 -export FEAST_BATCH_SERVING_URL=${FEAST_IP}:32092 +export FEAST_HISTORICAL_SERVING_URL=${FEAST_IP}:32092 ``` `netcat`, `telnet`, or even `curl` can be used to test whether all services are available and ports are open, but `grpc_cli` is the most powerful. It can be installed from [here](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md). @@ -107,7 +107,7 @@ ListProjects ### Testing Feast Batch Serving and Online Serving ```bash -grpc_cli ls ${FEAST_BATCH_SERVING_URL} feast.serving.ServingService +grpc_cli ls ${FEAST_HISTORICAL_SERVING_URL} feast.serving.ServingService ``` ```text @@ -145,11 +145,11 @@ In order to print the logs from these services, please run the commands below. ``` ```text -docker logs -f feast_batch-serving_1 +docker logs -f feast_historical_serving_1 ``` ```text -docker logs -f feast_online-serving_1 +docker logs -f feast_online_serving_1 ``` ### Google Kubernetes Engine diff --git a/docs/getting-started/deploying-feast/docker-compose.md b/docs/getting-started/deploying-feast/docker-compose.md index ca0c747a82..7fca5976c6 100644 --- a/docs/getting-started/deploying-feast/docker-compose.md +++ b/docs/getting-started/deploying-feast/docker-compose.md @@ -80,12 +80,12 @@ Configure the `.env` file based on your environment. At the very least you have | Parameter | Description | | :--- | :--- | | `FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY` | This should be your service account file name, for example `key.json`. | -| `FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY` | This should be your service account file name, for example `key.json` | +| `FEAST_HISTORICAL_SERVING_GCP_SERVICE_ACCOUNT_KEY` | This should be your service account file name, for example `key.json` | | `FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY` | This should be your service account file name, for example `key.json` | ### 3.3 Configure Historical Serving -We will also need to configure the `batch-serving.yml` file inside `infra/docker-compose/serving/`. This configuration is used to retrieve training datasets from Feast. At a minimum you will need to set: +We will also need to configure the `historical-serving.yml` file inside `infra/docker-compose/serving/`. This configuration is used to retrieve training datasets from Feast. At a minimum you will need to set: | Parameter | Description | | :--- | :--- | diff --git a/docs/installation/docker-compose.md b/docs/installation/docker-compose.md index a8895a0252..3c8c50862e 100644 --- a/docs/installation/docker-compose.md +++ b/docs/installation/docker-compose.md @@ -85,7 +85,7 @@ Configure the `.env` file based on your environment. At the very least you have | Parameter | Description | | :--- | :--- | | FEAST\_CORE\_GCP\_SERVICE\_ACCOUNT\_KEY | This should be your service account file name, for example `key.json`. | -| FEAST\_BATCH\_SERVING\_GCP\_SERVICE\_ACCOUNT\_KEY | This should be your service account file name, for example `key.json` | +| FEAST\_HISTORICAL\_SERVING\_GCP\_SERVICE\_ACCOUNT\_KEY | This should be your service account file name, for example `key.json` | | FEAST\_JUPYTER\_GCP\_SERVICE\_ACCOUNT\_KEY | This should be your service account file name, for example `key.json` | | FEAST\_JOB\_STAGING\_LOCATION | Google Cloud Storage bucket that Feast will use to stage data exports and batch retrieval requests, for example `gs://your-gcs-bucket/staging` | diff --git a/docs/installation/gke.md b/docs/installation/gke.md index c2becc91cd..6604188778 100644 --- a/docs/installation/gke.md +++ b/docs/installation/gke.md @@ -80,7 +80,7 @@ For this guide we will use `NodePort` for exposing Feast services. In order to d export FEAST_IP=$(kubectl describe nodes | grep ExternalIP | awk '{print $2}' | head -n 1) export FEAST_CORE_URL=${FEAST_IP}:32090 export FEAST_ONLINE_SERVING_URL=${FEAST_IP}:32091 -export FEAST_BATCH_SERVING_URL=${FEAST_IP}:32092 +export FEAST_HISTORICAL_SERVING_URL=${FEAST_IP}:32092 ``` Add firewall rules to open up ports on your Google Cloud Platform project: diff --git a/docs/user-guide/feature-retrieval.md b/docs/user-guide/feature-retrieval.md index 8ef9d3a5df..1cdad70920 100644 --- a/docs/user-guide/feature-retrieval.md +++ b/docs/user-guide/feature-retrieval.md @@ -116,7 +116,7 @@ The computation of statistics is not enabled by default. To indicate to Feast th ```python dataset = client.get_historical_features( feature_refs=features, - entity_rows=entity_df + entity_rows=entity_df, compute_statistics=True ) diff --git a/examples/basic/basic.ipynb b/examples/basic/basic.ipynb index 28bc456ef9..318ccfd3bd 100644 --- a/examples/basic/basic.ipynb +++ b/examples/basic/basic.ipynb @@ -43,7 +43,7 @@ "FEAST_ONLINE_SERVING_URL = os.getenv('FEAST_ONLINE_SERVING_URL', 'localhost:6566')\n", "\n", "# Feast Batch Serving allows for the retrieval of historical feature data\n", - "FEAST_BATCH_SERVING_URL = os.getenv('FEAST_BATCH_SERVING_URL', 'localhost:6567')" + "FEAST_HISTORICAL_SERVING_URL = os.getenv('FEAST_HISTORICAL_SERVING_URL', 'localhost:6567')" ] }, { @@ -466,8 +466,8 @@ "source": [ "online_features = client.get_online_features(\n", " feature_refs=[\n", - " f\"daily_transactions\",\n", - " f\"total_transactions\",\n", + " \"daily_transactions\",\n", + " \"total_transactions\",\n", " ],\n", " entity_rows=[\n", " {\n", @@ -592,7 +592,7 @@ "metadata": {}, "outputs": [], "source": [ - "batch_client = Client(core_url=FEAST_CORE_URL, serving_url=FEAST_BATCH_SERVING_URL)" + "batch_client = Client(core_url=FEAST_CORE_URL, serving_url=FEAST_HISTORICAL_SERVING_URL)" ] }, { @@ -614,8 +614,8 @@ "source": [ "job = batch_client.get_historical_features(\n", " feature_refs=[\n", - " f\"daily_transactions\", \n", - " f\"total_transactions\", \n", + " \"daily_transactions\",\n", + " \"total_transactions\",\n", " ],\n", " entity_rows=entity_rows\n", " )" @@ -754,4 +754,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/examples/feast-xgboost-churn-prediction-tutorial/Telecom Customer Churn Prediction (with Feast and XGBoost).ipynb b/examples/feast-xgboost-churn-prediction-tutorial/Telecom Customer Churn Prediction (with Feast and XGBoost).ipynb index f7adc80d8d..58097ca0b4 100644 --- a/examples/feast-xgboost-churn-prediction-tutorial/Telecom Customer Churn Prediction (with Feast and XGBoost).ipynb +++ b/examples/feast-xgboost-churn-prediction-tutorial/Telecom Customer Churn Prediction (with Feast and XGBoost).ipynb @@ -6752,7 +6752,7 @@ "FEAST_ONLINE_SERVING_URL = os.getenv('FEAST_ONLINE_SERVING_URL', 'localhost:6566')\n", "\n", "# Feast Batch Serving allows for the retrieval of historical feature data\n", - "FEAST_BATCH_SERVING_URL = os.getenv('FEAST_BATCH_SERVING_URL', 'localhost:6567')" + "FEAST_HISTORICAL_SERVING_URL = os.getenv('FEAST_HISTORICAL_SERVING_URL', 'localhost:6567')" ] }, { @@ -7187,7 +7187,7 @@ " def __init__(self, features, target, model_path=None):\n", " # Set up Feast clients to retrieve training and online serving data\n", " self._feast_online_client = Client(serving_url=os.environ['FEAST_ONLINE_SERVING_URL'])\n", - " self._feast_batch_client = Client(serving_url=os.environ['FEAST_BATCH_SERVING_URL'],\n", + " self._feast_batch_client = Client(serving_url=os.environ['FEAST_HISTORICAL_SERVING_URL'],\n", " core_url=os.environ['FEAST_CORE_URL'])\n", " \n", " # Path to either save models after training or load models for serving\n", diff --git a/infra/charts/feast/charts/feast-jupyter/templates/deployment.yaml b/infra/charts/feast/charts/feast-jupyter/templates/deployment.yaml index 8e6aea0173..858ac5eed0 100644 --- a/infra/charts/feast/charts/feast-jupyter/templates/deployment.yaml +++ b/infra/charts/feast/charts/feast-jupyter/templates/deployment.yaml @@ -38,7 +38,7 @@ spec: value: "{{ .Release.Name }}-feast-core:6565" - name: FEAST_ONLINE_SERVING_URL value: "{{ .Release.Name }}-feast-online-serving:6566" - - name: FEAST_BATCH_SERVING_URL + - name: FEAST_HISTORICAL_SERVING_URL value: "{{ .Release.Name }}-feast-batch-serving:6566" {{- if .Values.gcpServiceAccount.enabled }} - name: GOOGLE_APPLICATION_CREDENTIALS diff --git a/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml b/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml index 181982d7e8..f2a76e37b2 100644 --- a/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml +++ b/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml @@ -70,14 +70,14 @@ spec: entity_rows_df = df.copy(deep=True).rename(columns={"datetime": "event_timestamp"})[["event_timestamp", "customer_id"]] pandavro.to_avro("entity_rows.avro", entity_rows_df) - batch_serving_job = client.get_historical_features( + historical_serving_job = client.get_historical_features( entity_rows="file://entity_rows.avro", feature_refs=[ f"{project}/daily_transactions:1", f"{project}/total_transactions:1", ] ) - result_df = batch_serving_job.to_dataframe() + result_df = historical_serving_job.to_dataframe() print("Retrieved dataframe: ") print(result_df) diff --git a/infra/docker-compose/.env.sample b/infra/docker-compose/.env.sample index be071be994..5c1f5af819 100644 --- a/infra/docker-compose/.env.sample +++ b/infra/docker-compose/.env.sample @@ -1,20 +1,7 @@ -# General COMPOSE_PROJECT_NAME=feast -FEAST_VERSION=latest -FEAST_REPOSITORY_VERSION=v0.5-branch - -# Feast Core -FEAST_CORE_IMAGE=gcr.io/kf-feast/feast-core -FEAST_CORE_CONFIG=core.yml -FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY=placeholder.json - -# Feast Serving -FEAST_SERVING_IMAGE=gcr.io/kf-feast/feast-serving -# Feast Serving - Batch (BigQuery) -FEAST_BATCH_SERVING_CONFIG=batch-serving.yml -FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY=placeholder.json -# Feast Serving - Online (Redis) -FEAST_ONLINE_SERVING_CONFIG=online-serving.yml - -# Jupyter -FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY=placeholder.json +FEAST_VERSION=0.6.2 +GCP_SERVICE_ACCOUNT=./gcp-service-accounts/key.json +FEAST_CORE_CONFIG=./core/core.yml +FEAST_HISTORICAL_SERVING_CONFIG=./serving/historical-serving.yml +FEAST_HISTORICAL_SERVING_ENABLED=false +FEAST_ONLINE_SERVING_CONFIG=./serving/online-serving.yml \ No newline at end of file diff --git a/infra/docker-compose/core/core.yml b/infra/docker-compose/core/core.yml index f54d05a36b..7506a2dbe6 100644 --- a/infra/docker-compose/core/core.yml +++ b/infra/docker-compose/core/core.yml @@ -1,12 +1,13 @@ feast: jobs: - polling_interval_milliseconds: 30000 + polling_interval_milliseconds: 20000 job_update_timeout_seconds: 240 active_runner: direct runners: - name: direct type: DirectRunner - options: {} + options: + tempLocation: gs://bucket/tempLocation stream: type: kafka options: diff --git a/infra/docker-compose/docker-compose.batch.yml b/infra/docker-compose/docker-compose.batch.yml deleted file mode 100644 index 15b5e71875..0000000000 --- a/infra/docker-compose/docker-compose.batch.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: "3.7" - -services: - batch-serving: - image: ${FEAST_SERVING_IMAGE}:${FEAST_VERSION} - volumes: - - ./serving/${FEAST_BATCH_SERVING_CONFIG}:/etc/feast/application.yml - - ./gcp-service-accounts/${FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json - depends_on: - - redis - ports: - - 6567:6567 - restart: on-failure - environment: - GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json - command: - - "java" - - "-Xms1024m" - - "-Xmx1024m" - - "-jar" - - "/opt/feast/feast-serving.jar" - - "--spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml" - - redis: - image: redis:5-alpine - ports: - - "6379:6379" diff --git a/infra/docker-compose/docker-compose.dev.yml b/infra/docker-compose/docker-compose.dev.yml index 840c6dd267..a7058c5bf9 100644 --- a/infra/docker-compose/docker-compose.dev.yml +++ b/infra/docker-compose/docker-compose.dev.yml @@ -25,21 +25,18 @@ services: - spring-boot:run jupyter: - image: jupyter/minimal-notebook:619e9cc2fc07 + image: gcr.io/kf-feast/feast-jupyter:${FEAST_VERSION} volumes: - - ./gcp-service-accounts/${FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json - - ./jupyter/startup.sh:/etc/startup.sh + - ${GCP_SERVICE_ACCOUNT}:/etc/gcloud/service-accounts/key.json depends_on: - core environment: FEAST_CORE_URL: core:6565 - FEAST_ONLINE_SERVING_URL: online-serving:6566 - FEAST_BATCH_SERVING_URL: batch-serving:6567 + FEAST_ONLINE_SERVING_URL: online_serving:6566 + FEAST_HISTORICAL_SERVING_URL: historical_serving:6567 GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json - FEAST_REPOSITORY_VERSION: ${FEAST_REPOSITORY_VERSION} ports: - 8888:8888 - command: ["/etc/startup.sh"] kafka: image: confluentinc/cp-kafka:5.2.1 diff --git a/infra/docker-compose/docker-compose.online.yml b/infra/docker-compose/docker-compose.online.yml deleted file mode 100644 index 0e5a3cfaec..0000000000 --- a/infra/docker-compose/docker-compose.online.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: "3.7" - -services: - online-serving: - image: ${FEAST_SERVING_IMAGE}:${FEAST_VERSION} - volumes: - - ./serving/${FEAST_ONLINE_SERVING_CONFIG}:/etc/feast/application.yml - # Required if authentication is enabled on core and - # provider is 'google'. GOOGLE_APPLICATION_CREDENTIALS is used for connecting to core. - - ./gcp-service-accounts/${FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json - depends_on: - - redis - ports: - - 6566:6566 - restart: on-failure - environment: - GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json - command: - - java - - -jar - - /opt/feast/feast-serving.jar - - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml - - redis: - image: redis:5-alpine - ports: - - "6379:6379" diff --git a/infra/docker-compose/docker-compose.yml b/infra/docker-compose/docker-compose.yml index 5239531c8a..61fc873f41 100644 --- a/infra/docker-compose/docker-compose.yml +++ b/infra/docker-compose/docker-compose.yml @@ -2,10 +2,10 @@ version: "3.7" services: core: - image: ${FEAST_CORE_IMAGE}:${FEAST_VERSION} + image: gcr.io/kf-feast/feast-core:${FEAST_VERSION} volumes: - - ./core/${FEAST_CORE_CONFIG}:/etc/feast/application.yml - - ./gcp-service-accounts/${FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json + - ${FEAST_CORE_CONFIG}:/etc/feast/application.yml + - ${GCP_SERVICE_ACCOUNT}:/etc/gcloud/service-accounts/key.json environment: DB_HOST: db GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json @@ -22,23 +22,18 @@ services: - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml jupyter: - image: gcr.io/kf-feast/feast-jupyter:latest + image: gcr.io/kf-feast/feast-jupyter:${FEAST_VERSION} volumes: - - ./gcp-service-accounts/${FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json + - ${GCP_SERVICE_ACCOUNT}:/etc/gcloud/service-accounts/key.json depends_on: - core environment: FEAST_CORE_URL: core:6565 - FEAST_ONLINE_SERVING_URL: online-serving:6566 - FEAST_BATCH_SERVING_URL: batch-serving:6567 + FEAST_ONLINE_SERVING_URL: online_serving:6566 + FEAST_HISTORICAL_SERVING_URL: historical_serving:6567 GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json ports: - 8888:8888 - command: > - bash -c " - git clone https://github.com/feast-dev/feast.git || true - && cd feast && git checkout $$(git tag | sort -r --version-sort | head -n1 | cut -c1-4)-branch && cd .. - && start-notebook.sh --NotebookApp.token=''" kafka: image: confluentinc/cp-kafka:5.2.1 @@ -67,3 +62,39 @@ services: POSTGRES_PASSWORD: password ports: - "5432:5432" + + online_serving: + image: gcr.io/kf-feast/feast-serving:${FEAST_VERSION} + volumes: + - ${FEAST_ONLINE_SERVING_CONFIG}:/etc/feast/application.yml + depends_on: + - redis + ports: + - 6566:6566 + restart: on-failure + command: + - java + - -jar + - /opt/feast/feast-serving.jar + - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml + + historical_serving: + image: gcr.io/kf-feast/feast-serving:${FEAST_VERSION} + volumes: + - ${FEAST_HISTORICAL_SERVING_CONFIG}:/etc/feast/application.yml + - ${GCP_SERVICE_ACCOUNT}:/etc/gcloud/service-accounts/key.json + depends_on: + - redis + ports: + - 6567:6567 + restart: on-failure + environment: + GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json + command: > + bash -c "if [ $FEAST_HISTORICAL_SERVING_ENABLED != "true" ]; then echo \"Feast historical serving is disabled\"; sleep 10; exit 1; fi + && java -Xms1024m -Xmx1024m -jar /opt/feast/feast-serving.jar --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml" + + redis: + image: redis:5-alpine + ports: + - "6379:6379" \ No newline at end of file diff --git a/infra/docker-compose/serving/batch-serving.yml b/infra/docker-compose/serving/historical-serving.yml similarity index 87% rename from infra/docker-compose/serving/batch-serving.yml rename to infra/docker-compose/serving/historical-serving.yml index 3feb81c84e..892673bcd0 100644 --- a/infra/docker-compose/serving/batch-serving.yml +++ b/infra/docker-compose/serving/historical-serving.yml @@ -4,7 +4,7 @@ feast: stores: - name: historical type: BIGQUERY - # Changes required for batch serving to work + # Changes required for historical serving to work # Please see https://api.docs.feast.dev/grpc/feast.core.pb.html#Store for configuration options config: project_id: project @@ -21,4 +21,5 @@ feast: redis_port: 6379 grpc: - port: 6567 + server: + port: 6567 diff --git a/infra/docker/core/Dockerfile b/infra/docker/core/Dockerfile index f210e1c40c..2c6b4e5723 100644 --- a/infra/docker/core/Dockerfile +++ b/infra/docker/core/Dockerfile @@ -3,15 +3,34 @@ # ============================================================ FROM maven:3.6-jdk-11 as builder -ARG REVISION=dev -COPY . /build + WORKDIR /build -# + +COPY pom.xml . +COPY datatypes/java/pom.xml datatypes/java/pom.xml +COPY common/pom.xml common/pom.xml +COPY auth/pom.xml auth/pom.xml +COPY ingestion/pom.xml ingestion/pom.xml +COPY core/pom.xml core/pom.xml +COPY serving/pom.xml serving/pom.xml +COPY storage/api/pom.xml storage/api/pom.xml +COPY storage/connectors/pom.xml storage/connectors/pom.xml +COPY storage/connectors/redis/pom.xml storage/connectors/redis/pom.xml +COPY storage/connectors/bigquery/pom.xml storage/connectors/bigquery/pom.xml +COPY sdk/java/pom.xml sdk/java/pom.xml +COPY docs/coverage/java/pom.xml docs/coverage/java/pom.xml +COPY protos/ protos/ + # Setting Maven repository .m2 directory relative to /build folder gives the # user to optionally use cached repository when building the image by copying # the existing .m2 directory to $FEAST_REPO_ROOT/.m2 -# ENV MAVEN_OPTS="-Dmaven.repo.local=/build/.m2/repository -DdependencyLocationsEnabled=false" +COPY pom.xml .m2/* .m2/ +RUN mvn dependency:go-offline -DexcludeGroupIds:dev.feast 2>/dev/null || true + +COPY . . + +ARG REVISION=dev RUN mvn --also-make --projects core,ingestion -Drevision=$REVISION \ -DskipTests=true --batch-mode clean package # diff --git a/infra/docker/jupyter/Dockerfile b/infra/docker/jupyter/Dockerfile index 5aba9eaa33..a29b19d464 100644 --- a/infra/docker/jupyter/Dockerfile +++ b/infra/docker/jupyter/Dockerfile @@ -23,4 +23,6 @@ RUN pip install -e sdk/python -U USER $NB_UID WORKDIR $HOME -CMD start-notebook.sh --NotebookApp.token='' \ No newline at end of file +COPY examples . + +CMD ["start-notebook.sh", "--NotebookApp.token=''"] \ No newline at end of file diff --git a/infra/docker/serving/Dockerfile b/infra/docker/serving/Dockerfile index 8f2abf5b75..2e724e9a4c 100644 --- a/infra/docker/serving/Dockerfile +++ b/infra/docker/serving/Dockerfile @@ -3,15 +3,34 @@ # ============================================================ FROM maven:3.6-jdk-11 as builder -ARG REVISION=dev -COPY . /build + WORKDIR /build -# + +COPY pom.xml . +COPY datatypes/java/pom.xml datatypes/java/pom.xml +COPY common/pom.xml common/pom.xml +COPY auth/pom.xml auth/pom.xml +COPY ingestion/pom.xml ingestion/pom.xml +COPY core/pom.xml core/pom.xml +COPY serving/pom.xml serving/pom.xml +COPY storage/api/pom.xml storage/api/pom.xml +COPY storage/connectors/pom.xml storage/connectors/pom.xml +COPY storage/connectors/redis/pom.xml storage/connectors/redis/pom.xml +COPY storage/connectors/bigquery/pom.xml storage/connectors/bigquery/pom.xml +COPY sdk/java/pom.xml sdk/java/pom.xml +COPY docs/coverage/java/pom.xml docs/coverage/java/pom.xml +COPY protos/ protos/ + # Setting Maven repository .m2 directory relative to /build folder gives the # user to optionally use cached repository when building the image by copying # the existing .m2 directory to $FEAST_REPO_ROOT/.m2 -# ENV MAVEN_OPTS="-Dmaven.repo.local=/build/.m2/repository -DdependencyLocationsEnabled=false" +COPY pom.xml .m2/* .m2/ +RUN mvn dependency:go-offline -DexcludeGroupIds:dev.feast 2>/dev/null || true + +COPY . . + +ARG REVISION=dev RUN mvn --also-make --projects serving -Drevision=$REVISION \ -DskipTests=true --batch-mode clean package # diff --git a/infra/scripts/test-docker-compose.sh b/infra/scripts/test-docker-compose.sh index 3d92513c77..0d6e99f860 100755 --- a/infra/scripts/test-docker-compose.sh +++ b/infra/scripts/test-docker-compose.sh @@ -12,10 +12,7 @@ clean_up () { ARG=$? # Shut down docker-compose images - docker-compose -f docker-compose.yml -f docker-compose.online.yml down - - # Remove configuration file - rm .env + docker-compose down exit $ARG } @@ -30,7 +27,7 @@ cd ${PROJECT_ROOT_DIR}/infra/docker-compose/ cp .env.sample .env # Start Docker Compose containers -docker-compose -f docker-compose.yml -f docker-compose.online.yml up -d +docker-compose up -d # Get Jupyter container IP address export JUPYTER_DOCKER_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_jupyter_1) @@ -49,10 +46,10 @@ export FEAST_CORE_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSett ${PROJECT_ROOT_DIR}/infra/scripts/wait-for-it.sh ${FEAST_CORE_CONTAINER_IP_ADDRESS}:6565 --timeout=120 # Get Feast Online Serving container IP address -export FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_online-serving_1) +export FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_online_serving_1) # Wait for Feast Online Serving to be ready ${PROJECT_ROOT_DIR}/infra/scripts/wait-for-it.sh ${FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS}:6566 --timeout=120 # Run e2e tests for Redis -docker exec feast_jupyter_1 bash -c 'cd feast/tests/e2e/redis && pytest -s basic-ingest-redis-serving.py --core_url core:6565 --serving_url=online-serving:6566' +docker exec feast_jupyter_1 bash -c 'cd /feast/tests/e2e/redis && pytest --verbose -rs basic-ingest-redis-serving.py --core_url core:6565 --serving_url=online_serving:6566 --kafka_brokers=kafka:9092' diff --git a/infra/scripts/test-load.sh b/infra/scripts/test-load.sh new file mode 100755 index 0000000000..6ff6d86231 --- /dev/null +++ b/infra/scripts/test-load.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +set -e + +echo " +============================================================ +Running Load Tests +============================================================ +" + +clean_up() { + ARG=$? + + # Shut down docker-compose images + cd "${PROJECT_ROOT_DIR}"/infra/docker-compose + + docker-compose down + + exit $ARG +} + +# Get Feast project repository root and scripts directory +export PROJECT_ROOT_DIR=$(git rev-parse --show-toplevel) +export SCRIPTS_DIR=${PROJECT_ROOT_DIR}/infra/scripts +export COMPOSE_INTERACTIVE_NO_CLI=1 +source ${SCRIPTS_DIR}/setup-common-functions.sh + +if [ -z "$1" ] ; then + echo "No SHA/FEAST_VERSION provided as argument, using local HEAD"; + FEAST_VERSION=$(git rev-parse HEAD); + export FEAST_VERSION +else + echo "Using ${1} as SHA/FEAST_VERSION to test"; + FEAST_VERSION=${1} + export FEAST_VERSION +fi + +wait_for_docker_image gcr.io/kf-feast/feast-core:"${FEAST_VERSION}" +wait_for_docker_image gcr.io/kf-feast/feast-serving:"${FEAST_VERSION}" +wait_for_docker_image gcr.io/kf-feast/feast-jupyter:"${FEAST_VERSION}" + +# Clean up Docker Compose if failure +trap clean_up EXIT + +# Create Docker Compose configuration file +cd "${PROJECT_ROOT_DIR}"/infra/docker-compose/ +cp .env.sample .env + +# Start Docker Compose containers +FEAST_VERSION=${FEAST_VERSION} docker-compose up -d + +# Get Jupyter container IP address +export JUPYTER_DOCKER_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_jupyter_1) + +# Print Jupyter container information +docker inspect feast_jupyter_1 +docker logs feast_jupyter_1 + +# Wait for Jupyter Notebook Container to come online +"${PROJECT_ROOT_DIR}"/infra/scripts/wait-for-it.sh ${JUPYTER_DOCKER_CONTAINER_IP_ADDRESS}:8888 --timeout=60 + +# Get Feast Core container IP address +export FEAST_CORE_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_core_1) + +# Wait for Feast Core to be ready +"${PROJECT_ROOT_DIR}"/infra/scripts/wait-for-it.sh ${FEAST_CORE_CONTAINER_IP_ADDRESS}:6565 --timeout=120 + +# Get Feast Online Serving container IP address +export FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_online_serving_1) + +# Wait for Feast Online Serving to be ready +"${PROJECT_ROOT_DIR}"/infra/scripts/wait-for-it.sh ${FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS}:6566 --timeout=120 + +# Ingest data into Feast +pip install --user matplotlib feast pytz --upgrade +python "${PROJECT_ROOT_DIR}"/tests/load/ingest.py "${FEAST_CORE_CONTAINER_IP_ADDRESS}":6565 "${FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS}":6566 + +# Download load test tool and proxy +cd $(mktemp -d) +wget -c https://github.com/feast-dev/feast-load-test-proxy/releases/download/v0.1.1/feast-load-test-proxy_0.1.1_Linux_x86_64.tar.gz -O - | tar -xz +git clone https://github.com/giltene/wrk2.git +cd wrk2 +make +cd .. +cp wrk2/wrk . + +# Start load test server +LOAD_FEAST_SERVING_HOST=${FEAST_ONLINE_SERVING_CONTAINER_IP_ADDRESS} LOAD_FEAST_SERVING_PORT=6566 ./feast-load-test-proxy & +sleep 5 + +# Run load tests +./wrk -t2 -c10 -d30s -R20 --latency http://localhost:8080/echo +./wrk -t2 -c10 -d30s -R20 --latency http://localhost:8080/send?entity_count=10 > load_test_results_1fs_13f_10e_20rps +./wrk -t2 -c10 -d30s -R50 --latency http://localhost:8080/send?entity_count=10 > load_test_results_1fs_13f_10e_50rps +./wrk -t2 -c10 -d30s -R250 --latency http://localhost:8080/send?entity_count=10 > load_test_results_1fs_13f_10e_250rps +./wrk -t2 -c10 -d30s -R20 --latency http://localhost:8080/send?entity_count=50 > load_test_results_1fs_13f_50e_20rps +./wrk -t2 -c10 -d30s -R50 --latency http://localhost:8080/send?entity_count=50 > load_test_results_1fs_13f_50e_50rps +./wrk -t2 -c10 -d30s -R250 --latency http://localhost:8080/send?entity_count=50 > load_test_results_1fs_13f_50e_250rps + +# Print load test results +cat $(ls -lah | grep load_test_results | awk '{print $9}' | tr '\n' ' ') + +# Create hdr-plot of load tests +export PLOT_FILE_NAME="load_test_graph_${FEAST_VERSION}"_$(date "+%Y%m%d-%H%M%S").png +python $PROJECT_ROOT_DIR/tests/load/hdr_plot.py --output "$PLOT_FILE_NAME" --title "Load test: ${FEAST_VERSION}" $(ls -lah | grep load_test_results | awk '{print $9}' | tr '\n' ' ') + +# Persist artifact +mkdir -p "${PROJECT_ROOT_DIR}"/load-test-output/ +cp -r load_test_* "${PROJECT_ROOT_DIR}"/load-test-output/ \ No newline at end of file diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 392f6501d9..61140699f9 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -6,3 +6,4 @@ def pytest_addoption(parser): "--gcs_path", action="store", default="gs://feast-templocation-kf-feast/" ) parser.addoption("--enable_auth", action="store", default="False") + parser.addoption("--kafka_brokers", action="store", default="localhost:9092") diff --git a/tests/e2e/redis/basic-ingest-redis-serving.py b/tests/e2e/redis/basic-ingest-redis-serving.py index c1e25508d4..e6d3834b4d 100644 --- a/tests/e2e/redis/basic-ingest-redis-serving.py +++ b/tests/e2e/redis/basic-ingest-redis-serving.py @@ -4,6 +4,7 @@ import tempfile import time import uuid +from copy import copy from datetime import datetime, timedelta import grpc @@ -110,6 +111,11 @@ def enable_auth(pytestconfig): return True if pytestconfig.getoption("enable_auth").lower() == "true" else False +@pytest.fixture(scope="module") +def kafka_brokers(pytestconfig): + return pytestconfig.getoption("kafka_brokers") + + @pytest.fixture(scope="module") def client(core_url, serving_url, allow_dirty, enable_auth): # Get client for core and serving @@ -422,13 +428,19 @@ def try_get_features1(): response = client.get_online_features( entity_rows=online_request_entity, feature_refs=online_request_features ) - return response, True + is_ok = check_online_response( + "customer2_rating", nonlist_entity_dataframe, response + ) + return response, is_ok def try_get_features2(): response = client.get_online_features( entity_rows=online_request_entity2, feature_refs=online_request_features ) - return response, True + is_ok = check_online_response( + "customer2_rating", nonlist_entity_dataframe, response + ) + return response, is_ok online_features_actual1 = wait_retry_backoff( retry_fn=try_get_features1, @@ -522,13 +534,19 @@ def try_get_features1(): response = client.get_online_features( entity_rows=online_request_entity, feature_refs=online_request_features ) - return response, True + is_ok = check_online_response( + "district_rating", list_entity_dataframe, response + ) + return response, is_ok def try_get_features2(): response = client.get_online_features( entity_rows=online_request_entity2, feature_refs=online_request_features ) - return response, True + is_ok = check_online_response( + "district_rating", list_entity_dataframe, response + ) + return response, is_ok online_features_actual = wait_retry_backoff( retry_fn=try_get_features1, @@ -605,7 +623,8 @@ def try_get_features(): response = client.get_online_features( entity_rows=online_request_entity, feature_refs=online_request_features ) - return response, True + is_ok = check_online_response("driver_fs_rating", driver_df, response) + return response, is_ok online_features_actual = wait_retry_backoff( retry_fn=try_get_features, @@ -658,7 +677,8 @@ def try_get_features(): response = client.get_online_features( entity_rows=online_request_entity, feature_refs=online_request_features ) - return response, True + is_ok = check_online_response("cust_rating", cust_df, response) + return response, is_ok online_features_actual = wait_retry_backoff( retry_fn=try_get_features, @@ -1228,52 +1248,91 @@ def test_list_entities_and_features(client): ) -@pytest.mark.timeout(900) +@pytest.mark.timeout(500) @pytest.mark.run(order=70) -def test_sources_deduplicate_ingest_jobs(client): - source = KafkaSource("localhost:9092", "feast-features") - alt_source = KafkaSource("localhost:9092", "feast-data") - - def get_running_jobs(): - return [ - job - for job in client.list_ingest_jobs() - if job.status == IngestionJobStatus.RUNNING - ] - - # stop all ingest jobs - ingest_jobs = client.list_ingest_jobs() - for ingest_job in ingest_jobs: - client.stop_ingest_job(ingest_job) - for ingest_job in ingest_jobs: - ingest_job.wait(IngestionJobStatus.ABORTED) +def test_sources_deduplicate_ingest_jobs(client, kafka_brokers): + shared_source = KafkaSource(kafka_brokers, "dup_shared") + dup_source_fs_1 = FeatureSet( + name="duplicate_source_fs_1", + features=[Feature("fs1", ValueType.FLOAT), Feature("fs2", ValueType.FLOAT)], + entities=[Entity("e2", ValueType.INT64)], + source=shared_source, + ) + dup_source_fs_2 = copy(dup_source_fs_1) + dup_source_fs_2.name = "duplicate_source_fs_2" - # register multiple featuresets with the same source + def is_same_jobs(): + fs_1_jobs = client.list_ingest_jobs( + feature_set_ref=FeatureSetRef( + name=dup_source_fs_1.name, project=dup_source_fs_1.project + ) + ) + fs_2_jobs = client.list_ingest_jobs( + feature_set_ref=FeatureSetRef( + name=dup_source_fs_2.name, project=dup_source_fs_2.project + ) + ) + same = True + if not (len(fs_1_jobs) > 0 and len(fs_1_jobs) == len(fs_2_jobs)): + same = False + for fs_1_job in fs_1_jobs: + for fs_2_job in fs_2_jobs: + if ( + not fs_1_job.source.to_proto() == fs_2_job.source.to_proto() + and fs_1_job.source.to_proto() == shared_source.to_proto() + ): + same = False + if fs_1_job.id != fs_2_job.id: + same = False + return same + + def is_different_jobs(): + fs_1_jobs = client.list_ingest_jobs( + feature_set_ref=FeatureSetRef( + name=dup_source_fs_1.name, project=dup_source_fs_1.project + ) + ) + fs_2_jobs = client.list_ingest_jobs( + feature_set_ref=FeatureSetRef( + name=dup_source_fs_2.name, project=dup_source_fs_2.project + ) + ) + different = True + if not (len(fs_1_jobs) > 0 and len(fs_2_jobs) > 0): + different = False + for fs_1_job in fs_1_jobs: + if fs_1_job.source.to_proto() == alt_source.to_proto(): + different = False + for fs_2_job in fs_2_jobs: + if fs_2_job.source.to_proto() == shared_source.to_proto(): + different = False + for fs_1_job in fs_1_jobs: + for fs_2_job in fs_2_jobs: + if fs_1_job.id == fs_2_job.id: + different = False + return different + + # register multiple feature sets with the same source # only one ingest job should spawned due to test ingest job deduplication - cust_trans_fs = FeatureSet.from_yaml(f"{DIR_PATH}/basic/cust_trans_fs.yaml") - driver_fs = FeatureSet.from_yaml(f"{DIR_PATH}/basic/driver_fs.yaml") - cust_trans_fs.source, driver_fs.source = source, source - client.apply(cust_trans_fs) - client.apply(driver_fs) + client.apply(dup_source_fs_1) + client.apply(dup_source_fs_2) - while len(get_running_jobs()) != 1: - assert 0 <= len(get_running_jobs()) <= 1 + while not is_same_jobs(): time.sleep(1) - # update feature sets with different sources, should spawn 2 ingest jobs - driver_fs.source = alt_source - client.apply(driver_fs) + # update feature sets with different sources, should have different jobs + alt_source = KafkaSource(kafka_brokers, "alt_source") + dup_source_fs_2.source = alt_source + client.apply(dup_source_fs_2) - while len(get_running_jobs()) != 2: - assert 1 <= len(get_running_jobs()) <= 2 + while not is_different_jobs(): time.sleep(1) - # update feature sets with same source again, should spawn only 1 ingest job - driver_fs.source = source - client.apply(driver_fs) + # update feature sets with same source again, should have the same job + dup_source_fs_2.source = shared_source + client.apply(dup_source_fs_2) - while len(get_running_jobs()) != 1: - assert 1 <= len(get_running_jobs()) <= 2 + while not is_same_jobs(): time.sleep(1)