From d4a9ca84f37e04a2ea72c6bca43b47650221eaf1 Mon Sep 17 00:00:00 2001 From: claudevdm <33973061+claudevdm@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:54:03 -0500 Subject: [PATCH 001/135] Add input boxes for required user inputs (#33183) * Add input boxes for required user inputs * Remove unnecessary comments. --------- Co-authored-by: Claude --- .../beam-ml/automatic_model_refresh.ipynb | 621 +++---- .../bigquery_enrichment_transform.ipynb | 458 +++--- .../bigtable_enrichment_transform.ipynb | 40 +- .../vertex_ai_text_embeddings.ipynb | 211 +-- .../beam-ml/dataframe_api_preprocessing.ipynb | 1428 +++++++---------- .../gemma_2_sentiment_and_summarization.ipynb | 2 +- .../beam-ml/nlp_tensorflow_streaming.ipynb | 46 +- .../beam-ml/run_inference_pytorch.ipynb | 146 +- .../beam-ml/run_inference_sklearn.ipynb | 208 +-- .../beam-ml/run_inference_tensorflow.ipynb | 4 +- .../run_inference_tensorflow_with_tfx.ipynb | 404 +++-- .../beam-ml/run_inference_vertex_ai.ipynb | 19 +- .../beam-ml/run_inference_vllm.ipynb | 345 ++-- .../vertex_ai_feature_store_enrichment.ipynb | 8 +- examples/notebooks/healthcare/beam_nlp.ipynb | 413 +++-- 15 files changed, 1998 insertions(+), 2355 deletions(-) diff --git a/examples/notebooks/beam-ml/automatic_model_refresh.ipynb b/examples/notebooks/beam-ml/automatic_model_refresh.ipynb index 96717cfef60c..2f80846f313b 100644 --- a/examples/notebooks/beam-ml/automatic_model_refresh.ipynb +++ b/examples/notebooks/beam-ml/automatic_model_refresh.ipynb @@ -1,22 +1,21 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "include_colab_link": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "OsFaZscKSPvo" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -36,22 +35,13 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "cellView": "form", - "id": "OsFaZscKSPvo" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "ZUSiAR62SgO8" + }, "source": [ "# Update ML models in running pipelines\n", "\n", @@ -63,20 +53,13 @@ " View source on GitHub\n", " \n", "\n" - ], - "metadata": { - "id": "ZUSiAR62SgO8" - }, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "tBtqF5UpKJNZ" + }, "source": [ "This notebook demonstrates how to perform automatic model updates without stopping your Apache Beam pipeline.\n", "You can use side inputs to update your model in real time, even while the Apache Beam pipeline is running. The side input is passed in a `ModelHandler` configuration object. You can update the model either by leveraging one of Apache Beam's provided patterns, such as the `WatchFilePattern`, or by configuring a custom side input `PCollection` that defines the logic for the model update.\n", @@ -85,36 +68,19 @@ "For more information about side inputs, see the [Side inputs](https://beam.apache.org/documentation/programming-guide/#side-inputs) section in the Apache Beam Programming Guide.\n", "\n", "This example uses `WatchFilePattern` as a side input. `WatchFilePattern` is used to watch for file updates that match the `file_pattern` based on timestamps. It emits the latest `ModelMetadata`, which is used in the RunInference `PTransform` to automatically update the ML model without stopping the Apache Beam pipeline.\n" - ], - "metadata": { - "id": "tBtqF5UpKJNZ" - }, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "SPuXFowiTpWx" + }, "source": [ "## Before you begin\n", "Install the dependencies required to run this notebook.\n", "\n", "To use RunInference with side inputs for automatic model updates, use Apache Beam version 2.46.0 or later." - ], - "metadata": { - "id": "SPuXFowiTpWx" - }, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "code", @@ -122,23 +88,39 @@ "metadata": { "id": "1RyTYsFEIOlA" }, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "!pip install apache_beam[gcp]>=2.46.0 tensorflow==2.15.0 tensorflow_hub==0.16.1 keras==2.15.0 Pillow==11.0.0 --quiet" ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Rs4cwwNrIV9H" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "# Imports required for the notebook.\n", "import logging\n", "import time\n", + "import os\n", "from typing import Iterable\n", "from typing import Tuple\n", "\n", @@ -156,21 +138,23 @@ "import numpy\n", "from PIL import Image\n", "import tensorflow as tf" - ], - "metadata": { - "id": "Rs4cwwNrIV9H" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "jAKpPcmmGm03" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "# Authenticate to your Google Cloud account.\n", "def auth_to_colab():\n", @@ -178,21 +162,13 @@ " auth.authenticate_user()\n", "\n", "auth_to_colab()" - ], - "metadata": { - "id": "jAKpPcmmGm03" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "ORYNKhH3WQyP" + }, "source": [ "## Configure the runner\n", "\n", @@ -202,24 +178,37 @@ "* Configure the pipeline options for the pipeline to run on Dataflow. Make sure the pipeline is using streaming mode.\n", "\n", "In the following code, replace `BUCKET_NAME` with the the name of your Cloud Storage bucket." - ], - "metadata": { - "id": "ORYNKhH3WQyP" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wWjbnq6X-4uE" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "options = PipelineOptions()\n", "options.view_as(StandardOptions).streaming = True\n", "\n", - "BUCKET_NAME = '' # Replace with your bucket name.\n", + "# Replace with your bucket name.\n", + "BUCKET_NAME = '' # @param {type:'string'} \n", + "os.environ['BUCKET_NAME'] = BUCKET_NAME\n", "\n", "# Provide required pipeline options for the Dataflow Runner.\n", "options.view_as(StandardOptions).runner = \"DataflowRunner\"\n", "\n", "# Set the project to the default project in your current Google Cloud environment.\n", - "options.view_as(GoogleCloudOptions).project = ''\n", + "PROJECT_NAME = '' # @param {type:'string'}\n", + "options.view_as(GoogleCloudOptions).project = PROJECT_NAME\n", "\n", "# Set the Google Cloud region that you want to run Dataflow in.\n", "options.view_as(GoogleCloudOptions).region = 'us-central1'\n", @@ -244,113 +233,120 @@ "# To expedite the model update process, it's recommended to set num_workers>1.\n", "# https://github.com/apache/beam/issues/28776\n", "options.view_as(WorkerOptions).num_workers = 5" - ], - "metadata": { - "id": "wWjbnq6X-4uE" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", - "source": [ - "Install the `tensorflow` and `tensorflow_hub` dependencies on Dataflow. Use the `requirements_file` pipeline option to pass these dependencies." - ], "metadata": { "id": "HTJV8pO2Wcw4" - } + }, + "source": [ + "Install the `tensorflow` and `tensorflow_hub` dependencies on Dataflow. Use the `requirements_file` pipeline option to pass these dependencies." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "lEy4PkluWbdm" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "# In a requirements file, define the dependencies required for the pipeline.\n", "!printf 'tensorflow==2.15.0\\ntensorflow_hub==0.16.1\\nkeras==2.15.0\\nPillow==11.0.0' > ./requirements.txt\n", "# Install the pipeline dependencies on Dataflow.\n", "options.view_as(SetupOptions).requirements_file = './requirements.txt'" - ], - "metadata": { - "id": "lEy4PkluWbdm" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "_AUNH_GJk_NE" + }, "source": [ "## Use the TensorFlow model handler\n", " This example uses `TFModelHandlerTensor` as the model handler and the `resnet_101` model trained on [ImageNet](https://www.image-net.org/).\n", "\n", "\n", "For the Dataflow runner, you need to store the model in a remote location that the Apache Beam pipeline can access. For this example, download the `ResNet101` model, and upload it to the Google Cloud Storage bucket.\n" - ], - "metadata": { - "id": "_AUNH_GJk_NE" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ibkWiwVNvyrn" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "model = tf.keras.applications.resnet.ResNet101()\n", "model.save('resnet101_weights_tf_dim_ordering_tf_kernels.keras')\n", "# After saving the model locally, upload the model to GCS bucket and provide that gcs bucket `URI` as `model_uri` to the `TFModelHandler`\n", - "# Replace `BUCKET_NAME` value with actual bucket name.\n", - "!gsutil cp resnet101_weights_tf_dim_ordering_tf_kernels.keras gs:///dataflow/resnet101_weights_tf_dim_ordering_tf_kernels.keras" - ], - "metadata": { - "id": "ibkWiwVNvyrn" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + "!gsutil cp resnet101_weights_tf_dim_ordering_tf_kernels.keras gs://${BUCKET_NAME}/dataflow/resnet101_weights_tf_dim_ordering_tf_kernels.keras" + ] }, { "cell_type": "code", - "source": [ - "model_handler = TFModelHandlerTensor(\n", - " model_uri=dataflow_gcs_location + \"/resnet101_weights_tf_dim_ordering_tf_kernels.keras\")" - ], + "execution_count": null, "metadata": { "id": "kkSnsxwUk-Sp" }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "model_handler = TFModelHandlerTensor(\n", + " model_uri=dataflow_gcs_location + \"/resnet101_weights_tf_dim_ordering_tf_kernels.keras\")" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "tZH0r0sL-if5" + }, "source": [ "## Preprocess images\n", "\n", "Use `preprocess_image` to run the inference, read the image, and convert the image to a TensorFlow tensor." - ], - "metadata": { - "id": "tZH0r0sL-if5" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dU5imgTt-8Ne" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "def preprocess_image(image_name, image_dir):\n", " img = tf.keras.utils.get_file(image_name, image_dir + image_name)\n", @@ -358,21 +354,23 @@ " img = numpy.array(img) / 255.0\n", " img_tensor = tf.cast(tf.convert_to_tensor(img[...]), dtype=tf.float32)\n", " return img_tensor" - ], - "metadata": { - "id": "dU5imgTt-8Ne" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6V5tJxO6-gyt" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "class PostProcessor(beam.DoFn):\n", " \"\"\"Process the PredictionResult to get the predicted label.\n", @@ -387,62 +385,66 @@ " imagenet_labels = numpy.array(open(labels_path).read().splitlines())\n", " predicted_class_name = imagenet_labels[predicted_class]\n", " yield predicted_class_name.title(), element.model_id" - ], - "metadata": { - "id": "6V5tJxO6-gyt" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "code", - "source": [ - "# Define the pipeline object.\n", - "pipeline = beam.Pipeline(options=options)" - ], + "execution_count": null, "metadata": { "id": "GpdKk72O_NXT" }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Define the pipeline object.\n", + "pipeline = beam.Pipeline(options=options)" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "elZ53uxc_9Hv" + }, "source": [ "Next, review the pipeline steps and examine the code.\n", "\n", "### Pipeline steps\n" - ], - "metadata": { - "id": "elZ53uxc_9Hv" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "305tkV2sAD-S" + }, "source": [ "1. Create a `PeriodicImpulse` transform, which emits output every `n` seconds. The `PeriodicImpulse` transform generates an infinite sequence of elements with a given runtime interval.\n", "\n", " In this example, `PeriodicImpulse` mimics the Pub/Sub source. Because the inputs in a streaming pipeline arrive in intervals, use `PeriodicImpulse` to output elements at `m` intervals.\n", "To learn more about `PeriodicImpulse`, see the [`PeriodicImpulse` code](https://github.com/apache/beam/blob/9c52e0594d6f0e59cd17ee005acfb41da508e0d5/sdks/python/apache_beam/transforms/periodicsequence.py#L150)." - ], - "metadata": { - "id": "305tkV2sAD-S" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "vUFStz66_Tbb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "start_timestamp = time.time() # start timestamp of the periodic impulse\n", "end_timestamp = start_timestamp + 60 * 20 # end timestamp of the periodic impulse (will run for 20 minutes).\n", @@ -455,72 +457,76 @@ " start_timestamp=start_timestamp,\n", " stop_timestamp=end_timestamp,\n", " fire_interval=main_input_fire_interval))" - ], - "metadata": { - "id": "vUFStz66_Tbb" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "8-sal2rFAxP2" + }, "source": [ "2. To read and preprocess the images, use the `preprocess_image` function. This example uses `Cat-with-beanie.jpg` for all inferences.\n", "\n", " **Note**: The image used for prediction is licensed in CC-BY. The creator is listed in the [LICENSE.txt](https://storage.googleapis.com/apache-beam-samples/image_captioning/LICENSE.txt) file." - ], - "metadata": { - "id": "8-sal2rFAxP2" - } + ] }, { "cell_type": "markdown", - "source": [ - "![download.png](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAADgCAIAAACVT/22AAAKMWlDQ1BJQ0MgUHJvZmlsZQAAeJydlndUU9kWh8+9N71QkhCKlNBraFICSA29SJEuKjEJEErAkAAiNkRUcERRkaYIMijggKNDkbEiioUBUbHrBBlE1HFwFBuWSWStGd+8ee/Nm98f935rn73P3Wfvfda6AJD8gwXCTFgJgAyhWBTh58WIjYtnYAcBDPAAA2wA4HCzs0IW+EYCmQJ82IxsmRP4F726DiD5+yrTP4zBAP+flLlZIjEAUJiM5/L42VwZF8k4PVecJbdPyZi2NE3OMErOIlmCMlaTc/IsW3z2mWUPOfMyhDwZy3PO4mXw5Nwn4405Er6MkWAZF+cI+LkyviZjg3RJhkDGb+SxGXxONgAoktwu5nNTZGwtY5IoMoIt43kA4EjJX/DSL1jMzxPLD8XOzFouEiSniBkmXFOGjZMTi+HPz03ni8XMMA43jSPiMdiZGVkc4XIAZs/8WRR5bRmyIjvYODk4MG0tbb4o1H9d/JuS93aWXoR/7hlEH/jD9ld+mQ0AsKZltdn6h21pFQBd6wFQu/2HzWAvAIqyvnUOfXEeunxeUsTiLGcrq9zcXEsBn2spL+jv+p8Of0NffM9Svt3v5WF485M4knQxQ143bmZ6pkTEyM7icPkM5p+H+B8H/nUeFhH8JL6IL5RFRMumTCBMlrVbyBOIBZlChkD4n5r4D8P+pNm5lona+BHQllgCpSEaQH4eACgqESAJe2Qr0O99C8ZHA/nNi9GZmJ37z4L+fVe4TP7IFiR/jmNHRDK4ElHO7Jr8WgI0IABFQAPqQBvoAxPABLbAEbgAD+ADAkEoiARxYDHgghSQAUQgFxSAtaAYlIKtYCeoBnWgETSDNnAYdIFj4DQ4By6By2AE3AFSMA6egCnwCsxAEISFyBAVUod0IEPIHLKFWJAb5AMFQxFQHJQIJUNCSAIVQOugUqgcqobqoWboW+godBq6AA1Dt6BRaBL6FXoHIzAJpsFasBFsBbNgTzgIjoQXwcnwMjgfLoK3wJVwA3wQ7oRPw5fgEVgKP4GnEYAQETqiizARFsJGQpF4JAkRIauQEqQCaUDakB6kH7mKSJGnyFsUBkVFMVBMlAvKHxWF4qKWoVahNqOqUQdQnag+1FXUKGoK9RFNRmuizdHO6AB0LDoZnYsuRlegm9Ad6LPoEfQ4+hUGg6FjjDGOGH9MHCYVswKzGbMb0445hRnGjGGmsVisOtYc64oNxXKwYmwxtgp7EHsSewU7jn2DI+J0cLY4X1w8TogrxFXgWnAncFdwE7gZvBLeEO+MD8Xz8MvxZfhGfA9+CD+OnyEoE4wJroRIQiphLaGS0EY4S7hLeEEkEvWITsRwooC4hlhJPEQ8TxwlviVRSGYkNimBJCFtIe0nnSLdIr0gk8lGZA9yPFlM3kJuJp8h3ye/UaAqWCoEKPAUVivUKHQqXFF4pohXNFT0VFysmK9YoXhEcUjxqRJeyUiJrcRRWqVUo3RU6YbStDJV2UY5VDlDebNyi/IF5UcULMWI4kPhUYoo+yhnKGNUhKpPZVO51HXURupZ6jgNQzOmBdBSaaW0b2iDtCkVioqdSrRKnkqNynEVKR2hG9ED6On0Mvph+nX6O1UtVU9Vvuom1TbVK6qv1eaoeajx1UrU2tVG1N6pM9R91NPUt6l3qd/TQGmYaYRr5Grs0Tir8XQObY7LHO6ckjmH59zWhDXNNCM0V2ju0xzQnNbS1vLTytKq0jqj9VSbru2hnaq9Q/uE9qQOVcdNR6CzQ+ekzmOGCsOTkc6oZPQxpnQ1df11Jbr1uoO6M3rGelF6hXrtevf0Cfos/ST9Hfq9+lMGOgYhBgUGrQa3DfGGLMMUw12G/YavjYyNYow2GHUZPTJWMw4wzjduNb5rQjZxN1lm0mByzRRjyjJNM91tetkMNrM3SzGrMRsyh80dzAXmu82HLdAWThZCiwaLG0wS05OZw2xljlrSLYMtCy27LJ9ZGVjFW22z6rf6aG1vnW7daH3HhmITaFNo02Pzq62ZLde2xvbaXPJc37mr53bPfW5nbse322N3055qH2K/wb7X/oODo4PIoc1h0tHAMdGx1vEGi8YKY21mnXdCO3k5rXY65vTW2cFZ7HzY+RcXpkuaS4vLo3nG8/jzGueNueq5clzrXaVuDLdEt71uUnddd457g/sDD30PnkeTx4SnqWeq50HPZ17WXiKvDq/XbGf2SvYpb8Tbz7vEe9CH4hPlU+1z31fPN9m31XfKz95vhd8pf7R/kP82/xsBWgHcgOaAqUDHwJWBfUGkoAVB1UEPgs2CRcE9IXBIYMj2kLvzDecL53eFgtCA0O2h98KMw5aFfR+OCQ8Lrwl/GGETURDRv4C6YMmClgWvIr0iyyLvRJlESaJ6oxWjE6Kbo1/HeMeUx0hjrWJXxl6K04gTxHXHY+Oj45vipxf6LNy5cDzBPqE44foi40V5iy4s1licvvj4EsUlnCVHEtGJMYktie85oZwGzvTSgKW1S6e4bO4u7hOeB28Hb5Lvyi/nTyS5JpUnPUp2Td6ePJninlKR8lTAFlQLnqf6p9alvk4LTduf9ik9Jr09A5eRmHFUSBGmCfsytTPzMoezzLOKs6TLnJftXDYlChI1ZUPZi7K7xTTZz9SAxESyXjKa45ZTk/MmNzr3SJ5ynjBvYLnZ8k3LJ/J9879egVrBXdFboFuwtmB0pefK+lXQqqWrelfrry5aPb7Gb82BtYS1aWt/KLQuLC98uS5mXU+RVtGaorH1futbixWKRcU3NrhsqNuI2ijYOLhp7qaqTR9LeCUXS61LK0rfb+ZuvviVzVeVX33akrRlsMyhbM9WzFbh1uvb3LcdKFcuzy8f2x6yvXMHY0fJjpc7l+y8UGFXUbeLsEuyS1oZXNldZVC1tep9dUr1SI1XTXutZu2m2te7ebuv7PHY01anVVda926vYO/Ner/6zgajhop9mH05+x42Rjf2f836urlJo6m06cN+4X7pgYgDfc2Ozc0tmi1lrXCrpHXyYMLBy994f9Pdxmyrb6e3lx4ChySHHn+b+O31w0GHe4+wjrR9Z/hdbQe1o6QT6lzeOdWV0iXtjusePhp4tLfHpafje8vv9x/TPVZzXOV42QnCiaITn07mn5w+lXXq6enk02O9S3rvnIk9c60vvG/wbNDZ8+d8z53p9+w/ed71/LELzheOXmRd7LrkcKlzwH6g4wf7HzoGHQY7hxyHui87Xe4Znjd84or7ldNXva+euxZw7dLI/JHh61HXb95IuCG9ybv56Fb6ree3c27P3FlzF3235J7SvYr7mvcbfjT9sV3qID0+6j068GDBgztj3LEnP2X/9H686CH5YcWEzkTzI9tHxyZ9Jy8/Xvh4/EnWk5mnxT8r/1z7zOTZd794/DIwFTs1/lz0/NOvm1+ov9j/0u5l73TY9P1XGa9mXpe8UX9z4C3rbf+7mHcTM7nvse8rP5h+6PkY9PHup4xPn34D94Tz+6TMXDkAAQAASURBVHichP3Xt2xJeh+IfV9EbJs+8/hzvalbt3x1tQe70WgSAAnQDClySGo4miXxbV70oLX0b0hr6UEPkkZrRmuRHJrhiAAJEoYwDaIb7dDl63pzzr3Hn/SZ20TEp4cwe+e5RSq7+p4020R88YvfZyM2/rN/9zsMERAAgAgAABEBAADcH/t39Ut/MCABIBIRgL0OIhIRIgIRIgAgubPMO3MUoX1P5kJE1V0QQWtg6C9KRBcaYP+PSEQEhHChnQSABAQEgIgIQIT2eEB3O+1awMwJ/nxC00CC//8vc1bVPgJ/Ipo2ABARs6Kyn22/AAgIEY1k3O3Inou+D/Y3K9gvawTCRVEQuS4wrLXKtsi33bZKmxPJDgfU5I5V27B2Cd8OTVZkHhdE2ouydiBBbSjt+1Upk/vB/BVGMmgHnGzLDODMsPqPtfFAspgjcPK4iGvzHpFIuzv4MXCDY2/nYe0HAAEI0c4D/6sTKVENjgTMSXlFHnYc0UCVABmiF3c1Ydz40MooVB+qMas65GaWux4gkiZEtOCwLQRw6ERE8211LS8tRNDmW6yBD8HInqpbmIbY8UIrPCsef6wfXyAgI3QAIIsgK2QkM9DaSd53zQqPwLcEEQE0EFY0U8nCDiAB1qQO1a0MTJlFnAcUMjKD4MZ/RS5oIWxELTwy0FEa1DBOfgo5jJKdI4YgbfcJ7YXRCsl1zw6UQ4oXMZHrC/l54W9UcWrVNqBao9HPInBU5KYu1eRMrrNYURzWoWbpybUKyE2SCql2kLz47Im0Ipn6qNUmT4XBSuLVqDg025G9OPqORGsaDP0x5KBG6IjTH2PbSVBrl1Vz4JkT3fRZuZvneys4cNIwI7cqHKxRVG1WVw1ZYU2wDcKKIiwFoMEnICPSgCunIyKrtbDSUQjAABkBA2CrOp4BMsd/tdlee2deBEjEEMlCzoPeAdad4KYa2RNrbbBvCMxVGCIAMEMClhgJALVlZXSzYHWeIVYD7fSv/89+rBG5GQD0TbCnIFZIc+A17SVws6zqnZWQ66vnJY8Nx4U1jWwJr5oDQNWguFPs0NQEVaETERkgq3Wypp+djqxdzmCsrhsBfAvAmW019UzGgvAz58I5Ncl7QToGcx3XRhV4kwYAPMUiYk3otoNsFT1VOwmtfMndiqG/rMcSrp7lB8sKjFzH6iDwl6gI6UuuUGu/w0DtG4sL5i5DBscAjKzSR6ia6AYVEIChRTw4c8LJDo2OxNW2WCPJcXBl7dSGhyGaMWJ2grguERhTyCLP/0Z+9Kz0EYDVeL4aXrTWQdVpOzxeSismqYGPvVmNxqoJWZuZUB+Xumirc+qnrwiGKmvCWlDVvfwkdV/WXBwPGnO+QZmG+ssSn0Ukq+upisIqAgGOyFYnWgUXrJ1iIeJAzBz+aq32H/3twLLIxQasYhrN6FUidqNnNaZrngZS3iCqAYgANGntNQTWpofnYSBk9tJ1TNSuY8zIWjdq4+UsGqiEazkU681GNzLOdKumnZkJiMzCnGEFBqjo0A+Bx5GXniNjWuFdcy5WcPFS8K/qSwcuzwf1Qb9gNKxcyp5BNYVPABqcMVqHZiXBmuKtdcga60YoosYOq0aVtfB8c1bHxBmK5sW8MYe2gXXLFZmf+mS42YKPVgyl+ssZtbY1K9Kxk9fbwn4QrMeqrZvlrSkvddMCc5gb1pp2JyIEBpW5y6xBaZFMTpOvkqzzCqp56uRLTom4A9C2k/nW1k6z56wwaG0M7W211hZUXvwV8/pjV9w4g1qnaMFRwoXxRCIC7ZtcXRj8vZz/QLXWVoEC8AyP9U9o5zwQMYQqMOImVTVBViaDa5aAyouvjCBy6s9JcGWWgcOhJ27vXWG9LW7swPlADJCM2UtQ98l8c6GmdiuIr0qp1iEfgbH2KzmWZYCOL11YgBzHOF+ypqexGjE3sM6tQCsEz9++PWYK4IVGec+ZauC2SHFNNW8RVnmoenl311JZxY6VqnGMwLzbsaKjKvb1DSATs7pgV6LDiGs2eLD6sSB/oO945dza+9QvB9oaOwBE2uk7BjbmgH5GW22+QtXoohOWHpnviTV2KvMIkIDXBsTadVS11MCXMXZBzI7SsS4JRLQxCERC0CuTpzY2F6/jfqkrW/uNu4kdQj+RUJtfGTorwnXLdhGsCrCGkJ916K4LjsTRqW/ndtSaWtOkNaPMDicyrNxLE2IxHffidFJG31Nng3ioOWPSXdtLYGUaW0l/Cda9MWrOXiHZVWiRNtY2klUg1S2qLoDzkwDAxZjQAcO1zzRF+wFBZIjcdquunA3EaLWNKz8iAoiqM/a+tckH4AXqNYUNctcutjKDa7EJ86WdIXZeQCVuhAtT35zAvNCdiC2B+fiEH+GaO+mJ34LVaQFnTlrNhZVycghw4q8UGkPSNrLt4vxewyAjy40rto8lW9MjYoZcrUK0POx64Qfcwh2p0qoWqz7wSXRhUNz9nD1qcWdMCh+/gy9LW1zsdKUhLZPVLuvGzjWz+saLxKgCN6CWC8kHRKsGG8TrSuujuxR5htJfpkdsX4RVjlXTyPN+TQtaIjF6AgCZ0eyvqoNX57E7wrET1rBQYyQHYAepSjDoEj+V2nOdrIte12TvXWcrympkVsOQWL+5HWhy0RwN2t3Qm6FARMZjW417VPepUbQzvmoWoe2XYStHMdWEdKoP/HxzXfSZOh/5r+59AbXGeKpM/BWXyd6uzvUrP7tb1O0qrExFGzdwTcVqNvmsIfPsVHXFslLdEvDviFZasNJUcEThCLHS7g4iNcohx4jM36PGczUX114daxqhjkj06SqyQR8A67QyP0iGh4x0GVKlwoyRt8oPNU0F4IOFYJzy2pFkjRQ3jk43oTPprMZyagGt+nQhN9+FmhTdl4DMRWqrcUArTJ9W8m11t8XVy6ATpj/fDS0iAOgVR57cXK6NN9p0gxuzGgM64gRwwS+y2qyKpNpZ6uaSmYy1/tbEYBR0TSPZ3lZQrOYB1c6n6gh8BZyI9T8AAKKmD9AzXO0M52QAAoB28LHIsdqkAqBpen2ae3qw/OS8GXsNp0BruRDbM9TE0Sejq9/QBSMd0Xg95Nvm3WWnRV2a2ArJd5+qP479UFfBkUoEULu0U/KV6oNVcw1rJwEAECGgrvl8tr8rg+0ZF6imZ8gRmLt3pcfrkwOq36nGpeD6i0RU5THtMNiuX3AELjLlBS1fM4tesQqs0q4JrlID5BSBgzV4lWHvYnNGK3RGRKI+XLUqA0AiYnaO+ytWt6GaCqvBxxmgNdQ7M7QSi0sokzsbYWXW2ps6m94lVw1xIlgHq67vap1y/1pjzBCzs+XAWn3kZvuqiLyh4gRIVir22uTi+37G2hdDIPIFE2TJsqYM0TceEcEk7ldVTjWTPStWVloNgx6vaO+8gpJVX95fuvrtohPv54YJGFXM61T4xUvR6qe6mbBid5jGEhEgA9KeJmoNsoKpCBdqdzd4Jc3sQZ55EEyG0waKzYT2YPNE467FAH1I2SPFyxSppmfRKkHjV+Mrwje2o+VEBLAgRjchABCIISESrBSIVPzmbk4ezXRhSNALcsULr4ubAMgOGNQNa2/DOt1c6acVVWeu4SI8K/UfAACga+G1uv706MRaY+oGmLt07Ve40IdX4yI1qay6VmiHsQ7ui69Xvof6ePnbXRQjuFFAQOZu6rujbatthQg5KazC3JkBorqgs3JYfSJQpW78Bew/lp5qsTZnXTPP32hiYisT0ZCiV1Xo/JVKC62wEyEAdwl3G5ZAJG2igACrI+rnIDilie6CF+jKcZj3Ro1N4NMxrr9OHVRcUaUdnHlDfgSweuubVOlAZziad1QdVJnsTiDOu6prrsp/8a6tlznW7lgHUDUPV5FVfeUUXV04vq/1l6eDCgnk4r71bjuh+p77QIUxbCofAPwQ2Pa493buM0ThjU4TSK9ZPwSANsRJxrolBrZMxjGGLZmstDhYlFteNQ3yjXDq3N4OkQi0uWzFIC5XAd79RjRBeT9dqSZa329wISrHkxV7XeRKsLzsZpqLuPtP1WiaD1UcwqGEHLH6SVYB1dl3KxNtldouxHesjGslIljLHrhv3Mx5hSYNXrXSF+9Sh1pNBAgVlNHJz7d2BdarMvOjAwCgq7Pc0XaukfMxLWM7Dc0Y0+TI04q7GlU7ALUbE4DQldVlhepiv15X2hGx3qizlS4YIzWHtKJPcl0Cr+UulJbWJ5q5lyM/p4Yr0DGHhopRfD/ANm/FyfNdcCzlm1u5q/U2+0m2ah3WvIXKlPDMVrXKIG7FFFiNDXqS9WlPp838B6w3x4p21TR0XhSsvrT+EnSuQtlpFSJH5EAARBoqVodXX+hqzcw4sKrOrvJg3XBVpzhDwt6W7DiAK1oCXAk8+SGtcTMAEYmqDxVMfdKwrsKdTVLZ4dY3NCZlBdlKy1paR6xoHWoq3BzPTFn7ilqtTRKn9SrFWnWHfMvgIl+B9wncEXXZVxMSHHVZOXptZOMHqwzsu2475+ewvzr6fA1WCsuOjFN4tUbAynS9ACnvwXhKqyPVf3+RUJ20Lxy8ckylD6zYvM1jBeJrAF2g0LIMVQDCWu02eYr2sqhg4Fy7ipaZayf6Y5xgrEIC1zXjqDvCcgMGKydWB6xOdKN0kBGgdm6MA7EZS+ZPNO4XVnks6xIBkC05XXGi7d1ddT06UTnh2pwhWfJ0QjJmADq9VXP+6yrWNMrXwmJFcUZq5q09x5kPXiN4saD/tzK1jOZf8f8qrJi7uuGqtK5v2avodBem+psa7mus409xDm/d+7FOur+i18v+zFq62rTvS1tVNchb+eiJAXFVesYFcpFU1/PVqxHU7Sn72c8KAGSrVrVRWlUSuebMgbeQ6vaMMSjc5LOpT+bTJOTQ4sRUHzQvAxehA49R9FekSuExqCpMnRFoNbKXOpqlHd4CuOBmOlOdKm/Dj6sbt1VAeCUF1vKs7uQMLNdARMYsN5ATXcUdVf2/u8KKIGtDgBVFrQ7NxdJPU88AlqFXzMqLp1eYISedSpWBo7bqfJfErqYWwAVD1pv7VHuZ08kreGfXkDXDmL+NWbTkmmjzKibMY7GPCEAMrRqtKQV7J6dZai3yxzicWHFr2yUbNTAFqEgucWBZqqYpVl6OwKA2+Rzuq3nsZ4G/O1q+9DyEANoPk9c1XjsjIFQrCMyksUq58nFq96zcoMq2Ii9Uj4aaiFYzPW4OuLmHYIN3hrMtj7tX1aj61VfE5DBR00O1H1d65tpbI3iXP3Etqt5ZavcGja7BvUbD5gQLxAopXmqreqreNKehicjHeKAKNiIAOb6qjR4BIIqa3lztK75C7oh1bq+pM4DKlkJXEvKK0VeLOjDjWtSq0+sHoHWqEQz0ybr5RizMpkur0fISJHdkHVTgRtTTrXtPzltGJw7bPquf7aT3g1ybLOCMbrJXMHRkqyJqp2rDWL4idpWqzBfeNSR/WSunFZRWCueC2eoU1MUTVoQPlnHcLPT5sIqGWDVHvYnpPYG6MKu2X3R5V039WsPJ1toCgY2Dej0HFVycTnLfoikWqfcWoKYyyY2aw79BgWuxuawfbj+UK/asuZsCDVb5gvcevI7Q9SF3bSOqFj35yFRlezhp+EY4uYM1RK1jUdnT5LtGADWZO6MC6sPjpoO3n4zxg+B8BqiKOhyuSVu4VVdHrLwmB0E3AyqsYjXwFt8rKc1KJhUOakPmD6op+BV68KKg2lkXbk1ElRzd5az/pPUFw7F2DPnTa9qmIkxwQ+ksnKorPgLgZr+TCQIAc0FFEK+6eBeYyePN3xMdD1XyWJ3TlaJ2ROeNB3S6vFrJ6kspbHIfyCwt8oyLzkqoId9cnrmWubkP1veqW07Wd6rG12YTmF/uUo2UBQdU2DEKyPbb9qVmjFVThhBqJdhkKrOp0hpA6GJS6JgCnbnm4bhSmuS5ocaaVB/kVbCuGp2VpF6Fuzf0/U/oxnDFKnCX9YFYkx/xP5gBrYjDOXHeHvBWTsUP9eSGE40DazWVrTpCFL65vkHsFbyaUarPXy8uN1fAfnthqiFAtc7QDr5vnGkOA7xwRs1yBfCAqheNmFt7oiIA5iI/tUOs9C1sa2e5X1eaCbU2vaJnKwHUPtcmEVVXIQACZMwMle1Q7farI+Mv6zFa1XTWKjw9bqi+DgEv5NBXm10P5l0k44u2WwWymmtfb6qbpWiVD67Axk1rL4NV2brEksGcFxiBcwhXNYGLJiMC1JwG31Xj6fjjV9EKQKSdarYH2JynP74mQavk/Ky3NYJ+7nvmswxVK5jytrR2PeeA9WXQxqm22fkKzmi9AQSnVmoRUqrE6iRCta5UMnIcDPWWGvlWysoitBZPcCxoLobOCwNEs1uBu5rTM26kV6jXt6Cuyv0QXtDvK15szZFftTL9l5Ukav6WVXHkvNzapPPjWHtfN0Jr4jMjiL7pVawJHAwNFIicU26vsDJ5bBfd5HJLPjzmvKVQV+JERKSNHL1KcwNPNYvrQm+shvdHEgGCttFL5x/VMOqrRTx32gahNxaMFFxAEaz/zQCYn97GIHIzAW0b3ayo1I2b/dWV0V2V3Kx3CqNyLiutbgYXXQS2ukI1qH40fUiaavfy08DwsSdCN8ArPOSIFi6+XtXydVr1JoTx2KpBraz5utKujeBFVK5onrpercbFCMWYs1Zr4kW95ia87aphHkCwa/otnfkyBwtQ5u21SklYFq1nUXxrPIUS2dhKzdbyU7Am31pNgI8Je+WOZOHlpoanIDQ/2Wag5S9mDE1HUWbiWkI17VxR9DVtgIZQWPWxzlXuSGcuufCi740RKlVaE2sNtgNjruLfO4cLyIT00NGM722N3V/hEg9716RVZf2K4qrmHmKVz3SvV/Vv/XVB718wlpxfRX5kXm2qlfwFB9S/BRsTcL5TnczNZVfsX/NXGA9BW4fXmHLerDbmgzGlK/PIX1o7oKK9v+dtu4DTfVztSN31czLWCGgKNwlWnA2oT/AKwU412C5hRf0rwnNmqFuLvKLWqU4GVFWSr2b/rPdDNW3kysN8gsHalq4S1BqxHtG2Zdqrb9+1uklQp6gap10A1Kp9UiM2x5SWuhyW6p7QhVOsUn6lDLQaKYDqQr499RGpCbyaX37+umnvRw9sl8n10hhj5Gax1zaeGZFZ3e0miOk9EGiqxo/ckLr8kLkc+WSqKVGprDqvx4jcR2N42LbXZOwNn8qgIbfiH/xccxkG1xgCIiSbx3fGluPv2pSw3GML+Wvz8yJZ+Ex0pTRqNoDlL0f4Fz1ia0qYA1cBRH4CVzrolevV/oPK0LDTG9D99GUp9S9lshqPejXoJeKPctPnFXSuaH+yd0H4khs5RHmrsmq/+b7SMx4b1jAwhqEG0tVMAvSmEgEY9S5s+xEAkEgTugSvAbC5AQJpS3Lmsyt7RABysaGqEAkA0OYjagrS6keobghAzLhsrj++BMMfi64prjyOnEfFfDoObHfdncmbMk6MjhGhkik4drkwPABQHVYzb8zNwXrXNZokr7PsjHVhaAAy2zq69qObnA43WFV4+lG3zhNhrba/dhBRbbLVSGTFE6pfz+uFeocuzlCopFGnbK+VfLXkyglOvFgNvVNZda/DKQy7N1g1xGjULOnaLCY3ZPYvc6rLmHBAREgayVl0hibde/dRW7+HnIOEgKTRagyjzmrDa/4lBAIN5O/nceHJ1azl8GLxzfSdNE10BqsLzlnxomsfeHQ6nxvQmL61YanQuUoOjmepJi/bWu3bZTdQQ388YlV7X4WpEG0tgTEVnJIy7XQ2ghWDd7NoFVXkzsRV5q4gBMAY88C6cMyXMN9/5oXO6fTvjWK4cIBv8IV/fQO8T+J0JPiou5uermukLXVZmK6YdkREpAVWBKZtNNNaPnbDLbIkWs2sFceWkECb5cg2SoeWVhj5CDgikptW6NzeWtfQB6zrc86ebaqkzSw2R0NNIVZIBANp9PUdXiSGcR3fO9FDNWntHIOV+zp2cFUEPqFZ1xYmt4AVKVJVlI/1YcKq2Ssr5qqoUoUrK8RatmPldZHsoTZGr77oYkLyP/eq3F9HmXVevHCwvxdjTGtdk+bqbLcdqc6xGs9OWfM1AGlwYncAsjAQlXNX67PxGMjxW4267a8X+1YVR5oiOiRwaWhzTbKmkRGXsynAarAqfl9lNbyPr62B6MwKBzg3uwlMagxsUt21xR5B9urmpu52nupq4rR2e/UZ7PwGBFczapvukr727qYozDIk+Ru7s+uRf2b50Q6CBza4eCrYyeQA7eysVzyY2rh4WNCrduorqL3gZlUm1n/B5kFXwfAKWFeuU2uDwV8Feqd6nEjrtsYFPNlOG+AJfw83HOj64F3WlXaQb65T7uDu5t9aB5YIEN1ax0pYRITuf4AE2hp3hMhspM5ONa+pKx/IrSQhNzENrZmW+CVK2ql6oz7s8uhXyn5fHbkaFV3IirkRIjdCteHzDgB4ovUwtarK31Q7I9NJzImOagXOXl0a/sba5ITqVuD7Uj/Rf3Q2d2XJOBOsxotVrOG/+NKuDTUQ/5csCqrnzMjSkLZCtA1FdNuEW1E57nOWEBARCdAK3C5CRiGx6sp2fS05Brbt0y6uY+To4gz1lqJFp2tnFTfWnoXRF/LYZpvF42jNGPSDh4jgksCaKik4/8iBF4zjh8Dq0TcAQDT7770KTXJtqA+w7UG1pIGYLaomyyNQG1lcGY2aLVSjXqgUIDk8e15lUN3JN9j6XNWYr4DDHWbbUNUVOIvK//uqx4NV3Z1VIl/Cu5WAnMfpf/dBPa+OV+VpNZx1sqGqqGXMCrhCtuU0gx+HFjczAQBA6No5XozV8BIoZ1hUVGr1lzOnzEy2MKvW/1SD5jDqsOgCjE58bjaTE1dFUYioSXsM2oll/Vj0Q2VdJOdmVz2og6PqWPXOXsRbVDbTa4KmDvHAKhl5bK4UZa7MTfd/x6P1e7pgRk0uHjkeOm4CuJiMNVIc7KjWIW+AuS9r7rMRC658dBi6CKxqvC98U+t3bfRda10Gvn61Sjms/CE/92qsbXFRc3D8SURWxZOu8YJrvXFINKHPX/kZX8+21e1oo6S8biS7gJ385gLoM+4Ort62cSqwYiKn1ezmu5os3drHcph4PjMhUtMd26zKEndAMZKoPlWK6aJ+9EDxuoicejJjagREhHa5N9Z53k4MN5o1xnE/fhkJvEpC1cvVfLmTatErj3U3aq6/7nDvaNYogJxX4MDiW23TMPVz6yLyUvpyI9VpMts28AY6WIm4G1uGAoDarsrmFj4c5wNB5nvh5oibr34uVqzjw2PGHjJCQQDS2ulY1zr71k0yMl5eFTsyAQVA96ZqLAFjCGAel2FxAGBdHgsVUmaQ7MukZhCBMQC77WhN7lWays/4C+7VhbEEWEVK3TS3Ss4vgXCArcw7gBUGdy4Ueu6vq1fn29aYg1ysEd1MA3D7/FTfVA2tq2T0zqXjBt/n+sUJPCtYE2+l81hxR/1enjRIVxu0ebl5G4msR1gz0y9afpbinAI26t+FDKsIFPnpBHbZcZ26jNTMKDKs5GAeplTjWqiAau0OvyDVwMrcQCmjmD2JuPwokSbyMkEAZfvpLoHM7UNid+Wyjq/W5lvGGCIjAoaGMO3mPtqqZGdqVO5dXffXMPHlH6l2rMUSVOJmfjZ4jqX6mVYtMqpRu6Nc9EJ0NoNtqnc78BWjfqVtVq1Wprj/FT0OXqFS27Ia8znlAwSEjDkt5OC7cpIfb3BNttxYCbbePHKNdicanVyFhowEELVNEFfnVHdEBCIBXBipIGOOqJzRgd70NjF0Aqv+DVdbY8BckYiYk5fjXSs9czuTDTdJ/yrLhCC1BkclWmvrWiETnHPBOWOCMc4YcqZBo1ZGFsgYETFGjHHQhNzOZmYqQ1dTl25ymIhgDXernsGFjysc7DjLO8jVWebAGlW6UXLvHI5th91Y+AHxs8gw6KujXqnyVcX5qkJ3N1nRCeiH9EsDogY32kUi0ZGoOx1qgK5+81h13Fy7v7ODoZo5rArKVcrd2Xg1S916jE7zAAhVKjsa9kwCk4i3ZgGRX7pOdrJ6Oge/vgpA24y+9X+UUjVvirS2q9nMUVpr86/WWmmSWhtLxQCUMRaGYRAEURhGQRAFQSg4h4BAkyzQApRzLoiIkJGt5ajK1bwAqllZCdjHny6Mk51etVH+Eu3nTqupafexUqsVBH1iE3zLLiDEfFXxtgdkjZc9MtDbEDUurJuMF3SCw0s9gfwlx1RMVwnrS14XVD84f+kC6D0xO4vDuNB+LrHaQ+h07ZJGnuQmhZkFKObzBQGYImQyiXUiItCkwdWAGvCBVdlau9JQTaS1VlprAiBSWkmlEUBrJZVWpEmT1koRaa2kUlIpg0JFpElLJUmR0koRKSIA0lJqgEacdJutfrfbbjTbjYRRzCAEhgSaVElaA2OccwLiEIBWZtg0EUPGnJTr3i6Ck5gdBled5ATqe+fH78IoO2u3JkkEb+pRzWq3I1LdF83WHZ7DLcOt6DSXU6lFcNwlPNjJq9SaQqysuaovuFJtWPObLvo9dSL/z70ukHfVPKfE607eRSuFmN8qjaxtYztqL0FeKCst9d0nIHF4eqqJSJPSSlkgWn6TSmvQWpOSysAINUklldYladBaAWmtldIGjUprTZq0UkoTkDKRLQKlNQNCBK0tzxkAmC3kydpwIKUsZamknAZhmWUcMQ4CGQQ60GQmBihd5KA1IKcwAGCIjBEn0pqYzUe7YUXPGZXzawMBUE/2XBiPCpjODiQCt9ae6JURssis2JQcbNxlPOGhHQJ7nDXnoX5B0ySsobbGlNW4OUPMVxf4eB9Ahc7K79b/GSS6pNqXqH1ErbU1TAl8s2n1YzVZX+FRqBSYUwhA7umL5KXh7KKaTJ0yNH6c+IvPPlOktdbSlN0ppbQyjCKJFGnUWluKNY9DJQJQiAjELVUh5wztw7IY5wwJOOchIuM8YAw5RwDBuODmxRAZ40wwzu3WJoyIpCxny2yZLTVQGMWIjDSVSinTJIZaS1mWWkrOOJBGAsZQK8aY2ye6St/UctneC4aawCqIVGWaflCdYe1iG17KLvtSw2ltABwTOEh66kBnETjpV1WjFg2OiN2Yr7QTwJWr1p2h2hnWliB3NUMzVvXri0bzRRhdSLsbO9Gk170cTHPqhilU0Ux78cr4uKCAPMOS9dWNsQgMzVbylqG1k8bKfBMvjl8CczAB5IiaMYYoAELGGBMUMMZQMM4Y45wFXDDOGWLAOGPczFHOOeOcMyY4t941Y/ZXxgLOOGMaUDDGjW/ODHsyhmD8dNJUynKZ57PFYplnSymBMQVEQEprpTSg1qSkLFVRaMaACDnnSmhOmohpAm5IBd3WobWcvNfm3vyq2z6vDJj7vsaL5ELCZljoyx2O2mesX9iF78AbBubZZORrWQzIqhv40h034RBXblG7tYWUb3HNVrnwpmraqnnjZ9wFB3/1xEp3V5ijCtNVBM3WuGF1M3Q6xFbPMXc3++1FjVO7r7hz5ZrhM4aMIWOccWSMc8ZQMAbIiSFnDJEJhgyZ4Cg4BxcEMgJx33BEYAZ8BnsI6DeEcfEiRHNBZIiu3Aq01qWUQZgJEbClKOczBQSaSGkg0kohggZSUpZloRkHRC4DCjRpTVoR48Ye0mi8Om8K2j92eBkD68lZ4dVs1FcGcXVcycMCCejL3eGaWYn+LuDcT7COl0v2VosPnS9sdzJatRTrd8cauMz3nv/cBHIHuxZdPGPloyM/589e6FCVWa31EWyEa0UCDmtE3sjyxIsmaoPIjD4yc89mIaqQ5Uoc37dU3L5+gwA5IgCabClniGiZzizfMTFH5vddAIM931rkzGysY5G5kj90tQ5+3yKDTdd4sIar1sBYqSQXQgguEECTIq1ISyk554wxjVqRKqXUqBFRBaVSkmsbuTKz1xqZlUXoYt0AAOZhzL6itmZMeRtA/2ez0s7YW40xoR9cIlctZe4DaJW7T4IQ82ZFzfkwDXPjqX1hg8U6OUlWBkTVYIsEK9tawyqGBHv9L+P7evyoupG/qZ9Rq1rF0179ItafN5dy85Tqixc1gLaocUOENhpei4DU5oIhXjHoDmx00LAdmKQ+GvZjCIzZ+mDmaceZHxo0kOFK8M5P3QV2g+aF5QrJKj0LyDiBZgDEmOCcM64ANAERlaXUoTIxKamUZqQJlFIEWnCujXlKittZa29V1Wq6+2o7kljjpdVaAagNTDX0F1fzAFj146jXkl39flj3GzQhQxeItvnZuifrTMlajKC6mbtPjVNXScvGfSpqdwL31ii5ca3FLC4SKq7gr24ZrDRnxU4186xmkddVv+diJ0DzzpxONV7zt7GGgTvTm14EACJNG+TN3hW7yTGmITxrL5uLaCMTRjasiojA7EN/q+fOVdMCAVETWE/bJZycyQyaMaUUU8r6UMA0oo1REZlgFjA00VklpQaUQkitAyLQJsRkfR2sZaidbVPPeRos262ia4NtRV8HQT1iYgiSwC/EsMTnj6xsxBqNoRegCyMgoDeOLbNa/qxOdjTvFLSBGtR64gas1pTarb19XKnvFV6qRhhr+W+HVLTWkY9ouQmwMgeqnK6bRN6aMrVvnuEr46DiAMeZq1wMdevLhxcED5gpF3GGA6GjT6uyjUK0T1a1IvZPtNBam0chIkNknBt2dKG4+hYPhIxbBwa9dDSR0hq15pxLw9wAJnVEJJXDCJjnfJLW1h5FZUxPE2PV5GRBBLbFQCuKnhm9gs4Q+LJXHWc+vIc1vYHgRg9tssN71nWCcQOBhCiJSqXnpSIgqfSoKENr7iNn0A1EzFkoOBg5eHuUIfjgqysaomowTU7O+kW+M/WQ5EVQojdzbWFEld+yqtweq0kjOi+whuVagMny3MrtKiaqGyy2yhGs0jWttRh0ee4LM6ZSTGYmCIYINoJIYPdpRLNFqDMWV0pdjOCYQx4xxhCJMWRMIPpKOMM4HBxKLHAMl7jhRBeCI1JEzMSLrIvFCMHYpkor52QgIhKCVlqZ8KvWSilhph6ZJ0BY9XNhlBxp2VX2tW8qfq0FvK0qv2DZ+fY6FeDlXrsZESCWAOeZPCvkSVZIpV8ucwYwLItny+z1VmstDJ4tFuOs3EjChMHlNHq73xrEETP13eiKtrxt6ywQbxxo10nH5Gj+rOhlwzfgZ5EjtJVwmGu6m5+e9cnpzBqyqPK4qxiq41Gyyr5S3ZWfamdD9Wgh8MWu9taVavelw0RAJAQPHMd7i4V57kRE6zbUuJe7xI+ZWWicJEQAE2JyE4wUQ9SARJoZHBO5XZerdhs4Kq2ZQSgiMiYELxhqK3NGWiulgKEpvzD5KSWVVgrIZk7N0xlNAQqSsTeo7tPolUyF92+8PVl5AKb+1wxUXdlVgvfoQKdSwT5kWxOM8vJgoX4xXe7n5Wtp/GJZ/tHB2d+8vMMAn40mCpdf6YZnRfHT4fCG7r3RiP74ZPiz02ES8K+vde902pHggd2f0sKKKhihtTOqSWOa7cDg2mg1qOuy01dud0jfo5ozvmKlIK5eCirU1V5klXJl9PgfrDFji9uwbhzXo1D+3qyGh4pEAYTZU9Bk261zZOjT23FY2UJeZbKqQtR776ZPtrWmDWRntxWDcTYrNVrzlxmiAkCzAYrRAwRaa6k12YpVICDiDBC1Mll8pU0+n+z/TaQLyC8M8ZrL7Thfk2yd+KoPq06r0QMXvqyMeDMBEM6yMlMkNQHC0bJ4upTExJ+P57OiuJWmz5fFrVYzFfywoGku201xuZFKUqdK306SqVInhA0hNNGnw/lZrkNEjriRRruNOBGstuUuOKfMzpyVgIVtk1epNbeGISIoRUopzrkpaLzocrkhAa8bV3/03zqL3JUXrMz66joGZGgnjtM4hndr6HQ2qzuiNmbmhsLoOO2SMM7kQl+fx40NxGqVLAxtOIQ8PNEiEam2PR6iCSK4XtSMPMttruFgzWAExhhDBiYuD9oSu5tohps1aa2Mc6Q1aCCNNZbxD8y02sr/BRfPcErJDzm4GpsVNPgxc+LzysuVYEMm6dks35tn+8v8+TK/mcYa8EVRPsqKrSD42qB7fzrth0E/bq7F0X62vNPpXG00/uRsuFB0NW48Xi4Zsq93B52Ac9BPZvMfHJy/2UhezmdLpW42G391e3Cr2+LMrMujWqOdJ1U1zprH9aYSQFEWk8n45Ox0Op0mSYLILu9eardadeuxQsQF9e2MGPQ/ei70hFdX4vaCPodXv6iuyc+TKTqTtzr9glkmLNUwZ1YhmuyQUwJ+dMERKJrV6wx8N4CIbLmxScjVXDbmGlRvMtSnSQVy5meI+wxSKqmUIs21tqkBBBOd12TqWWoPEAULP+fLO6+0GkbyITHw2sTGSiuLvVLfgC6GVJ2liealmhZqoek4108X+cez+b3Dk6+s9UeKSlWOpD6dTIjzb3aai4D/fDonhC+U/MHx+Vf63QDhZZZ1gvDr/cFhtvjXe/tNxt/ptglxK0kvp8nxYnmz2eQIZ1n+o9PJ80JLwMtJeKMRBojCZE2AGDIC5986Kw2cp6K0yuez8dnRdDKcj89OTo7LUgVJIhiWs9Mbd95tNdvWG6gpblzVHjXYVuq/ApeNDVUAtiIj8FEj8H6pw/HKZKjF/ZzkLZz8FBAm2ehs4Qo3zHozoLX1ujz5+1VfjKFG4D6rROgvYAK0WgMwby7bhyvWnWXTDJsaJRKMG4s2YJyAFJk6FucEASEyYIwIbBWVUkbFO1CRBmKakNlyXnebCl7WiDRSXrHNV1Q3+GnphpAQl1KNc3mwKM6LcizpSV40uXhRSAHwm1cvZYg/G082wnCsqRFFqeDHUj8r5PU03Y7CoywPpLqWJNea6UFRTkspEa63Wv/w+lVQaiELjTjRuBbHaUSfnJ0d5vKtXvtUqqcno4NSDkT4rU6cCNZLEs44J1VqnQq2mYTdgHNnr2uCIs/zxXg2PJ6Mz8siAxbEcXrl6g2ttdIglUSZjw+fNa7fFSKoOMKPBlgoYW0hHq0ypEEsWTbSK9cgiz2H8HqMz4q5fpzHpfeuagreFCw7T8H+4w0rj24XtvOmmLml1lozEzdyKqHiWyJyQVoX6vMa6cKLuTCK/cgYmBQooiyVUorsniXGLjXsTsYGVUq5v1KEIeeccVZdx6ooqpQ7IFAtC14Tra1Xssay5RJNOJf6PC+Hhcylmin9YpFnSsecPVpkzxbLd9qtsZR3koRINxnrBPyoLG7G8d1BNyf6s9PhdhJfTZMux+Ns+cs76w2OPz073w6CS1Hw+Xg0kvp2q7WdJjpb/M9P9jbSxq2WjgVrJqkupy8X+bv9LmbZ3Wb6YDJ7kKnNNJAlU6DH2fLPjk97Ufhmu7mbhJtJ0Ap4isCWk3xyfHz4YjqbIxNRnMg8V1KJMMrzXBPkpQwD0dVKFpkBKFWYpAofiLWUhFd4VSmfobP6BIcqfIL2gZzWACCP75XsSC18BhVtgz/AkKbQCOgD12h4DsF6sgZAFdTJcgmgjwZXNmulBtBW56DddsLYqG7WVOkytLF3v1WYMRy5iyAoIKW1sVBtbIvZ+aC1zmVJi3khNVssgY04E4ssWywzpZSWstlu7Wxt97vddqtFYAIz5KkTELkLJeZKj7JyWsqlVO0wGJdKk95IotNc/fTkfJiXW40kZmwk1X5e5poEw6KUSymjMPx0PDs6PV+7uvtut3W0XA4Q16MgQfpkPOmF0XYcxYi/82Tvdr+znSbdMPxoMmkwvp4mcyn3hmNVyLjbTYVYRsnNgD85PN6N49NMjYpiLU4I8c9Hs0BTP2ncHgx++/GzQZz+rZtX8rL4i+mCR/GlNPro+OQjxot8eVvQr241OpBPxpOSRKc7KJcLqSQRLPNseHiU5wVyzrhotdt5USpdZ74Vf4nsGFUj4o+oRxRW8+ZW9TtF5wvoTa2f110VF1vDb8Utq13QfS/cvr8e4PalzdIf+zUCaO0XalS8XNnYzPkSFT2hjUFgbZcRtmIQg6kX8QEG43wwxjmwqu5Fa9sYIgSbONCkkUgr/dkXnz978kwpmeXlcpmVZQmAUqoSIGk01zc2vv7BB++89dZg0I9EoAlM6RYBHGby49FiUcqCKNT6YLmcSxkxcaR0qakjxHFe7u29vLo5SJLk0TJ7NptvxmEvDDnnL2QZhdFQqs2Qp+s9xvl/Op+cTGedZvP9XvuHwzHXcq4UAd+bTlRRrsXJN9b696ezo2V+pdFgjA2i8Eaa/Nns7Acn56+1Gg/my93B+neuXCatn0xnqMVGI2VIf/Ly6JsbG3d77YSx0e7Wz84nT+fLUqujXA7K5R//xV8U52fddvKd3f6vv7aN+WiumAiTBggpS8nEYrGYTmdZXsyzPF8uAUBpKKTsdTobeU5Nb15axemw4j0IN96v6D6bgrH8WLcNLQq9f+KDfQiepGq4ruH1IjqJEEB4uxacpqOac2R6YJxDi1MA+8B2RN8Td4qzPQHcNPLTjlbSW1iFwBnachPS2t4XgTPGEKV57hECEGmluOAAwETAkJli3TSKv/bee99+/71QYLZcaFmGYaiByrw4Pj394uHTn33x5J88evwf/uAPdi/vrvX7nW43iRMWp0eK/8nZ/GQpsdNm3c41rXdb6SBOzsvys8kEcsmD6Ea78e3XbxZaP1vKe0/2W3H8lUu7PxsOl3lZar0s85SxD7bXP5tM9ubLsdK/sr15UpY/n8wWSv/1zY1mwP+XJ/s7ofilqzdPsuLH55OlUkEYbUfBh+ej02X+br+FAf/sxcH6jau/tt7544PTf3F4+pev7WrGQ17+dDj8eq97o9V4vszeKPX96fgP7+3dubqNoH/2+PFkPNHD03gyvMKKv37r8ru3rz1/8UJK2eoMgjAOWcCV0oBFUY6n89FkEogAkGnSSqv5bCGVRO72jXMPr6+zoBkIH4r34ZaqsMIhEcGFgVfY0PmfgATKfOswfbFsqrJXa34/uYCa8EBHRKNAnaGADswO38ZZcfmsGt5NyNcarc7WrNL/jke9rWubYeLhPh1qVAZHxl31HwEoTcr4Ssb+AEacE0NyMVBEFjbbrThWOJxOJxzCVqfNGe+t7bz/3ge/cvjyP/zHP/mLB3ufjsZRFERJHAjOkJEQDalDScsghptv7DX6nz8tO+uD1zd7b7Waw0Qj4wutn07mo6xAwTe31iLGPz4ZZlINWo290SgbT1+/ujspJSPsR2JbiJOiGEupFf2d7bV7kzkCAuc3eu1lkf/7oxOYL5uE37117c1ee/Li8NNHz9/ovnW91VwsM6XVsNC3e53z5bIdCEySHKh4cTxpNG/2eg8nsz8+OT04n6go+s7Wxh/d++Lw44+bctmnrFku/8Gvfvfa7ubRyZkIEuRqMpk2GzoIQy7C9a1eb1DOFvnp2VmZZY1GEnFEwlmWl2UJvmbF/TFFyh6aKwxnQ0s1a69W8kI2a+n50cfIvQvv4vn2GBeKX3UD3I0qdJF/ErZN4tjISh3f4AKUAJ7MwNsnKzwKjvap7sVVN3fUT7WPAE7BGA1vU53IGHKGDEmTVtrsUmviSSYWZbP4WmtSSslCU5o2tNbHpyej4ZCIAPlsWfbXtv63/5u/8Xe+/0EvjXSpymVeFlKWCpVKApZyakzOwp/8UfqLHzQoG8/mP/3wi5PzaYzseLGcKV0w1mmlJVDAg1Gp749nu61mNwzm86zf6dxstRZSf3Z8dr4selH8cDh9r9O+0WpMNTyYzv7wky+y83FPhM20MRCiuP/gW1uD1zvNo6zsJMm3blxZSyJkvMgKPZoQgx8dnrJF/vx0xBgfxLGIo8eTGUh9cD7++fODnX7n8lrrf713//H9BxvL0eXl6QZkf/d7X799/dLZcEzIC6lyqaXWIk7iRrvV7THGo7R56dKltUFfkZZlQUBKKhHwOEnSpGkItD5YdVDWIy1+lGsaGS06/UhaU8GHP8wPzluqxanI+TIWMzVAmFONKWlDjT7s6ljScJsxSohcqY25LlttguvgCuzq8wEATMUeVZEsHzNykclVZWGSSQatZqWUTxeZ85BzYEiuyomIiLQGCOLm1sZW2mwWZTkdj4UIGs02D2ISjV/61rf/q19+v9+OC6nyLFdK5YssL0ogEEEQhEEyPmn/7E/ig6da8P3ZfCHlWhTebSRrUYCE+eHx8Wiy00rbafysKJ8u8tuXtjd6ncfzvET+j+7e2mq3fvj0YLfRuN5uX0+Te8PJ+eno9uXd/8PX3w04+3S6yB8+SvNlnjYbYfB4vvizFydXO+1C6d1mM8iyX+y9RGDv9zvnWfbnL4+HpfqT/WPJgv3R7F/9xedBFP/GzvpXOw18+fzwP/77wd5nV2LVbkZ/6f23X7t5czKZZ3mZlTJJG51Od2NrhwXhIl8OpxOplSyy5XIZxvGg12EcNcE8K0ql02abB6FFnTPT6tyJF1iqBlX71423c3TAsaP5T5N9KqXVzn6sL+LjwsVdXZVDPQiD+9r/apaEK10jUpaZHeTRwdZAyVycwFbemeXv6PS7bZq9FgBW6+YAwCxPNv4+AXCzWIQxk3GVSiljJBCgJptFsLaBJtKgyGa9GCadQbcoRsPzuNWJWx3ORZnnRCgl3X3znUYj/eGH9754fjJfLAVjXKowChWRJCBkLFt2P/qhvvra6Pqbe6XaXOs90jCV6mw4ub6zs9lMZ0WxjthP4stpdH883R/PfvXapWEhrzTSPzs+H8Qhj6OhlM0o/Pzo5FYzvbOzmRBcbiTHUuk0vvz+ezHC6WKpBH+a56+V8nQyHc6z9NKV0enZv77/bDMJi15/VJb//smLv3p1a7GYH7caj6dxJ4nf3N0a51mM1B4eru0MGo1oPYq/8vbbZ+enWlEUJ8V8UZRqs7++WCzCMMnL5Xw6ZWQWIOSqyIi04EJrUlqv9bob69uMcY8LeqWi2dNqhVTnTtiojVvg4egTKgL0lmC1SLFyVKy3tMpnHrXW4fLeC5FA5ySZiCZaRV7FjKByyVfAjz5XSWSr5SurwrXbTSi0hgn5yaSJfATU+PLovHizqQgjsLuEABn+RCBuwlCMEUMCWwSq7R6inIfRzuUba+vbwBgQlHmGZu8dFhBrXL319s7ulSdPHv7gF/efHg6JSAOVpJVSoDUxZEyw5w/ak9Hk6t3DNKFlWU7nDOnF2SibzDe2N2Yo1gi+GE6vd9v3xvOzvHw+nl5Pwm6aTjQ0EQ/mixKI0uTxIlsMJ3/88Nn1tf5RUc46a41AnGbLH7w4FEm8EQanZdlJ40+OT5iOwjR9Y2Pweqf1cDT8yXiaPTscb/S/c2nn/mgssvmTZ8//jFQrTXZvvrb4eLPTFDHAW6/dnExGRVlGcTNMmlu7V4MgQsYG61ukdZ4tVWegSZ+dHIVhNBgMnj2dSSlLSbnUrUYzbTTBaS1HiBVisF5O6iHr/1S5X/LHw0pxI9aQYpx3unjNV10lspVKzgy1VxZmkZnZVttSofV2NBGzsSJvWyKYQiY7Xep+FIDzlMB13J7J7PNAEMyWoOjMl5olXrMJqgirJgIyRSGkAbjLNzKw6U4FpJQ0G0Bo0JwxHoSNIFCl1EpiEAAAaaW0lnEhyzII0zff7W3vXPr43sM//cX9WVYKzoHZjf+AQDEmhsfd+bhYH3Ru3u5t9I6VHp+NFkl4mhdX0+gP9g+CIDgATNP0pCieFvn/5+mLnAevt9KdJJqXRab18nR4ab2fav2VW5f/zcf3ck3fv3ntvbXeVhp3AvE7v/tHrV6/tTb41lpv/+x8/9GTN7a33u20fvL85aX1XqIndH58NtqEnfX1JAl2d+/tHd7qdw7m8x+fnq+laSuCdhRtrq89e/6Mi2iwllKWvZwvNrZ20iRhnCMXiFiWeSnLOE0n08lskbMglEVRSinCYPfq1bTRrFtZjlIqrBBUQEQHniqcVOGsFj4iAl+D5QHnrghYjXi9ntmPexW0XHXRBAOosoA26WPylszkOc0P2mhZ53FXTOlor7KCtc3jOyOaTOBCE3AG9ikNjkqNLLwv7xIRIJAxxjQAkS5Lu+rDGMIMGXAOQGSynVqZzLwpSWYMgyAMw6gsC600ApRlQUoiY4wzpbFUvNW/9Je+uXHtypXf+dOfPD04C8JQSqlLCWADEIKx2dFxfOX6r1zZ/pOjs5dPnl+6vPsP7t4aBLwTit/9wY+//s2v7AXil9d6XY6//Xs/uHn71q/evLSZROfLbKzkj5I444It80QE5csDJnV+5fLpMr/daYYigDC8tbN5rRn/7pP9NIoHzVQge3e992I2/539I5EtMUlUWeZET6bzF1mOefbpwcmdy9v9gxdqMh0TXt3aIeRKY7/bHQzWCHkUJ0naCIKAALLlYjI+f/r0yXA2LfPl/t7L44PDIs8E50zwt95//627byPjX+I3rGh58t84twbrP7n0v/PcV71vMFrRee7GOoSV+tHavaqleStzxrpDfomE+c8kkJSDhMsTAXM7Z6IDZtU4e4/6dYlIawI3cQAdRRH4HXZqxGlX5ZkFyWaxMoIpgzfAJLNRjrkqAuOAqLV2G+5ocy3SWkolpVREKIQCKlSptVKIUmsNCEEYJg1AXkjc2bn8N7//S2/cugKchWEYhIFdGsiYGGyEb7x5OJysJfFkNIHz07VG/OB8kkt9KQxZXo4KdX50KoT46b0nSZ595/rOw/Hsf7n3LOIs04BRPJ/M51n2yWiqW+13b9/49u7G4XT+py9PPj6bRIO1uNe7M+iCkh8+3jt/+ODl4ctPx4uvbq31QWWHR+XLl51G+nKZX+40Xy7meZErhi/Hs9cuXc2v3N4b50sl86I4GQ73j49m2ZILxjiTspiOh2cnh/svnh+eHi/L/PHjJx/df3h0epoXRVGUCuDt99771b/y6420WaGz5q1QxZAmSISkfeVXVTHngt6Ol6C2kIicQQguUF5zhWHVzLW39HrfO2fevdEE1kmy8we9c1Zz4oyuJ22YFarl9cyayWQdcFvN5EnQGwcOmQjKWZwVrP288QFWH19AxhHNZk4m1VnNVcZN36T26XhZFEXAAwh80Ax4GE3nhVaSEZVFXpRlqbUQIgwEY+kyW66tbf+176azP/iTvWeHTAiBTGU5aJLd/vuba6WCvWUxB7z19a/95ntv/mj/6CAvf+/B81/6+vvv7qwLwf7s+Pzw5OybX33/g8u7+5Ppp0enaRz/9MMvysn8O3euPpjOPn70vNVIw36/1HSp1/7dR3vroeg1k79398bz4eRXblyJovAXxYTC+POz4U/PR7fbHX0nGvW7T8az5+WLcDQuk1heuvrkxREFZ4Moij/49otO74vpYePk5PHJSXl4MCuKa5evdjq9KAgF50UhgfGk0QrTZtruPnn6ZO/Z86NFxqNo48135O23KYjspgw2W2O1qkeJ2yTuIpDQ4bHyeIynYcfx1bOoslj9BHCQAq8w60Reu4r3rEVNv3vYoAM3ObSRS1qBrUElAPug4iq+5U60H8nRpJ2E7gD9ZR4cY0wp5cwHZMg45yCYLv22ZJaABWOMM20iZHZ/KEVEoHUhS8pRA0AQBoFggne7fQIqsyyKYqVkWRQSSGlFHCLOVbZoN5pvXrt8eHSuSsU5E0JEb73f++Z3Z8ienZ0/G03V6fHVW7cSxiTn/+r+0ybAd67unGT5jw5Op5NJ7+zlla++9YuT8zu9VtpI/69/+vO21v/Hb78fCnYljf/li8PtOPpLV3YDoJv9zheT+Z+dnK8R/+nJ6IeP945Ozr/91u32G+88f/T09NHTq/1eS2nJeLC+dnw8/MrG2no3/eHpmItoXGSUZTwKW7NpHqSfdbaXhyezklGmP/zi0eP9l+04EVHEBUdNQoggCKRSs+l8OZ/P5gsVis6tOweNtecvT+9ubtzptokqOxIvQAds0bNTxwR2IaTVZl6Tm/p861v4wn9vC1TDXvtYMyQu+PJOnVb+u6Er4VyiFXSu4tU8/tXu+Wkeu+J8M9srH0+rB9LQl8LXwhb2mNXb1Btt2Q/ttiO+np501VOzSZgmUqQ1kSJSWmktzaxSZZkTLAo5lZRGQRqIRpxEYSiVIq2zMl/mWV4Wi2w5G48GrealS7vRR18sdC6JSsQrN26/s7a2HgW/V6ifffrZ37515Vt3rs/y4loz+eG98d2ttTQMbkbBt9Z7P1jMkktXbm5v/+zobA30tVbj58PJBsCTefafHjzd6nfY1StPXhz/84/vh+2mPDvvdzu3+90XPNBad5upSkKlJGXZdn/wl1+7EnP+dq/5//rki48++jxsd7qN9O1ea6PX+6Nf/OLlF/f0u+9/9fqVGxH7BZMPT+U9GRXNHb7VvDzo6pP9h88e6dmcsmXQ7QftTsCQFblOGjpIZKK6V2+xze3ifHI0nf+Hx88Hb9weRKHN3VUJnosq2EdtnBFKDn3eUvS1oeji7WiTiXXMEUF9/Z0lvi+p5/8S3iISjrpNjGllJXj9Kmj2fbJGJHFCRNRoV4m4xaquYS7HQKuzBJz6tu/rFnFVr4cm1RkwxhkDxkyg3tZ6AjBAYynqUmqpSVkzlDQpIk5EQEqrZ4viRU4fjo6+1e8ca70WikEkEo4NHgQRB025VjNZtIke7b/Ms0Igiig+275+XyT3Hr+4FIpmp1X0Nvfj1h8enH3+4vDNy1vrN6/96MXx/XvP9Hj62nqvc/Pm3icP/vjpizIQv/34RdBItzrNNrDradR/6+aPXh6dHw05gztrnTc2+3+ky0ir6dHJV9fX/vLO2t1OQwL93//t758+2fvub/x6X4jf+/mn7//lb8Jsyc5O4/7g4XD06U9+/o9/4/stpSGKQtAfH5+Fm4PF6dns8/saUW/vKKX3gkZvsD3OKNrlMeK4VFkpRZr0moku5HyZdS9vnyslCorb7XLvxdPp4hcn59+9tMVtTBG8H25QZiOXThGjz3xXbpLRcuTYrgYRYyNcMOEMImrq/kvReeHl4gAgvCdk5kTd1PCAM61htr3Vii1m9+02WsEbl4gAJururQUfob3QCB9MZYhS+4fQGLGgN8G1RSFppXnAiXHidstvk+w0u0WadZ4AUEq1LPWS2EjqAtnPRrMNwX56OryWxO+t9Q6L8koieo2dU2qPqMiam+9/99ep29dJ40qURCI4ni3+4vlBvMh22o12EAgOT8+Gs9PzvNv9+2/d7At2rxUfzxeT4fh779zeTqK73VbIdh/PF//0j3/y1TduMYBbzcZrb9w6/K3fPz487dy8cqvV3H3j1j/9wQ+fffT5L/+jv3+yyH/nsyd/9yuv77aT4WL81Uubd/utjzb7f3R0PnyxH4fRP/zG+yWp//Hx4//13uOT8yEfrH/3+uX/9PnDPwQaPHkM52fq/Q+SjTX56Gk/ChcZUiFnaTvrdZL5rDw4WQQJRK0tmg9Ph0W3y0s1PDjevHa50+0eHpz8OE06YfD+xqAGn5ptZkfcfqtJGVPO6l+LHLt7j1PmGvz41gKcFWdhnUJrnEU2RLpySnUWgHsON/qZQC6uUKc6e1Gbf3W7c9U4b+VQcCit2b/abeC7Yu448mbVe+IMGWMcmbYhU1TalYuA2amXkDFiTCOQtIs7takc1ZqASGsk6gbIQA9CETJ8Iw2/PuhsCEFF8e+Oz3/rowfPxvlBRvtK/D8fj0927m5+8K0fp4PPWPy9zfVf3Rj8tWs7t0JBp8O/fm3ne7sbf2Nn/VvXdl6enuHZ2UYgbrebVxrptSSmh4/6ef52v/One0eHk/lbvU633fz86GxG9O8e7k2z4u7Na6zZ3Gk1vhhOf3hwdnNnKx6fqPm8FQVxt/l/+fnnj18eX711c1jKw1l2Y2vj9x48Pn3wRSvkDycLhvyDOzc//dnPijDavX6tH0bdRhw8ubd8+UL1ByyMmUaxs71/eJyfDyGKIc+ns6wcrHciwc+HmaIiiUPBTk7PdbsVhsHJ+ShupE2G2WL5o+cvJ3nhuIB5tDhWQmujGV+35lEAEIB23jmRSZsjOq7FOor8WPuPGsgZDB4EUB3nj6zhiLkIwCocaxetx49s8BI8z5nWo9bVkmp65ZZQY3W7i1BNibglm7QaRmCCm+0btNa6MFtCutYgIGPM6HRt/CSllCliMUvsEQTovuAfdNtzRU3OTws5HU42onAjivTp8OnB8Rvdxgfr3WA8+fMPP8ey+DubvQ86zX/6F18cLbPrafLffuu9S5e2/ulPP/39Fyf3zydf3dncefv1MghGhQwYy5ReRDEUy2eHxzd67buDzr85PD3Pyw+uX7qxvX6rlV4dtH9wcJakabvdRsCvbHTbcfCLpeSt5uPHTxDZB+u9FodZEG698VYnEA9H0504utrr5m99Zbl1WYH+83uPu2F89a03eLMzHo1+609/ErQ7jIvpxra6dpNzoYgKWYrTo+z0PBusi80NkGq+zKnTjjiGAStns7DfbbQaAaLotAVgpmHz0hZjLOS81PoCHlbYyBR5uPo7qP1nQeyeweUvYQOT3uV3cNK1KgtvIzjPujIQ6hjDGvz8PgYrYVj7lVuupMEsbwezkzdzS+bdXKP62lhyzYSay1WDuw2VoSvy8wUo5ktmQ7LAGeOME2Kp/SI552khklnbSaSUIqXIxURNcNRs6LyUshkEy7Jci4JRIZ8uSwqj97vN6NqlPaV+cjJaD8NvvXY1L+XhPHuz29puJE9OR/uzzOzs/+2dDRTiu9uDRIhJXkrGkzReiwJFdK2RnM/menQ2fnz/3z15OVb6H9zYPc/ynz0/EFm2P8/u9DrdQPzw6Yu3ttcJ4MFoPpplc2D0ze99zJOPT4f3J/MsCPibb/z4fPI//OLeh8PpvclsbzjS62tTJf/oT3706cPHv/vDn015fE44kTrttN5O02+8/0H/8jU4OysOXs4fPFbjGUax7K2p6aIoqNWIwpPjJbH48m5jNhnPFhPk0Ou24jAWotVrM6XGy4IYC8PggsazKMMKhmbuu81T/ZA6Jl3Vy+Doxptz/lcXQAfvalV4ALdxQQ0bmgg1OSIkYU09gytnEtdhbWKV9V1KqFYsooEYcG9rugZVvFiDOxprw+zuQGYD35oZ6tHsthdlHJHbBeCklXI7PwMCQ8Y0gEkjKa0lUWgr87RSSmp9nqmXpWA8OMrKlLMn84w3kmYUbiXxRr9zI4mutxp708Wnhf7gK2++M+g8HM22mumVmL34/PPi8ub+bLkWBn/tzpUfHp3/+uWtJC/WABln24342Xj2jd11CSR/5ftqOFwq1Q+DTiAixGBjcF/Rdl7uTedzgGBtcIrQEOxfPno+kXK5zMIo/vruhiL66qCzPzx/+vBJp9//W3evbSfxOM+CNEpGIxWKxt3Xfv3aztPheKrViIoG8J3r14so3Iyjm7duHokAGbvUbEqVv5zO2OmIQGLajHudxXRWnJ6VRyetyztJuz1bLEe5ZL2WOB8WgGvNdO/F4fpr1zXRIi/7UbjKSMYvd0Co+ezmLQJUxW0+mujRWSMjuPhaiZK6uVABwwcuAXws3T70lXlG9VPBX9/qc7soA2yWvE6HtVv6r7WbRjWvfsUA0P6JzbXr1IIJAGbzXUQT6zSekKkrZEDc1tvbpW9aa2kwqZRWsijL+WIxXMwl0W4ScaBBHHHGp1pfX+vtpDEHWA8DTrop+EkhoSx/fbMfMDzI8i/Gs4OiPC3yAPF6u1EofavT/tpG75Pz8c128x/eusw2N39v7/jH55P/8ZNHEeNfv3YVrl7/0dl4f5H9Pz57/J8OzkINnTQ+zPI7vXagFQ0n393dvNJM3lzr/sqV7a2ylM/3N8KwLGWWFXe7bXj4sByNOeK/f7T3clnkivD8nB8c9ZrpRhB8Z3tjlGf8yePZeDSaL/74/sNPDk9CxtT5SC6yNhNfu3ypV+QciXZ3eKez1u50Ww3otIMb13ivv7G5HirZioM4DHcGPcqzRVk2Qw5Ex9PFy9lcO78CaqNvi9PIu0CWusyxhggv2G810iX/zYUIY3V87V42OGWVradxz9JmxD2KvYdUXQ6rq6FJ5+g60SOAezpRlYKqgkc1WvXtcwFfwkoidur54xCRM/MIGrMdmV0IL5Wy9a8EnDEE0EorqVSpZFkWZbnMs8lkfDI8O11kPIi7YVCSjhljyP7qRv+vb6/1w0AA/M2dtW+v9wFgLQq+sdE/LMphXt7ttbZD8UvvvrW/feXfPNr7ydnkrChOi/JsWfzgbPI/PdzvpBGQ/snJsBEGC8Y/PDpLSV8PxC+vd9/tt7+10f+DDz8Tx6e/sbv+w8d7y/Hk1y9vlk+e5tP5J2fTm83GdhB8cOvqZDwr8+J2p/Hw+GwqFaSN33jn7tfWe4M0+rdfPM7ORpmCb9y5+c217j//6N5plpcoFBfrg36nmawF7On58NHey7VGozsanp0cP11k0dkZFAUuFtn5+dl4mknFsiwHGGfZQaaS9XVotobz7GWYXrt9c8nErZvX1pJwt5X0IqPlSVtr0ngw2i7zBk2g3ZJij0BFpDwF+kH1IKu7H1jzklcIjZzbpZ3i95lCR0/kaqAIiDlr44KfZC5H1RsCZvi0Os6jHLyn5VEI3qZkrB5YRbtDpMcw1qEJlW1qK+cRmV1YrEkTSU3SuvPMhKC01lLJLM/PxqMnBy8/fnjv40cPh7liIiyJSsJc67lSD2aLmZS/fTw8XGQbcTSS+ufDWQDw7qDz09Px77w8+1dPD59N51/ttb/a75xr/ZPhBJXeisO3+u3//vbu+73GP/tPf/FeFPyf3n3tb1za+Md3rkxL+X/7F/9OHx0nnO8NJzf7HQj47d31QRzuNqL/90f3//hwOMmzZVG8Nmg/HM8ejmdrnXbUTg9m8xzZMon+/cf3UZaPTs6fjmfrrWY3CbOz4+DJoyxtLAjTfuefffYw23/eyhYTLl7MlrLdUwHb+/iTrFSD996TcfRk78WSC9XvQ6PByuzs2f6Mc0piNpnAoyezrJCjcf70ebn/cjhdCCFUUUitx6UMhIgCAWgtf7LLZv36xYor/PqJilSdF1FB1J3wqsNxwQdy7pV9TIyJZnmqskhyH5AINAnXjlVnitzVLKCdIvbGgHHKHPfVjNLKgibfW5dltwu0XKmrPxgdrGsbhJvtyBEAzENqtNZIxICQiAEA4xRw0lpqtciWo/nyfLY4H03yxTJoNvnmtZ5UgWDnUv3obPi3Lm19uH/8Vzb7gygYST1WWnC2UOogL+fT+W9e2mgG4miZ/dbDvXxd/t2rW7nWv7139K9/+tEO0d/5/rc3InG33/nnjx4zJS/1Or/38PnfuH3laqcRhCxot17rtvZOFSP65huvncwXB5P5u1d294Lwn/34I0a0zPPtNBZB8NPTEWolFvP3d9Y/Ozj5leu7x+fDnxzsiyh6ucxn09n17fXzkyM1WNdpqvLia4PuT4r8cHJWdgd6mV1VmifBpw9eBpyJZmPv+Ly9vk5PH8oXL4CFEKei39WnZ3g+Ut1OO42LvGDZfCFl3EzLZSbiYDmdtgIhlWbEpFKzotRE3EACcXVraXRekUafcXLf+iG/wCweQnVN6N9UC57qUSF7G0ddTrVaBBIQWIBaiHl6rkGzNkuICIhh5bG7s8CWz9VMZnKumdtEh5zpXQEYa1tMoTufyO6IgnaFMJpdwH1ZiKFWAFSKFkWRLbNlKeezxXQ6L/OCNDUQR+Ph2WLRSNIbSbjfbIzzfBAFz2aLv3Jl63/67Mnv/+hn//vvffN6K/39e0+ePX72j3/tO39yePrtrcFoNP3dp8+3W43rrTQGKtJkPjwLArE/XbQbydvvvrWVxHf77YPR5D/uHyHnbDF7rZUMorC52f/5yXC8zJ49ePy13c020D+6tPGHQL9IgkfjxZ/uHd1KwmSr/zuP9qjd+3g4+9EvPr3/bG+xu8Nff+OzJ88PlTx+/mLz6mWxfamUcO/ho881KME7a2tFo6tPR/zzTx/tPWev3YZGozgbrbUa5Wx2Pp1yTXpjhzY2BGJZFuF0BFKFyKaAvWa82Ntb9vsyinvdjp6Nz4m2+p3FbNHvtYHg5XRxs9dhjPmxqVwiAL+U0iBgBbl1LluFKdbR45BX94+8ljdHO4YCJG2fJu/oi9zNhL9abSG8jxSAe76XW+RfD7JahW51/mqzrVaoJ2a9Y+jSAMb2dqf4qJPT3eDMU4EImqSUeVkIjkzKpSzOx+OD4Xg8nJgqJSklaWKADEEvs/zk8EW73+hvXum0302jaam6SfTZePb+dP7Weu+jZ/ufn493uu3rW4N7z/eeHp3s9rpfnA5Zv5PleRoGL+fLWSEL4NMo7SNdWuv90cHpkdTH9x78xuvX39teezlfPp0u7rz37pKxo0UWM3yr1wKiA33zt1+c8Cx/vxn/6rVL7W73Z/tHyeHZt7cG7/Y7R8vsoNduBuK/+85X//jgJBHs8qBbIF3rtr9zZefT8eTFcDzY2exzHCKPkuR6HAaCnutd7LYkIZUSAoECjj75JGykFCdquYT5XNy/B2EMl7YDpeXOjiAqigKUZlqzIOw0kjTL5kW5jONCiLVOGIaCEANe3wUJHJV4Zeuw5FVijfLqoKzDl8APtz/Gesyv8i6aZ2lbyJstUQ06tYsYESIKchvUuFbaIuXaRKkZumAJz6wYJjCbjqH18WstqJgXAc2CDSRA9E924ASIdkMHIw8fOK7NV2SMCeSAUBQFap0tF4UsT0bnJ6fH0/FUliUQMG4fOmsWN4HWw71nZVHwN74yThJkPOH4TqvxdJn/4dn4q93WndvXP53Ov7I5eLoUjStXPjse/p/fuPlsuvjTk/FiMn90Nvz69nojjjd77Ww+eTmZXY3CX7u0MSzLB430//v08O1e82ojaQveiG79wcnwlqT3uq1L7WYzyvqM3Uzi33zrllTlH3/+8MXp+dvbGxvN5NHZ6Z9//vlH9+5f3hzI61f+3cHJ7OQ4DgTb3u13msvx+Wd7T54/fNhKkmgwkGkSZhl9cVT2252y2BRx0oqXo5E6HeciDDYGRRgiF4HgshHxQBAXGkAUWUFQHJ9AGLSaDX52StMJIuYcIlmWp6fTZnuWxkkoGpwFgsdCcES0WUpPJaveTy2O7YbEbe+4aoZqupjHNuPnnSTnU1viM/FMUw9l4oh2h1BA//gd24R/9lv/wSwPAkeKZqGFybdWUX1to6jmxuaxG0ZBMMadUnahAADSimwUyHl5aLfXNUYCQ0TG7N7J5gwCG9Qsy0W2HE8n5+Px+XQ4GU9kWTICJcs8LybTyWQ6zpYZAgRC8EBwzrTSSmsETJIYGUPOB5ubV+++HW1dL0SERP0oWgBqgLIoIiH2lvl6FGrGJlJdMo9u4Ow8K07GU57ElxrJIAoXpbw3W76cZ3c7zff6raXSPzuffDGe//1r22tp9Mnp+ZPh5FK/myAKWfz48wd/sf/itUGn3Ujm58Ozg/3xwUss8ka7qRtpORrHHJMyu7Q5SDvNaVFmo8nLg+NZpuL1tZjxG1e2H9z7gqTSWqfNxmKxWB8MeuuD8fERaRqNZ9liITgvEHnS4GFIUi9bHb51ydS/8bgxyTIkAMExCBLBaTZfMCaSeBBHyzxXgI1QpEkYiGA7Dtth8Ob64Eavw6CSf50eyVdlQAWyeuiwBuNqcQRetBUvHukvaBCozVIgswlZdbvaM8SIRD2F6FW4Jm3dbfsYdq38VvBuXtnZhUCgmd0s20ZMNWkCrbQmIjSPmHHARQBVm2GGeJXSRk3LUipNUpbz5XIxm56en52OzmfjUZ7lpSyLIi+yXJYlaQ2c8UCIQACilFJpigKRNpIgjgCx0+5sXrume1tLHp3m+XC57AueBmEYJS3BR1KlgqdhECBsRkE3jv7j4Vkm5ZvN9N2d9eeL/Omi6IZBrmQC+o1+eyMKfnY+3B+OSqW/2u/eOzn6n/f2954/S89PppvrOk17XDenk6/K6fTzz5+PJnme72yubbZ4JJqv3bp67/GzIgXB+Vff+WpZSk302cNHe/svl1mezbOgEe5ub1watIOrW6dn40GvNRpP2kn/+u2rf/6DH22u9+OQt9bbx6dqluV6mXOSLYgKQjocZk/uMQRgjHe6zWxJpUTGIQyJCyjydrMVrK2HYSDPz4QGHcWzKOJpQ29uta/spmGAXnnBRQiiz6pXg7USbyGXOvIj61m2ArQ/y0HTRF5tZZ790u0pV8UQVl5CSmlvbc+ynoqpoWNIiKjcE2CV1syt4eREyu4Uh9p562QedAB+vZBSSkqzua/WpZJKSgD7pFqyTwWlvCzHs+loOs2zLC9LJaUs5Gw+G03GZbZUZSlLaTcE1YQAnDMRCMa5JI0auGBpGvabKQ8FRvHm2nrjymt78fpPDs4wf9nncEmNh6OzrNnLm102HZdRkwAeI1zdujRTellmAOzW5tY0n//kYP9yq/F+v/v49OiTg0NdZO/221+MRgfTKVvOscgeB2EjYleXc3r+LEK8EzRPz44H7dbbb91+vLf34ejs8o3do9Pz/mBAUr37+vWP7j86OD67fuXy6zevPN9/eT6f7e7uChEwzsusiMPg6s5Gq9GMEAJkoRBa6vffeW9UFE/v3Rv02u1mmhfl1UtbGpDOhtcubWV5cXw+4owrpbjAbjNdZDnMRpyxrMg5w3wx4QCMcyoWej5UaRKSzhfLQlOcNhZhYzIcz5b5WVZ8bXv9jV6bu/gLw2oLLefgVkuIVuJPZC3EFUh5C7YCcVX4bBKe5kTz/ENy+ScX90RnG7odmEkTkVBaItgHWDGG2vvfWnNEs7rKPT1bgdmdnuyGvyanaUpFAQg0mOL2UpayLAslVVlmebbMllmeZXmelfksW0opEUiTlmVZSCmLcr5YTheL5WyuitJuCWqeO0vE3Tp50sC0RgTGUAjOuV1HH8VxpxF306TdaoIQFARs4/LPoXPvi/tXhnuXIiXzJSjZYkzIZXt5Op0uWFEGAZMaFiePG1HcScJ2q4MHI5VlrxeZOi7uPdBRlO6enRXnpyeCzUaTq4MecX4+HCPATKrRaLLe771x5/Ynn95b3+xvb27MlpmSspHGh2dD0nBtd/unn37623/4n5SWTHApi2VRPHr+8uXhyecPnvV6nWKRkVJhGm31exu9XgDUabeOTs+nWXgyWzz47LN2I+m125e3Nw9Pzg5Phy9eHAVxyJCtd9vHJ2el0o1mYz6exEkcp/FwNI0QW712WUpcZKVWQnCOoMsim+luI02bDS0VIgSQqXJS7D39Qspn08VfvXn562tdtgowS4dWQ37JEmRwURrHrUj2gdeV1epVpFf97mrWojDhKyRbSufKUip/yDNoCQACEEWgNBBo/yBRbeo2NCmlbHDVTh2zolKhL00iAAClZVnKoizmy/lsNpvNZ6Usy6KYLmfTxVIWWZnn88VimWdaE2gt8zKXUpZKuwfOIENun2pjFijblDworQk0IhcoOAejLBiLwqCRhJ00bjcbcRxjHMet7iLtHcyzG7S4kuhyPGZaFYTEWTfBSOswwDBOwig4G8+CfK5UTjLMF7OlpkajIUqZRmK+mE6OD3Y2NkaylU2nd65dns3nnWa6v/dyssw5MMZYp9v5gz/5s2aa/Nrr3yLS//K3/8Novmg10q2NdRHHAYNOKI6ns29+8FZAsNDqeDjKtC7yXM7noGmt32k1kkYjPj4bCeQ6mz1/efTe++932q1///v/sdtMMylzonkuMQyhKN9+87W8KOaT6cvxZDbPOt1Wr5UEoJeLZRSF/Vaz12k1kvj0fBgGQVEWy0IWUqVRAKRJSS1BE3CBXKqCyTjSfDaccv4XrcblNN5JogpQKxissaFLRroPFX3W9h686MmjLdp3gDZWBVK1nY2PbAISahO5r7xkxoSxQIgbl98WUxLZBymCW1ZhlvdWZgq5DTdriUopZZZnk+n4+Pzk7PxkPh7LItdS5UW+yPIAmdRqkRfLotSlVFIVUhERN1vbMcY5E8yn7BEQTWYTNSEQR+QcQs5LAKlUzMNmFMVR0IzjOIyAYCFlg4s0TuaM3e6kR8t+fjoJgiDP1SCNoyDoJMlkOonDcGtj/fh8FAouhBBax4jLPG9FkSiyzfUNEYTzZSnl7MEXD7bWN7a2t8psqaRaLLO0kbxz906/1/3wi3s/+NHPVFn+5vf/0nA4BMYODo46ve5av9PpdS9vbGitHj58HoRBwNjZ+agoihfHZwcvj5SU/UH3vbfu7G5tkFT7B0dT4vcePSkWs1/79V9f73V/8eM/DzhLk+Qsy8Mw+OLJ8wihncbns/k8y5IwjMJwrdduN9Px+QgB0zgSnIdROJnOkGG71Yzj+PR8hJDzKJRKkgYlVZrGhdRmG2ulyuloWC6L+WjO4ni6u0FpjK5g14MTfbWaNzXrxOjIk9w+oy40qV2U08cjrdfjntMBmuwGCcavB7cxGWoAl3x3XpsWBv5ak0b7hHcwViSCBs0INZFyRUNKSSK7gI4h5lyAtSeoLEtZlnmZn4+Hh4cvp2cni+lMlbKUKtOqVIoRlER5UUql0S6yJ0JQBESaA9MAGkAAlQSAIAAER47IGBM+ikEEQAEP4igIo6ARRUkS8VBoLngYxs3mUbr2uxOVsPmNiG80wvOJ7idpu5EA6aPTMyC9PuiGcdxpNvIiX+/3EPH5/kvGeWdjrSjKo7OzxTIDxP76xrVr11SeSa02L106Ozl+svditlhub25c3tl+ur8PSEqqLx4921objIcjALx+4+pX3rjz4aefvQTYXuvPpjMeBVmWX7+0+/OPP3vx4sDE7zhjr1290u92P/nii9licZbJl0+efft73+l3upgvRcB7G5vTvJiNJ/PpvNNIllqNxpNlXiDptX53o9+5vrsxns6KPF9mRZbl/W47y/OAB6BBar27tbG1Nnj0fP/g+KwZx6WS86IslGokcRSKvJAh6AhUPhljL5Bau0eZO2Ss+jpQ24XG56Vx9RFt9lcHYiQDwirxCWjMTASt/fJfN/7WAYIq1O7PQgAU1jTVWqPSQFJKX3nKAEgIEzMqykIqleV5WRaFLMsicwuFCBBLpbI8K6XU2aLIFrPJZD6eyaKUpEutpNK51mUptdJSSjCLQ2zk0hUWasUYAHGJKIkYAucsCYTg3CyOY+bZx0ARMhGIOAyiKGo1m2kc5UBBGK5tbNDgyk9l+mL/SWM5/d7lpIk6C4JIcK1pMp1NZ4s3Xruxu7P9/OXx8xcvACgSnAiajXR90Ot2Wo+e7J0NR8dnw1YjVYCdXu/uzduz+fzTz+9P8/LF8XC93WilycuDw0iIACAnGk6ng7XBeDpdFsV0PN0/OrmyvfXhvUcPn+31d7azxWyyWLx1++a1q5eeHBzNJtO007i0u9VI0zCKAs7zZbb3+Pna7s5wOnv++OEH77/7jW99689/+vM//ejTfrcZtVMgkpJarcbV3S3OUANs9jvtdmuZ5VKqUspGmkZRIAIRx7FWMi9VnhetZrqzPsgLeXQ+jAMRcM4Qi1Ii55yxXEoFiKBQq3KxfDlbXmulfFWlYw2XUENh/ScPJheJtD/bA43z4+qaq+oN0ohu32W3sSi5oqpVU5eAQJgrS01EpEgXZVmWpVKyLAsOKIKAcU5EWsk8yxZ5Ns8Xy/l0OhmPZvM8y4hIA+ZFCVpHDJhSAoAxLMqyVNKkJ0mTlrKUCjUhgEbQRNzlOYmII0NSqJEYA4KQs4CzNAjSKNKkF3kBpEUgEJgG4pyHYRAGIk2TTrMxnc8ni2UUha20wQc0EOLNG5dffvHJ2cFep5220uT47DyNwlazubU+2FjfLDVurg0Q8Xh43u/1sqKIw4Ahe/7iKI7CVqs9ycvj8Uwg3Ly00wjZoLt9Np09+vQLu202UiONbl/d3b919Ucf3d/d2uAAHDGJgs8/f7C+sUZRMFvmB0fH77z/Xgh6d9A+GY4eHRyR1qR0Mwg/uPva9s7u6dEhZ7hYZFEUh4y9fPLkm2+/OZ8vijIfnp6urfeOjs/W1npr3W6v01GkdjbWpVKCiwBJK73W743Gk8PTYasZa61ms2WeFXmeh1H08Nl+lhWB4K1GMo/DeZZLwEgIpZQmSqNIKlUQKQVMSV2Wv/9k/7VuczuOtOdI7Vc9OgjWV1vYx6rYWOmq8iewD4Eh/xBEq/cqe7By7cHZojXnDNyzu215gEBkZJ65XqpSyflivsiW2XIxmc1Aa855EAaIyBFBlfNsOZmMFpPpbDafLpZZlhdKm4amnLMwCDjPQZOmoiikCTMBSEVmgy6bbQVAjvYhBwTIMGBccKbRBF5ZFIoQkTMslMrKclkUseABF0EQEJIGUAg5QBPZYpnNF0upVIJMypKVy+3GIKdg0Yjzs+zxZHQ8GseBaDXSjbW1Qb+viRaLRb5c7u3v9zutMs8E0Gg4C8OIAeRZnmfLVIg8CsIoWd/a1oTHJ8fZbNzttUfDYVmUYRA0m+12q722tsbZg/l8gchK0pzzLJs/Pzi+vLuZpM2rV5KtVnN9vT88OX7w4vH58Wk2XQjO37x5dWttsJhOy7IMgiAMuS7L4XB4+85NhtTrdD7++NPZfJmEwZ0bV9+8+1qRl81m4/TsfDJfXN+91EyTxWJeFFlR5M00aaSZVroEtczLl8fn82VmiQxRcB7HYTOJI8aXpczLJSKmOpJSIWeNOC2UEkmiFJ2PJqO83E1iiw/wGtmqcxPkAXBL6WoZbKOj3bN8zOoL7crsCBHJFSNTVaVELvtvyXole2V0qtulg4iEJm1L0ZUsyqIoisV8djY6Pz87z4tMgw4QQsYF50A6z/LFbJ5n+VLKZVGWUhFAwDAWQgDIUpZKlVqTUlIprZQiUkpLbbOnAMamRPNoWq21WQAPnCvGjD2Sci4QNUBWllJlpdICkTNknAWcl/bxSCiISMplKYGglSSDwaC9uf2UtX/3fCqnw/fzUTPkJ8P5bJFRGj05OBr0unEULbLFaHTOGV7d3Tg+Oc+y5dbG4Ma1q51uf75YTqbTHaL5fPH4+Yvz+WKZl5sbW8D5YG3t4d5LIhJCcM7DKFkqdWVnfa3Tevni4Hw8vHH1+htvj1/u7R/s7W9vbrz/ztvNZnN0crqzsdOJoiBgj56/DJP47ddvfv1rHwRxslzOwygw9f8sEMBwdnraa7WIi/7G2ht377QajVa3vZzOF/n40uXLN65fX2ZZyHkjbTSW8+l8JpV6tn80WWRFUc4Wy7KUSmkbrAEATVLpuVJBEAjOdZFrgABgmWcMIEzifDbXPEz7g3kUJXGUCqFddKcK3tvMYG2jjVoyp+7gk4vuAwJoDW6xsg2tG9iRKbcgALelPKBGV0f8SsDfx/YFIiolS1nKspjN54v5fD6bz8aT6WyilkuulSKSnCFjUsM8z8s8L5WWSmmtOQEiJIxzBKUVIJIGqaSSqtSkpDYBeUQMze4TyMCWh4LZlNY8m9Nk581i0dI9d8asfwqY27fJJHwJEICTJg3zPO+kjUaaNBuNKAohavTaXRyfyNMTORo9PjvMizxIYgVwY3vr1vUbSdJQWidxEgVikWXzLNte722ubwzWt4KoofT5YpGJMCo03r3z2vOXByfHR8046ne7rTRpthpnp8ODo9PpbNbvbybNzqA/uHNj96cf3X/+4uDWtfh73/zmk63HP/rFJ6DZ22+81Ww05WsyYKzsd5+9fLm+tRlE43feegOIsmXGGD84OjobjstS5vMFIF55685ar3d8cDAdnl3b2TGLsLWSN65cKvP86PhwMp4ss6JQdH5+Jjju9FsMdTsJIQnXuq2iKKeLbJ7lpVJKKkQw0z0rinaa9JtNwwpFKRd5vsxLGXDNGJcq7SffvX5pJw61UlWu6BXrE2pOknGRwBKp3/5I2yXJaBeyuWwmeZiaOJTP2ZPWzDpolUWLtQ3hDSsLrXUp5SJbzuazk/Oz8Xi0mM0Ws2mxmDOlCFFqPc9yM8OyvFBSEgBjTJitHAAJoJCq1PZBpdzEoZQtkUPGwkAEAdeMmXVt0ixf18SQcbMPqNbkHjuqiEqAkEHAGRMCAZAxpfSyKAutBeMEUBApUjFApOSg2Y2TJFeywdi4KEUcJoKdHp8kJKMoTuJod9C5feUyY/zw5Gh4fjYaTUqlwoALzqezpdLAg0QE0WBtu9vbmC/mWSkDzoXgw+FwNh1vbW5eu3R5b//Fw3uPs7KczTNCAk2Dta2rly//7OMH9548H4+n3//er6RhuLm5vrE50LJc6w8QkQueL9ud9kCww7/yy7+UBmI8HDa7nZCJsizmi2VeSgDY3lq/de2K0qqZJts77+ZF/mJ/LwyDQb+7zPL9/WehwHYj2dvfe/D8aDpfvP3atcvb671WNF8sc6nDMN7Z3l4sl5/df/T4+UtZlou8kEov80IWUsUUhKKUCgA0gCJQIqZGlxifa4o1dQLBkCqv24eWHFJ9vMnkkmw9vFXYJvLoiNvbl+DQqXX9OUYm6e4rSyrCdHUkzmytdvEUWqqiyCfT6dHZ6cnJ8Xg8KpZzyjKlFUPkyEops7LUAKXSpEkAcM5CzgCYVkoTFFLaPecEF4HgjEEhZV6WUgJpJMoBCCEOOTGmSlkqBYicc84YMvPgTRfGReSBMOWljLGAcc6ZMVOkVKVWjHFimGsCwEaatJqNfrcbxkkhJWTTgtJZrqVI+OBSSsvs5GWe5be2N+IoODw+/OiL+8/3D5ZlIaVuN9JGFCRx3Nw/EFEjjeOsKPO8BFCNMHjx8mCjP3jt2nUiPZ/P0yT94O23tJI/+/BTU8NbFosyX671un/7r/7Kh/cefvT5/WtXLokovnvrxkZ/kERhnMSMMc54HEVf/8Y3ci1J6vagNZJqeD48G40Xi3m3074bh5e3t25ev8IZ29/fD6KoVPKLR0/2X7ycL5ff/cZXEHUzDZMk3hh0XhwczMaTXr/7/hu3Nza34jjpKsVFoJQSjMWd5nq/vVhmjSQ6PR8fnY1Kpcu8GI4mCICCAwAyBkzwuKFEQI32RERc6ZYQdaa0oXVjHDrutBCygNKueNRp+1rRKLkaJVDOeaqVnoB90AC5bJF2BRmgNdmqKp9lNTboZDo6m4xfHh+fnp7MxqMiy3RRgJKkiTiXpAuptCJNGjVxhmZHGmOjmOdoEwBnwIUIozDkopBykeVFURrTGoGAtJQyB+DIlCaOLImiKBSylKXJjUoNSMQ4cIYIkWCMcxGIThIrgKJUpZQaKEIRBEGBwJQmZI0gEIhSK6bkMlu2kvhrnfjjvLgn4vHNN1U5vy2gODncOx91uq295/tP9w6yUs3meV4UZ+djxlgrjbVWy9lw0Gkdnk0Vss21te2trTt3bkkpJ7NlXmgmlFSy2+197b13yix7vr937crlIAyJdCONte589xtf/Y9/+kPB2WI66Xa7jSju9QacQZJEnHEA2Nzcvnvz5tOnjz+7/zCMo/lkqkm1GunN6zeDIFhmy70XL37+0Sc33rj7+qXLy+n4z3/+F8+PhhGot1/f3d3cSONeGEbtduvsfKy1/qX333znrbd5EDMRj0dDAAaolC6Pjs8+ufe00UhbrdZskQvOG3G40LooSs0QlQICBIXtpg4izYVqd7e31v/K9Z231zrGdkVbiWkxQ3YLGQI0z7T0vrvFrtP3jget2kfn/dfj/LVNGWv2g0/vO+MWwFoI1j8BBPHR558OJ5PpZCzzpZaSa+IIKAJAVECylIjEOSJxwYg545e5QDtjLA4Dbda1KT0vl4tllmW5if8zzgQyY5dIQM4w4oJxnkZhIESBqPNcmVAYY1xwwZmZe4ngjThinC/zfF4UUlHAMYmjNIkyqRZSKg15URZFOZ3NE0IexXGQrCXRb261QITPs3w8kk9Ys8NCHE32T4Y8ihfLfDqbc87jMOitdTuN5LVru2+8dqvf64og6Owd/JPf+r0g3rt77fIH7761tj746Onhzz96EPHiv/lb39cgGu3OX/r2t2bTyWw2XdvYjuJYnqs8z8Io+d7X30ta7aKQO5tbrV4/SVPOuSu+hm63u7W9OxufHR8dTGazZhJvrW2sr69nWfbpJ5/de/pccNgYdGfD04NnIo6Cv/rLX//F/ecaYHNtjTOBjDUb8f0Hj+492v+V73zre9/9Xqe3rrUu8mI4HMZRMFsUw/PR/UfPJ7P52qArlUyTqNVI0zgadFpKU1GWi6KYzpeax7y7RlECLJDA3t0Y/PLOhoGYtRstKtHuZmM4VWuwTwM0Vpgr/TXVbX7rMWdZevhWVigg1OMADt0O0DaGiqu75Zvovbj/+KHKc6F1yJgABEQym4JzxqQSnAkec7M1KEFeFHlRaOeREQITQSAEcpaXZZ4XeZFneVFK++wmJAIOXJg9VpkQvBFHURAqrRExiaKAs0VR5lJKAhOnIgTBWCMIQiFMrACIkKEQXAiuCMwDaqSSGjRyliRxHMfLPBueH7W77dvt7W/IJo7pxTI6ba2L6ai1PMry7Gw0KxR1uu1eM9FSfvP9N69dudzrDcIoVqoMg/DWrVvXdj/++N6j/SQKv7j3lfDNm1e2fvzhgzQQg8GAsRgAdacnZfHk8cMvfvzzr7z3/tvvfWs8OhsPT7O80MA2tna7vXXGeBTHrljWJpL7a+tJowUMtVJbmxvr65vno/Mf/vwXn3z4edzpNNstfXjSCNgkZHmccM7eu32Jc2SIUhX5ovz4sy9++tHDv/Ir3/2173+fMT6bzQBIiCBJG0cnJ7LIHu+9fHk6FEEwXxZxFANgEkdm6/RSaUCc5AWLG9jdhCBm7a7iYavbeWOtIwxyXBQdXRTI1GqC20jGZIdMYQcZ1BnvxkGTLE2iO9aF6O3WjOSNWqt7fTmfy1c5UcFKuTKgkIulAC3sIzq96kapCYhCLjjnJtOUF0VRytIWdiDnLIrDVpoKzhFQMKGlmtvqT9Jamx0cgEEYiEgYpS0EF4o0ADDERhIpHQJbUgakNAcQCIJzzplGKksZMJYEIZGWRIr02XzeCMIoCnkgBOPdVuvmlSv93mCR50maJnES8iAW+m7KZ6rxbrf127qM8v6VsLXZagh2QqQ2W+nl9X6v19tYXwvDSBEAchEEhBAE4d/7m785/2f/6uGzvdl8cXx8evvGlW+8sb6zscbsA/hYFHGZT8fDsw+/eNpb382z7M7dN9e3Lj1/+jiMk6TRFkJEURxHEeecITJAjaSkCsO41e4209Q8u0RJeXh0/Gz/QJLO5nMAGBfF6Wi2Nmhv9LutRhwHvFR6scxHs8Vksuj3Bv/dP/iv775+lwfxYjpSslwu53t7z/YPj569PMjyfD6bTybTXquVRqGUsihlo5FIqc6H42VZLotCKSUaHaW1DkQep81u52/fvf56t1FKiYyZCJHDUBWHh1qwqbI17XuPTct+9pkYFkMuFK+dEkdHujaw6jKb6DwudDEtF1k3jRECNDdBHLfujpmNQpQWDAVnGmhZFFlelEWplEY7A0gI3m21Ntf6RDQcT4ssF0LEQaCk1JqYyQYwxhnjCAhIWiFx4+4IzsNAMMYLVWgixpkg0giFVpyzNIqiOMzLEhHSJEKAolRZWRaktFLLPIdAsCDCMARCSbrRaCCy8WSUL5dbQqRB53Yz/Xg8XhMh27n28/sfbu49v7LWu355O0G2vr6ZpOn+i5e9bqvXG0RRLMKItC4V9QYb/7v/+u/8i9/+nU8+u392cn5wdHLzylY7YM+ePm61e81mK5PZx5988uG9p61WtyyW//b3fvzg4cO/9/f+wdUbt8+Oj0LGojjt9PpBEAjGzMIBk0ojgk6332l3ZFmkaTqfTfdfvMznyzAMpVSz0QiI5kCn56MHfF8wxjiP4mi9371x9eqv/co7b73xVhSnJydHe3ufnp4Pn+/vLcsyWy6fvjweNBMGsFgsu83GlUtbjTSWspRKDcfTk8m0yEulZBnEMNiUSavIchGlcx6sp8k7G/0AmfJbHxlXxlKd5UsXUoeaCieya9vs4g1vkvrYqFnDSVjVgRhAe1fJl0Q5LrbBArJxKLSPWAIgIKGkVIgISEiC8ZAHjHMA4EwTQVaURVlmRZGV0i38RSaY4DwUIuAMkTjnQSCyLAsED4MgK0smlQlbcIaCSJaSNEWhMMF5xtBEeUoli1JyZGkYlkIXWmuiQARRGABjQRCEnMdRpLRWtBQKAwxaaRJGYa5pXsrZdDocnXNOSlMcJePxKA7EcjBYUDJXwcEie342Srvd3btfhUc/bTfi3c3NWZa1u/1mms5m8z//6UeXdrc21tcbaSOK4m5/g4uw1Rn8t3/373x+//7PPv70+YuDn3366LNHz9vtZpqmoRDn56PjszGxoN/Lnj1/lufZaDx69+13Xnv9zVk05EHQ6nTSNDY2iR94RCzLQmmVNBp8eEZaTaeTg+PTQukwjERARZEHQdBK404r7baa/W4nTRs729uv37nb6fbm8/mDh/dPT09Hw7PDk5Nllk9n81lZAuPrnSZTajiadFvpG6/diKPw4Oh4/+B0sVwi40iaVIlRqnuX8iDWRKwVLqOYgvCDrUFDMLtEAlxhkSYA0OjyR+i8bOeMg9PgBGZz5QqeF7LzVsVXBSTg0KixngL1J5CvKfF1JNYJE7lUGiBENJvGRoEIAqGVLgoqyjLP86IoCynd/olIDAWKNArDKCzK8nw4jqJAK2X6RESkNAdExs3m3DlpxnjKeBSEXAhkGHBu1tAFXGRZwRDTOCq1XshSaR2HASIulzkHxIRzzuIo0gCKdFnK4WIZKcUAGJFZx7LMsqIoAVicpmmcKk3rrCCt3mrGzy5tb8fJrTTak4vR9LCb55PZdDqbbm3u7u7SycnJTz7+ott4trnWXet1WieH3e6A8YCQ3755+83X747Ho4OT46OT0/lyeXR6tpgvgzDZXONBIDhjKkrCra2drc37D+/duv1amqaL+Xw+m8RJHLrAjYkYSqnLstRKN5O00+loXYIqTFkjB9AIAedX1jtvv3b19dduDza246ShlJ7NF188vE8ERZmPR6OyLONAbK/3F8uciPphGAb85eHJ6WTa6zTfvntrc63/yecPv3i0BwzjKGrEkVJqHMezsCUBismIdbq6O9jttb9zbeeblzZMEZyBoS2udKGjClPkYkuVUehodCWf5DJGfim5NxGoxppk1qXZcGdVPuLpuYZUv/xJMGQCgQPEIoiDUDCmlVpkxSLLZF4URVm65w0CAeMsCEQSBlEUxVFIQMu8WGY5YywKhFSKI3BEYkwASKlyJRljwOxDwky6qCjKQpYI2GrwRhopqZFhgLzFmAnpGUMCOQNEwThnjCPjyErGllmeSdlqJN1GI4lCxoRUMJ0viIvu2nocJ9lizri41GhebTY30vgX87IkosHOsNFqqglNZ4fD8635NIziwWDQbaSCY16o+09eaHqOhBpZVpaMi521QavZGAzWu+3Ozsb6+3fvtNrtKAyBdLPZ5pw/e/rwj3/8IRAxVKAKxrlWeb6YzecN3mpxt+rVpCzKImdcrO1cSRuNxXQEpJMoVFopJYNQ9Nrt7fXe1uZalKR5nj14/HRZSMFwNl92O61eM+Wkx5MpAyqKcp4V/U4zK9XR2ehsNNla79+6emm5zP7khz8fjqcgBBdcE02XWSGCIu3LuMWJxSJcNNuNVvPvvfPa24OWts/F8LFO6747l8ijzmpdr5LNr7YcqQqLmrVrNude51FwgSpnD9i4fc30rEHfpuy9WUEAIBKzQJMzxpiUSipVFHKRLfOizEupTZEHACAILsJQxGEkhACGZu2Hto/owjAMmZQiECIQZHZFNAs47QNLqCiLrAQkEIwjw6KU56NpFIZxFJrWFaUkoHYYaKK81JyY4BwZkq1W5rIsA864EAqw4Gyt2RgMBgQggfqDdWBcI2s1mygEMZ4myfsxa0fF42UhARut9uNRHNJxYzhuPn/ywZvv337tjSAMHzx62Gs12u32+WR6dHiyKPIwCEopi+VCKl0qNV/mUlO/09pe61+/cmVne+ta2mp1O1euXH97OPzxZ49PBZyeHDTb/SCMhAi0UmUpMQg4Z1KZ5zyiCAIRBDQbHx8dnhy9jKKw0UjNyqxWI71yaTOOAkXs6fO90TSbZ3kplVQqTeL5fCYJDk/Ollm2zItBu1USHZ+cTsfTNIluX92+cmn7fDR7+vKIEBuNlCMCQ5HEQ+JjasxEpDUIpEyEZdL4jWs7d3pNpezTZRjaLb4McWinbpHQrn90j0YgW7hJFd25Pbh9QZOPENmNh92xRABGszNCMFstmZXyPk7lZ4UDNyJp8/RNJoihCEQzjUqlZ8tMKkVSyUKWZVlK6ScK54wLlibJ+qDXTOOiVOaAQDDGWByFYSgAKImjspSkNZAG4L7RpZRJFEVhsMxyAkriWHBuqvQ5xyQOT0fTWbZMomhRlouiEARJxBhH8+iPOAozKbks0yTBQDDGm41G0kgbjWar3Vkoef/h/fW1tfDS1SBthWEYx2kaJw0ukijuz7NhHh8tM6m7i8uvNYvpXM2/ePTF9cvX37j7zs727pNnT6aHB1EQ7GytS6nSJELGxpPZ0dm54GJr0Jgvl9li+ej5i+f7L/u99uWtjffffqvT6TaaLUbqP/zpj+JQ/Pqv/bUgbDTa3SCMyrIkojiKzJOdGGNRlJRFLou8LMvpfPH85cH5eIqcpa1m3Gwo4MDD0TRrNptbm51SytPTsyd7+804VArSJO60GuPZfDJfjMfTVjPZWetfeusOIpVlOctV1Om8s76OnJdSglRBFA7ny2f7w6kGPZ/xRhMaretrve/cvPyt7QE3qCACArMPmNFt2kXTbVCSmXXrZLFrF0Rax4WY21KZ/JcubumIcBVyaJU3+WfBgLbEbbzp2i4SCKBtCh1ACx9r1ZpAkyxKVUpTzImamA07oeA8DII4DJMoSpKYIDfPfmPIk1CEUSilBC0FxygMi6IEpavNShyNB5xJIRhiyJmJkHHE5TJfLPNlngsCXcrJZJpJRQBSSq2p0UjjCKMwCAMRl4EG4IxHSRInaZw2lmUZ5jko1Wq1ev21brvfSJsiCKIgYpyLgG9E4XY7PZ5l/8P98U/Pxq2g0dncGBfT2fDw/NGD13Z21vtrX1vbHA7PHjy8d3Z6XBTli+MzZCwU/Prl3WWWCcZuXb00mU4WWW60Uhjw0+GQiVBr2txYOx7PHz7f/+rJ8fa1O0EYg6sSV0qZrfUZwzAI0rTR7q21z06KstSAZSmTNO11O0kcD9YGu5ubWlMYiIPDw363szYYrA36Usmzs1EYCNB6d2Pt2tbabL6QSm2tD6I4ZoyHTZamjaTRjMJQCBEEotT044fPPj3NZixhACXwZdJs9/v/zVfu3O6kUmm3IsOqVJfStAkkF9m0jyFgTvGiY04bM9VOudtfccUUdTFOs9bXFX+a/UIQzO7AJrBktxAnv1sjeXsDzHVQJEEQBFyRVrJkSAhUSqm0AgNsRMZYGIpGo9FKkzAMlFaT6VxrzRCSOOIMw4AzhELrUirQmnMQgmnFUGvBuUkpcM4LKfPpjDPebqSc86Isy1Lm2jzNjIBIa5JSMc6QQBItizIuyjhWSimgoJMmnPFRlsWcN4MgkGU2mpwul7rIhRBr6xth3DBPQfaPQDMPA+GcDxrJN9Z7B4tsP5fXuz2gzo+1SJbTfJKvzZ53A7bd77/15lvHh3v37z+4cWmLiWCZZS+PToui6LYay+Xi9du3AHC+WDKkJEmbrc50kSuFUbP7/lvpjZ2dwfbVKE6iMOQctdbmvsiACq0BASAIo0arc/XGbRGIz7/4vMwyFoSFNlngQkkVCN5I4sFgsFjM+73BxtpgPJ3O8jLLsyjgj/YOllm+3usMep0gjNJGK24kcZKKMIqCQARBGARpEP3RvUd/9uwsU6AZQy0LQBknX9lZ323EZSn9okskF9OxhGdD9I7rwJdtvLoLQ5UNclamJrdpElZ4dlFL912tGMXfUbsKEp/uJxdh9ZcXaRqbBdFKk1IkpVZKAyBnLBQMAJCzRhy3G400jQMhgkCYwATnLBAcEaRSUkoE4AxKRRwxYLwAaboXBgHj5rEIyBA5Z0IwAiqVUloLxgRjRFSWUgNJJakEHohIiJBzEy4lIE2UxkkUJ+W5WmbLWbbsNZvpYIDIsjzvdDqETKkiYFwwIXjAAyGECIUQXCBiGolfvb7zznr36WSeMn5/NHsskVjzWr8/oTxEdV4qmc0VRrfu3M2y5fD8/PD4NA74Zq+fxmGr2cyyRac7SBvNQATIeRhGZVEIIQbrG+ubu5vrm0GchoGIwsCg02wdYB5Hhpo0IwKKoki1ujtXbk6X2dPn+71Wc9DvP3m+LxgOh+e9brcsy267vTEYKKVe7O+dj8ZxGGQMO+3219/pLpZLBGQi6G9t9drdMAgwEFyIkIkwDE9mix88ePCn957OxtMoSYK0PQui9W7rl6/tfP/yugCtwe164BBWcWkNg1U5nMv0XMSo2+HLsW3tdBsBUB5gRGif82qPMgVw1oNHxtzTFldaUd2USJSlYghFWZalKopSSUlaAxBnLOA8CAQXIgpDHgiNEEVBHEetNGmk0Xi6mM4WjSTkis2KQnDebKST2VKqAt22KUrpLC+5wDAMm3EiBEcAKdV0nmmto1A4W8fk1YAjk+i2M2VMCM4519rmscIg3FlbW5ZFTsS5oEBESdJqtjqdPiCejofnozMUQgRBxCJu8w22poUxdrnb2u0081Ii4neW+bSUgzQBikZSPl9Ot6L+7e3rnTjI5tOjg30M0pBjK42iQORSLubL0+EoEOLq5ctJo5PEyWQ8bLQ661uXe4N1Yedq9fKCNirerOIXQjAAQtzY3P7at/4SkCpm492NQRxHjUaj1UgbaQMZV7Icj0bLPAsCZsyDRqPBGaSNZpAkzWY7abYE4xwZFyIKglme//aHn/3kycuTyYITYRyp9c0pD9rt5n//3s3X20mplNlmwCfdq4Q4QR0ZxrmhVx7ZUYcsur0U7ctlgcgyY5Vk8rGnyktf5Ul0G+RSVUxSJQQsgy7zXEpVFEUpldndXQNxxIAzxtBYB4yxgPM0EEkUCs4JSCqSSgWCM4b5oljmRRoFSRwJIUaT2TIr7OITTZokAYtjJCLSVCqV53lRSkRMQtFI4rJURb4EAEWglUa3mC4RnHE2XWStNBGCL/OCcx7HsSY9nkwyrUnwZpJmSi2yBRCAkkoVSpZSlrIouQlMuIcy2NAbYhQGdze7m610nOVlqf7J88OfnZzvRmEqdNqg87JoBfHV196+eecttZxl80mRL7gItKKz8ZiLsNVpB2EcRclWu9dsdZqtVhIFRHY3NT9y5Ha7MMoO7eNNkDGWLeZK69u3X0uS9MWTB0mcaFVMpzPGxXyZtZqN8XisVTGbjJZ5SURRFDXTWCpM291mpxuHIRdhFISc80zKF5PpD+89/MGnD2WhkzSlNC2TRrvVvNZq/N07V262wlxKR2UWMpqIedq6sBWtz0z6b5zC9Y9hJecYgUelx1dlJfhtkMDFmHzk0yWrajEpD0p7fMXmKMwazbKUiohzzpnJypvHwHDGWKfZ6HU7jKHWWkq1zPLzoWy3Gv1ui3OuNCVpI4rj8WQ6mmVpyFuNJMuK5TIDIrO7PCJxZER6Os+WeaGUCoQIuQBAxrjSpVkehVpLpYxzKBii4IJxRRBFYRRFWmcAtMwWw8kUlN7udiMRnI9Gs8WCtO51uuvrWyJMSOvFcg4MwSpaBAROYJ6hDAAILA3DJAg3W/E8K7+fF5+ejz4cTr53aTvH8P5k8uenZ3ca6dfWumtR0G302o02B5RKNjt9IijypeA8jJNGo9Fstkw4QrnHibo13NVScbtyABFAm122GQBqzRgDoss371y5+fpyMjw6Otg/OjkfnXXH55PpjDMYjsen5+Oz88mdm5caEev0NgbdfqPZYkIwwNPZ/OcvDp+eT6bz7Gg0K1mkG0L3BnGv/feu/f/o+rMlyZIkSxDjRUTupott7h7uHhG5VVZWVVNXD6iHgBnQPIBo3gEC5g34BvwM/gGfgEcQDdEsBAwGQC9V1VWdlZWRkRG+26aqdxMRZsaD3KtmkdUwD4rwMFPT5V4WXg4fPvzVby63r9oqIEiS9eaDmZZJtMXqcO2jL4sCz5s5F2P5qWtdqypbS6G14F8fDwa6eoInU376zvk5f5oUnL8DzxOJ818QnIhIFjNTs0DUhjrllLNUVajrqqnrF9f73XYbY+z7fhjHw6mPMaOmytN2u62rqmubKoTHY98Pp6E377jUirLMlhISZRFEFDNEcMQIqAgFUlYDdq5sMiRGWUIFRrXDNAHYPrZVCNuu8T48nPrb02lb1QTWD0dE2m02ZspIJprGU0ZsmdM8ReYybksMZeaPCNakHAHRgdvU9F+8vm48/4eH056pYni92VxO+RZgBP9/+3h/ezr8V9fbb7rmZ7uNxWkae4QyEs1t1zVN45gZ0NCyloKddP0qZZ/kDIgqkkUkpTgODjE4V4y18lVVN5dXN9dffX3z6cPf/Nv/+e7T+2GaP9097rb7f/WvfnE6HrZtuHlx02yvBoPvP36eRFOW//jpy8e70+NxyEiz97rZpN1l1TX/21+9/a+/voKyurTcZX0yBV3ReAB4GkxbvSKep92f5G7OXPlnCOjq7J5DRmuc/4krPLtMePZry/fXTtEZXIVnwef54x2AiSkCsFkZ4wRGw+S93282222nhnePB0fETHlSUfEes8rh2BvA5QX1g51Oxya40XHfD+OkCxRa8ClA77z33gePzI6dihASe1cXWhoiOTYBImYkyQIAjGgpZQBm6scxZgnBv7i63LTtpqpCcOTctt2GUDVVU9WNCyFUlfM+isxTX9eNI1LNBm7dbA6FUbMm6GYITLRv6v/q65f/xVdXWXXMcjvEl7X/3eMJQP9st/t3D4f/6x8+/+cvb/5Pl9dX2yrnzM5V7abbbB17Wq8yEeGzNU5FNa3cGCJWMBWdx+n2y6fD423p28XH++bVW2YHgCbK7Jqmy+S7y5ff/Hzz1z5c7ff7/X6O0zRNYvBpmv/m999/Oc1D1MeYT1GOx5O1Lex2v7y5+K+/+UoRX7Xh266SmP/UJnAdVl/xyZ/8+MlOFgqIlYJqJcHhefao6CSWeLBu9LRzzf+svD9nq+dS60986tkuz+ntT9/RU4MU/+pf/2c5JiYyBO/c5cW2bmoiRMNhTrV3SbKZtHUVHKuqc9y0TUwionUVNm1jAHePh3GY+2Ho+2GcYoEARQSJC2Llq+Cdd84BgKkiADM7z6I6z6l4NRGZVdXAOdfWFTNVzjd1aLrWISFTHcK2bes6XOwvjN2YIhEz8367vb56udtdOKJxGoi56XZVVbVt530IvmJHRMjEZwNVW+5ByYzLPTKzJDLFPMQ4xKRAd1P8/eH4bVv9q9cvGMQMkNAAq1D5wgssI6qIolo0L8qstYquECOISBynT58+/n//5t9++PTx19+8veyaerO/fP1t8JVzzsyyZCISyTnO89j3p+NxOM2nByb7YdB/8/EYqtrU/vD+8w+Pj7jZNCFQ8JuXL9/suv/ml6/f1F5SFFEp87PnvO7ssZ6F1zOQRM8IdIsRPWHvBbSk1XHqc2dXhnIXaPOnVCZ4eob15VZ5nMX6n9niUm+cS66fBv6C3jsCLLl9XfuuK+0ev9+0TdM8noZxGAwElFUkqjim4KpN2zZ1gwgx53kaJaUUo2pi1F1XNZWTnOc5ncZY3piqxikmSiXfR3a2Fl5N8KYwx5iLwiIAOmbHSOSd984F7755cSkKv3v3IaesKlGaUNUxZwDYb3fsmENQYjH17HeXN95XROwcEzsih+eW7/r5yxxp+aYhlk0CpYBlotr7XVPFlGeRbeWvKhdTfJimi6aufEkVClP+qVwoUr9cemNyFrOyJTE1SPP48e//X7d/92/VwH/zutle7l++9t4jURl7LLO1atb3p9PDl9PxkCRfv/rmwxDf3304jPnd5499TC+ur/7Ft9/sN81fv9i/6OoXTbP1bCoxRhF53tdZzcbwJzd+ocqXHy5nSHUZdysx2sDOwNCyz1rPiaeZrH3IpTyHM5z6HMuEp9rombU+Xf9ztPmJZf8zVwoATiQnySBgYESsBsd+fDz0Vxe77Xaz23aS0zBM0zzN0zTFpKpJtKlHQ1DReR4rR7vWU+sB2pRlnOLjcZxjVkAFJUTnnBGqaYxxRGyauqoCIqpaElsELYiyqWNum7oIh1TEjsv7mUqqAGYxZdPps936ENq27XP2RNL3OUvgV5tu50MFxETsXXDkaBlQwMUsVw+6gEErOL36FaOl9c8VEHEOTJvKl4iDVPaewRnVFhE7a/QjYvmkZjFGU5Wph+lR5lHjIMPjXj/u6/z6z//zb37zL0PbMTsEMtOcc3knagqI7XbXdJuLsf90OPy3v/3+//39+3q333399n9/uctgPvhf77tvL3YOLcdoBiJalpg991tncOccggFWJQZTPa9dsfWzI61UtMKvo3WebEWOFttRe25ATz96+tLnNvccpTprkDxXznkOfP4JDQ8XSVCX8qKxkFIepgkASgU/9P23377pmqv9xe5yv308HL58kfEY72NOd0cz846DZ+/Z2uqi6rZdE0J4PI2H4TaLFIUwAiBmdmwAqiYqZWJE1Zo6AELOOaU0pVQkx5tQX2833jskSillk02oTsN0HMZ+HMcs+67dbToX2NfVwzTaPF1d3wR2TG7OklL0VeXY+TX4nj/tchWe2eh6J88/X442AhoCOfTK3rkCo5R9dilLVq29R1wWlqrJOW1YdH2ZQwiZSMbD9Om3px9/9/7LXR/zv/njY/fmN/+rv/5ft123CAIAGigVGFi15ImE+DjFv789pZR7F/76z3/19dU+OPdmU99sujYEzy7OUxIlHyQmXbEtldXPrUofsPpme/bZy1b1J5+3atTDsytiyz7jc320xnAzwJ9UXWcwAM4z9ef04lkqCQBL5loSvAJ0rK+x/tbyeFsxWlM1RGeiTOSYpKzEEgFEXwUzBdE5xpRTzHmeJmJu60BE45ymOXrvura53G+JcUpJhxiSjtMEZUAewExs4cIiOzYzzJhyUtMpRgCrghvneZ7nLALMRJTATjFuEKtAiuCRN03Tj9M0z/00j0kc04urfVVV4N1ls/E+7Npt8CGKfL6/q6qmabeeHRKKqYoxMCIy0Pkq/gnA8bxNcjZeW7zpMhZgBqpo2ZjAg0s5JZGCyzt2pfnMzFYmxBEBkMi53Qv38/9y5lcP+fcc/P/mX3/9s5//AgBNFRxIzmpFLVBzFjMTk2FO/3D3aEg/TvGvr/f/y59/TQWmAphE5iwofUQKvvLBm6mrUEcxMwNDwpVe/FMYp9xwNABewaDF2tYEAJ9V6XbGJ88t+OfX6yz6dfZ/9icQ0k/h0jPXCcpg8bNffMqPn52f9VeWH2HZ8uEdt7VPYqfTwGaGVFV12zZZdZrGmCXGRGg5ZUKovGubOmZzjKI2xtQ1gYiGcYoTxJRiytOc5pimKABAqsEbm9ZV1bW1qU0xBc8IeOwnUa2r0BJlNTXrnHeAKee6Cl9dXY7T/HDozbQO4fbYm0jtvahNKVUDfH/3eJqnfde9ur4JVT2mNEyjmb568bqqayT2zpV1dZIVmJAUFjbikxN9ss4ycrAe65VSbss1ZPToTDVlISRHJiIAEHMyfXq24reZCMBE1MjdfPvr/auv52EAg/F0ZOeWBjRYTinOkwHY0moWVf1q01SM/+U3r4go5bTyiC1QYdMyqsVpBEQXAjnPdQ1xKpsqFmVqhCXXXM+gLYNrslok6MpFUoCVybSCSvj0uz8x8sUB/7QGen7az50nXSdCi8iSrvSZf+Yd1uPzU1+7Pn+5I66E/zHKPM9JBAG9c+wYAULw3vuH4ynH6JhjKuFYg/cGIIRiMIxzP4ybtmKmrJYVH09zP8aYxMBKnatmKcasmiQTUhGzGeY4zjMRBl8jUZaY1Vow59gAKu+C9+OcssygOsWoYNttVznu+8EBTTDdno51VZ/6McDd5X6bU/qcZ5QkOb1587Ou3SCgmGpKhAjgGdBIi6h5cRN/0pb8yd9XCHAx3GJUSyWUAICZk2QoU+OmKUvKOaYcswDiRddsqkBoKpkBqiqkmFRyShEAytCgmJUiScHMhBBaz7vGm9nYH6umY3YiYqZEzAgqeTktiAYWx9Fs4KqqNjs5PKhKoamVxPOc0i1TFiWs/0n+95wyt4AbS2IA//zrmRn9NBNdje9pz9ZPSqP1kPynn/L5v89/ef4qLqacsxiAqjCRD2G/7TabRszu7h9TakwtiyCAqExTfDgOjOS9qypfV5UBJDWiZrvZpKxTfDRAVSOipmlg3XNvavMcU4zBeefdlFKMkRCDrwxwmOMcUx1C19R1HXKW0zAOc3JMSKhIWa1mDoSFGHCcJu/4zeVVUzfvT4/34+Cb6mevvjLmKcXD2L+Ic/SO1ZUYXSSYy709ry1dGymFX4uwEnDWS1pu6PMEoCwsKQ4TdV1wSoimwoxmnFUt5ynGL6ee2P3m5XXwFYdwru6ziKjYIseiAkk1g2RclpOjipRIG8eTbzbsHIBJnJmdcz6ntBbLiIg5x3gY0bmq3Uz9oUDITzf7nGqDncP6E1b0nAXyVPuvlfXT7z5Z0p8Y0/l4P9n3swv4E2N99n179rpPYf1cUK1GD4tLB7eoQSJ67+u6Ct61XV1V1TzncZol5xB8FeoquHg4nYZJsjjHAJpFUspV8G1dEWJKaYoppdw1VYoxiTJRUwUgGscxQraUVCwDFpkxQHDeOe9hcfJIRGoQYxpjUpFN21ShBbBxijGnnEWdtCH4uvrx3YfrrmWik6S2aVTs/vHovPuLX/z6Zbc9jv0/ffcPX7/52f7iCpC888ysYJBVCQtCdMaqEZD/tMW8LHt8usSLk1lqj+W/ts5IFAo5oGOuAUBVVP7N3eGHMX11e/jXl1002FeVI9xXTGZieppmBCPNc85q0PJyYM7VNxJZzpojgnnndZ41ZyNi5zUnWyoMW1DXcc4p1t12Hk45zmcDWe7xWiTBuj4LdNlZ9RRe4dkne2ay+J+quP/ETS5G9vxsPy/V7Se9ejv/FgA8yTTj+QiVN4DP2vSufKtMZgbvEbE/DcfjKGpMOCC8vL7Ybbo5ZyTynsvT5Gzeg6jGlEx1inHTNuQcEW26RtQAUUQNQHImxMp7QkwxqSqTg7KHE7BspSGzxlPFfOz7snq3qiomFlm22agIM9VVcMEzQMscQuhTvOz2L/b7u/7YbS7fvv4659Q/3k45TYCB3t3df3n56u3F/jrGRJSpiEgWimpBU5jOF3ptsix1/p/6jKcjjcvC6me3BwGQEM3YqPb+b+6O//Oh90T/+Hh4xfr9FBX5v388/pfb6v/w9gbRpjkxU60xiCA7pIAKBEiAyCQigIjOaRZEyoAUvOUMZiqZvJeUVESl7K5SAMhxHnKuuo2Z5TQv1c5P4/jySc8+71nyd/aKf4KcP/eUPwEvn2OZy3qPhS5z9n/nFupT8vDc0FcsFs7Txk+3oPxweRtODcCUVFRojlHVUs7MXFUB0AFAVmWmCh0SNnXtKM4x5ZwIzZTBOyIGoKyGWUSECTdd44NDs3GK4zSnqGZlVcgCthXV3PJeU85ZNXhfVX7KmR233hPgNM2gIqJTTF1dt22zbdu2CneHY+29ZiEEVL3aX/zFr//ix88f//7v/v2ubX/17c+96qfbL/3D/as3X19fv5qnYSZyzjdVretlLUwSXdZFPiVIhShz3jyGz6KkWfETVnrramWcwyGAmBVWChL8zcPx//JPP/51G+7NDlG/3rV/cbX9NMt71e/j9MMw/aL1O4ZCMDRENCWwlShgZlbu9NJKIFLJ7JyAGJS9P8Leq6lGUS1NAUNEVZmHU2g6U805PYvuTyX2k6vDn9aK/0nk/HmOuJjPfyIHff7M9uxXllPx01ThbNn2TGvkWSkKz3xBeaQ6RhOFnDVJpDkBoqFtgru62LVNHTyHEGLO85xyygjqHBfQNKs5gizq1LZVqOsq5ixJh3E2haapnHMGs5mVTiCYLSo/qp658N3LB2UiAFC1yrlN2wKCaFl1h0l0ivPN5cXLywsxm2OapjmpxpxCcC9vXjSb7T/8/rcPDw9MBETv775c7y9ev3rddFvR/Ic//Pbm5evrq1d1qHNOMUUicqX1icDEBaImwmXh5wqYn1O05YIut2BZn6cqktMiwAQIVNaRAiK83jT/x29fvQ58Efz7ab5pm5ppV6X/c3s5ioGJrt2dxVkgoxkSnVsva263JMdIBGbIbJKt1CIGRFSq/udmJDlP/bFqOjU9Q/e4OrY/MaxzWY3PCEf/3PLKd/SntOXnR/d8ifSnRmk/bWL95JntKcdYar4/qczMEMpaV3DnF1109wi9D0Qcgt/tt4FJVO4eDsdTn1M2FdWi5oPM1DbVHGWe4/3DQbRjQkfo2naeYozJnOaUwZQIELng82qaUo6iVfCOfYwp5VzsJYvsmrYK7jTNlXNNVT2cTuM0eefrphHVEHwWNQBP1Hbt22++3u92f/junwjx1cVV17ZieuoHR3R9efPhy/v+eHzx4tU4jff3n0+Hu+BDaNqqarNkMHPOMxNTEThjWIC6Mz0NaCmI1z2OCudpXVVBRCKGBa0D1AWp/qpr/3c/r2LOIvLtrs0iamaKBNCSLroyZb+UKi2eA1cMdgUOVo9ipoisJsxcFqmBQc7RsFRsT1VR+YuKTMOpbrppONlPgzucyyBYq/pnEdz+U2X4swACf2Jnz//yp988pxPPDH1F4J8euqYNcnamuLQMbM2jABDc+vaW9xB8aJqanTv1Y9vU3LUx6zTFOCciZOcsC6oF75goxtSfBhGt6goYL3abtq6c901dDcMwxwQI3jvvuESWmJJkFVEtRSuloj8OZoTIxMMcYxZX+kDEu64jJMd0s992XWuAn77cmhoEt7m8PPVj//iYRXzw1xcXTHh7f5hVfFXffvngiH/1q9+4qvnx3R//zbvvX7/86te//ksFNDXvfZYMgN65um7qwGAoubD40QyQjAgXPbbiHmRZAl0KbVVl9lg2qpSiCklsWUdmSMSsYAWHN8kgGQqOCbIM+C5ZAcIiH702s57d0eI/EUFFDQnLqkIEKEGc6LkHPduf5jyPg6uqOI5PdnNO+NZHPuXW///tcgm16/88fy148r7rz/75V7HdgsuuvBRcCqeVFbA8/RpU7CyKvLycqyqfs2jZjVTOlSoaSJbbu8eYEhEBYt02lXdt2xDRPI2HY386DZIFCduqqqvQeN9WdajCNM0q4p2TnHdd470Dg893j9OcVMqlpuBdCL7yzsxSFlObcpbj0Xned93Fxa5tmxhzziCqlXfsuGnqw3E49H1m3G12P75//2q7/ebtm8/39xfdZrvpTv345uXLm5tX4zwhNGZ693D/3bu/ufv04e3bt7uLq7svn9i5bbcLdU3OO18hoimoWRkUJuaiRBy8N3zyNabPTz5kk1ITMJKBZRUEICRFxKJVbFr0xtY7sCwJBwAgWgzCAEyRGJjOFrkUYmaABEiw2C8qgErGspDKVERyTshsa+b3VHQDGEAxXx9CnOcla1yhh+dJ5BKF4ZmbLAH9mcnCszLo3Jz8yZIk/BPztBU7sNUfnst8xKcmp9kqg7OEnnOGQ7gUS6aA4Lz3gGh6pt8Alu2DoNM0pRQ3XXtztd9tuxBCU4WU8+3dwxzTiupp8ME5J2qH44l6RMKymogdq9g0pSmmw3EYp5glI1IdQlUHJEQEWYhAmFXnaa4tBBdP/TjOaY5RRMukxDzHUz9mkV99++0k+o/ffQcASNQPQ/But93WVY1I7WafVU3leHzo2s398XD7+cMvfvHLN1+9/fD+B0kpSmqr+vLi6ubm5eWLr3KmmCMiiuSStIhKW7dmy+7dP7l5VpryOT3P/XPORGRPHnC5E8GxY55jMY9VMw5gqYfKUB0TIi0M6oWvoaZGCFguEK76mrYM8Zb9vJJTGbnS8zv7aUSO8xTqmoj1zDwqkO0/QydW612LFvhJunnuaCxpYvGChGtg/6n80tmnmhmu45qr08ZlKRIuj1yxvifnamsf6+ltoWu7ulz1OeaSJprqtq1D8LcPh2makXC3bRihDc4AjqdhGoeS5xJzFcK2a4eYYsxNHerghnnuh2gAqtYP8xxTznkYx5SzAniCUnk6IAbKkMt+PAZMoimlw0mGYfSO67q+vLzo6qqua+e9iDrvFPHh/g7MJMs4Tv0wusrv2o4QQ1XFnIbTQXPabvauasLh4X/x1/+ZIf0//6f/cRrHum1eXl7P0/zv/+5vfv6LX445vXj5FgFOhwcFaJsu5bhpd6aljWmIBPh86LtsQxEwOCvbqMhiAbgkfLisMUFCzIpMWSEX+KZw+emcz3FRElyW/5bKHckB6vMArypLhgqmAMV9iirquvZgNZaz5yv/TvPsQtCoiya3mf6pr9OnxPeMjuvq+nBFJp8kZg2QymPO5rt0n3A1cQOAZ0UPnh21mpbjtiAUxZSfoCU9E/cKVLtEfbdtAiGa6mmIc4wiQgRq1rX1OM0jWOUYNR9PhxxHx6wpgWkd2Ll6jknEbh8OKQsRT3MkRFObYxYRAyiiWVNMKWezMpkEWS0vxFoBM0dUh2rKcY6gak1bFWYTMgfvnPPOcUppu9lcXVz88cN7NGDmvh9yzlHFJPXjeH152bXd8Xg4PD68ffMN180PP3y32+67uv3db//+ctMN3h2n+ePtF0d0eXGlCGL46dO7dDoS49ff/ll/ug9VVzTzUYt5aBlqOk9xmC5pJYADMFERlRKZVcW7gIi67ODFcqOXmT01tFX0dTW+NYiDwbLaGaDQn/mc262RDgFW9TmRnJJIXlb5LNT9n3j6c7sop0RMGvMadRcLXbHes/tciO5qRgC2KC0v7Bo4O0GDwhzFp3pr9ZAGy/rDAmuu45rL1g9bX6BYvhoiLU9sBgiGeB5GWf1qWbMEjtillMtnzCLjlBCyqJU6pq5CcCiSEbya5iRMtN20O9B+nB8lDykP46yiVRViygAQvKvqKqUEgDGbyawqRUFHwdCU1yn4kp+54EUFVStedm9Xwfvg99tt3TaO0Mz2uwvv/e3d7b5tQXTO6dgP/TgR4nbTVt61TTPM84/v37VNQ879+Mffd93m1YtXd7cf97vdfnfhvD+ejuPQf/funQb/6tXb4+HxH//hb9M8/+qXv9psvzDi5usLW+jwGgBKwnQe1zSzLLlI8SAWxExKTV6mPNTUMZuirtt3yx94qpiXe1lo+biMJwJqWT9ugIrIT9j1spUFz65pdWUl2cgiWsYTnu12sXKiYDFBU1Xisk3oyX8aPmWOCzVkhYMWuYViOSXaLvSi8jA8H4enHGEtXlbDXtBiWIujMztpSTRxbW0iAFKhv5XsG5ac1QxBTMGgsMUwpTzFPIwppaRmpdSum6YJbs7qxFqgqqpTzqhKYCnb4TQ9nsac1TEpEhFLlj6mXIfgPRIP45xSyqkUFQtrwS/zzMhEazqiKaYk2QCaurrc7oCgaWoAPB5PhHh9eZEl398PouqcyyLBe+94itEHf+yH3/34fkr54vLCVC8urv7p9/8Yc7q8vLn7/FFNAen7H38AhJuLSzP75S9+WW13t18+f/jxu6ZpLi4uI8C7d38M7FzVtE2LzE23sWZjZt45Ji7WqaYxpUIzWLM+IEIClGWUv2x4fAq7Kc2LldiiGgyICFRi8AK7qqy3r9w/QeSnnK/cZVNb3wPiUy/2yb5K/H1WERdvVbykFZBVZdGkJVp7Rc9tbX0xfIrQa2pamKZPFVUhNNia/xTLfNYNwqdGkT0p6BWPWV5DdS2zSule0tmnrHRx6mbm5pjAICbJWbxnx6hmIjpPc+UdVt4MuLCO2c1JYlJTGcY55cyIGUBFDDCmOIxCCETglqkIK8a3MM8BHbF3jpmYmZ3LWaA0UQC9c9McY8oxRwyBs+Q0A8DlxRYMPn6+jTGG4AGw8h4BArHv6ix5irHThrxDw8vrm4+fP8U4v337zd/+/d+Nx9Nm2xnCOIw+BMt5f3n1+uVX96fTH7/7XV1XQz98+fLl17/5y5TTOM23/+7/4xG+evP2xatvDv5xv79SCYSYVyGgkuQtbgnRyrYcRFEEQJHs2CEgM+esxWmBKgEoMZqpChEXB1xIXmaiOWnO5D0RQ7loy7xlua1lcGjZdyo55ZSW8FeOBDwVFksNshbnZ1DIRBbB/GJuqrAKKp01vNbzsox+PquknhCf5VydE8dzYbg693ORtuTThmWOdAkBq+9dnhPxfC7O/twQwBTPTXkwxz7klMuZ8Y6JfEoiElU1paRgnjllnWOifhBTUTVVJuyampGkH89oHKEhUk55xsjOMaIZJpEygwtgIpJyxgLBSFYzx+ydA8CSpKac5yxM0k9T63zd1I/9+HgcmjrEnKecdm0nWaY4dV3TVvVp6KcYk1ntQ1XVHz59+u4P371+8+bd+x8Dgdbhy5fb4zDGlK4uLm5uXoSmiXP89PHd5Wbb1HWeY319w4C//aff37x49bd/8+9f3Vy7UP3dP/zdX/z6L+ebPlRN1bSIxMwhVLR0vJQdn+//Ot5UdAMyswOwLJJyWu9kyUORic20JJqw4FDFnSiqAvPKGIH1flrxj5qlCFLM0xjjbGoGhkaL5xFd8wAzKN3bpRhbK2wrKxFWwGyt/JDOQfrML1JYhOXxTBxerPVcSz0VW2cY9AwB4NnMivktaTTg+owAZ3r0E3awAvzFrksaUJ4QXdt1x+OxFGwpSXmrTCiCc8zTOHnuxjmmlLabpgqOmHxw6r2oVlVV1ZXkHLOM05xSVjUkMjBRyTmrCiGWJxVVAJQ455x8Dt45dIwOF5U8R66umIgJGawJoWKXY+rnSbKodE3dAKOqzpr7GBsfvHdJNac8TdPd4/HDw+nv/ubfv7i5dsGneQ7OHY+37z5+BsQ6eCYapylnvX+4y9MUmsbMfvbNt7vdxfc/fG+it18+OUQR+b//9//dz968vf386eOHHy8vrtpu02z3l9evgg/2VJlClAwAniBpzpKYHBhkSSK5jFUgErrFJhQFiZdweXYPhEhc8FPNCZDYLbtBwNCQSxFpalaYpGnOklU0a8Yy0KxqJuUtrcjmGoqX/yx3XmHtkRWzL4iCySo3QmVH0rMVXUuye0aFDADLiponn7omjKV0On89++vK+i5+8qy2eH6Y6TM4dUEfdHXYAADgXl5fmsg4TM4bO5UsKWY1IGZE6vtJRDdtzVVIKRECCQpReWNE2LWBsD710xxz8OgcI2LhmKYsVoI7MSFhSkW3PIuKzoBQOzZRNW2qGj1TFlhTEwLo5ylnGebIiI/9OKVc12Gapl23ud7vg3Mh+K5rs5lmOfXDu8+fVPLP3ryZ+z7PM+93v/j5z/e73e3D48PD4fv37yNY03af3v+432xjjI7dj+/fnaZ5mqdfffv1x0+fLvbb4+Hw4mJfef/+4wfHdDw8MuLNzQvLOV1eV1VjAN4FE02aHbEQZ82mqiClrBbNZ0wRlq6IIdHCMTc833qThVpK7CRHS8kQFRwVfsBaPpeSSCTHeVqTJVBTNCqJqYE9MZUBDHTF/FeqK6LpQrrGRfprjcugVlQQbdnDsVq4rdH8yaDU7Mw8xuVUrMZoAAAKdtYggLM12+pX7VlDy85w/pKYPund2E8M3PXDmLJ47wAw5xycY6JpmhHNOQKAnHWaEzOfevXBVz6ktLAumqZqKp/FQuVfXu+KUZ7GeZzigkQQEaGI0uK6nxcQsQ5BTeMsaujUERKATTE2REwcswxTFMlKdNbDceyOpz6myETe+Vc319f7C+f83f3dzX7XEL37/Onjx89dXX9NvO82X79544Nnoq+Cv/7q9b/727853h/2m+7icq+q+/32TVV9/DiMw+C9+/Nf/uJ3f/je1Mbh+Ob1W+/dH77/Pkve7XaHu89//MM/fvXV21DVdbclYnauabeDSJJYh2ZJrtZUzJ6xKwzQ+VBcqaiZChgsfA4AAEUm0lUxgKQUX7AgB6o5l7nNlauiALrkkiq2SM2fsaaFS28ABEuHfXGLRYV2ibrP5RdtdbdwLnpsIcoAgKEaPBU9qGC0ppJrNQ7FjBfuz3OEoER5BSRAfXLpq9z4OUeys5ozrP6/fAw3p5QlL5mAihKGyiNBirnsPDJQIvRZ6qbyvmLnppjnrJ4UoSrLgOq6Ct6PU/z45Z6Jm7qOqdRGwMw5C1iZ6CU1LZ5D1KYYg/dokHJCRF9z27ZVFdqqhiW82ThKzoominjqh1I4b7t2s+kIoAr+5YuXMaVtW2fVx+NxmqY0zxcXF/v9TsGY+XK/u7m6urq8fv/p83g4jeOYyvQSIjPfPd4fjkdT/dW33xCiiDw8Hl6/vLm82H+5vWWmHz/effN2bJoBTaahlxQPD18Acbe9lJzqpkOirIKIiIxUGCRWEr6zUKgVEQQELHPlCFDUhnSBFYtmTvFJarks1pWcJGfNKeU0p0UxGBFVwExtAbkWrpGB4TKVtqCturi/kmguosVrP9UWSsC5vwolSVzWKZg+ddLOmWxxo2gA9KxFCSuqD2e/f3aF51T1qWlUMktcIfw1xqyyPKtnXio+QNc2NQMM3vXjpKpgSgjOOREtA8kIBGbB0aapxznOMYJBTjmBNXVu6ooQx5jnmEUtpkxmITCgiZbN2yriRAUkkyoYnrnXMSU1YyQQUJGYU1a5dpel7eoc++D6EdQ0CUA0MCOm4EIUeXg4pG13HMemP+23G2badJv9fv/u/TsQ9d41VfDMMUUD3HQbQ3q4v/3zX/383afPn7/cEyI693A4uo+fReTmcj/N0+Px0NYhuMu6rgqmO07zpm23bX08Pswxtu0WEKrgRfI8nZCpblrvAwCknAByQXKLTFcp1WHp1WBeEH7IOcOKqpSVZ7jeYBWFs+tVlRjnNOcUc86Sc85pwXdK+DRd88mSZS4pxNpKOtfpdvZYxZ2XDNcWMPQpzyy1O61Li5dAfi5h1r75Cvg/83bnBy4fakl8n2eoZ7D2J+XQ+hJrqHkq7lf/C65t6qaqnHdlGKJoVDChGaQskkU0xRQBAMmFyoNqzFlzruoqqw1z9s4R4ek0MHNThxRT2SxjANbYFLzcH2QWWMDAp/kVMyNE9AgAIoKqWVQVLrab7abz7Jhy5TgwE7NjIoBd1znnHk9HMUCDz59vD8fDVzcv6rqAtm6/2e67TkRPp36co3fsvdteXH1490PbNiGEKc4Xm+7hcDwceyQCkctNV4fq/vEQvM+iZvrh05eu3f7+D98/PD4iwHd//GHT1k3TgGmOU1XtiB0Ano6HZnvhfAVYHKLmZM75pde5msqSc6qmnBaitCgYIDGVsXNVMy3cZFAoSnwqZTvIlGJUEQNVlbLg+sl4ylc5AWujG88LiwtOuVZPz1ua6xDdancGRsumw2clzLNhkTPiDmiwHBJYnxEBlzO5ErWW/AFxwUefg/p4zjaXRPWMiRWaCK4VfxnIdwgYKj/GOQRP2MwTD+M8z7OqFsQPzAiJmFJKpVt1GmdTBaS6aVmAHAFQzNliQiLvHSMGRwaYxLIXcmyTCZxR3+X9qoElZRVmt2areZhmDJ68I+eAqKqqrqouLnZJpAm+Cv7Uj4YbSXI8nk6E33792rHLOX/6/JmdQ4DPd/fjODKzAjim3WbXHw/7rmubGsF2m/bvf/u7vp9ELTgHYA+Hx3mevPfH03BztVcVIgJNc5xVJSV9eDyo6X5/pap395+nmK8uL8dpHMbeOc7TWLcbKrJTgCKZ2BXRKGIHBqpZl9TTJGV2zN5pXsY1i12W6geITKRMHeWcUoqSs4pITstocOGWPsGEz4J8ufOEQOuylzNgeW7hrKZ3JhQtRlh8KoIZnFuQ5ycv40IE+ATWL65vVRhbSiQ711SI+IT72/qEBbh66tsvD17fJpgV7VIDWPcuA7i7h8cXl7vah8izCSPzdtMh4jQnYfHgiLmqAiL1w9SDOWYfvCrPcybTTVsz0ZBSXdcxikj2zJuuEZFhnKY5TXNyzjFRybtyVitI4HI+IYuAgXMumxkii8qcBjftd7vdtkveXe33b1++OI6Dc3y13zl2D4fT6dRHScfTMCb5cHurWU7zlHPe1LUB5pya4Ku6mlM69KcUZxWbU2yrepymLNpt2kbyxXYzTdP9wwEM1OzXv/r5m5fXovrjhw/9ODimnOTL3UPK8g3xZnfx+eH++x/et93xuh+OD/fj0P8qi4lImhGZiFyoyHtyys6LZsrJl12jOSOAqgCgqjIW78kGqqLE3gBVcrFByTmLzNMoOYmKgiosY/gLYgMLfWNJDkzXOgNFtRTrstAAFoB2NaMFzCle8Mx2IwDQ83DSmm/YatxmaKBriVtwAFh75etbOhf7TzATLm7IbBlBO9t9yS8AziO25av0QRZ/utRPrj/1cZqK0qepwhzZcagqdkNNVU650CXnecpZidk7V6ux9yr57uHYT9FUg3dVFarAwyRZVdSISAEAiZkZ0TmHiIVejkWNSu3MNCvnuPTysmQ1lSyaUl2F0DZVFbKKY3bEaMjE2+2mbRvN8vv5h9M4xml+PB2naUazT6qO+XK/e3l9dbXfV1X17tOnH959GMZxGqcpxs1mc3Ox7do6xnT38LjtWlV7PPX/8i/+7Nc//yZJ/nJ39/s/vMtqwXERsDud+v/4u9+/fvPNTHA3zH/8dPtmnOd++N0//f7h0P+Lf/GXu92pcd774EPl62azu1yKJMkSah+qMqFR3F4pPBGBijwTqJosBZNIQftTimqaJUtKpcu5AqIFk8en0LuEzadypfjFUjTj+ohFDRPXqLo6UzrDkKuV6FK2r89cCCCLn1vLdizmpE91efG0uKpEPOWWa1qwUpFLurxWTctvnmcVzxZ+fofucOwRkZ3bbjehrjeAMaWYkndeJINBzjmmVAZ3QEUEY85d8ORDyhJPvWNOIkDUNaEKDgBENKoycxVIRZBwKTOXiqFcbQAQQkQiJSRgIlRRRqir0FbVNM0GVoUw0BgcAeAUo6l556qqIsLD6ThM09QP4zAeTv0cIwIQU11Vl7utqX748tkUvOP9pr29uz+eeud407XHcVK1u8cjItZVYObf/Opn37x9lVX7/jSOU9s20zSpalWHn3/z1ePhlFL+7rt/+vWv/+z1zXXvXR5Or66vQWZCPTw+quTBeQS4vLpxOTG7qttKnGGdDXI+lFOoJmBYpmZIlZgRwETLP5JzyjHnJJI155K72iKV94SFLzQ5WPpOVhL6c1xagRxcCpW1Vl6yghUtX/PjJ/LA8s3FQa6Gu/yIFnMtAfgpjUQAKmqMT4CnmgEtT7u8UTsH8lIuPWvCrVIrT19rgQ8A4C4udmYmIsd+qELYdM2ph6oK3rmc8zQnG0a30BwppVy0FOdpZudCVQXvCE2TpJTDflNVVYzJzBoXqiocjv04jEzkvXPOaZaY5QlaKDnVgtkSoTMEQyTmdrfpp/n21Hsam354PBx98HVdBWIi6odhjvHj7e2HT1/iNCPAZtN12K2QDc4x/fHDx92m27TtOM9meLHfFRIwGXji+2M/TXPbNsMw5ZxSSh8+fd51fcy5CuHNq+vjqf98+2gizru//LOfuVCJ5q3XVz//um1+8/B4C0D7XdvUVdd1VdUBkqo2XRdCVS6tr2qRhEhZMwiBmWp+DkOrgmVcrAELyJdzjDFFyVlywem0cMll1YkodcxSzdjzruMTxoxr4vjMFa1hdGXCwTlJffKAK3wL51D/pDuiazuyhLtzeSSqRnp2eM/B1vIiq09VtIXpjE/nxOA55o9wttq1HWauqsIcU1bLcwQDFQak66urOSbJqZqjqmAi7xiJYZxySmXpAswREL1z45xEJGdhpqqqADDnxIRzyqdhGueUk4TgHbsZ4notjBANsaDRSzWmQkQG1o/jdd7dXOxzzsfHw+HY18G/uNzXIUwx5SzjPKmB937bNBLCzdXFVy9fxpSmeQ5VuH849McTAoro4XgChMv97mLXfazC7eHYz1OcYxX8zdVFP8XPd/c5JQEL3n/4+GXTdduuIYK2DteXm4vt5jRM96fx5rq52F82bRecC1Xz6tXbaRpD1RFR1zXeVz7UWYS8D3UDCCKJfe1DU6aZJMUzplNOJROtJfJiZCnFeZ5yTinOWfLC7Suw8XqMAQHLHCkUKchFXn7BiZ5F1hX3gZWuX6x3VWWCBQJavO0y6AwGIEs9DmcjO2eZZ5s7HzMzW6ux5bsrkADnR5UibMlxVdfE5KliKz+n5+gVLpwqBXBfvtwX6K4KgRDLCJvkNKecY0pZnQ/OOWYGM8mLuLo6ZmIQ7U89EGYRJsxZ5zQwYfC+9IHGOYpoFoljTHOUp+0CS9auxXmUTTMZkAwAR+2/Tz++fHndOj8hSZ7NLGdFxNMwHIdBTV9eXKRpnqa5qcOc8vtPnzdt5Z0jtcttGxinOToCIqqrSrMchzGrff3VKwNQyXUIj4c+xlxX4WGacpYvtw93D4df/2JTRMlF01cvLt+++urz7e39f/zu8XBwRDlLyrcvbubLy6uUzYUgpmIIgM75qm7jPKZ5qrqtgcV5cL5iZjPIKSKUFbumqpqTEbNzCGgGOc4xznGeYpxFpFTutvjNglCVuLoMnVpxoIqIQLROL2HZ/XkGbQyfxM/PsXaxicV+Vj+p68rG59+FMxp1xrJWu7GnX129/9k/r8H8jGqd04EltVv94zn9XTz36pLPlo2lSLp/OLRNBUhZlByLmpo6orZpBrOqot2uVbWc0jRH0QBgCuYBJEvOqeQ+KiohnPpxTtkxbdraOY4xoYGYzTHGORaWl5oRoq3Y7FNubgaqiECqHjGLHA595R2IJJE5JT+ObgieMHjHzJu2Gab58mL/9tWN815UxxjfffycYu7aZk4JweZ5ZuK80cv9znl3Xe03m1ZE+3749OXu4dg7x8H7m+vLeYrvjp/3280U09bo4Xh88+r6+upaALJq3VSmmtIc4yhG//j77/D7P75586aqq+DcjDEAzIC82YWmi8OJ5yk0XZY5zgORK+PzKmIay9U3tSxJRdh5IATElOI4DnGeYs7FenC5m1qwQTUjpNV/FUcMJfIyGhHpMhp65rydgaTVONdBElsodcsD1yQBV+7egkQ+C/5P7lCfynA4t9SfQU0FSyhHyM5euCQNYmAL4HD+3urjn3lcRCwCz+XzuJzzMFNTV4QUY1K1ENz+4qJtG2Ym1N1mk7I8HI7kfFXXh8MRwBLxbHNK2cxMFh6dqG27pm6qlPIwziK5TNHTAh0rITgmM1DVYqnlzZZ1RmoGZRmI6q7pri8vMthpmmIWFT0+HEikaZqL7Wbb1Z65q6td11R1NU2zmKWY2rpJHO8eHvt+KNhKVVf7lKtQpZSHND8cjllknOZQ+b/+q18Tu3fv3t3dH+aUuqaOKZ/6sR+GaU43lxfHfvhyf59S8j7c3h8u99s5SYrx4XD6/t2H+8fji5c3F5vN9eVlcEFyStPgQx1cmKcBCR25lEQtRxNids6X1vpCvlvmmRSZUoyScwkvjssoMzAZAIGKSjYreBMCMpArK1TtfEtLHFomTc7R3tbYuth7yQfWNvqS9i1i5uXYlEJ29R3FVlbyGwKYLK5x0T5eQACzsoDG1mhNhnbuRuFakRlgoRCUiqPwVdYhu5KcnNNoOD8MwJlZnEYH5p0rWgSqELxXEQCLMQ/j1DT1brOZY5rnOE2zmbJXAxQdC26SJZ81VVSUEERV1VKSmFIxU1szKaRS1Zdc6Xy9lgyKCrFScn/q1UxyNsmaZeyzY2q69hTj4dOn2/uqCt4xA+ChH+7uHx4eDzkmBGDvysXt2padG6f5+z/+yLxsjClMqz//5c9evbh+9+HjtutE9OXNRds0x37YbjY5p4sdiOT3Hz7fH45NXTFxCH6Omcl9fHy4eziM0/xPv/99jLHfbhxgW7fBeVVNcSq8u3kYoKpNNUsyU5mn4AM7j4CGRkhnEDFN4zT2c5wJlIJDMC4SlQAm2cSySrQsksyQ2NkSuBdsbknxCtKJiMXLrpno+bqW0snW4I2rB3sGkp9L7OdpACwgFyCu436rokLJ0Jb/eSZsuy5OXvnNuAjrruG7hHRd84zFwJfS6uyKz87LYUGFUqpyruttacE/HE+OKcZ0PJ2mOXYxHvspptzWoW3rUHlHNIzzPcI0zRqFkGLK0zhpzv2i407OOchyrtPVQAxMDVTWDKN8FDhf2uINRDVOcYDBED1yW9UnHUz0dOoR4fLiotk0fYxzSpumxhHuHx4fjsftpiuicG3bjvNUlj/d3d3NKRFi0zRmqmKIuOmaH9+9/3J/3zVN1zZNU7+8vnCOQ1Vd7PZm+k/ffTdHff/5lgjfvLz2jkNgJP/4eArOgxoZSMy3n28t57ap9/ttXQWwMig0W8k0cyRya3RGydHMnHOEfHZRojnGKaVZJRWGPhESIROqKJhoTpKipBlVaQGmzFZl01L02KIYa7zQ5sH0+TDqkvHbOadcSylEOI/4LsTNNXdcy6IVxcSlsb7a8FLvrdXQalII5yUjT8nkGX1aC6sSNLW8h7WHUI4BPU9YAQDRVVUoW89MlUodENPj4fjier9p64fjaZrjbtshoedl5crFdlN5R3g8nbyKmBo7z46z5Kyy6dqmrQlJ1UQtx5whQ1EgkDPndb1F5Qroyj1bpNUWzAERxKSufExuzDPEeDyail7InoJ37ACwH+fb+/vdZvOLb79x3h37/vHhMAzTaRglppwTMHn2cV42iLJ3SPTjx89fv35FiA+P8a//6s8v99txnrfdtm0aM3375qthmP/ww485yeHYbzdtXfm23d09HKYYv3r1IqbkGLfbdretm9qlFEXEOVs6OqXmMwU2QCgk7JJpmZloJnJLLqdqKqYCmkspC8CgIKo5JU3TPPXTNKlmACAkYkNEIzV0WhpAuojIF20INSurpwDPFQwUJfZSbi9bFQCWSgB04evgYnDnGuicGCxhzbQoQeMZJTzDorbknraIvwMtNXjJMBcm6Yqwltuu5ZlLfxSXE2Nr6xPOR8AhQJG9iCk9PByy6ByjY3e931Dt6+CHcZpj3jTN3f3jaRhNtR/GTdfKwrsBZhLRnLKIFNLUpmvbtp3mKKLa1OWNapaMshSaz5gLiFhS0sL/MUSYiySONW0NhQyKVD5MznI89dM0b7q2auq7xwfLUp7l3fuPx74fpinO8zRFyQkVyBECDFNvIuxcCKG28Jjz61cvguMvdw83lxfbrjv2PRiEEMTADC4url9c0bsPH/7w/buPn28/fbl7/dVN225fXl89+Idv3ry5utyd+tN+t7u62O/3F03TMTM8u7trI7d4NTAs1Fheot7iXiTlmNI8T4PmCCCBUAkFUFVTitM8zTGKKpqAWRlkgqzeBSVRK7RxBMDiYpTIMS1usHBSy0/hp2o2WCLVApESLCZki7jf01dxgGfwH5ah5CUZhRV4R4RC7afVny5eeWXzrYo9oAhPNCszxDO7dC29znDX0ogwV1c+JhymGLOcTj0RBe+cd5/vDs6dALAKYbE9xMq7LGoG/TAhQlNXkiXGJBJTSipCRNM0/uEPP4QqOO+rKtQhVI4fAFJKwXyClMSYyQdvBiK5iLMvufQyLqiSNKt657pNpyLOcVkYXCj6GezUD8McvePgXRXCMExfhrv+1BfB0VIpm5klAwAi8t674Nn7Kcab66vLy4vH43HbNt5zP/QAVldVERA9nfrNZkfO/ebPfqkqp+Px05f7FGOM88vr/X7bbLfbFzfXx9Oprpu6akJVBx8KVAfPv5aKBRdqE5GBETECIaKq5JzSNE7DaRgOKBlNlAwMRHLKaY5pSsuwHq2VSMlCk0cgBWQDVDVEcs45ZkQUNUYzKFL1CGcwfJlORrVi17hCAmcbKpXKgrgv9m2ABFqmSZ+sfCnRC7BQyCZlBmABws7wPuAa4RGL7hWU8aiSDyMY8FJAL2+3GPg5IzAAZ0jEzjlNKY0pEbNqBYBjnszMOb/fdo758XAcx8kH75xjxL4fYozOORd8VnXqYozFf8SYRWWc5q5r99sNIk0pOuamaUacsxqD1HW13+1EpO/7mBIIruSpBYBDNTM5HY5FWGae5mUAHEALImWJRXLE6H2Y0xzjNE3lUBZ421YADgm9dyEE7zlLQqS2ae4eHtGgl3Ga59cvrq+vLh+Px4uL6+1mezjczfOYstvvL3/182/jNLbN+8dTXyCI3c2NITd123Z7ACPi0ujLOS83dVn+fTbLsscBl5mPp7TNUpofD7e3t58kjpAjqjgw1JzFprJ52kAAkZgJiR2wA3Jski0hKnPhvSPzqrhZcgRaQCiiMv1WOA+LEdDqHBcLWRuU5dqKLFjAcsitNIBWsGhFjojwJ2b0VKcvhlf+FITnjCzo2u4ssZtwmUQ/Aw8r/Ln2HswAwJUqpq4bQxz7QWOa5rnsCTbCbdfmXMUYqyqI6BzjNEUmSCnnnLNoXdelSxRzjlmKClfpMLHjKaby5bgMLmdiUtOU8zhOTVtvNpspzillSWmeY2k5AJGYMVoWmGNk50SfWhBqCgKFCI2ISXJCLnvEbf0qRo4AzAxExFRXfo5pnueqqt5/+Nh1m66p1ezt6xfMZAbOeSJMKW67/eF0PPWfr69e7PeXqe1Cu/ub//C3265pmrqqKnIVs/fOWyljVwlZK3xXeMJ3ibjIJgMRLNW3AnAJ+ExODWOKp4fHNE8oCSRbFgETRSAUIGXnmBw7YGVnzgGgslNmR2zeO2ZHVNZSo5qgQtn6DQaiBgDFDkqeVGyjEJ0WV1lo/kvpsoA+a7fp3M2Bc0JWCBVl+zzC8wnntY+12nGxxMX4DIpYry47P+BcTj0dl3VZyvmZiz92m+1mGMbgXV373abtx2nsRzNj79i5rmsN8dOX+xBcCKHt2hxjylKchKj1w1BOmveubZuUUk4JkIo+o5l1bTPOpKJoggjBOUJUUSYMzicUyskxo0HOIks9alBwXVRMqUiAr5XHCqcgEhMTmYGUr6KRuyLNpcvimJ3juqpSSnNKRpSLDDT7fhic4/1u808/fLi+GF6/vL5/uD/54367HafheDzst1vv64fD8XJ/9c3bt1Vdh1BhMS0kMyNiRDaRdTYI1y8oeQUgItHTfYClE5NklhTH8SQiTdXmNinQPPQx55yWvrshKRqiOmbvzcgoWxUMiUmNSJ0zREMEpmWrYVnnp1IolUtqsTCTDEUXUJwJrbQ0AQqdTBdjWqr2pUBZYKIzBL/8t0D/+GRb68+xwE1rG+n8W2t/vpwJwnPjdkVs16przd/h7IZNzeWcg3dNXTvPAKTwOI0TiAXvEanw5It8vZltKs9NdewHxOrUj9M05Tkuu7+c224753x/PB1Px5QTR8eurFxlJhYil5JlSWZECITTVFSbivSIOeaVPLMc3DKbz6p1FRAwznNSQUDHjr3zzi3D+yKmiiXpXvsiZZ0XM4vY8dQjknO8CF0RH09HU+u23cfPXx6Pp6EfwaypQxZ5+/p1XTUA+PhwD8j9NKvq26/eYMkg2RGxAZZhYCyrjvGcQS0V3QKcreopiIiFSwyQ8jScDsfj4zCcSvkfQiVSpG/IKKIsGm4lAOecUxbDDMijS84xO0fEwYeUXF1VWgVRVRe8d6svt3NvxgAJQeFp9ZGuE54GZdoTABbyUSlnVxTgGTj67IytBrloTZbwX6buyaiwLM4oTXmiUi+qAZ/j+YKDL667nOpl09cZDTVTMDcM4+V+d3mxH6bp0I8AEOp6HkYRIbY8ZzFlwipUwPzw8Ng2NSzPguycD5aHcZqmk0o1VdeXl+2my6ZxmmKKNFMIgYl16d4zB984NjNQjbnMziuY5YV3boBIRCKLoiQgECIQIyAHj+rK+u4SrpjZQJkpxlTaWk/HVoXJkXOBnUimBc5TNJCUsgp7j4D39w+FVgIAbVNnydOU/uLPfl5X9T/8/vsXVxddt5Wc5mm4uHyhgGZISLZQLSyLQEkkzplTwcdwEbl7MtBS7ZqJ5HEaHw8P0zRIWnOglGLMomjkAIyxiJRp0dMp4AgSZNGYiDiFEJiJjRVMDQiZCEWtnINyBdbeh5mZ5nOfU3Hl4S4AT0HG1ppmgTmXuaU1Pq9/oXNnaHGm628uHxlMQdath7ikp0tqscKquHbrF+9Lq/Nex51/0iZ1bV23TV2Gg2vvQURCdoRV8N4HVRnH6XTqcxqqugohlAaQijBzW9fZ+5Qz5wyIaY6fPn6qqsoHj8SQc8556HvnPRGBllm2SsxATSTHlJJkWxp5Aqti2RIW194SMzsiAUMjzwucR0TddrPpmqEfHh+PAKl8BFyPLQAyc9e1Taicd4QQY4opDf2QUyZEx06ylCGklNIwTqqKCN/98V0InhC+3B1ykr/6ixdd10hOMcW2u4BlldGCv5TUU02Zym5EMCvq+7S60dU4kYrcvHceF5AX1ExMk+gU8xSzKx0OAgUC5lLiOEQHAMSGTMSAWOAR74N3ntGZgUpWAiQnsDQwF3LQk5kiFEp4wSuhvJt1+LiEeIIiTrhy6Qvw9CdPhavBLvG4YOjwfBhkSQ7OOaYVrAuBVO1MeVk9CRhaliUxXds1CwiFAO50Og3jVFWhpGuA4IMn5pxzSqMPjp1j5yRLjFFVYyQEqOtAaCKSVcvAmkiWLPM8j+M0znPZRpTmmHN2zjFSUSZiJkRi76qqUgBOCcg0ZStjuGC2rLJ6ymxKsKtCUK8i6gCRuW6qtmmc91VTXyD1Y5jXKr74g65rry8v2bGqbruOGdVgHGfv3IFOOUuZKwcDEzsdTz74/W47x/jp9g4Ad9tOlU7DPE39zc01AJqh5BxCXZCgM+RpsHTY0bC4f1ySv2XestxGJFpq0zINl6OUoWRywQOYOWZYKjwzMzBrVKSUzwAA66aaEh/VRCQCZBEimh3XIYQQmBlpWaRbxgFWi1m9KqyHo8hKWonVpcO8QHLPQvozJVsAU80ATFTCMaxaOgvsClC6LeXt4dIseHqGwlx7KreeJHDO2Wtpi0KR/St+1h1PAxOlVNd1oIR1XbdN/Xg4ZREVTSKIGOqKgKZpHMdpnmd23OWmaZrC09l2Tds2p364u39ERPbujOIyMTKpyBCnsv7HETtmcux2O+9YqjpzFoNxGJd+nFkWwTUyAqD3vttunPcxxpyFwAxMVU/9oCervKur0HQtgKFaKZUMoGqqTdc1oRrGQVRR6auXNynLp+C6bXc69sMwlfbPlNLh8egrH95+dRrldOqnYazrutl0VxcX7z9+2e8urm++YhcQIOdYIratze51P5CtIX5B6UusffKhBoAgItM8HE/HYZhMs2d2zokCsqKCSjZAIvKOiYhpDdCqWXROOueckqSkZaifHXt23vlQOQAEJKfmPAIgE6rqUtEXGUe0M8t9yYJWMoQuNCV8yl9XJmTxEPhk5nYmSZfIvSp2L2nl2fqWMH6ePF4h0jUleMKVCjRTsl4pqmx6dtfgYoyFPDuNIzG/uHZNU1dVDYDGOsfEjJcX+7ZppmmepvH+/nEch5zySQfnuKlrBX/sx9MwZlVyblNVOeXCCmXvETHlPI5jnOeUEiLmlDWlTdddXF5cEt3dP07TjFjU/J8CQ/nQzOyrOlRB1nwsiw7zlFJm4q5ti9txhJuuDcxmFkplxuyD2293zNdgNs1z1zRf7u69921dN3UlYo+Ph3mamCnGrFkOh1OOiRD7YUw5i9nrVy+ato0pppR8aFyockoqWVWLCvOStC2hcyVglJoDCYmw/FmRxSxpmqZpGud50pwyEzMjgGdCY2VCAMfkHTMioRGqqmQpUgk2RxmneZpTUkMi77hyvmsbH7rF+NY3Y7bARes0CBVDNANaS8lzwrrkhmsyivTUOoJnDm+trZfwVip9Lfsd1rGTUhKVfSlECGtdf75Q53xjQbbsHNwXX6uLR14AcWeqQCg5IyEjz3HGExX1wGma1KzyjWQZ57mq3HZ7fXmxu729/3x7P/QDmE1hqurKee+8++rFjfcOAI7H0+HYRxEHgER1CLvNJub0+fZuGkdVaZrGAMZ+IMdF/kpXbwRqsAKZzByqwAj9sTfQlCTFGQAIIDCHqvKeYVHzQDLwTN57JCoMnKaqEGyepqap37x6iQgpp5xzjOnFzY3m7BnnubnYb6c5fv5y//n2vqqqEKpxnlXUIXaBX7140XU7YlfKSheCZsqQACBLWsH5pewsi2myZAfOsXtyKsVPIBCiqmURFUmiKWVCgDIpgcBEROgIVvKxJs1lNE2WYZCUYoxziiKGaHW9aZqubdqm9j5474nZMRNS8VNJFBEcczG9BWrEMsX5NDEHS3WyVtxPE55WcgpbqylY4UnEFapcLLeUtlIaEfQshVgvzrn0WZ5HVns/5xKm5b6X5sGyZcVpFkV0zvu6IqTDcbh/OJYyuSBH3jkwSbMeHw/eu8uL3cuXN8j88cPnaZrmGHNKoQreezSrm1pEnONQBTUlYjAz1aqubjZXSPTly21/OhV4KIswYt00oQrjOK54w/IxHFHwftO2zrlhGKdhLBVVoTUG7x1RkUuAnCvvqxBCCNM0IlLXdV3XMNE0z/Mc1bRrm65tX1xfVp6dr6qq+nJ35xjJuYvd7uHxUNfh9vYRifb7bZmrRDQi9M6FqsaV/6U5mRkzgREbq503Fa29uaVaV5HM4IAYgGDBtJGQQwhV8EsdgyQqKcWUpTgSzxgdES40ZQQtLKEi4EpgnjARAFBVN9eX+6vLy65r6yoQO8eMS0wH0Gc7txft+iVNVls2/gkAAwAUKjSoWqmTnhzbiggsQX/NX5dLYWZarH11jUimKmvgXrzgs448IJTXWoGwdRh55Vkv4JSBrWssXCpnkWIScd4557qma5tWVUSyrZV1TGmcZxU5nfrdbnt9dQFmX77cl0gXU5pjmubYzrEKgZkckyn64FW0H8cxxg+fPhFQ1zRFZX2aJnbOORdUDWzZBFj6WgAIRs6Ftokip2FIMWph7gEQEhAZQFKriGrvfPBqdup7s4UC1o+jqrZNNc+RiB4PJwO4vkhVVbdtJ6qq0g+9qe27jhGPp1PlfV1X7Fzwvu8Hx1RVNTuva1Ve9GIRUFUk5wWct+X2LJo0ZT0Nu2X5UWksFXkDREQUyVkkppyyppyhCCESlQEFM9UEEyzlFyM4QiYEYkBgIiarA3vfuarebrcXu31T184xlm7S2iQoyh9rzAcRMUJCgpVTVMBzpsXmVBUBDQt+hmuGWlJDKDXeikYt7nRtiJRyTp87VFxnoJ8wPztDK2CmgMtIdHlWXeP+uV+1Ml4BAEprfUlmEdAxe+9TSkmSiuYspfh3TMGxmn78fHc69TfXV6pWVpoSczkxkvPpeBqY6rpGIiaqEZHRAIa+Tyk5dtc3N7s9jcMIZkRUhVCc5ZKp2DLwh0xMbGJjnHLZnarGRIwEjN57JvSePfESIAlNCxk5jcNoiNeXFzFGJAzB13Wlau8/fqmCB4C2qXbbXV3V9/ePP7z/xzcvr8tNuNhtx2l++9WrLHkcxhB8FSrnvAEsx7WEJDWRnKVMXUqpLXipaNAWZSxaoPD1sq9RTCTnQn5bq3oDQueY0HK24hRK+1TAMqJzzKxIXHzUZts1TVe3XdNsmNgARK103WHFtmzx6LYAQFjE5stWkKWTtLxyGRp+FnlXZZ3nRmaotLI/zUpEWEqcxSjPqNDaKQV4srznF2F9agPAkr+u8PCaBz+lHWYG4JquNTMmBgQkjCmZQRUCGJhomTrJEhM+QQ6H0zBMMyM758ocZhWCimYRx7zdds7xMEXnnJg4dteXF3NM4zSaQRUYknG3IQTHTEybrgXV27uHRcC/pDvEQCR54d2pmiOq6trMFDQE31Y1EBWjAUQ2E8RDjNM8qSgifkiprarNtvPeV1U1DFPOWQ3meXbOp5y7pn5xsZWcfv+HH0vNXIUQk1xc7HebTYqxbaqqqgpSAQCqYklK/00k55xNZalGS/FROptES9JmRcjWFJQIkDCnPMd5mKYpJjNzXGyFAMAxxag5WUqiIqBSArT3DsuoFmIIoa6but003ab0uuaUSyuOEN2yFIWR2GwVU16rt5I5rdUPIQCcx0oLE6kc86LcXswUccF6V31aW0eS1gpqrcbObSsoYuWGAMtQgC2WVox1IR8uQrmrehmeSSa2NEp1ZUUBOO89GjjnRBUQnOOyAfviYi8q93f30zQXvFBVJWfvXQh+nqQAwq6cfTAlc94XHlOZD4wpxZybGqsQ1Gn2vq1rAGDm64vtHNPd4+M0joExeD53BQ2AiOq2qdqNSpJBHTtw3rFDQgKoHTNREiEzds57n2IcYoICkZgCuyx5enione+H7fHUI5GpXWy3vGmrur7Y7w6nU07p4eGh6DOOczycTkm0qcLxeCTiU9+XzNI7t6ZlRXRDRUUkiyQwO7c0ccXt1wTLcBm2XMBtUxXN8zyfTofj6TBNk2fk4IJjIlQmBJ3mFMVkTmaKCHUVgiNfusXOhaqu267bdCE0YDjOsZ/GApYRQ+V929SVeS5COrbgU2fnhYigi8hCSaiLuxUtK/LQAAqlgdcGREHH1kFkNLBz1X8GgYvpPqt2llC8+kpb6qTzFuc1A3jiJp6FzVY28xktRURXVVXOuWkrIhrG2cxc8Ju23W46M/XO9f1wOBxTSrTsldK+H4vYFTOHEABBshhYVdXsuOxUMFNGaOo6eBdTTDHWwVd11TYVAHZN049TTDFOU8r58nKPf/zRkhaYm733oUIiSUrO1XUNZR5a1FSkrNIgMoQ4p2kcUxYog9VgubSuRQzAN/T4eHg8nqrgzSzn/On+7qub67sqTCldbDdTnNnxy1c3CDCneH9/qCp/fbkfpth1m03XFEJCqc2hcJeWFa6GAEVMmcjR+U6t0anYZXERJWcpGfYwDqfTqe+HYRiDI4SKqWJiJqq9l7Y2kQkkpVwElACM0AJD8BQCegZSmcdhmPNpGMseFeewCgwMmjkvK0aX3cl/stoLDASFkBQAlzZBUc43QaRnB2ydVILzx1JVe95jx2Wnk1mpulYlvYXdaQBl6mqZiDz3mFafuuSwazBfsodSjZ1fBQzcfr83zd65lAXnhACXu+1u2wGBGXnvmqYuxA4wkJTGcSqfIaakoqYaqsCOmcgxVcGbOTFIMZqoGey23a5rDsNoprvtlomHYTicTjnn3abbb1tVu9xvf/bN19//4YcSetA5ruumaUNd5ZQgSxZhIMeWhQpqLSlLzlb2GqkhAJbtOUspBT6EJMoEdVWpKjGdTifn3f3D4fb+4c2rF3cim7bedt0U0+Vuy4QpppRyU1ebTVdVvGmb7bYtTQFmlsJaAss5F+9C5MyMaFkqe3Yr63V/lk2BmRbZkDnnWPRw1HSOyfFCzchqCOYYPXPZzEmgKiqE6ha37fM85jRFvevjnLIVAqggKpiKqqhktXPBsyy3KD6Scdl/XogjxfEr0ULFQxJEJHRET+gYPstDYdEQXz0gPCeL2ApqrqAbFDirLIFeGSnLA21F9Q20KE8U1kXpmp4helMDAueZfN2WOayri13BgcfpbrvtLi/2L64vd5sup/THD59jTKfj4fGRzCyLDv2gqnUd6roqSbMP3gCHfkgpzTGpaE65rcNXLy598HeHUxVC27bf//h+ntNm23775qZr24fD8eF4MoC6rlJMyNy0zdXl5W6z7cfx8Pg4xZTjrCKjiuZsa5timV5acnIzBTBBRCRkYgQUVXIMBC+vb64v9uW6DOPonDOAcY5EtNu4oR+2XUvsri53wzAZyPXl1W7bimRmMtUsUc0hoKioSokeJcU692bWlsgaAZc7eq6SDABSzvM898OUUkYERkCElHIpTHJKKSXJsuKV5lBBxRQkGZkKaB/jGGXKEAUNzDnHTLVDLmurwUTVs8Kic3jev2oAoFj2WayZcoEtzRCBkUqWSEAGkBHLB3ySOT1bGBaCvRGCyaq2gFhWJC4Z5EqJtDUrhVU3qlyeJROFFYGCFTpdW6ELpI8Iqm4YBvY+54ymTVNf7rfTFE/DAADOuU1bi+r98dTUgRGOJ+y6lhCO/dDUAQm994u5gOWcAVBUc1aE5Soc+/Hv/uN3dRUuL3ei+eFw3Gy64/ET4+by8qLttnMW5/zt7f2CZRC2TXNzdRXqKkp2jst1AijZn5oIwJPw3zIys+xEI1vYT+g8Bx9KzgAAPvhffPPm8dSf+rEKXlVD8Mdj/+7D59Mw1nVV1/XjYSCCUz+1zenm6pLYqaGa5pwQpOjDSE5lWXwxvTJBIWKAsJZTCE9Ts08VLgOC6uF0fHh8HMbRMXpytmx8N1NNKaeSAjJ7RgITQ2L0rmxN0WGYslq2wv6h4F1VcVfXPnjvGNk5dmKoBkxlXLgEFF1bkWVr3mo6q6vLGYAXGMzMSnOfzr0HW7n3sJZ9z5qU8LSgttjb8mC1Yr4L/LomrE8Zgy1lOwAA4wLJLWnnuco3AwB3d/+w6TZ1UyFSFu28u9l27dCkGA/HU6mKzFRFiHC36fp+VFMfqpJalFXe7JiZ4xxjEgRyjpwLRNS0bRHo+nz7cDgNL24uQqhev3q57br7x+P7D59fvLBt12mjl/vtl89f2LHfbC6/enV1c01IKaY4TvMc8zyLyOIP1kjxFDsQC4Nsyf+ICoQxp8jOVRjGOJ+G8f2nL01bf/Xyep5jGQkZpul0OmXRlHIV9NAPY9+/+eplynLs+8v9pffBlk2F8RzwVGXN64u/hjLHLuukh63wYBmjLc5DVIjIsYNSPs6as6uzD56dK9W+sYHzbCulgwAITAxMdU5SVoUTZCZ0zgcOm9pt2hCCRyIpI8tAWVF0aQqnnM/+HlciSzFTVaJis2vtwsxY8Jx1Azetqq5gcEZZl9q+2Fk5rCsmtQaQJX1d2vFrXnvOD0qHae3+rzmnlbwWzhSAAgE4RIwpMaP3PKf8+HiQ3KkaOvd4ON3fPVzstyEEBdtvWu8dO5YsOethmr1j7z0i+uB3m/Z4Gvq+B6BpNmZXVSGlHOdY7KYfhvoYXr9qHdmbV1fb7ebdpy9ZPn395vW267569fLz3cPx1Fd13Xjf1cGHahz6j2hzjDEWreEzjF/QNWLPBTMmIlMpooMqYiKi5h07A0Qkg9NpyFmuL7aaJKtuu3aSVFfe8/7T7f3j8RRCeHl9daxCSokRqhDmOHehcezmec650OaLyt+512K69GOYtJACFHGhjBGWaaSlri1Oixm99545qUrOmZAZUYAJ0TSJgCktjGcgMFQVkZg1GywUIjMkrIkdagBFzRINwLJhQmbnEZ0ZpKzTnOZ5nqa5GMfCGyd2jokZkFxZH+icqhYBVyIEoLN3LB+yMAxU4Tx2b0tBsxhSacevQFKh7q92CWVIfw36pa5XKLgBLV53wT9X77OKmxgAmquaJqV0OA2qaip1FSTrpmu2bcfE4zhOc5xTUrUUZ88MAHNMYsbMxBScA4QQfEqZHG93u5TSME0ppeAdmA7DmESmcWy7ZrvtRHWc5inGTdt2Tf3p9q5pmov9/vrq+uu3r999+ARMofJ1XV1fXV9fXbndxvi3h8+c+n6eJhEpnsuHyjHbKjKMgFkFwUA0RpBShaQ8i6YUTye32W3die/u7pu63m/boW8Ld+bVzeV+t5li3nTdHKdf//LbYZqiSDkEw9B37YaZNceYo4oyoxHTmkURAREvgt/ETA4KOYCWYdqCg5aStyT6wVMVWDSDFehOsJBcEZkwJpslg6krZsrMzlceagQzUEBH4GmZaYpqGrNaVjMgBjJnRIwiNk5pGMd+mPphlJQBrMwXOOaq8iEEdhzKDKRzxKyqwTtjXpC1QouHwpVHXuc0114RnuuedWPzMi1ccP6lolqxzhKun0DUdVxOn1WSxSJlzecXFMzAtU0DdfV4PPVDjwop5XGKt85tu3p/sa+rKqdU9qjePx4QoK6qzaa9vtzfml3utm3XTNNUenebribCH999FBHnXFZrm4adi/MsOeck4zTvdtvjaXCOHKIjAKQ/vnv/9ds3N69fzznNcxzH2aY4joOjFzeXV5f7i6tu8x9++49fPnw8HQ5xHDVnAHDeNXXdVMHMspqKOCYDO51OAgCZVLKplMS/ClUc43GeEZEc3z9WF9tNEmnbpvIupkzMnum2H+I8v371YpynOaamcobSDz2W/cEGZpbFQJSf5jYL/5KXze8/BfQWwOlZyVLgd8ccfCBUhwAAOWsZYxMAYvKrriwxe0fOccWEoFDm1s1ETZGQyJCSARM7Ju88MhsyIuWc5jiP03Tqh1M/pHkq8bocIu+9d857R0zeOb8sPfdVVdVVVVUBQIkcEZ3ppMuuxRW0KslWaUrpOfVZu6ylT7mw6kzLaJqt4MaSpCOWPTq0WnlJBmytLBhpGZqL88zOeeeD95IyMYlpnud5nh+PQ1WFtm32u00IPsXkvNt03W7blvp9t9s0dXU4nD59vq2bum0qM1C1qqqc45ur/Xa7yWIq0m66z1/u7u7uL7bdy5dXMemU0qaprrftw3H4fHt7c/PCeb/dbcVs6vsvnz7uu2az2Ww3u7/61Z81df0fu/b9Dz88PDzmafaEvq6NCiccm8AhhHIMtt2mrrKUPwCqxgDTNI/z7J2rQpVFjqd+GCdAeEn0+e4wTVMIXkT7YVgmNAkeHk9vXr2s64a5SE1JycsK/aWYGjM5ZMJlCKRgpEwOyia4Qr0lOkMzi1cycwx1IAACMFVLOUczptJ5J+fJOy6rugkNzUiTqZYOpQAoEDISOWQXgg+LPiYhUTZOOReGSE4pzlOcxnmeUaVM0TEzMlNx8lwmI533vqpC29Rz3bRNs2lbH0yJjblIQiwZZylo1rrz3EfiYlhqpR6Sxa0uWJusZq1rQ2lpMsGZSrKSa5/lb1r6+4DueDwhESMwsWvctm0FIOUiO5z7YYwxxhjbtmna2jMHR455Ur25vmya6njqvWNAHMYpHE9zTOM0Vd4H5yRLCOGXr199ur33pz6nfOqHz3eP37x99e3bF5++3B5Pp21X13X98PD423/87RCn4MPlfp9i8sTHx8Px4b4Nzabbvn756svD7Xw6BaJxnsFMY0opiTko/e2iCWaGAMxUheCCr9pWpvnxcDyqkBQyvzla7o9jqrwv7uHh8TiM8xRnRjyehqv9lphSlm9ef1XX9TkKFW9ScileSh+ErAbiEFUZCpy/gNNnhKlwiCyLmGRTUYPKMyEpWBbNKWdREVMxIlRzxFx7xwRQyHlZRFKRVzVidr5yyExNFXZt40MooLkYgFjKBAAIKlkkZclJcwaVUrcbrPMoi4myY3bBzXOwMl9F5BwbgA/IRCIFHi3EDlQDybl8PCIs43dSahxYkgKzsjF3CdxSli3COa+FonTG5xbXOq+9QgV2/pGZuXEYAJGYQgie3TDNarrdbrY3V4dTfzgc52lOMSJAVQUkvH14TDm/uLnqNpsY5+1207WN9+724RhT7vshjhMBsOOH07C7SCH4n33z5re/+66qw+HUG8D7j7evXr64udzPMY7j5MjGcfzhhx/atlNCQuy6hgDv7h5MNQT/6tXby93u9c2Lx9vbY99LSnmeQbToOjvvyHFgbupqTpnBCLBq6zevXjh2X77cMeH11X4cp34YPLskeY6JEczg9vauqirn3P39PSI1dX0/nNq6lZy3281ut51S9N4T01LWApDZuTsPBqqCCCLFUz6BjrZ2QWE1U0JidlVV1VVIKaJpYUMyITpCsIJSSFa1TEgelsoJc0oxDSklEQNgdlWNVeVqh23FPriq8kQIarNoEiO0lLNkycXsRVXFRNZeF6KqFE4goiILUc5sat67kKosOcZExLR+TEQsi6zxzPVcNywv2czyuHMHH/AZc54WT7oQmYlWqGrZMbLSROCpcaVLegSA4BAJCb3ziNiPI5h1bVv2Zu26jtn1x2PKGZBijGAuJQnep5Rvbx9Siiml66vLqq62G4kxHQ6HpmtD8DHlNM+3n2+/a8Lbr17udtuPX+6r4DdtfXc4PRxOlxcXVQjTOBZN4X6cRQyZRPXF9bUPPuU8zfHx8WG7u9hu9t++fptzDN7fO344nXKWGqBI3Q7DoBBdCGKmgMwURIfTgIhlXoCYri52++1WVac5TvMcvD8cj48Pj23bXlxcIGJKkdgR8uPxgI6TCjNt2rr2IVA4RyWAIvDLS5GEi6oblcGeEqnUgIsM1TJxVrIrIvTe+xBCCDmnMx0MAIr/K6BjykaQQRTAQAQkx5SnLGU1cPBGjJK9SgbLYNmUDagkuWiGpqZaCJAp5aLOvlKiYSnenpYciKqiQKmAVFVWPcKy+GaF9E0AHRMRmloRn4BlkGON13YWMoFnlNAClC4VUklyFBCXtc+2etelNFrnR8r/GQI6XwcAIGbnHDtnqqZ2PJ6yyPXlbu+dSsZproIHw2GMTPR4ON3ePRpCCN4xl/H2yntE7DaditZ19fHj55zyoR++fHkIobq82P3s69cPjwdTy2a3d/ffvn2bLvafb+9ubx/6YR7nuQqx67o5p007brt267oQguU8DaftZn95+eLPQqib7vu20/cfjg8PMs6FA2pmQ8o6TioCiOT9cRjv7x9LJOo2m6YKpbk0DsOifQI2z7OKxhinadZlcasgsyENw9jVNSGNU9x0mRMqETOvedgCvkJRpyPjJXxhqUNwaTItzFwod0MlpZhzLuMoomKS5yjDNEvKK4Vy8RyaJBGoWhJRkUJOMTMiqEUNyPvkfHRjJHKESOSyLbRTQkOErGpgxX+W5I/PZwhsAXnKxyAkdrSo1xqYZpGU85KflEX2RLAoZ67wvgEuumKll1foJvYUOgrAtLTdbQWsy4UyWqceoDSl7Jwi4DpEurhZV3QZGNkAVHWKMcZYBV9VYZxiQcjalolpTqmsRCrn0nlXhdC1TRbxzg/jBGb77abwmolwf7G72O+qJjgmEdnvti+uL999/BxjOg3j4fD48ubm8Xj68d2n4/GUUp7GqdzPh4fH/eUFImiMh8MRAJq6vX755sXli227vby4cm3zH/727x8fjjnmMjBUSAnMpGpxGBBwJHREoa58XdGEVFVTjKdTryLe+1M/ZjVDnFN+OJ00ZRERyuyYEVTy8XjabDomkiyZkI1XqJnKTA8qANhZN6SwnEiE2T2bqi1egUpDf44xxjnlbAaO2EiL85mTTHNcFIDBGNEzBbduvipebuUKC6AiipmKxBhHYgB0voBfCGXQouz/hGXAlxFhHYsuGQcTQZk/sXUAuTQ7DHJWp1r6SYgIzCbCAGVG2Vby3mqRZ/BywaHWLtOThN1aDOnZPMEgi66ud5F5MgNFKIt412lPUDCHZo6dc26a52mezdQ5Zu+K5pZ3DAApSco5WEEASUQNLHiHAMM4dm3T1LWI9MP48vpCzT58uq2b5s1XN23X7rcbVfvjD++7Tfvt29cXu+3d/cN+24hkNQvOGeAcY5qjqkqS0FQxp83n21AFU5VNF1Os6qYK1f76Zddtfl7XL65uXlxd/3f/w//j/R++xwTMDAbeMRKlnBAcmJFjDj6Kfvly54O/ubz0zAUR1Cwpp8qHnCKYaYxFb2xOsfNt1zTZ1HknotMc67pwTRacE4nMsNC0zYCJHLOacWkdFTdYeoLPaiRcun4mlj0B1U4ylqctNYtzLqdU/B0wMoKpeUeO0AC9Z1VX8FXvKHhGIDFMApANBWsHXApn1Zg1xmSSQZXAmACZzIxLMki0IOQlA8bSrSmsYU0irmi1qoFZFilbfYnQlMonRESwUiyspbgVd1mm5J7mjHnBbhefWnrFZcSq4BoLy3+1Wjgrh69oKyI6FUkpMVFb18xsZpXzxIwIcZ6D7+qqApvMrEwbIlFdhQJ7qVpK+XQaVVQBNl272XT9MKScry92bdPkJF9u76sqPB5OADDNcds1Kc3brg2e+1P/3Q/v+34owI2IxDir5FylL19ut7utqngmZjw+Pn5pPnKo9hfXdagqH/71X/5L7/x/+z/8j+9+/wfpe43zlBOxI+fIsqpYsphFzbxzdVWJSI6JmBxzVmFmIKtDJSLsuPQr0xy9d03beu9e3lxdXV7UdX1GAgUEoAhkoiqQWrFXOM/TFHIGmKrxsgeqXPlSk6BnrrwfmSVnXcIYIiJTGXgBoqfGtJqBISMQsQAqY1F+V0UzMqCshMaVq5grI1ZEE4hJxzmOc0opS15asqVmh0JtWXyoYdlju5CqSwZb2HIqqlky5YJJgRXxRirJI4oKIYKVDW24wqAABaVHW/v4RebgCdXHpRFma5vDZNG6XzT3cGm/L/ZdcAMnYpjyBNA0zcV+t+0aUe37YZrmcZKmCqPpHBMilgaDmbVtc3WxnebYD9OpHwhximmcpk3X3d4/Hk99OWgiYqpTTOM8A2IWAdPLi0tEmOb5y/3h/v7xh/efU8yExMxF2EPA8jSNw7DdbT27VXTTDodHHz5WoWq3F8zUtdt/+eu/AID/qa6///6P0+lkScoOVslJRbSkRwgK4KaoIsH7y8tLFbl/ePDOtU09V5WBtW0bQnCOh2FUkbZtmrqqq1AFf7HdOO/HccwplsLVIXrHziEXljygKqgYOjiDfcVSCQUQEFdhWwAids4D0BxlTnmc0zRHyQX21sLsoDKqU5TTJCdznm1tOoIYAAExsAIahZJKEgGwiKacj/30eBxPw9xPMeeMYExF8Q4IkLFMrBQneE5OIOsSAVQsZaUsRLlw82BJO4rntVULYrEkfd5kX7nJBTXlc/WOiCvzb4kqixkuoiVSvOw54KyggKoRmRNVVGW1nFLOeY6pwNCErKYfPn6JOXdte3190TZVGQgvcR8ARTITEnHsx2EYS8ibpjmL6KRVXe02HTEP09i1NTNPU5znJCIPj6eY8u+/++HUj8TkwK/NCairWk1Pfd8dT69eXjvvUsrTPIUQ7u9v67qu6oZdS4Sbzf6vf/NXu83mP/zHf/jdP313eDyMj8dxnrzjFFNOidi1bRu8F8nDlASwypkAtrtdW9XOk6myc5cXu912o6rzHMdprKtwud/VdV2CoZnVdZOZYpxzkR0yFikkUcgAZtHMiJDYOSQAKIkQEtPTyKNpwT+N1CyJjnOa5jTFmGKOsUhUWfkPIhAgM3lHCZSX51hLKNKsmhRb4OBDP6akAyGA6hjTcZiPx9PpNEzjVAi7YKXiXmaV8Lm+kkFxq4Rn2P1JwlJUEUnJcEXaiWjZu0aA5/K9IJq2Up/WLxErsSAvMX0ZIzyPdzACPBtFWrNYK52Bwo8jBWcqkiEjeO9yln6cJOdxnGOcY0oIcLHf3dxcXu53zFhCUzGlrg59TzFnQk051XXlmad5VlMTTWbznNqXTaiC907Xaax+mO4eT/ePx3mOwzghqCOXzZwr6xyEENqmnXM+HI7bTeO9c8xf7u6nOe62u+3mmGIMdYPEnvBie/EXv2xeXb14/erl3/7933/84X0YpjTNRIOvKt+2dVUVTKPIJ2Uzk4yio1mLVfCha1tmRsBt13318kVKkRDrqmqaJoTKAB8Oh9OpF0Mt60cyMBMjAljKgoDOeUA085JzKoU8Cy2Rs3iKJX4BQBYxUVWNMZsYQREIMUBgxy67Ocac85wTZYiJvRMm0rXqKjFymqkf4zCnOYn3wxKzAVK2aY7jOKYYJSXJmVau+1KLEBLAcgNxUaa1VYhgsdJ1sYfZMtJbyu9zik1Ey1KFIvVTxIYX4tKahK/D44Qrrrmw+59kSdciHp6p45QsFlZzBQNz5Yp67w2ggAumVrpH5NzVfvfi+qJpGu9YAWKcDscTIg5D1zR1COHqwo3DFLyP83zq+3GcmCirEmKc53EYr6+vjgD9MJpZP/Smev9w6PthnucqODB3sd8C4uPhFGNSkZQyEm67dp7j/cOhrjwTeucOpxMRD9MwTUPVdoGpkMSbugkvXrVNc7G7+Mfr33789OX+/jGXESUwIt50zaatifBwOFqZaidqmsYzg+mu28CicMRNs7m+anJOzBR8xc7N80SApjpNU5aMpaYWJRQgAgAkdEzOPfFxEU3VBISxeCtEOC/h0ZTyOKc5ZlXNZoAUPAXvRDUX+V9YFJ0KMxpMMz1Rh8tWxCzqsuacx3Fynpl96T5m0RijpKSaVaR0DhhtzTqXCkYLYPlUfy9qVnregQBwFn9U1TLnU6xcoQgzCSAScVEgxHUhaOEenjU4zkNwKwj7BKWdlZ7X7xReyfkcrkUSgLu8viyJ0zhNwzDUIQBAjDMivXpxc3mxTzn3w5RFihzSPCdEQCIXwnZTT+N4O47jOB8Oh2me26ZGtDLp4hz74K8v9oR4PPWEMEcZp2Pf90M/DMO42bQF5ri+vCjFLBM+HE79qd9uNvv9rh/GOUbnfcoCgHf3D0i46bZtu3HeEy4pOTm3313+5Z+1b796e39/++7jxz+++/H27m7sRxCZ4uyDe7nb7OoqNM3N9Yuuazdth2bj0JeCwBFXzhNRCKFcl3Kx5mk69sfj6RhjArAijZAKW4UBmDwXgkhpHZaq9KnjBypAvCTRZqo6pzzFPMecRXPOIooGa3O79ACXDmT5pXMxa2eUlNCxc75M2RAgRlGNySTnlMq0foGZFhmwFRQq70nO+jTPxjAUlh6DrIVSCfZudatgpgpFuhEWCYYyUmIASISqi0bVmYas69rLc9FjtkxaP0sGFnB0JSmYnhforGQ8h4BznEVVRQkoZjHTum6cc6Y2T1MuG/tirirftU1p5DLR8TikmCTnOaaHh8M0TT4UnVtYHAriy5urly9eTCmZWdNU3ofTMKUsh+MppUxEu91GVA+nHlfN4q9eXn8AIIJffvt6mOLd42PK2RGrChI/PDz+7vvvQte+9Z6ajnEhDDlHznVN017tr169evPN19+8+/DuD9//cP943227b796/ermRdM028120+0WEA+5LKlMaZKcoQD1gDnFnCIimshUzSI6TqPKIqirjOyYEIiQbZ3bRCvoI7GpKS/PvwCB610yBHLes3PFCJ2Dog6dRUubsNgnsitEOzAoQmLl8UXVh5kZSU1zloUwkXLKWUVKP6Cg8aWuIgIuk+eFf7EilbgO9JXcgxa6arHWkgoubQJf6n9bXKCaiepKgNflZCmULGoRTVvU1IqZPXXVAUB0ZZnimsICrMjU8m9Yl9CVpMGBQfB+nGbnedNtQggiEmPMIqdTP05T2zSd95uuqUKY5nma0xyTY/PBplm9D1nUwPYX+7atNMvx1AOY926aYZomZmJ22+2mqYJjPh57WE/vMIzeu8vmYp6jiHablpnmKb68uZzm2TFf7LY5y3EYsggCpDmq2u3nLx93P1xuLr0PhStUrjISMpBv27quL7b7b15//Zs/+02McwhVW7chBGbnuMyEcYlTRR8hS1X0wMqMmk8+zU5FlWVj29ev3w7j+HD/YIsXRLd0OYjo/1fWlzVJciTn+RGRmXX1NTM4uFxSF2W2K5ErM+n/v+uND6KkXRDkClhgMcBMd1dXVR4R4e568IisWglmAHp6uqqzMj38+Nz9+1idn1zQWEpRJAmhdlP8OftWk5mllOdlllI8x0VkKRKCqFoRLSIkAkgamIOKKVyHs91HQy6FAJMlU3XuQdEqOysiCEBemTumBQQI7LI+jfXzNtK669JqGSqqwaskqG2BZlIu0MhgtdejToSMykxaQU5rkRnQ84HW4L01U6tbRvVv1x+whoRWd65wfR1AcPVSJNoOQ9/1S8mlVMYbVWPmzWZ4erjfDN1lmqd5MdVSihO/Oxf9MPTvnx4B0aSM05RTZkZjTql8+vw6zfN2Oxx2m5KLiuRSxnHsukiE0zTNc9KisevLPG/6+Hi3/+Hjs6o93t+fp7mLHSJGotp5Q7hcLudxkpxU9Le//YeHD18hddxGN7w7gszM3HX9brev9wXqsHgtN82s0QICGHpbEsHQQohFjYOYZgQk5qHv7w53aUnTNJqpAZmq6/ENnfkABBiIKl5LYOe+8yfjqjAyT+Pn1+c///zL68txGqciDjeKk6aoqKhTpNZ3qdRHVbLFAA3N54mMauTVai6ga0mCzQIRkZkMkQCL2G1W556p9i49eiOymvhIlROnmgDwWtiLmvtJIoA2fK2qa9LpDrH6TjOvnHTd8GwZurYRE2g+3tYtu+ZzYXXYZgAQ3o7HGFtLvZRh6O/2OxF9O51yTiJlnufz5ZxyPy/L29u55GSAOZOo7Laboe/6vgPT4/H0w48fc8rMpFLmccIQlmVZ5nE/DPvd9vPnl6UUr+aJKWIU1SXnl+Pb0+NDCHwe568/vNvvt88vx83Qh8BLSkSwpDROc4yhjzGGcFnS88vx+z99/3j/sNnuAj8iYOCay9hNWIkc2u1YCwJoGb0nr6xKCFiggLgcHnMIkhEJQQARh34YhgERiVlFVKGYGUhnUkRCjNbc91ooYcXFEb1iMjVVkTKN8/l0fj2epmnKjc/H7dLLlrZ0Ve0T2qav+yesOiG26mFQm568AvBQc8/A6ImWeJG+Tk4BGJDnkYIGBmKgIkgC6rMr4lTsFfRZf6kBqgGajyJoLanEqAHvZiJCiC7S0owNWhy4LjJdO24tC/KkuDXuYW2YAkAIxKaaVZGZCWMgKdkMDrvtdhgATIo8v55jnLxNstvtzOWMnE4CcdP3IllVY9+B2WWap3EG02GzSTl/en65v79/ergvuXx+eSPip8e7nPI0L0hoqpfLqGp3d3sV/dNPv9ztd2h2PB6ZaLfbiGDX96/H03gZN5thM/QuIvrp0+efPv707sOXsd/2Qy/1Pq5OAqhlgQAVAGlnst4XJTJVInMw3b2R184q6uALEYUQu9gZQEoZfBSXsYAtuVAoUSSEQOi7H0De+AarDIZmagqmueRxPL+d3pZpKsuUl1mKz69Wxl2/t2ujUOvwbt1+YGifytvooCsg4+nlLcaNCIM3JwFc9kUdpScI5DcBtXLZeHPK1LCospqoUx2YqIooIXmI57oMCKZeBiliPZFXG8QmdLUS1xi0lYL6ceg6GVoN0LtcazIDdSTP3w3MLABiKYUCMWEfY8kyp0REXYw+R7Lfb0MIOWcA6AIV0Wle7u8O758ei5Tz+XyZplJK3/dfvH/69Mun17eTqCEAMavZ5+cXJuqHoeviZogiQwj8nN92203fdy5DP47jMAwcwsdPL2/n0VSnRZBOSLjZ9M4abgDjOKtoiNHMTufL9z/88PDuabPdc3hX0y5ne/zLzHw1WWtbyv7hGzRt0HSaPaAhAsWAhCKEIhx1v9v1fX86n52d3gyJTYXqHp96YlmI2Ut1NrMmpuYdRSnldD69HY+XyyWnZCpoyqAG5gsRRlghyfbItbJU1b0BbGABIJhhRTRbp7FB78hM3n8l8uFiNXBQBSITNXhStdgKdXp17WqctR9gzbZuzknzSczV7RVRpnUx8KYcXKWMW37piBys43kV62xYl7WEWGsIFFXPygAsUOAhUAjBzJ7fTmCKhkRUYjkcdtutSydE3G4A9Hy6HN/OMYanh7uH+8Pr8fh2PuecD/vdbrv55dPz+TyaqsvuDl3UUn76+dM8z09PTyHGIroZBgBMOe0326fHu58/PTNzP/S7bT+nwkgIQEy7GBDhMs4xhE3fxxhUShEdpznkEiMvRb/7/gdTtSz/4T/+5v7dB7AOIzrFu5/mFQJuRnnNyn1F08lCEFthQYjAaOjxrmZViJvN9v7ubhwv8zyr+Bqw525WREQMUJlNxYRVVEkNyZrFGwAUkbQsaZnzsoCUgCaEhAwVbgQDqNTlqutz9cul1nQxrOhhhSuxwuBQh4YBCTvGyAhIapDFzFtchMHFnRAV0GWDwMWV3DE2tQ7fVwb3kQ0tU1M0VFUAAjK6mZzzH0b0ee3q9lazXuv0m8B17ba3L2ogcNIGz3Bw3ZtFCA93ewMspcw5iyoDdF1AgBh5uxnAUEUCD10Xp2lKuQxD/8X7p4e7nWp5PZ5EZDP0h21/nufTecwixIQIMYau786XUaWMl5E5/NXXX0oX53kexzEQbYZutxmeHh/MIAa+vz/Qeco5dTEQQgguRCmXad5vN/vdxic6xmlx+DCGYGAfP/5cyj9eLpff/Pbv3335NeAWQsWr1nB/a6YtiGij3FAEcHYh8fBmLh0qJTuvTlHVwGEY+hACE5takYLkQhlkZiLKEQFBzLhKZNwsjps6WsQhumvSym5cGVyl+W6wSuB5kz+1SgKguk/w1LM2LeufwQDBZ1EdlzVA5y8lAmaMTIHaOryjN17yAAIYke8V14gjaiKmCiLAbP4ZDE1rBQ9QkInwirRjQ46qoa833k1wRVrMfbOfqNZeu75LS1bd8B3+N8MwjbMSDsOwDSGGAACBedN3+8N+6LpSJCX5/PLmyf7Qd/f3u/vDYUnpx58+vb6dnAT55e3y/PnldDqbKiFRF+72u76L4zQ59QO0RH5Z0ul8GS/TF+/fpyJdjH/7619N8xwDH3bDPMNhv0ulmFpOi+MvXeDArKrD0HdddxnnlAsiRGJDez0ef//NN1nKfwZ7/9WvzIYQAt2YI1xDjyG6Fps3bNVVNNR1F1TNtOSU05KXRaXUghiBOOx3hxi7k15KKWoaiVrCVyFPJ2VgDkxc/UR1f6amIcT7+4d3797P8zL3s5QCKqWISMEiRQTMyCmTbgGgmpkhNoYSJlwVDQzASbrALBBFxsAElZRBTTUiMFEgjETtfQEN1FX8GgZaTcopSwkQzKVOTU3VPKCDl2a8+mtPmB1sa7lTm51b+5a+Ue8zTOhQEa6Aa2WE8bH566vWFNU5DsDCOI4xRuw6ZBpih2ix67549wiI85xqbtvY8D03mZd0vlwu49jHsN304zT9/Mvn09sJEZ32qO/idrsRKYgQ2CcmZZrmXBVXMRV5PZ7u7ndE2HcRwAihlDIvqe/C4/1BDVKKx9NlSfP5TbvY5ZReU9ptht2mI4J5XpJACCwmb6fTH7/7bhiGjsPjF19Rpb+sYkt2Q0ForRIyAxHnGpOa+bkWr6mWoipLWtCLAyJk2u52T48Pb29vS81ZDcCHmdANXW/ZCsxUFEyaIg0S0n63+/qrL4ksLYsvNqioqlzGaZ6XeZrnaSpLKqWQKoAFdBQIGI3b4g9C1Qx22fM6gk4YAwYmYgSArCZi7JA/YSAicn5iED+S9VKB1ll2aPpETeLDTyY28NIfELtCcsud6gWtZnU9sjUFsAa/39ZSUAv2inf5oamG5ZVuS3v9r0M/9EM/IMA0zqJyt993MV7GKfbdYb81Uzf81+NbSnkz9OM4//zLc8o5MMUuppxTyjHwbrdlDt5q2++2fcfjuLgmZwisIs/PL0S0rmPPKQ1Lt+kjaNkOXZFSSpmmaZ5HAnh4uNvvhqGP07y8HM9pOauUlHJKadP3wzAMfX8Zp2UuMUYA+/zp8//I/6Sl/P3v/uvDh6+67trMWH1nM9BKoVNDjohlM8JVAAAU0klEQVQrvZqolmyiOaWcZibKOad5jl1HHIZ++PDui7e3t1LyspiqNXHJCg/lIlEkWhsG97holVWGiGKM2+3m4W6/LAEQCTEwRWZ3iCmVcZrf3k7H49vlfJnGUdLi9IDNkRpUGseKwjuvCSEGxuCDz0h+4hyAgNY+1iZG6KCStQwB0Nj/Cq6dr9XkzEzBGKyIsoFVeWFTA18guX5OJCKHe625hVoJ3DyCa24KrU/QlBoqCtaK2mazAIgY+mEAAG+YbTeb7WaQUhBx33XELAJD3xsYB96Fbd91x7fTOI5mlgmnKQGCSjFVV84khN7FN8w3zDGECICvxzMAPNwdfEp/M3RoWkqeTYjg3eNDypazT/rJp+eXnJM3n97d7fuue3l5ndKSMuVctBSVMgzDZujnJaWUWIRDPL29/f6bf0bmf/gv/+3h3ZfO/nzrO/0fUZ/WUfBSybF7KVKSqJaS1URUfTt5LsnA+h4Dh81mc3d3fzydVM2joWuEurn7ZISocuU0WNP8+gUhdTEG5sLeyHTRTQ4hEtHhQO8M8ldf5JzGcXp9Pb28vr6+Hi+ns6YEph5IoaV+DUqjwBhDna9KIimrgSFSdAYyX1ABUDVXAyX0IU5AF+9qdYqDkQAgarG6TwfjIXBYz7rBKg2vWNeDV/j1mne2fytE2tgc1vdABGCsaBo2LMKal8WbWxfSkjwJ22yGu/1OVHNKRLTMy+UyLkvaboa7w/7x4T4wPb+8juOcixRnU0FERBFx3h816yIPm6GLQUWYYxchMJqJFEHmYehcE5sQRcsyT8qMaGPf+XOMgU3AVMZ5joGYsKiGEB7u73IpqjbPKiKXaU6lDH3fdbH49EaBjPD6+vLNt/887Ha/4e7w8BhjXA+iNXTYP3xt/YmoFKtTOwUAfcyAiUwEwPq+rxQHgfuu326Gvot5SX43vdwpIszk3SMRp0EXqgU2rc7AzFJK85JySj73TQhMAbFGW0Qahj7QQR7t668kp/R8PP7y8y8f//zx9eW1LKkOVACuOyRdpMiIxISYRKesZtYxxUAxUDWOlte1C0aDRo8P5hu+qwFds1t16/GMYuXmNwKDtm/kS/FNCWSt3GGt8lcss/72ViN52ipqDbwGaA/Jv1rzB0MI5/M5MHd9L0VO5wsTiRlSEpVlXsQgMOciqioERHQ4bJeUx3H26biUMjP1fURERo0h7LfDdtOfx7mIhIAxhi4ENT0cDofd5sePn0rOgYmQci59F/vA87Lst/22C6UPS/bDDSKiJRFGUNh0PAz9ZZz6LoyLOgBNYF2kwj0nzKWAaWQoy/Ttt38Ydrvf7H7Xdb3vErWehqeL2cSk5JyWZbzMy1RymecplZxTulwu0+U8TvNlHKd5STlpKSpKIbhevKn1fTS1yBzIwRfMRTopMXSe4NYUoiVStgY0oixlmhcmCrEO+/SddV0k32NzbXeiXb/B3e5wODzd3x/22z/+8fvnnz+ntIDnnWgGFKOTgRERLWJzElGL7AeMAyIhFKgmogBeqRCBKDQYx9bGWgvX0K7XmhHXf1SNqxRj7QtVf+m912piCP/PxmBNWf3+rzm6451XIKBG+YoDXAslAAsi4tG5SOm6oKq+u+XEYPu+N7Pj8Q0A+j5uh76PztqDSwrjZSKSYdOHGEwNI3dDT8zTtKRlJoTtbui76NFov9vMaVly9hnJ2MWHIW6HIVdRoqHrY99Hn7rBSgBquWRE6EL3dLc9n08FYb87FHGiIjrs+t12k4ucx7kU3Qw9MlrKnz5+/D/dN3eHu1Jknqd5msbxMo7j+Xyaxul0OU/jNLrk2zy7Y1MEyaWUDIjEDKrMrAbe+0OEEHjouoe7u6en+4fDduBoToPp6aC2Hnq742YOULuMhnEI93f3l8tpmWeVUkrxOXaHG82gw87bWqoCGfqu32w2zCEXOZ/H6TJKya7KbIDR1eh8lUp0TiWLej3KgYNXRoBkVgB1PSV1J7giTf57nV1RTU2JfdXMTGpC1BDZFnDV1h+t5FRt2xOc56FBntU8bd3ywCv1/FpI2fV7a+SH1ZXXEN910flPOAbRK6Y2dN3Qx1JkmiZiGvqeENSsFCHEh4c7MJgOyzLPuRRABAZVNrW386Qli8pmGDbD0HV+t5AQUi5oQkxD3x12w9BHM8tZN32362PH0IG8XfA8zmYKpqbFFKdc5iUzcd/15/PRpyoQaMrLp9cLM5nBksu8ZFH10XH77/8YkBgxO5qj9TarFxzXxAjqkSfutwN2Xcm5C7zZDGYQQ4yBY+RI9OMvz7mUKRc5HqeUXt82D4fdX33x2K0TVQamUkphLoGDEjuICC3CMtF2s/vw/ot5mY8vn6WU5Gv12lAEr2gYzCDlUkQGHQKH/XZz2O+7vgNE393pAm0iIyIB5iznJKUIB45MHVMM3LEPF4MaYsVcnSyy1s8ebREAQdFFPAy5Si9ALZEcuIHKxlvb5dV0gAwIkW59J9xaFzTErNke4K1vrT93BQpuav+bFwJAIGIzW1LCXJjR5w4J0VTO55xTJqZh6D3zm5dEYBy7/XbbdbEUSWl2AF9FUhFULVIA8eFuv9/vNpshBEKRcZ6nSS7jlHMhMJMyT/M4TiaSckGiTy9v05J1WS5LHuekRZFIAXIWABAz8WxJ69SPj3m120yE6BpnDdhBW7sUBsgUQyBmcNhTxZ+CAAQwJCZmy0UAus1GU0pzssBzLuxxvGgMrKWAWVablgWILks6nS9/92//+uHurqiIlFzIAEMouWRiFqnL5j5LogYU+O5w//WXX0vJ57ejw3ArDsUUQlBELKLTNOWS+3642x9UrY+hC9HxUCbqmAlRDHKROUkRDUx9oL4LQ+AQAiCKT0itjsjvk88p+7QIgrpUMQAYtLUNU1UPCBU2p1r0GxgBrrdX0XvuWOEqwLrCVZVr3LPC+vv/vy9uc8/qpKtZ37hY8CIJwYpqYy7H0MUuRwQTNSbaxk3OZV4SghHRdjv8+ssP7x7vmclUcu7PfZ9ymqZFL+NlSSllM8spv7xeFKGksizzkkopJeUiClbhx3ppdX6mHqu6boitaXbTCmqwBDa7A+v7jpjHcfJDSE5I4LEMcf/01B/2m74//vSxlLx/fNrttv12+Paf/rfj8GQmZqRqpkKBCEEEkUrO5CR3mgWgeNXffq2ITcvCRJ9Tmv7Xv/zm7/72w9N9kQKZkIJHM5FCRCLA7PHNPwrGrru/fyqSfzSYLqfWLii5MC5zylkNRORymVLJm83Gw/C8LFnFAALjEJkIFUBFp1SKKBP2kTdd6COHwNAcJCGwuZKx943AsClyuHX6NEkbdyqiQK71B0T1IBOCoSkh15ebI/dOzF5dI1FFNFuqXQe5V3d6Dd14PRPXx3vbC71iyf6tUHLy5Al9Y8EZ9IoQIjEjwGW8iKgUQcS+6/a77dvr6XQ8zSm5bsU4ZxE5T9M8LUWtqFopVmn1aibTnH514h5dvPVgNXmutSZWYgH1DAZUCakWmW5+oqYCxIioCiLZcy4T9SBpRZylo8zz01cfdveHy/PLdLmcxwsEvFxO5lNkhABYZUGKACr5vTUwU9IqKaBtrZ2Q0JCYDbGIFjUEyHn8n3/419/9p39/t9+JFNMiJQsHQhLMAIgoCGrkhIcIiH3fPz68E7XnzyG5uA8REqVi83yelrTMaVkWIALDE5/Ssnz85WWeZkJwvnA1SKJLKqkoEXaB+8Aup+SR3Wyl7qqYe+X2rF4R18UkA59RMbvRj3Oks9qKGxgSVO7/6u+rHdY/tI3N6voaRXRD8HHNAW4zgBs3uXaQbssydzSuduqqJb6SYmVJvl4j1mZbmkTZZZqej2+ilfy/fmTC2lpoJQJWVj4lbAwpWHtdgCDSph0QsfaOgYmM6s42EiNCSdlXPZG5nTIjZslFTJ3iAwhFPBkidRUT36pBBIBlGv/0zbcqlVQ7ny6fz2cRicQYgvkcbt0R9t1bD7VcJ46d1c33exAICAjNVPyB+Z0xe7mMv//2T7/7zb+LwdKyeoyq6mCmzExgQAZWPQ1z2O/uum5AxMDRZ2tSyqfz6eX19Zfl87RkMNAiry+v8zgdT6cyzQGRyTvizvUiRLSJFAPHQFWLEcBXLg3RAVkD1FaA0NWJAiBBZchBbfi8udxUq+9xHQiogZtW4hv/ayc7gSY5fq1+/H0rHEa1adRstMX0vzDF1XGu7+/1QvCRQZeq0gYZqpo6S4CPmdSBNChiAE2/o5Ua0LABu7p09AYxUWMd8DlwIkV0CW0MoePgFYK7F4C6mswtlDiJMIhKyVKkpkn105m07BsQjcldgVNZYtVGA1N1/9RSPehCrFmA4xwtDtXM1dPbGLz1ylqRca2llvPUARMBB4rR1VOeX0+vx9PT450VQsoGUKT0XSche6EVYgQItfAQUZG+73fbfQwxhFCLNpGnp3fv34/v3j1/9/33f/rhxx8/firLwqBsQqaAoICmNmXJRZgxBu4jd13sYqSG5DhCpAZSR+zB5wZ8mUIrY0MNWW4lAUGrAyEidI7bhiBDS5QNXSCs3kqqVdJNZolVMQLEqq/0b1q7t6s1ri7zLxwnrq+oxGMAEFyVR1Swvq7CqvWtoYqGae00GFRx3CpgX/1oiyX+cg4hdhGRvOpUNS3ZT23d0DbQItOSKmq8TgqQ3xcFAAKqd0FN/fIIpW2ZWz0O6IeePG1vC0cYWK4pbFuaRIC6OuZsa2ae+SMGZKe1r8hKQmLWXFwecm1DOQRpAAURuZBqiB0xTSn//Hz86sOTc9XHGGIIoiZpURWLilh5AXzXMYSw6frA0Yc369YDABKGwLvt5vH+/unp6Ztvv/3lp5/yNEYCBhKDopZLyUUNoAu06UIXA4fg5Q9jZRfzR1H5u7V6eh+sqv6krntAnbOzSqy/XgggNk4cvHGlNTez9Ut/4Zo1ErVhwesNtxpTbQ33t2DcreOE1pLFduGIEJD4WgFUC7WVuKyaf8Vi1y0T0yY9iIDrgun6i31QB8FW2vaVMkpbc78+b0Q0tDVNrhJEgDXBUKzTWw51oI9F+bVRQxCb0/ZLVIXKKOd4HiG6rwVfloPKCOzllBYLTP5snTLJ3bKJBGJPNOv8uidrqGbq6bMVEcwAQcwuWTwKeoOVQzA1p05ELGZKRBy6GHo/vYEjVa5Or63FzCU+LIbw8PBwOBy+/uKLf/3jv3z3x2/H1xfJUkRTKbmYmXWRQwgUmCudrgJQMdCGuzeXYT4jpw3P/EvPtaZ+BFUIgpl8lg4JyYeu1dwH11vdkKgaM2m1O2gWA1Wy+9Ykrj1QuO4bXd0qrtkqXOslNQMIfoIcFHA/4Y8EW+BTbaOyiCvBxNoZa1F9nT8ARwOaL/KOAbpktyGtVJrNE3qeBC1/bR+hJkHg5Fa3rn1NGLQ11xBx3QX09EDluhpYf+paollTP6nfN1W1AoimRlWWDwCsqCATuC6Lj3+hVdm06h8MDdCMmNOcUirUU8qFQzAtuRTnIo0x9v1mt9v3/cYNlOoGRXsHADMrLu7BrhGDzPzh/Ye7w/7Du8dv/vD7H/70Yz6PToDXRRpiiDF2zA5OO/zu8TG0FhGs8xCoq/34HSGAutJqoADMFJidv5KZXfpbnaaHzNePSK3uK5iBmrfSDUypqsyoGQFBW5harRXbI7uxSAODdZMJrwG/rot5IxqJQDVwDKZa1XEBWy9r3SgADq3DBUbEiOsZrWRU9QJW4/c82we/Ddb2N7QawVq6oLCOfpEUBwV9VNHM5Si6vjvsp5cXLeLKj+t5rElxjVGK2OgrbnqMyEziCJGfKN9Qg7prVtMEBEABIzNP9LniDz6dWdc13ZzNFOsX6MvhaErABJCWdJnmGNgAci6qMs9zEYkh9F3Xd7ELMbDLDVQc11o9a35hKgZiVlFFzx77fvvll7+6jOPHz8fl9SSqMfIQOUYOTB7Trfmu5ubqLVh5ofysqvojaoPrHsF8x44CkEsmVTkGf8wG5s7K6tq7Qht2r12g5tFAa/sDKmiDayMNWm/dh0wBoA0A6C26szr9a0dU1cBC4GDkV9+yzBXz9yQFVwqK1ZFdE4LqJAFahmotCBgYmppJqb/MBZkBEZCYQgzU93le+s32/a//5vP3352eP/nkm98/DhyGAZlXuoHmQ/2Wo62p/urHGxeeX7yWQmui0k5KayZXdwrY+hmInjzUil4rhQG1zNMqveD67mCEqqiqhJRULuOy7bsQ9ZxzkYKEfey7GBFRREQzC4OBS87VigRJwVRFck55RgAIaIDcVkSRHDp93O52hhRi6ALFyEhciRPBRF1WGOlKKltp3hXWR2k1ufSPpVp/DpGIkchtE4m9dFQzMedchIpauas29CE8n1wz5JrOX2MgoK9IA1R2xTYnbpW/pBXWWjmhK2TQ7iy0+RJ/r9CYxFo9ezPLX61hHUg0xXYckdZThboiKus0YU05tCqPQisIyTcfgTnsHx9ou3n788+B+fD4eH55eXv5bG1AC8yAKA5DWhYTqQOD1bdd8d0GSHjMaFnP6rO19javiVe7EbWkb6mtf2y8Ge+u1r/CBgBonrbadZxOGwsWqojlnEpOl1HUlJm2my1VsFZLkZQyGDEbMleCFwaj6ziJFEHweX8hysxBW7L3eH//b/7mr89vx+V4JIDaO1YpAMVA1Vu8GJmtldV+gNvYKtQWsHtvtSKVS9DpJzgEInZtEiBSF7kz8HWkla+UjMzZmKAen+Y/cS1IoO3lwU0BtP5XVrTJy6D66JzpxLHv+hRW6wsVWGnfamlus83V/66bMrjaH4A76ht7lurq6oPFa+FVk0VCNB8QE6MlA6CJ5mXG1kZyX4vMHAIA2rI45o/12lrN6G/rw93tTNj16AEAEhN4U98BPzPTumpOgOZdaPVHxL5eiTXpqEmVrSxbjfN//WDr/xTBEFR0zmVKScWQMDCbWS4lFAakEDRnAcgBkFwD07nNlCpMiBS7IS2jSVYzJEk5OTMCMyPS4/3h3ePdz9MIIoC1/WMAYi7dC4YE4Pp0gK5600YLPexXIohKKA5iQOs4FDMyM3GFPG+Ki3rP1JiaMmqlGSOX4VLzHN3M72ZlnKwv9lu5GkmN0c3D113VenbcsNHaipQ/7v8LuhRpugWd6nYAAAAASUVORK5CYII=)" - ], "metadata": { "id": "gW4cE8bhXS-d" - } + }, + "source": [ + "![download.png](data:image/png;base64,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)" + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dGg11TpV_aV6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "image_data = (periodic_impulse | beam.Map(lambda x: \"Cat-with-beanie.jpg\")\n", " | \"ReadImage\" >> beam.Map(lambda image_name: preprocess_image(\n", " image_name=image_name, image_dir='https://storage.googleapis.com/apache-beam-samples/image_captioning/')))" - ], - "metadata": { - "id": "dGg11TpV_aV6" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "eB0-ewd-BCKE" + }, "source": [ "3. Pass the images to the RunInference `PTransform`. RunInference takes `model_handler` and `model_metadata_pcoll` as input parameters.\n", " * `model_metadata_pcoll` is a side input `PCollection` to the RunInference `PTransform`. This side input updates the `model_uri` in the `model_handler` while the Apache Beam pipeline runs.\n", " * Use `WatchFilePattern` as side input to watch a `file_pattern` matching `.keras` files. In this case, the `file_pattern` is `'gs://BUCKET_NAME/dataflow/*keras'`.\n", "\n" - ], - "metadata": { - "id": "eB0-ewd-BCKE" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "_AjvvexJ_hUq" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ " # The side input used to watch for the .keras file and update the model_uri of the TFModelHandlerTensor.\n", "file_pattern = dataflow_gcs_location + '/*.keras'\n", @@ -534,108 +540,117 @@ " | \"ApplyWindowing\" >> beam.WindowInto(beam.window.FixedWindows(10))\n", " | \"RunInference\" >> RunInference(model_handler=model_handler,\n", " model_metadata_pcoll=side_input_pcoll))" - ], - "metadata": { - "id": "_AjvvexJ_hUq" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "lTA4wRWNDVis" + }, "source": [ "4. Post-process the `PredictionResult` object.\n", "When the inference is complete, RunInference outputs a `PredictionResult` object that contains the fields `example`, `inference`, and `model_id`. The `model_id` field identifies the model used to run the inference. The `PostProcessor` returns the predicted label and the model ID used to run the inference on the predicted label." - ], - "metadata": { - "id": "lTA4wRWNDVis" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9TB76fo-_vZJ" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], "source": [ "post_processor = (\n", " inferences\n", " | \"PostProcessResults\" >> beam.ParDo(PostProcessor())\n", " | \"LogResults\" >> beam.Map(logging.info))" - ], - "metadata": { - "id": "9TB76fo-_vZJ" - }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "wYp-mBHHjOjA" + }, "source": [ "### Watch for the model update\n", "\n", "After the pipeline starts processing data, when you see output emitted from the RunInference `PTransform`, upload a `resnet152` model saved in the `.keras` format to a Google Cloud Storage bucket location that matches the `file_pattern` you defined earlier.\n" - ], - "metadata": { - "id": "wYp-mBHHjOjA" - } + ] }, { "cell_type": "code", - "source": [ - "model = tf.keras.applications.resnet.ResNet152()\n", - "model.save('resnet152_weights_tf_dim_ordering_tf_kernels.keras')\n", - "# Replace the `BUCKET_NAME` with the actual bucket name.\n", - "!gsutil cp resnet152_weights_tf_dim_ordering_tf_kernels.keras gs:///resnet152_weights_tf_dim_ordering_tf_kernels.keras" - ], + "execution_count": null, "metadata": { "id": "FpUfNBSWH9Xy" }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "model = tf.keras.applications.resnet.ResNet152()\n", + "model.save('resnet152_weights_tf_dim_ordering_tf_kernels.keras')\n", + "!gsutil cp resnet152_weights_tf_dim_ordering_tf_kernels.keras gs://${BUCKET_NAME}/resnet152_weights_tf_dim_ordering_tf_kernels.keras" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "_ty03jDnKdKR" + }, "source": [ "## Run the pipeline\n", "\n", "Use the following code to run the pipeline." - ], - "metadata": { - "id": "_ty03jDnKdKR" - } + ] }, { "cell_type": "code", - "source": [ - "# Run the pipeline.\n", - "result = pipeline.run().wait_until_finish()" - ], + "execution_count": null, "metadata": { "id": "wd0VJLeLEWBU" }, - "execution_count": null, - "outputs": [{ - "output_type": "stream", - "name": "stdout", - "text": [ - "\n" - ] - }] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Run the pipeline.\n", + "result = pipeline.run().wait_until_finish()" + ] } - ] + ], + "metadata": { + "colab": { + "include_colab_link": true, + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/examples/notebooks/beam-ml/bigquery_enrichment_transform.ipynb b/examples/notebooks/beam-ml/bigquery_enrichment_transform.ipynb index 182b88b9c72a..dedaa6b65a5e 100644 --- a/examples/notebooks/beam-ml/bigquery_enrichment_transform.ipynb +++ b/examples/notebooks/beam-ml/bigquery_enrichment_transform.ipynb @@ -1,22 +1,13 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "include_colab_link": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "55h6JBJeJGqg" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -36,16 +27,13 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "id": "55h6JBJeJGqg", - "cellView": "form" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "YrOuxMeKJZxC" + }, "source": [ "# Use Apache Beam and BigQuery to enrich data\n", "\n", @@ -57,13 +45,13 @@ " View source on GitHub\n", " \n", "\n" - ], - "metadata": { - "id": "YrOuxMeKJZxC" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "pf2bL-PmJScZ" + }, "source": [ "This notebook shows how to enrich data by using the Apache Beam [enrichment transform](https://beam.apache.org/documentation/transforms/python/elementwise/enrichment/) with [BigQuery](https://cloud.google.com/bigquery/docs/overview). The enrichment transform is an Apache Beam turnkey transform that lets you enrich data by using a key-value lookup. This transform has the following features:\n", "\n", @@ -79,38 +67,40 @@ "\n", "### Install Apache Beam\n", "To use the enrichment transform with the built-in BigQuery handler, install the Apache Beam SDK version 2.57.0 or later." - ], - "metadata": { - "id": "pf2bL-PmJScZ" - } + ] }, { "cell_type": "code", - "source": [ - "!pip install torch\n", - "!pip install apache_beam[interactive,gcp]==2.57.0 --quiet" - ], + "execution_count": null, "metadata": { "id": "oVbWf73FJSzf" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "!pip install torch\n", + "!pip install apache_beam[interactive,gcp]==2.57.0 --quiet" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "siSUsfR5tKX9" + }, "source": [ "Import the following modules:\n", "- Pub/Sub for streaming data\n", "- BigQuery for enrichment\n", "- Apache Beam for running the streaming pipeline\n", "- PyTorch to predict customer churn" - ], - "metadata": { - "id": "siSUsfR5tKX9" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "p6bruDqFJkXE" + }, + "outputs": [], "source": [ "import datetime\n", "import json\n", @@ -137,49 +127,47 @@ "import pandas as pd\n", "\n", "from sklearn.preprocessing import LabelEncoder" - ], - "metadata": { - "id": "p6bruDqFJkXE" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "t0QfhuUlJozO" + }, "source": [ "### Authenticate with Google Cloud\n", "This notebook reads data from Pub/Sub and BigQuery. To use your Google Cloud account, authenticate this notebook.\n", "To prepare for this step, replace `` with your Google Cloud project ID." - ], - "metadata": { - "id": "t0QfhuUlJozO" - } + ] }, { "cell_type": "code", - "source": [ - "PROJECT_ID = \"\"\n" - ], + "execution_count": null, "metadata": { "id": "RwoBZjD1JwnD" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "PROJECT_ID = \"\" # @param {type:'string'}\n" + ] }, { "cell_type": "code", - "source": [ - "from google.colab import auth\n", - "auth.authenticate_user(project_id=PROJECT_ID)" - ], + "execution_count": null, "metadata": { "id": "rVAyQxoeKflB" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "from google.colab import auth\n", + "auth.authenticate_user(project_id=PROJECT_ID)" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "1vDwknoHKoa-" + }, "source": [ "### Set up the BigQuery tables\n", "\n", @@ -187,36 +175,38 @@ "\n", "- Replace `` with the name of your BigQuery dataset. Only letters (uppercase or lowercase), numbers, and underscores are allowed.\n", "- If the dataset does not exist, a new dataset with this ID is created." - ], - "metadata": { - "id": "1vDwknoHKoa-" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UxeGFqSJu-G6" + }, + "outputs": [], "source": [ - "DATASET_ID = \"\"\n", + "DATASET_ID = \"\" # @param {type:'string'}\n", "\n", "CUSTOMERS_TABLE_ID = f'{PROJECT_ID}.{DATASET_ID}.customers'\n", "USAGE_TABLE_ID = f'{PROJECT_ID}.{DATASET_ID}.usage'" - ], - "metadata": { - "id": "UxeGFqSJu-G6" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Create customer and usage tables, and insert fake data." - ], "metadata": { "id": "Gw4RfZavyfpo" - } + }, + "source": [ + "Create customer and usage tables, and insert fake data." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-QRZC4v0KipK" + }, + "outputs": [], "source": [ "client = bigquery.Client(project=PROJECT_ID)\n", "\n", @@ -276,33 +266,33 @@ "job.result() # Wait for the job to complete.\n", "\n", "print(f\"Usage table created and populated: {USAGE_TABLE_ID}\")" - ], - "metadata": { - "id": "-QRZC4v0KipK" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "### Train the model" - ], "metadata": { "id": "PZCjCzxaLOJt" - } + }, + "source": [ + "### Train the model" + ] }, { "cell_type": "markdown", - "source": [ - "Create sample data and train a simple model for churn prediction." - ], "metadata": { "id": "R4dIHclDLfIj" - } + }, + "source": [ + "Create sample data and train a simple model for churn prediction." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YoMjdqJ1KxOM" + }, + "outputs": [], "source": [ "# Create fake training data\n", "data = {\n", @@ -319,51 +309,51 @@ "df = pd.DataFrame(data)\n", "df['plan'] = plan_encoder.transform(data['plan'])\n", "\n" - ], - "metadata": { - "id": "YoMjdqJ1KxOM" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "EgIFJx76MF3v" + }, "source": [ "Preprocess the data:\n", "\n", "1. Convert the lists to tensors.\n", "2. Separate the features from the expected prediction." - ], - "metadata": { - "id": "EgIFJx76MF3v" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "P-8lKzdzLnGo" + }, + "outputs": [], "source": [ "features = ['age', 'plan', 'contract_length', 'avg_monthly_calls', 'avg_monthly_data_usage_gb']\n", "target = 'churned'\n", "\n", "X = torch.tensor(df[features].values, dtype=torch.float)\n", "Y = torch.tensor(df[target], dtype=torch.float)" - ], - "metadata": { - "id": "P-8lKzdzLnGo" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Define a model that has five input features and predicts a single value." - ], "metadata": { "id": "4mcNOez1MQZP" - } + }, + "source": [ + "Define a model that has five input features and predicts a single value." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YvdPNlzoMTtl" + }, + "outputs": [], "source": [ "def build_model(n_inputs, n_outputs):\n", " \"\"\"build_model builds and returns a model that takes\n", @@ -375,24 +365,24 @@ " torch.nn.ReLU(),\n", " torch.nn.Linear(16, n_outputs),\n", " torch.nn.Sigmoid())" - ], - "metadata": { - "id": "YvdPNlzoMTtl" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Train the model." - ], "metadata": { "id": "GaLBmcvrMOWy" - } + }, + "source": [ + "Train the model." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0XqctMiPMaim" + }, + "outputs": [], "source": [ "model = build_model(n_inputs=5, n_outputs=1)\n", "\n", @@ -407,61 +397,61 @@ " loss = loss_fn(pred, Y[i].unsqueeze(0))\n", " loss.backward()\n", " optimizer.step()" - ], - "metadata": { - "id": "0XqctMiPMaim" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Save the model to the `STATE_DICT_PATH` variable." - ], "metadata": { "id": "m7MD6RwGMdyU" - } + }, + "source": [ + "Save the model to the `STATE_DICT_PATH` variable." + ] }, { "cell_type": "code", - "source": [ - "STATE_DICT_PATH = './model.pth'\n", - "torch.save(model.state_dict(), STATE_DICT_PATH)" - ], + "execution_count": null, "metadata": { "id": "Q9WIjw53MgcR" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "STATE_DICT_PATH = './model.pth'\n", + "torch.save(model.state_dict(), STATE_DICT_PATH)" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "CJVYA0N0MnZS" + }, "source": [ "### Publish messages to Pub/Sub\n", "Create the Pub/Sub topic and subscription to use for data streaming." - ], - "metadata": { - "id": "CJVYA0N0MnZS" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0uwZz_ijyzL8" + }, + "outputs": [], "source": [ "# Replace with the name of your Pub/Sub topic.\n", - "TOPIC = \"\"\n", + "TOPIC = \"\" # @param {type:'string'}\n", "\n", "# Replace with the subscription for your topic.\n", - "SUBSCRIPTION = \"\"" - ], - "metadata": { - "id": "0uwZz_ijyzL8" - }, - "execution_count": null, - "outputs": [] + "SUBSCRIPTION = \"\" # @param {type:'string'}" + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hIgsCWIozdDu" + }, + "outputs": [], "source": [ "from google.api_core.exceptions import AlreadyExists\n", "\n", @@ -482,25 +472,25 @@ " print(f\"Created subscription: {subscription.name}\")\n", "except AlreadyExists:\n", " print(f\"Subscription {subscription_path} already exists.\")" - ], - "metadata": { - "id": "hIgsCWIozdDu" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "VqUaFm_yywjU" + }, "source": [ "\n", "Use the Pub/Sub Python client to publish messages." - ], - "metadata": { - "id": "VqUaFm_yywjU" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "fOq1uNXvMku-" + }, + "outputs": [], "source": [ "messages = [\n", " {'customer_id': i}\n", @@ -510,15 +500,13 @@ "for message in messages:\n", " data = json.dumps(message).encode('utf-8')\n", " publish_future = publisher.publish(topic_path, data)" - ], - "metadata": { - "id": "fOq1uNXvMku-" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "giXOGruKM8ZL" + }, "source": [ "## Use the BigQuery enrichment handler\n", "\n", @@ -566,13 +554,15 @@ "* One for usage data that uses a custom aggregation query by using the `query_fn` function\n", "\n", "These handlers are used in the Enrichment transforms in this pipeline to fetch and join data from BigQuery with the streaming data." - ], - "metadata": { - "id": "giXOGruKM8ZL" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "C8XLmBDeMyrB" + }, + "outputs": [], "source": [ "user_data_handler = BigQueryEnrichmentHandler(\n", " project=PROJECT_ID,\n", @@ -613,37 +603,37 @@ " project=PROJECT_ID,\n", " query_fn=usage_data_query_fn\n", ")" - ], - "metadata": { - "id": "C8XLmBDeMyrB" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "3oPYypvmPiyg" + }, "source": [ "In this example:\n", "1. The `user_data_handler` handler uses the `table_name`, `row_restriction_template`, and `fields` parameter combination to fetch customer data.\n", "2. The `usage_data_handler` handler uses the `query_fn` parameter to execute a more complex query that aggregates usage data." - ], - "metadata": { - "id": "3oPYypvmPiyg" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "ksON9uOBQbZm" + }, "source": [ "## Use the `PytorchModelHandlerTensor` interface to run inference\n", "\n", "Define functions to convert enriched data to the tensor format for the model." - ], - "metadata": { - "id": "ksON9uOBQbZm" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XgPontIVP0Cv" + }, + "outputs": [], "source": [ "def convert_row_to_tensor(customer_data):\n", " import pandas as pd\n", @@ -656,69 +646,69 @@ " model_class=build_model,\n", " model_params={'n_inputs':5, 'n_outputs':1}\n", ")).with_preprocess_fn(convert_row_to_tensor)" - ], - "metadata": { - "id": "XgPontIVP0Cv" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Define a `DoFn` to format the output." - ], "metadata": { "id": "O9e7ddgGQxh2" - } + }, + "source": [ + "Define a `DoFn` to format the output." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NMj0V5VyQukk" + }, + "outputs": [], "source": [ "class PostProcessor(beam.DoFn):\n", " def process(self, element, *args, **kwargs):\n", " print('Customer %d churn risk: %s' % (element[0], \"High\" if element[1].inference[0].item() > 0.5 else \"Low\"))" - ], - "metadata": { - "id": "NMj0V5VyQukk" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "-N3a1s2FQ66z" + }, "source": [ "## Run the pipeline\n", "\n", "Configure the pipeline to run in streaming mode." - ], - "metadata": { - "id": "-N3a1s2FQ66z" - } + ] }, { "cell_type": "code", - "source": [ - "options = pipeline_options.PipelineOptions()\n", - "options.view_as(pipeline_options.StandardOptions).streaming = True # Streaming mode is set True" - ], + "execution_count": null, "metadata": { "id": "rgJeV-jWQ4wo" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "options = pipeline_options.PipelineOptions()\n", + "options.view_as(pipeline_options.StandardOptions).streaming = True # Streaming mode is set True" + ] }, { "cell_type": "markdown", - "source": [ - "Pub/Sub sends the data in bytes. Convert the data to `beam.Row` objects by using a `DoFn`." - ], "metadata": { "id": "NRljYVR5RCMi" - } + }, + "source": [ + "Pub/Sub sends the data in bytes. Convert the data to `beam.Row` objects by using a `DoFn`." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Bb-e3yjtQ2iU" + }, + "outputs": [], "source": [ "class DecodeBytes(beam.DoFn):\n", " \"\"\"\n", @@ -729,26 +719,26 @@ " def process(self, element, *args, **kwargs):\n", " element_dict = json.loads(element.decode('utf-8'))\n", " yield beam.Row(**element_dict)" - ], - "metadata": { - "id": "Bb-e3yjtQ2iU" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "Q1HV8wH-RIbj" + }, "source": [ "Use the following code to run the pipeline.\n", "\n", "**Note:** Because this pipeline is a streaming pipeline, you need to manually stop the cell. If you don't stop the cell, the pipeline continues to run." - ], - "metadata": { - "id": "Q1HV8wH-RIbj" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "y6HBH8yoRFp2" + }, + "outputs": [], "source": [ "with beam.Pipeline(options=options) as p:\n", " _ = (p\n", @@ -760,12 +750,22 @@ " | \"RunInference\" >> RunInference(keyed_model_handler)\n", " | \"Format Output\" >> beam.ParDo(PostProcessor())\n", " )" - ], - "metadata": { - "id": "y6HBH8yoRFp2" - }, - "execution_count": null, - "outputs": [] + ] } - ] + ], + "metadata": { + "colab": { + "include_colab_link": true, + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/examples/notebooks/beam-ml/bigtable_enrichment_transform.ipynb b/examples/notebooks/beam-ml/bigtable_enrichment_transform.ipynb index 95be8b1d957c..f2e63d2e4f06 100644 --- a/examples/notebooks/beam-ml/bigtable_enrichment_transform.ipynb +++ b/examples/notebooks/beam-ml/bigtable_enrichment_transform.ipynb @@ -151,9 +151,9 @@ }, "outputs": [], "source": [ - "PROJECT_ID = \"\"\n", - "INSTANCE_ID = \"\"\n", - "TABLE_ID = \"\"" + "PROJECT_ID = \"\" # @param {type:'string'}\n", + "INSTANCE_ID = \"\" # @param {type:'string'}\n", + "TABLE_ID = \"\" # @param {type:'string'}" ] }, { @@ -457,10 +457,10 @@ "outputs": [], "source": [ "# Replace with the name of your Pub/Sub topic.\n", - "TOPIC = \"\"\n", + "TOPIC = \"\" # @param {type:'string'}\n", "\n", "# Replace with the subscription for your topic.\n", - "SUBSCRIPTION = \"\"\n" + "SUBSCRIPTION = \"\" # @param {type:'string'}\n" ] }, { @@ -532,16 +532,16 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "UEpjy_IsW4P4" + }, "source": [ "The `row_key` parameter represents the field in input schema (`beam.Row`) that contains the row key for a row in the table.\n", "\n", "Starting with Apache Beam version 2.54.0, you can perform either of the following tasks when a table uses composite row keys:\n", "* Modify the input schema to contain the row key in the format required by Bigtable.\n", "* Use a custom enrichment handler. For more information, see the [example handler with composite row key support](https://www.toptal.com/developers/paste-gd/BYFGUL08#)." - ], - "metadata": { - "id": "UEpjy_IsW4P4" - } + ] }, { "cell_type": "code", @@ -636,6 +636,9 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "fe3bIclV1jZ5" + }, "source": [ "To provide a `lambda` function for using a custom join with the enrichment transform, see the following example.\n", "\n", @@ -648,13 +651,13 @@ " ...\n", " )\n", "```" - ], - "metadata": { - "id": "fe3bIclV1jZ5" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "uilxdknE3ihO" + }, "source": [ "Because the enrichment transform makes API calls to the remote service, use the `timeout` parameter to specify a timeout duration of 10 seconds:\n", "\n", @@ -667,10 +670,7 @@ " ...\n", " )\n", "```" - ], - "metadata": { - "id": "uilxdknE3ihO" - } + ] }, { "cell_type": "markdown", @@ -855,11 +855,11 @@ ], "metadata": { "colab": { - "provenance": [], - "toc_visible": true, "collapsed_sections": [ "RpqZFfFfA_Dt" - ] + ], + "provenance": [], + "toc_visible": true }, "kernelspec": { "display_name": "Python 3", diff --git a/examples/notebooks/beam-ml/data_preprocessing/vertex_ai_text_embeddings.ipynb b/examples/notebooks/beam-ml/data_preprocessing/vertex_ai_text_embeddings.ipynb index 49e2f35b13be..4d816ef97fb0 100644 --- a/examples/notebooks/beam-ml/data_preprocessing/vertex_ai_text_embeddings.ipynb +++ b/examples/notebooks/beam-ml/data_preprocessing/vertex_ai_text_embeddings.ipynb @@ -1,18 +1,4 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "code", @@ -44,6 +30,9 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "ZUSiAR62SgO8" + }, "source": [ "# Generate text embeddings by using the Vertex AI API\n", "\n", @@ -55,13 +44,13 @@ " View source on GitHub\n", " \n", "\n" - ], - "metadata": { - "id": "ZUSiAR62SgO8" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "bkpSCGCWlqAf" + }, "source": [ "Text embeddings are a way to represent text as numerical vectors. This process lets computers understand and process text data, which is essential for many natural language processing (NLP) tasks.\n", "\n", @@ -84,71 +73,72 @@ "* Do one of the following tasks:\n", " * Configure credentials for your Google Cloud project. For more information, see [Google Auth Library for Python](https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#module-google.auth).\n", " * Store the path to a service account JSON file by using the [GOOGLE_APPLICATION_CREDENTIALS](https://cloud.google.com/docs/authentication/application-default-credentials#GAC) environment variable." - ], - "metadata": { - "id": "bkpSCGCWlqAf" - } + ] }, { "cell_type": "markdown", - "source": [ - "To use your Google Cloud account, authenticate this notebook." - ], "metadata": { "id": "W29FgO5Qv2ew" - } + }, + "source": [ + "To use your Google Cloud account, authenticate this notebook." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "nYyyGYt3licq" + }, + "outputs": [], "source": [ "from google.colab import auth\n", "auth.authenticate_user()\n", "\n", - "project = '' # Replace with a valid Google Cloud project ID." - ], - "metadata": { - "id": "nYyyGYt3licq" - }, - "execution_count": null, - "outputs": [] + "# Replace with a valid Google Cloud project ID.\n", + "project = '' # @param {type:'string'}" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "UQROd16ZDN5y" + }, "source": [ "## Install dependencies\n", " Install Apache Beam and the dependencies required for the Vertex AI text-embeddings API." - ], - "metadata": { - "id": "UQROd16ZDN5y" - } + ] }, { "cell_type": "code", - "source": [ - "! pip install apache_beam[gcp]>=2.53.0 --quiet" - ], + "execution_count": null, "metadata": { "id": "BTxob7d5DLBM" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "! pip install apache_beam[gcp]>=2.53.0 --quiet" + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SkMhR7H6n1P0" + }, + "outputs": [], "source": [ "import tempfile\n", "import apache_beam as beam\n", "from apache_beam.ml.transforms.base import MLTransform\n", "from apache_beam.ml.transforms.embeddings.vertex_ai import VertexAITextEmbeddings" - ], - "metadata": { - "id": "SkMhR7H6n1P0" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "cokOaX2kzyke" + }, "source": [ "## Transform the data\n", "\n", @@ -156,25 +146,27 @@ "\n", "### Use MLTransform in write mode\n", "\n", - "In `write` mode, `MLTransform` saves the transforms and their attributes to an artifact location. Then, when you run `MLTransform` in `read` mode, these transforms are used. This process ensures that you're applying the same preprocessing steps when you train your model and when you serve the model in production or test its accuracy." - ], - "metadata": { - "id": "cokOaX2kzyke" - } + "In `write` mode, `MLTransform` saves the transforms and their attributes to an artifact location. Then, when you run `MLTransform` in `read` mode, these transforms are used. This process ensures that you're applying the same preprocessing steps when you train your model and when you serve the model in production or test its accuracy." + ] }, { "cell_type": "markdown", + "metadata": { + "id": "-x7fVvuy-aDs" + }, "source": [ "### Get the data\n", "\n", "`MLTransform` processes dictionaries that include column names and their associated text data. To generate embeddings for specific columns, specify these column names in the `columns` argument of `VertexAITextEmbeddings`. This transform uses the the Vertex AI text-embeddings API for online predictions to generate an embeddings vector for each sentence." - ], - "metadata": { - "id": "-x7fVvuy-aDs" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "be-vR159pylF" + }, + "outputs": [], "source": [ "artifact_location = tempfile.mkdtemp(prefix='vertex_ai')\n", "\n", @@ -201,32 +193,11 @@ " for key in d.keys():\n", " d[key] = d[key][:10]\n", " return d" - ], - "metadata": { - "id": "be-vR159pylF" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", - "source": [ - "embedding_transform = VertexAITextEmbeddings(\n", - " model_name=text_embedding_model_name, columns=['x'], project=project)\n", - "\n", - "with beam.Pipeline() as pipeline:\n", - " data_pcoll = (\n", - " pipeline\n", - " | \"CreateData\" >> beam.Create(content))\n", - " transformed_pcoll = (\n", - " data_pcoll\n", - " | \"MLTransform\" >> MLTransform(write_artifact_location=artifact_location).with_transform(embedding_transform))\n", - "\n", - " # Show only the first ten elements of the embeddings to prevent clutter in the output.\n", - " transformed_pcoll | beam.Map(truncate_embeddings) | 'LogOutput' >> beam.Map(print)\n", - "\n", - " transformed_pcoll | \"PrintEmbeddingShape\" >> beam.Map(lambda x: print(f\"Embedding shape: {len(x['x'])}\"))" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -234,11 +205,10 @@ "id": "UQGm1be3p7lM", "outputId": "b41172ca-1c73-4952-ca87-bfe45ca88a6c" }, - "execution_count": null, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "{'x': [0.041293490678071976, -0.010302993468940258, -0.048611514270305634, -0.01360565796494484, 0.06441926211118698, 0.022573700174689293, 0.016446372494101524, -0.033894773572683334, 0.004581860266625881, 0.060710687190294266]}\n", "Embedding shape: 10\n", @@ -248,23 +218,58 @@ "Embedding shape: 10\n" ] } + ], + "source": [ + "embedding_transform = VertexAITextEmbeddings(\n", + " model_name=text_embedding_model_name, columns=['x'], project=project)\n", + "\n", + "with beam.Pipeline() as pipeline:\n", + " data_pcoll = (\n", + " pipeline\n", + " | \"CreateData\" >> beam.Create(content))\n", + " transformed_pcoll = (\n", + " data_pcoll\n", + " | \"MLTransform\" >> MLTransform(write_artifact_location=artifact_location).with_transform(embedding_transform))\n", + "\n", + " # Show only the first ten elements of the embeddings to prevent clutter in the output.\n", + " transformed_pcoll | beam.Map(truncate_embeddings) | 'LogOutput' >> beam.Map(print)\n", + "\n", + " transformed_pcoll | \"PrintEmbeddingShape\" >> beam.Map(lambda x: print(f\"Embedding shape: {len(x['x'])}\"))" ] }, { "cell_type": "markdown", + "metadata": { + "id": "JLkmQkiLx_6h" + }, "source": [ "### Use MLTransform in read mode\n", "\n", "In `read` mode, `MLTransform` uses the artifacts saved during `write` mode. In this example, the transform and its attributes are loaded from the saved artifacts. You don't need to specify artifacts again during `read` mode.\n", "\n", "In this way, `MLTransform` provides consistent preprocessing steps for training and inference workloads." - ], - "metadata": { - "id": "JLkmQkiLx_6h" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "r8Y5vgfLx_Xu", + "outputId": "e7cbf6b7-5c31-4efa-90cf-7a8a108ecc77" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'x': [0.04782044142484665, -0.010078949853777885, -0.05793016776442528, -0.026060665026307106, 0.05756739526987076, 0.02292264811694622, 0.014818413183093071, -0.03718176111578941, -0.005486017093062401, 0.04709304869174957]}\n", + "{'x': [0.042911216616630554, -0.007554919924587011, -0.08996245265007019, -0.02607591263949871, 0.0008614308317191899, -0.023671219125390053, 0.03999944031238556, -0.02983051724731922, -0.015057179145514965, 0.022963201627135277]}\n" + ] + } + ], "source": [ "test_content = [\n", " {\n", @@ -284,25 +289,21 @@ " | \"MLTransform\" >> MLTransform(read_artifact_location=artifact_location))\n", "\n", " transformed_pcoll | beam.Map(truncate_embeddings) | 'LogOutput' >> beam.Map(print)\n" - ], - "metadata": { - "id": "r8Y5vgfLx_Xu", - "colab": { - "base_uri": "https://localhost:8080/" - }, - "outputId": "e7cbf6b7-5c31-4efa-90cf-7a8a108ecc77" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "{'x': [0.04782044142484665, -0.010078949853777885, -0.05793016776442528, -0.026060665026307106, 0.05756739526987076, 0.02292264811694622, 0.014818413183093071, -0.03718176111578941, -0.005486017093062401, 0.04709304869174957]}\n", - "{'x': [0.042911216616630554, -0.007554919924587011, -0.08996245265007019, -0.02607591263949871, 0.0008614308317191899, -0.023671219125390053, 0.03999944031238556, -0.02983051724731922, -0.015057179145514965, 0.022963201627135277]}\n" - ] - } ] } - ] + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/examples/notebooks/beam-ml/dataframe_api_preprocessing.ipynb b/examples/notebooks/beam-ml/dataframe_api_preprocessing.ipynb index a488caf7d3ac..3af7455222a9 100644 --- a/examples/notebooks/beam-ml/dataframe_api_preprocessing.ipynb +++ b/examples/notebooks/beam-ml/dataframe_api_preprocessing.ipynb @@ -2,6 +2,12 @@ "cells": [ { "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "sARMhsXz8yR1" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -21,16 +27,13 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "cellView": "form", - "id": "sARMhsXz8yR1" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "A8xNRyZMW1yK" + }, "source": [ "# Preprocessing with the Apache Beam DataFrames API\n", "\n", @@ -44,13 +47,13 @@ " View source on GitHub\n", " \n", "\n" - ], - "metadata": { - "id": "A8xNRyZMW1yK" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "iFZC1inKuUCy" + }, "source": [ "For rapid execution, Pandas loads all of the data into memory on a single machine (one node). This configuration works well when dealing with small-scale datasets. However, many projects involve datasets that are too big to fit in memory. These use cases generally require parallel data processing frameworks, such as Apache Beam.\n", "\n", @@ -71,21 +74,18 @@ "\n", "In this example, the first section demonstrates how to build and execute a pipeline locally using the interactive runner.\n", "The second section uses a distributed runner to demonstrate how to run the pipeline on the full dataset.\n" - ], - "metadata": { - "id": "iFZC1inKuUCy" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "A0f2HJ22D4lt" + }, "source": [ "## Install Apache Beam\n", "\n", "To explore the elements within a `PCollection`, install Apache Beam with the `interactive` component to use the Interactive runner. The DataFrames API methods invoked in this example are available in Apache Beam SDK versions 2.43 and later.\n" - ], - "metadata": { - "id": "A0f2HJ22D4lt" - } + ] }, { "cell_type": "markdown", @@ -100,8 +100,8 @@ "cell_type": "code", "execution_count": null, "metadata": { - "id": "-OJC0Xn5Um-C", - "beam:comment": "TODO(https://github.com/apache/issues/23961): Just install 2.43.0 once it's released, [`issue 23276`](https://github.com/apache/beam/issues/23276) is currently not implemented for Beam 2.42 (required fix for implementing `str.get_dummies()`" + "beam:comment": "TODO(https://github.com/apache/issues/23961): Just install 2.43.0 once it's released, [`issue 23276`](https://github.com/apache/beam/issues/23276) is currently not implemented for Beam 2.42 (required fix for implementing `str.get_dummies()`", + "id": "-OJC0Xn5Um-C" }, "outputs": [], "source": [ @@ -114,6 +114,9 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "3NO6RgB7GkkE" + }, "source": [ "## Local exploration with the Interactive Beam runner\n", "Use the [Interactive Beam](https://beam.apache.org/releases/pydoc/2.20.0/apache_beam.runners.interactive.interactive_beam.html) runner to explore and develop your pipeline.\n", @@ -121,10 +124,7 @@ "\n", "\n", "This section uses a subset of the original dataset, because the notebook instance has limited compute resources.\n" - ], - "metadata": { - "id": "3NO6RgB7GkkE" - } + ] }, { "cell_type": "markdown", @@ -186,13 +186,13 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "cvAu5T0ENjuQ" + }, "source": [ "\n", "Inspect the dataset columns and their types." - ], - "metadata": { - "id": "cvAu5T0ENjuQ" - } + ] }, { "cell_type": "code", @@ -206,7 +206,6 @@ }, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "spk_id int64\n", @@ -225,8 +224,9 @@ "dtype: object" ] }, + "execution_count": 27, "metadata": {}, - "execution_count": 27 + "output_type": "execute_result" } ], "source": [ @@ -235,12 +235,12 @@ }, { "cell_type": "markdown", - "source": [ - "When using Interactive Beam, to bring a Beam DataFrame into local memory as a Pandas DataFrame, use `ib.collect()`." - ], "metadata": { "id": "1Wa6fpbyQige" - } + }, + "source": [ + "When using Interactive Beam, to bring a Beam DataFrame into local memory as a Pandas DataFrame, use `ib.collect()`." + ] }, { "cell_type": "code", @@ -255,11 +255,7 @@ }, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -268,101 +264,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_79206f341d7de09f6cacdd05be309575\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_79206f341d7de09f6cacdd05be309575\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_79206f341d7de09f6cacdd05be309575\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_79206f341d7de09f6cacdd05be309575\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": [ - " spk_id full_name near_earth_object \\\n", - "0 2000001 1 Ceres N \n", - "1 2000002 2 Pallas N \n", - "2 2000003 3 Juno N \n", - "3 2000004 4 Vesta N \n", - "4 2000005 5 Astraea N \n", - "... ... ... ... \n", - "9994 2009995 9995 Alouette (4805 P-L) N \n", - "9995 2009996 9996 ANS (9070 P-L) N \n", - "9996 2009997 9997 COBE (1217 T-1) N \n", - "9997 2009998 9998 ISO (1293 T-1) N \n", - "9998 2009999 9999 Wiles (4196 T-2) N \n", - "\n", - " absolute_magnitude diameter albedo diameter_sigma eccentricity \\\n", - "0 3.40 939.400 0.0900 0.200 0.076009 \n", - "1 4.20 545.000 0.1010 18.000 0.229972 \n", - "2 5.33 246.596 0.2140 10.594 0.256936 \n", - "3 3.00 525.400 0.4228 0.200 0.088721 \n", - "4 6.90 106.699 0.2740 3.140 0.190913 \n", - "... ... ... ... ... ... \n", - "9994 15.10 2.564 0.2450 0.550 0.160610 \n", - "9995 13.60 8.978 0.1130 0.376 0.235174 \n", - "9996 14.30 NaN NaN NaN 0.113059 \n", - "9997 15.10 2.235 0.3880 0.373 0.093852 \n", - "9998 13.00 7.148 0.2620 0.065 0.071351 \n", - "\n", - " inclination moid_ld object_class semi_major_axis_au_unit \\\n", - "0 10.594067 620.640533 MBA 2.769165 \n", - "1 34.832932 480.348639 MBA 2.773841 \n", - "2 12.991043 402.514639 MBA 2.668285 \n", - "3 7.141771 443.451432 MBA 2.361418 \n", - "4 5.367427 426.433027 MBA 2.574037 \n", - "... ... ... ... ... \n", - "9994 2.311731 388.723233 MBA 2.390249 \n", - "9995 7.657713 444.194746 MBA 2.796605 \n", - "9996 2.459643 495.460110 MBA 2.545674 \n", - "9997 3.912263 373.848377 MBA 2.160961 \n", - "9998 3.198839 632.144398 MBA 2.839917 \n", - "\n", - " hazardous_flag \n", - "0 N \n", - "1 N \n", - "2 N \n", - "3 N \n", - "4 N \n", - "... ... \n", - "9994 N \n", - "9995 N \n", - "9996 N \n", - "9997 N \n", - "9998 N \n", - "\n", - "[9999 rows x 13 columns]" - ], "text/html": [ "\n", "
\n", @@ -657,10 +575,66 @@ "
\n", " \n", " " + ], + "text/plain": [ + " spk_id full_name near_earth_object \\\n", + "0 2000001 1 Ceres N \n", + "1 2000002 2 Pallas N \n", + "2 2000003 3 Juno N \n", + "3 2000004 4 Vesta N \n", + "4 2000005 5 Astraea N \n", + "... ... ... ... \n", + "9994 2009995 9995 Alouette (4805 P-L) N \n", + "9995 2009996 9996 ANS (9070 P-L) N \n", + "9996 2009997 9997 COBE (1217 T-1) N \n", + "9997 2009998 9998 ISO (1293 T-1) N \n", + "9998 2009999 9999 Wiles (4196 T-2) N \n", + "\n", + " absolute_magnitude diameter albedo diameter_sigma eccentricity \\\n", + "0 3.40 939.400 0.0900 0.200 0.076009 \n", + "1 4.20 545.000 0.1010 18.000 0.229972 \n", + "2 5.33 246.596 0.2140 10.594 0.256936 \n", + "3 3.00 525.400 0.4228 0.200 0.088721 \n", + "4 6.90 106.699 0.2740 3.140 0.190913 \n", + "... ... ... ... ... ... \n", + "9994 15.10 2.564 0.2450 0.550 0.160610 \n", + "9995 13.60 8.978 0.1130 0.376 0.235174 \n", + "9996 14.30 NaN NaN NaN 0.113059 \n", + "9997 15.10 2.235 0.3880 0.373 0.093852 \n", + "9998 13.00 7.148 0.2620 0.065 0.071351 \n", + "\n", + " inclination moid_ld object_class semi_major_axis_au_unit \\\n", + "0 10.594067 620.640533 MBA 2.769165 \n", + "1 34.832932 480.348639 MBA 2.773841 \n", + "2 12.991043 402.514639 MBA 2.668285 \n", + "3 7.141771 443.451432 MBA 2.361418 \n", + "4 5.367427 426.433027 MBA 2.574037 \n", + "... ... ... ... ... \n", + "9994 2.311731 388.723233 MBA 2.390249 \n", + "9995 7.657713 444.194746 MBA 2.796605 \n", + "9996 2.459643 495.460110 MBA 2.545674 \n", + "9997 3.912263 373.848377 MBA 2.160961 \n", + "9998 3.198839 632.144398 MBA 2.839917 \n", + "\n", + " hazardous_flag \n", + "0 N \n", + "1 N \n", + "2 N \n", + "3 N \n", + "4 N \n", + "... ... \n", + "9994 N \n", + "9995 N \n", + "9996 N \n", + "9997 N \n", + "9998 N \n", + "\n", + "[9999 rows x 13 columns]" ] }, + "execution_count": 28, "metadata": {}, - "execution_count": 28 + "output_type": "execute_result" } ], "source": [ @@ -669,34 +643,29 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "8jV9odKhNyF2" + }, "source": [ "The datasets contain the following two types of columns:\n", "\n", "* **Numerical columns:** Use [normalization](https://developers.google.com/machine-learning/data-prep/transform/normalization) to transform these columns so that they can be used to train a machine learning model.\n", "\n", "* **Categorical columns:** Transform those columns with [one-hot encoding](https://developers.google.com/machine-learning/data-prep/transform/transform-categorical) to use them during training. \n" - ], - "metadata": { - "id": "8jV9odKhNyF2" - } + ] }, { "cell_type": "markdown", - "source": [ - "Use the standard pandas command `DataFrame.describe()` to generate descriptive statistics for the numerical columns, such as percentile, mean, std, and so on. " - ], "metadata": { "id": "MGAErO0lAYws" - } + }, + "source": [ + "Use the standard pandas command `DataFrame.describe()` to generate descriptive statistics for the numerical columns, such as percentile, mean, std, and so on. " + ] }, { "cell_type": "code", - "source": [ - "with dataframe.allow_non_parallel_operations():\n", - " beam_df_description = ib.collect(beam_df.describe())\n", - "\n", - "beam_df_description" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -705,14 +674,9 @@ "id": "Befv697VBGM7", "outputId": "bb465020-94e4-4b3c-fda6-6e43da199be1" }, - "execution_count": null, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -721,77 +685,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_98687cb0060a8077a8abab6e464e4a75\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_98687cb0060a8077a8abab6e464e4a75\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_98687cb0060a8077a8abab6e464e4a75\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_98687cb0060a8077a8abab6e464e4a75\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": [ - " spk_id absolute_magnitude diameter albedo \\\n", - "count 9.999000e+03 9999.000000 8688.000000 8672.000000 \n", - "mean 2.005000e+06 12.675380 19.245446 0.197723 \n", - "std 2.886607e+03 1.639609 30.190191 0.138819 \n", - "min 2.000001e+06 3.000000 0.300000 0.008000 \n", - "25% 2.002500e+06 11.900000 5.614000 0.074000 \n", - "50% 2.005000e+06 12.900000 9.814000 0.187000 \n", - "75% 2.007500e+06 13.700000 19.156750 0.283000 \n", - "max 2.009999e+06 20.700000 939.400000 1.000000 \n", - "\n", - " diameter_sigma eccentricity inclination moid_ld \\\n", - "count 8591.000000 9999.000000 9999.000000 9999.000000 \n", - "mean 0.454072 0.148716 7.890742 509.805237 \n", - "std 1.093676 0.083803 6.336244 205.046582 \n", - "min 0.006000 0.001003 0.042716 0.131028 \n", - "25% 0.120000 0.093780 3.220137 377.829197 \n", - "50% 0.201000 0.140335 6.018836 470.650523 \n", - "75% 0.375000 0.187092 10.918176 636.010802 \n", - "max 39.297000 0.889831 68.018875 4241.524913 \n", - "\n", - " semi_major_axis_au_unit \n", - "count 9999.000000 \n", - "mean 2.689836 \n", - "std 0.607190 \n", - "min 0.832048 \n", - "25% 2.340816 \n", - "50% 2.614468 \n", - "75% 3.005449 \n", - "max 24.667968 " - ], "text/html": [ "\n", "
\n", @@ -1001,27 +911,65 @@ "
\n", " \n", " " - ] - }, - "metadata": {}, - "execution_count": 21 - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D9uJtHLSSAMC" - }, - "source": [ - "Before running any transformations, verify that all of the columns need to be used for model training. Start by looking at the column description provided by the [JPL website](https://ssd.jpl.nasa.gov/sbdb_query.cgi):\n", - "\n", - "* **spk_id:** Object primary SPK-ID.\n", - "* **full_name:** Asteroid name.\n", - "* **near_earth_object:** Near-earth object flag.\n", - "* **absolute_magnitude:** The apparent magnitude an object would have if it were located at a distance of 10 parsecs.\n", - "* **diameter:** Object diameter (from equivalent sphere) km unit.\n", - "* **albedo:** A measure of the diffuse reflection of solar radiation out of the total solar radiation, measured on a scale from 0 to 1.\n", + ], + "text/plain": [ + " spk_id absolute_magnitude diameter albedo \\\n", + "count 9.999000e+03 9999.000000 8688.000000 8672.000000 \n", + "mean 2.005000e+06 12.675380 19.245446 0.197723 \n", + "std 2.886607e+03 1.639609 30.190191 0.138819 \n", + "min 2.000001e+06 3.000000 0.300000 0.008000 \n", + "25% 2.002500e+06 11.900000 5.614000 0.074000 \n", + "50% 2.005000e+06 12.900000 9.814000 0.187000 \n", + "75% 2.007500e+06 13.700000 19.156750 0.283000 \n", + "max 2.009999e+06 20.700000 939.400000 1.000000 \n", + "\n", + " diameter_sigma eccentricity inclination moid_ld \\\n", + "count 8591.000000 9999.000000 9999.000000 9999.000000 \n", + "mean 0.454072 0.148716 7.890742 509.805237 \n", + "std 1.093676 0.083803 6.336244 205.046582 \n", + "min 0.006000 0.001003 0.042716 0.131028 \n", + "25% 0.120000 0.093780 3.220137 377.829197 \n", + "50% 0.201000 0.140335 6.018836 470.650523 \n", + "75% 0.375000 0.187092 10.918176 636.010802 \n", + "max 39.297000 0.889831 68.018875 4241.524913 \n", + "\n", + " semi_major_axis_au_unit \n", + "count 9999.000000 \n", + "mean 2.689836 \n", + "std 0.607190 \n", + "min 0.832048 \n", + "25% 2.340816 \n", + "50% 2.614468 \n", + "75% 3.005449 \n", + "max 24.667968 " + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with dataframe.allow_non_parallel_operations():\n", + " beam_df_description = ib.collect(beam_df.describe())\n", + "\n", + "beam_df_description" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D9uJtHLSSAMC" + }, + "source": [ + "Before running any transformations, verify that all of the columns need to be used for model training. Start by looking at the column description provided by the [JPL website](https://ssd.jpl.nasa.gov/sbdb_query.cgi):\n", + "\n", + "* **spk_id:** Object primary SPK-ID.\n", + "* **full_name:** Asteroid name.\n", + "* **near_earth_object:** Near-earth object flag.\n", + "* **absolute_magnitude:** The apparent magnitude an object would have if it were located at a distance of 10 parsecs.\n", + "* **diameter:** Object diameter (from equivalent sphere) km unit.\n", + "* **albedo:** A measure of the diffuse reflection of solar radiation out of the total solar radiation, measured on a scale from 0 to 1.\n", "* **diameter_sigma:** 1-sigma uncertainty in object diameter km unit.\n", "* **eccentricity:** A value between 0 and 1 that refers to how flat or round the asteroid is.\n", "* **inclination:** The angle with respect to the x-y ecliptic plane.\n", @@ -1073,19 +1021,15 @@ }, "outputs": [ { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "/content/beam/sdks/python/apache_beam/dataframe/frame_base.py:145: RuntimeWarning: invalid value encountered in long_scalars\n", " lambda left, right: getattr(left, op)(right), name=op, args=[other])\n" ] }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -1094,45 +1038,22 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_868f8ad001ab00c7013b65472a513917\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_868f8ad001ab00c7013b65472a513917\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_868f8ad001ab00c7013b65472a513917\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_868f8ad001ab00c7013b65472a513917\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { "text/plain": [ "near_earth_object 0.000000\n", @@ -1149,8 +1070,9 @@ "dtype: float64" ] }, + "execution_count": 30, "metadata": {}, - "execution_count": 30 + "output_type": "execute_result" } ], "source": [ @@ -1170,20 +1092,16 @@ "cell_type": "code", "execution_count": null, "metadata": { - "id": "tHYeCHREwvyB", "colab": { "base_uri": "https://localhost:8080/", "height": 538 }, + "id": "tHYeCHREwvyB", "outputId": "3be686d0-f56a-4054-a71a-d3019bf379e8" }, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -1192,75 +1110,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_f88b77f183371d1a45fa87bed4a545f6\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_f88b77f183371d1a45fa87bed4a545f6\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_f88b77f183371d1a45fa87bed4a545f6\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_f88b77f183371d1a45fa87bed4a545f6\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": [ - " near_earth_object absolute_magnitude eccentricity inclination \\\n", - "0 N 3.40 0.076009 10.594067 \n", - "1 N 4.20 0.229972 34.832932 \n", - "2 N 5.33 0.256936 12.991043 \n", - "3 N 3.00 0.088721 7.141771 \n", - "4 N 6.90 0.190913 5.367427 \n", - "... ... ... ... ... \n", - "9994 N 15.10 0.160610 2.311731 \n", - "9995 N 13.60 0.235174 7.657713 \n", - "9996 N 14.30 0.113059 2.459643 \n", - "9997 N 15.10 0.093852 3.912263 \n", - "9998 N 13.00 0.071351 3.198839 \n", - "\n", - " moid_ld object_class semi_major_axis_au_unit hazardous_flag \n", - "0 620.640533 MBA 2.769165 N \n", - "1 480.348639 MBA 2.773841 N \n", - "2 402.514639 MBA 2.668285 N \n", - "3 443.451432 MBA 2.361418 N \n", - "4 426.433027 MBA 2.574037 N \n", - "... ... ... ... ... \n", - "9994 388.723233 MBA 2.390249 N \n", - "9995 444.194746 MBA 2.796605 N \n", - "9996 495.460110 MBA 2.545674 N \n", - "9997 373.848377 MBA 2.160961 N \n", - "9998 632.144398 MBA 2.839917 N \n", - "\n", - "[9999 rows x 8 columns]" - ], "text/html": [ "\n", "
\n", @@ -1495,10 +1361,40 @@ "
\n", " \n", " " + ], + "text/plain": [ + " near_earth_object absolute_magnitude eccentricity inclination \\\n", + "0 N 3.40 0.076009 10.594067 \n", + "1 N 4.20 0.229972 34.832932 \n", + "2 N 5.33 0.256936 12.991043 \n", + "3 N 3.00 0.088721 7.141771 \n", + "4 N 6.90 0.190913 5.367427 \n", + "... ... ... ... ... \n", + "9994 N 15.10 0.160610 2.311731 \n", + "9995 N 13.60 0.235174 7.657713 \n", + "9996 N 14.30 0.113059 2.459643 \n", + "9997 N 15.10 0.093852 3.912263 \n", + "9998 N 13.00 0.071351 3.198839 \n", + "\n", + " moid_ld object_class semi_major_axis_au_unit hazardous_flag \n", + "0 620.640533 MBA 2.769165 N \n", + "1 480.348639 MBA 2.773841 N \n", + "2 402.514639 MBA 2.668285 N \n", + "3 443.451432 MBA 2.361418 N \n", + "4 426.433027 MBA 2.574037 N \n", + "... ... ... ... ... \n", + "9994 388.723233 MBA 2.390249 N \n", + "9995 444.194746 MBA 2.796605 N \n", + "9996 495.460110 MBA 2.545674 N \n", + "9997 373.848377 MBA 2.160961 N \n", + "9998 632.144398 MBA 2.839917 N \n", + "\n", + "[9999 rows x 8 columns]" ] }, + "execution_count": 31, "metadata": {}, - "execution_count": 31 + "output_type": "execute_result" } ], "source": [ @@ -1559,19 +1455,15 @@ }, "outputs": [ { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "/content/beam/sdks/python/apache_beam/dataframe/frame_base.py:145: RuntimeWarning: invalid value encountered in double_scalars\n", " lambda left, right: getattr(left, op)(right), name=op, args=[other])\n" ] }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -1580,75 +1472,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_55302fa5950ce6ceb9f99ff9a168097a\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_55302fa5950ce6ceb9f99ff9a168097a\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_55302fa5950ce6ceb9f99ff9a168097a\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_55302fa5950ce6ceb9f99ff9a168097a\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": [ - " absolute_magnitude eccentricity inclination moid_ld \\\n", - "306 -1.570727 -0.062543 -0.278518 0.373194 \n", - "310 -1.631718 -1.724526 -0.736389 1.087833 \n", - "546 -1.753698 1.028793 1.415303 -0.339489 \n", - "635 -1.875678 0.244869 0.005905 0.214107 \n", - "701 -3.278451 -1.570523 2.006145 1.542754 \n", - "... ... ... ... ... \n", - "9697 0.807888 -1.151809 -0.082944 -0.129556 \n", - "9813 1.722740 0.844551 -0.583247 -1.006447 \n", - "9868 0.807888 -0.207399 -0.784665 -0.462136 \n", - "9903 0.868878 0.460086 0.092258 -0.107597 \n", - "9956 0.746898 -0.234132 -0.161116 -0.601379 \n", - "\n", - " semi_major_axis_au_unit \n", - "306 0.357201 \n", - "310 0.344233 \n", - "546 0.139080 \n", - "635 0.367559 \n", - "701 0.829337 \n", - "... ... \n", - "9697 -0.533538 \n", - "9813 -0.677961 \n", - "9868 -0.539794 \n", - "9903 0.071794 \n", - "9956 -0.664887 \n", - "\n", - "[9999 rows x 5 columns]" - ], "text/html": [ "\n", "
\n", @@ -1847,10 +1687,40 @@ "
\n", " \n", " " + ], + "text/plain": [ + " absolute_magnitude eccentricity inclination moid_ld \\\n", + "306 -1.570727 -0.062543 -0.278518 0.373194 \n", + "310 -1.631718 -1.724526 -0.736389 1.087833 \n", + "546 -1.753698 1.028793 1.415303 -0.339489 \n", + "635 -1.875678 0.244869 0.005905 0.214107 \n", + "701 -3.278451 -1.570523 2.006145 1.542754 \n", + "... ... ... ... ... \n", + "9697 0.807888 -1.151809 -0.082944 -0.129556 \n", + "9813 1.722740 0.844551 -0.583247 -1.006447 \n", + "9868 0.807888 -0.207399 -0.784665 -0.462136 \n", + "9903 0.868878 0.460086 0.092258 -0.107597 \n", + "9956 0.746898 -0.234132 -0.161116 -0.601379 \n", + "\n", + " semi_major_axis_au_unit \n", + "306 0.357201 \n", + "310 0.344233 \n", + "546 0.139080 \n", + "635 0.367559 \n", + "701 0.829337 \n", + "... ... \n", + "9697 -0.533538 \n", + "9813 -0.677961 \n", + "9868 -0.539794 \n", + "9903 0.071794 \n", + "9956 -0.664887 \n", + "\n", + "[9999 rows x 5 columns]" ] }, + "execution_count": 33, "metadata": {}, - "execution_count": 33 + "output_type": "execute_result" } ], "source": [ @@ -1895,12 +1765,7 @@ }, { "cell_type": "code", - "source": [ - "for categorical_col in categorical_cols:\n", - " beam_df_categorical = get_one_hot_encoding(df=beam_df, categorical_col=categorical_col)\n", - " beam_df_numericals = beam_df_numericals.merge(beam_df_categorical, left_index = True, right_index = True)\n", - "ib.collect(beam_df_numericals)" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1909,14 +1774,9 @@ "id": "k9rvtWqHf6Qw", "outputId": "b8d8ae57-6dba-45b4-e7ae-e4b14084eede" }, - "execution_count": null, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -1925,49 +1785,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_6b2563c7f661bc0fc5729c2577d6f232\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_6b2563c7f661bc0fc5729c2577d6f232\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_6b2563c7f661bc0fc5729c2577d6f232\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_6b2563c7f661bc0fc5729c2577d6f232\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -1976,49 +1810,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_6fa896083b128ad99059af69a3d7fc7e\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_6fa896083b128ad99059af69a3d7fc7e\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_6fa896083b128ad99059af69a3d7fc7e\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_6fa896083b128ad99059af69a3d7fc7e\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -2027,49 +1835,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_6339347de9805da541eba53abaee2d5e\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_6339347de9805da541eba53abaee2d5e\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_6339347de9805da541eba53abaee2d5e\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_6339347de9805da541eba53abaee2d5e\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -2078,127 +1860,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_1af5b908898a1e5949dcc20549f650eb\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_1af5b908898a1e5949dcc20549f650eb\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_1af5b908898a1e5949dcc20549f650eb\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_1af5b908898a1e5949dcc20549f650eb\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": [ - " absolute_magnitude eccentricity inclination moid_ld \\\n", - "0 -5.657067 -0.867596 0.426645 0.540537 \n", - "12 -3.583402 -0.756931 1.364340 0.238610 \n", - "47 -3.400432 -0.912290 -0.211925 1.136060 \n", - "381 -2.363599 0.271412 -0.078826 0.535299 \n", - "515 -2.729540 1.469775 0.799915 -0.602881 \n", - "... ... ... ... ... \n", - "9146 0.563927 -0.508757 -0.327512 -0.637391 \n", - "9657 1.478779 0.487849 -0.637779 -0.648240 \n", - "9704 0.380957 -0.238383 0.443053 0.670490 \n", - "9879 1.295809 -0.442966 -0.698505 -0.494818 \n", - "9980 0.746898 -1.455992 -0.849144 0.592902 \n", - "\n", - " semi_major_axis_au_unit near_earth_object_N near_earth_object_Y \\\n", - "0 0.130649 1 0 \n", - "12 -0.187375 1 0 \n", - "47 0.691182 1 0 \n", - "381 0.712755 1 0 \n", - "515 -0.014654 1 0 \n", - "... ... ... ... \n", - "9146 -0.820638 1 0 \n", - "9657 -0.468778 1 0 \n", - "9704 0.587128 1 0 \n", - "9879 -0.662602 1 0 \n", - "9980 -0.022726 1 0 \n", - "\n", - " near_earth_object_nan object_class_AMO object_class_APO ... \\\n", - "0 0 0 0 ... \n", - "12 0 0 0 ... \n", - "47 0 0 0 ... \n", - "381 0 0 0 ... \n", - "515 0 0 0 ... \n", - "... ... ... ... ... \n", - "9146 0 0 0 ... \n", - "9657 0 0 0 ... \n", - "9704 0 0 0 ... \n", - "9879 0 0 0 ... \n", - "9980 0 0 0 ... \n", - "\n", - " object_class_CEN object_class_IMB object_class_MBA object_class_MCA \\\n", - "0 0 0 1 0 \n", - "12 0 0 1 0 \n", - "47 0 0 1 0 \n", - "381 0 0 1 0 \n", - "515 0 0 1 0 \n", - "... ... ... ... ... \n", - "9146 0 0 1 0 \n", - "9657 0 0 1 0 \n", - "9704 0 0 1 0 \n", - "9879 0 0 1 0 \n", - "9980 0 0 1 0 \n", - "\n", - " object_class_OMB object_class_TJN object_class_nan hazardous_flag_N \\\n", - "0 0 0 0 1 \n", - "12 0 0 0 1 \n", - "47 0 0 0 1 \n", - "381 0 0 0 1 \n", - "515 0 0 0 1 \n", - "... ... ... ... ... \n", - "9146 0 0 0 1 \n", - "9657 0 0 0 1 \n", - "9704 0 0 0 1 \n", - "9879 0 0 0 1 \n", - "9980 0 0 0 1 \n", - "\n", - " hazardous_flag_Y hazardous_flag_nan \n", - "0 0 0 \n", - "12 0 0 \n", - "47 0 0 \n", - "381 0 0 \n", - "515 0 0 \n", - "... ... ... \n", - "9146 0 0 \n", - "9657 0 0 \n", - "9704 0 0 \n", - "9879 0 0 \n", - "9980 0 0 \n", - "\n", - "[9999 rows x 22 columns]" - ], "text/html": [ "\n", "
\n", @@ -2589,11 +2267,99 @@ "
\n", " \n", " " + ], + "text/plain": [ + " absolute_magnitude eccentricity inclination moid_ld \\\n", + "0 -5.657067 -0.867596 0.426645 0.540537 \n", + "12 -3.583402 -0.756931 1.364340 0.238610 \n", + "47 -3.400432 -0.912290 -0.211925 1.136060 \n", + "381 -2.363599 0.271412 -0.078826 0.535299 \n", + "515 -2.729540 1.469775 0.799915 -0.602881 \n", + "... ... ... ... ... \n", + "9146 0.563927 -0.508757 -0.327512 -0.637391 \n", + "9657 1.478779 0.487849 -0.637779 -0.648240 \n", + "9704 0.380957 -0.238383 0.443053 0.670490 \n", + "9879 1.295809 -0.442966 -0.698505 -0.494818 \n", + "9980 0.746898 -1.455992 -0.849144 0.592902 \n", + "\n", + " semi_major_axis_au_unit near_earth_object_N near_earth_object_Y \\\n", + "0 0.130649 1 0 \n", + "12 -0.187375 1 0 \n", + "47 0.691182 1 0 \n", + "381 0.712755 1 0 \n", + "515 -0.014654 1 0 \n", + "... ... ... ... \n", + "9146 -0.820638 1 0 \n", + "9657 -0.468778 1 0 \n", + "9704 0.587128 1 0 \n", + "9879 -0.662602 1 0 \n", + "9980 -0.022726 1 0 \n", + "\n", + " near_earth_object_nan object_class_AMO object_class_APO ... \\\n", + "0 0 0 0 ... \n", + "12 0 0 0 ... \n", + "47 0 0 0 ... \n", + "381 0 0 0 ... \n", + "515 0 0 0 ... \n", + "... ... ... ... ... \n", + "9146 0 0 0 ... \n", + "9657 0 0 0 ... \n", + "9704 0 0 0 ... \n", + "9879 0 0 0 ... \n", + "9980 0 0 0 ... \n", + "\n", + " object_class_CEN object_class_IMB object_class_MBA object_class_MCA \\\n", + "0 0 0 1 0 \n", + "12 0 0 1 0 \n", + "47 0 0 1 0 \n", + "381 0 0 1 0 \n", + "515 0 0 1 0 \n", + "... ... ... ... ... \n", + "9146 0 0 1 0 \n", + "9657 0 0 1 0 \n", + "9704 0 0 1 0 \n", + "9879 0 0 1 0 \n", + "9980 0 0 1 0 \n", + "\n", + " object_class_OMB object_class_TJN object_class_nan hazardous_flag_N \\\n", + "0 0 0 0 1 \n", + "12 0 0 0 1 \n", + "47 0 0 0 1 \n", + "381 0 0 0 1 \n", + "515 0 0 0 1 \n", + "... ... ... ... ... \n", + "9146 0 0 0 1 \n", + "9657 0 0 0 1 \n", + "9704 0 0 0 1 \n", + "9879 0 0 0 1 \n", + "9980 0 0 0 1 \n", + "\n", + " hazardous_flag_Y hazardous_flag_nan \n", + "0 0 0 \n", + "12 0 0 \n", + "47 0 0 \n", + "381 0 0 \n", + "515 0 0 \n", + "... ... ... \n", + "9146 0 0 \n", + "9657 0 0 \n", + "9704 0 0 \n", + "9879 0 0 \n", + "9980 0 0 \n", + "\n", + "[9999 rows x 22 columns]" ] }, + "execution_count": 35, "metadata": {}, - "execution_count": 35 + "output_type": "execute_result" } + ], + "source": [ + "for categorical_col in categorical_cols:\n", + " beam_df_categorical = get_one_hot_encoding(df=beam_df, categorical_col=categorical_col)\n", + " beam_df_numericals = beam_df_numericals.merge(beam_df_categorical, left_index = True, right_index = True)\n", + "ib.collect(beam_df_numericals)" ] }, { @@ -2613,28 +2379,24 @@ "cell_type": "code", "execution_count": null, "metadata": { - "id": "ndaSNond0v8Q", "colab": { "base_uri": "https://localhost:8080/", "height": 651 }, + "id": "ndaSNond0v8Q", "outputId": "b265e915-e649-44e4-a31a-95ac85c0ebf6" }, "outputs": [ { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "/content/beam/sdks/python/apache_beam/dataframe/frame_base.py:145: RuntimeWarning: invalid value encountered in double_scalars\n", " lambda left, right: getattr(left, op)(right), name=op, args=[other])\n" ] }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -2643,49 +2405,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_cb06c945824aa1bb68aa31ad7e601b74\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_cb06c945824aa1bb68aa31ad7e601b74\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_cb06c945824aa1bb68aa31ad7e601b74\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_cb06c945824aa1bb68aa31ad7e601b74\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -2694,49 +2430,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_fb923f80fecb72b4fa55e5cfdba16d23\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_fb923f80fecb72b4fa55e5cfdba16d23\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_fb923f80fecb72b4fa55e5cfdba16d23\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_fb923f80fecb72b4fa55e5cfdba16d23\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -2745,49 +2455,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_3f4b1a0f483cd017e004e11816a91d3b\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_3f4b1a0f483cd017e004e11816a91d3b\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_3f4b1a0f483cd017e004e11816a91d3b\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_3f4b1a0f483cd017e004e11816a91d3b\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -2796,127 +2480,23 @@ " Processing... collect\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_fce8902eccbfaa17e32ba0c7c242ccec\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_fce8902eccbfaa17e32ba0c7c242ccec\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_fce8902eccbfaa17e32ba0c7c242ccec\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_fce8902eccbfaa17e32ba0c7c242ccec\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "execute_result", "data": { - "text/plain": [ - " absolute_magnitude eccentricity inclination moid_ld \\\n", - "0 -5.657067 -0.867596 0.426645 0.540537 \n", - "12 -3.583402 -0.756931 1.364340 0.238610 \n", - "47 -3.400432 -0.912290 -0.211925 1.136060 \n", - "381 -2.363599 0.271412 -0.078826 0.535299 \n", - "515 -2.729540 1.469775 0.799915 -0.602881 \n", - "... ... ... ... ... \n", - "9146 0.563927 -0.508757 -0.327512 -0.637391 \n", - "9657 1.478779 0.487849 -0.637779 -0.648240 \n", - "9704 0.380957 -0.238383 0.443053 0.670490 \n", - "9879 1.295809 -0.442966 -0.698505 -0.494818 \n", - "9980 0.746898 -1.455992 -0.849144 0.592902 \n", - "\n", - " semi_major_axis_au_unit near_earth_object_N near_earth_object_Y \\\n", - "0 0.130649 1 0 \n", - "12 -0.187375 1 0 \n", - "47 0.691182 1 0 \n", - "381 0.712755 1 0 \n", - "515 -0.014654 1 0 \n", - "... ... ... ... \n", - "9146 -0.820638 1 0 \n", - "9657 -0.468778 1 0 \n", - "9704 0.587128 1 0 \n", - "9879 -0.662602 1 0 \n", - "9980 -0.022726 1 0 \n", - "\n", - " near_earth_object_nan object_class_AMO object_class_APO ... \\\n", - "0 0 0 0 ... \n", - "12 0 0 0 ... \n", - "47 0 0 0 ... \n", - "381 0 0 0 ... \n", - "515 0 0 0 ... \n", - "... ... ... ... ... \n", - "9146 0 0 0 ... \n", - "9657 0 0 0 ... \n", - "9704 0 0 0 ... \n", - "9879 0 0 0 ... \n", - "9980 0 0 0 ... \n", - "\n", - " object_class_CEN object_class_IMB object_class_MBA object_class_MCA \\\n", - "0 0 0 1 0 \n", - "12 0 0 1 0 \n", - "47 0 0 1 0 \n", - "381 0 0 1 0 \n", - "515 0 0 1 0 \n", - "... ... ... ... ... \n", - "9146 0 0 1 0 \n", - "9657 0 0 1 0 \n", - "9704 0 0 1 0 \n", - "9879 0 0 1 0 \n", - "9980 0 0 1 0 \n", - "\n", - " object_class_OMB object_class_TJN object_class_nan hazardous_flag_N \\\n", - "0 0 0 0 1 \n", - "12 0 0 0 1 \n", - "47 0 0 0 1 \n", - "381 0 0 0 1 \n", - "515 0 0 0 1 \n", - "... ... ... ... ... \n", - "9146 0 0 0 1 \n", - "9657 0 0 0 1 \n", - "9704 0 0 0 1 \n", - "9879 0 0 0 1 \n", - "9980 0 0 0 1 \n", - "\n", - " hazardous_flag_Y hazardous_flag_nan \n", - "0 0 0 \n", - "12 0 0 \n", - "47 0 0 \n", - "381 0 0 \n", - "515 0 0 \n", - "... ... ... \n", - "9146 0 0 \n", - "9657 0 0 \n", - "9704 0 0 \n", - "9879 0 0 \n", - "9980 0 0 \n", - "\n", - "[9999 rows x 22 columns]" - ], "text/html": [ "\n", "
\n", @@ -3307,10 +2887,92 @@ "
\n", " \n", " " + ], + "text/plain": [ + " absolute_magnitude eccentricity inclination moid_ld \\\n", + "0 -5.657067 -0.867596 0.426645 0.540537 \n", + "12 -3.583402 -0.756931 1.364340 0.238610 \n", + "47 -3.400432 -0.912290 -0.211925 1.136060 \n", + "381 -2.363599 0.271412 -0.078826 0.535299 \n", + "515 -2.729540 1.469775 0.799915 -0.602881 \n", + "... ... ... ... ... \n", + "9146 0.563927 -0.508757 -0.327512 -0.637391 \n", + "9657 1.478779 0.487849 -0.637779 -0.648240 \n", + "9704 0.380957 -0.238383 0.443053 0.670490 \n", + "9879 1.295809 -0.442966 -0.698505 -0.494818 \n", + "9980 0.746898 -1.455992 -0.849144 0.592902 \n", + "\n", + " semi_major_axis_au_unit near_earth_object_N near_earth_object_Y \\\n", + "0 0.130649 1 0 \n", + "12 -0.187375 1 0 \n", + "47 0.691182 1 0 \n", + "381 0.712755 1 0 \n", + "515 -0.014654 1 0 \n", + "... ... ... ... \n", + "9146 -0.820638 1 0 \n", + "9657 -0.468778 1 0 \n", + "9704 0.587128 1 0 \n", + "9879 -0.662602 1 0 \n", + "9980 -0.022726 1 0 \n", + "\n", + " near_earth_object_nan object_class_AMO object_class_APO ... \\\n", + "0 0 0 0 ... \n", + "12 0 0 0 ... \n", + "47 0 0 0 ... \n", + "381 0 0 0 ... \n", + "515 0 0 0 ... \n", + "... ... ... ... ... \n", + "9146 0 0 0 ... \n", + "9657 0 0 0 ... \n", + "9704 0 0 0 ... \n", + "9879 0 0 0 ... \n", + "9980 0 0 0 ... \n", + "\n", + " object_class_CEN object_class_IMB object_class_MBA object_class_MCA \\\n", + "0 0 0 1 0 \n", + "12 0 0 1 0 \n", + "47 0 0 1 0 \n", + "381 0 0 1 0 \n", + "515 0 0 1 0 \n", + "... ... ... ... ... \n", + "9146 0 0 1 0 \n", + "9657 0 0 1 0 \n", + "9704 0 0 1 0 \n", + "9879 0 0 1 0 \n", + "9980 0 0 1 0 \n", + "\n", + " object_class_OMB object_class_TJN object_class_nan hazardous_flag_N \\\n", + "0 0 0 0 1 \n", + "12 0 0 0 1 \n", + "47 0 0 0 1 \n", + "381 0 0 0 1 \n", + "515 0 0 0 1 \n", + "... ... ... ... ... \n", + "9146 0 0 0 1 \n", + "9657 0 0 0 1 \n", + "9704 0 0 0 1 \n", + "9879 0 0 0 1 \n", + "9980 0 0 0 1 \n", + "\n", + " hazardous_flag_Y hazardous_flag_nan \n", + "0 0 0 \n", + "12 0 0 \n", + "47 0 0 \n", + "381 0 0 \n", + "515 0 0 \n", + "... ... ... \n", + "9146 0 0 \n", + "9657 0 0 \n", + "9704 0 0 \n", + "9879 0 0 \n", + "9980 0 0 \n", + "\n", + "[9999 rows x 22 columns]" ] }, + "execution_count": 36, "metadata": {}, - "execution_count": 36 + "output_type": "execute_result" } ], "source": [ @@ -3356,31 +3018,36 @@ }, { "cell_type": "code", - "source": [ - "PROJECT_ID = \"\"\n", - "REGION = \"us-central1\"\n", - "TEMP_DIR = \"gs:///tmp\"\n", - "OUTPUT_DIR = \"gs:///dataframe-result\"" - ], + "execution_count": null, "metadata": { "id": "dDBYbMEWbL4t" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "PROJECT_ID = \"\" # @param {type:'string'}\n", + "REGION = \"us-central1\"\n", + "TEMP_DIR = \"gs:///tmp\" # @param {type:'string'}\n", + "OUTPUT_DIR = \"gs:///dataframe-result\" # @param {type:'string'}" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "Qk1GaYoSc9-1" + }, "source": [ "These steps process the full dataset, `full.csv`, which contains approximately one million rows. To materialize the deferred DataFrame, these steps also write the results to a CSV file instead of using `ib.collect()`.\n", "\n", "To switch from an interactive runner to a distributed runner, update the pipeline options. The rest of the pipeline steps don't change." - ], - "metadata": { - "id": "Qk1GaYoSc9-1" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1XovR0gKbMlK" + }, + "outputs": [], "source": [ "# Specify the location of the source CSV file (the full dataset).\n", "source_csv_file = 'gs://apache-beam-samples/nasa_jpl_asteroid/full.csv'\n", @@ -3417,44 +3084,42 @@ "\n", "# Write the preprocessed dataset to a CSV file.\n", "beam_df_numericals.to_csv(os.path.join(OUTPUT_DIR, \"preprocessed_data.csv\"))" - ], - "metadata": { - "id": "1XovR0gKbMlK" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Submit and run the pipeline." - ], "metadata": { "id": "a789u4Yecs_g" - } + }, + "source": [ + "Submit and run the pipeline." + ] }, { "cell_type": "code", - "source": [ - "p.run().wait_until_finish()" - ], + "execution_count": null, "metadata": { "id": "pbUlC102bPaZ" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "p.run().wait_until_finish()" + ] }, { "cell_type": "markdown", - "source": [ - "Wait while the pipeline job runs." - ], "metadata": { "id": "dzdqmzKzTOng" - } + }, + "source": [ + "Wait while the pipeline job runs." + ] }, { "cell_type": "markdown", + "metadata": { + "id": "UOLr6YgOOSVQ" + }, "source": [ "## What's next \n", "\n", @@ -3464,13 +3129,13 @@ "[Structured data classification from scratch](https://keras.io/examples/structured_data/structured_data_classification_from_scratch/).\n", "\n", "To continue learning, find another dataset to use with the Apache Beam DataFrames API processing. Think carefully about which features to include in your model and how to represent them.\n" - ], - "metadata": { - "id": "UOLr6YgOOSVQ" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "nG9WXXVcMCe_" + }, "source": [ "## Resources\n", "\n", @@ -3479,10 +3144,7 @@ "* [10 minutes to Pandas](https://pandas.pydata.org/pandas-docs/stable/user_guide/10min.html) - A quickstart guide to the Pandas DataFrames.\n", "* [Pandas DataFrame API](https://pandas.pydata.org/pandas-docs/stable/reference/frame.html) - The API reference for the Pandas DataFrames.\n", "* [Data preparation and feature training in ML](https://developers.google.com/machine-learning/data-prep) - A guideline about data transformation for ML training." - ], - "metadata": { - "id": "nG9WXXVcMCe_" - } + ] } ], "metadata": { diff --git a/examples/notebooks/beam-ml/gemma_2_sentiment_and_summarization.ipynb b/examples/notebooks/beam-ml/gemma_2_sentiment_and_summarization.ipynb index 686c19da7f66..1b20270f327a 100644 --- a/examples/notebooks/beam-ml/gemma_2_sentiment_and_summarization.ipynb +++ b/examples/notebooks/beam-ml/gemma_2_sentiment_and_summarization.ipynb @@ -367,7 +367,7 @@ "# options.view_as(WorkerOptions).disk_size_gb=200\n", "# options.view_as(GoogleCloudOptions).dataflow_service_options=[\"worker_accelerator=type:nvidia-l4;count:1;install-nvidia-driver\"]\n", "\n", - "topic_reviews=\"\"" + "topic_reviews=\"\" # @param {type:'string'}" ] }, { diff --git a/examples/notebooks/beam-ml/nlp_tensorflow_streaming.ipynb b/examples/notebooks/beam-ml/nlp_tensorflow_streaming.ipynb index f9a263e39030..6f5048e7e8ee 100644 --- a/examples/notebooks/beam-ml/nlp_tensorflow_streaming.ipynb +++ b/examples/notebooks/beam-ml/nlp_tensorflow_streaming.ipynb @@ -496,23 +496,23 @@ }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Epoch 1/10\n", "25/25 [==============================] - ETA: 0s - loss: 0.5931 - accuracy: 0.7650" ] }, { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "WARNING:absl:Found untraced functions such as _update_step_xla, lstm_cell_7_layer_call_fn, lstm_cell_7_layer_call_and_return_conditional_losses, lstm_cell_8_layer_call_fn, lstm_cell_8_layer_call_and_return_conditional_losses while saving (showing 5 of 9). These functions will not be directly callable after loading.\n" ] }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r25/25 [==============================] - 60s 2s/step - loss: 0.5931 - accuracy: 0.7650 - val_loss: 0.3625 - val_accuracy: 0.8900\n", "Epoch 2/10\n", @@ -536,14 +536,14 @@ ] }, { - "output_type": "execute_result", "data": { "text/plain": [ "" ] }, + "execution_count": 57, "metadata": {}, - "execution_count": 57 + "output_type": "execute_result" } ], "source": [ @@ -604,14 +604,14 @@ }, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "" ] }, + "execution_count": 59, "metadata": {}, - "execution_count": 59 + "output_type": "execute_result" } ], "source": [ @@ -641,8 +641,8 @@ }, "outputs": [ { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "WARNING:absl:Found untraced functions such as _update_step_xla, lstm_cell_7_layer_call_fn, lstm_cell_7_layer_call_and_return_conditional_losses, lstm_cell_8_layer_call_fn, lstm_cell_8_layer_call_and_return_conditional_losses while saving (showing 5 of 9). These functions will not be directly callable after loading.\n" ] @@ -706,8 +706,10 @@ "source": [ "import os\n", "from google.cloud import pubsub_v1\n", - "PROJECT_ID = '' # Add your project ID here\n", - "TOPIC = '' # Add your topic name here\n", + "# Add your project ID here\n", + "PROJECT_ID = '' # @param {type:'string'}\n", + "# Add your topic name here\n", + "TOPIC = '' # @param {type:'string'}\n", "publisher = pubsub_v1.PublisherClient()\n", "topic_name = 'projects/{project_id}/topics/{topic}'.format(\n", " project_id = PROJECT_ID,\n", @@ -739,8 +741,8 @@ }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Can’t wait to watch you guys grow . Harmonies are on point and the oversized early 90’s blazers are a great touch.\n", "Amazing performance! Such an inspiring group ❤\n", @@ -908,8 +910,8 @@ }, "outputs": [], "source": [ - "# path to the topic\n", - "TOPIC_PATH = '' # Add the path to your topic here" + "# Add the path to your topic here\n", + "TOPIC_PATH = '' # @param {type:'string'}" ] }, { @@ -920,18 +922,18 @@ }, "outputs": [], "source": [ - "# path to the subscription\n", - "SUBS_PATH = '' # Add the path to your subscription here" + "# Add the path to your subscription here\n", + "SUBS_PATH = '' # @param {type:'string'}" ] }, { "cell_type": "markdown", - "source": [ - "Importing InteractiveRunner" - ], "metadata": { "id": "UliBhojEfxhq" - } + }, + "source": [ + "Importing InteractiveRunner" + ] }, { "cell_type": "code", @@ -986,8 +988,8 @@ }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Can’t wait to watch you guys grow . Harmonies are on point and the oversized early 90’s blazers are a great touch.\n", "Amazing performance! Such an inspiring group ❤\n", @@ -1048,7 +1050,6 @@ }, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "[[[0.852806806564331, 0.14719319343566895], 'positive'],\n", @@ -1059,8 +1060,9 @@ " [[0.8648154735565186, 0.13518451154232025], 'positive']]" ] }, + "execution_count": 38, "metadata": {}, - "execution_count": 38 + "output_type": "execute_result" } ], "source": [ diff --git a/examples/notebooks/beam-ml/run_inference_pytorch.ipynb b/examples/notebooks/beam-ml/run_inference_pytorch.ipynb index eaf46be16bbd..93dd12dd20ab 100644 --- a/examples/notebooks/beam-ml/run_inference_pytorch.ipynb +++ b/examples/notebooks/beam-ml/run_inference_pytorch.ipynb @@ -1,22 +1,13 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "collapsed_sections": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "code", + "execution_count": 3, + "metadata": { + "cellView": "form", + "id": "C1rAsD2L-hSO" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -36,13 +27,7 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "cellView": "form", - "id": "C1rAsD2L-hSO" - }, - "execution_count": 3, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -95,23 +80,23 @@ }, { "cell_type": "code", - "source": [ - "!pip install apache_beam[gcp,dataframe] --quiet" - ], + "execution_count": null, "metadata": { "id": "loxD-rOVchRn" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "!pip install apache_beam[gcp,dataframe] --quiet" + ] }, { "cell_type": "code", "execution_count": 39, "metadata": { - "id": "7f841596-f217-46d2-b64e-1952db4de4cb", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "7f841596-f217-46d2-b64e-1952db4de4cb", "outputId": "09e0026a-cf8e-455c-9580-bfaef44683ce" }, "outputs": [], @@ -151,15 +136,15 @@ }, { "cell_type": "code", - "source": [ - "from google.colab import auth\n", - "auth.authenticate_user()" - ], + "execution_count": 41, "metadata": { "id": "V0E35R5Ka2cE" }, - "execution_count": 41, - "outputs": [] + "outputs": [], + "source": [ + "from google.colab import auth\n", + "auth.authenticate_user()" + ] }, { "cell_type": "code", @@ -170,8 +155,8 @@ "outputs": [], "source": [ "# Constants\n", - "project = \"\"\n", - "bucket = \"\"\n", + "project = \"\" # @param {type:'string'}\n", + "bucket = \"\" # @param {type:'string'}\n", "\n", "# To avoid warnings, set the project.\n", "os.environ['GOOGLE_CLOUD_PROJECT'] = project\n", @@ -183,8 +168,8 @@ { "cell_type": "markdown", "metadata": { - "tags": [], - "id": "b2b7cedc-79f5-4599-8178-e5da35dba032" + "id": "b2b7cedc-79f5-4599-8178-e5da35dba032", + "tags": [] }, "source": [ "## Create data and PyTorch models for the RunInference transform\n", @@ -294,16 +279,16 @@ "cell_type": "code", "execution_count": 46, "metadata": { - "id": "882bbada-4f6d-4370-a047-c5961e564ee8", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "882bbada-4f6d-4370-a047-c5961e564ee8", "outputId": "ab7242a9-76eb-4760-d74e-c725261e2a34" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "True\n" ] @@ -384,16 +369,16 @@ "cell_type": "code", "execution_count": 49, "metadata": { - "id": "42b2ca0f-5d44-4d15-a313-f3d56ae7f675", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "42b2ca0f-5d44-4d15-a313-f3d56ae7f675", "outputId": "9cb2f268-a500-4ad5-a075-856c87b8e3be" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "True\n" ] @@ -430,16 +415,16 @@ "cell_type": "code", "execution_count": 50, "metadata": { - "id": "e488a821-3b70-4284-96f3-ddee4dcb9d71", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "e488a821-3b70-4284-96f3-ddee4dcb9d71", "outputId": "add9af31-1cc6-496f-a6e4-3fb185c0de25" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "PredictionResult(example=tensor([20.]), inference=tensor([102.0095], grad_fn=))\n", "PredictionResult(example=tensor([40.]), inference=tensor([201.2056], grad_fn=))\n", @@ -483,16 +468,16 @@ "cell_type": "code", "execution_count": 51, "metadata": { - "id": "96f38a5a-4db0-4c39-8ce7-80d9f9911b48", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "96f38a5a-4db0-4c39-8ce7-80d9f9911b48", "outputId": "b1d689a2-9336-40b2-a984-538bec888cc9" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "input is 20.0 output is 102.00947570800781\n", "input is 40.0 output is 201.20559692382812\n", @@ -576,7 +561,7 @@ " yield (f\"key: {key}, input: {input_value.item()} output: {output_value.item()}\" )" ] }, - { + { "cell_type": "markdown", "metadata": { "id": "f22da313-5bf8-4334-865b-bbfafc374e63" @@ -592,7 +577,7 @@ "id": "c9b0fb49-d605-4f26-931a-57f42b0ad253" }, "source": [ - "#### Use BigQuery as the source", + "#### Use BigQuery as the source\n", "Follow these steps to use BigQuery as your source." ] }, @@ -627,47 +612,47 @@ }, { "cell_type": "code", - "source": [ - "!gcloud config set project $project" - ], + "execution_count": 54, "metadata": { - "id": "7mgnryX-Zlfs", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "7mgnryX-Zlfs", "outputId": "6e608e98-8369-45aa-c983-e62296202c52" }, - "execution_count": 54, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Updated property [core/project].\n" ] } + ], + "source": [ + "!gcloud config set project $project" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { - "id": "a6a984cd-2e92-4c44-821b-9bf1dd52fb7d", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "a6a984cd-2e92-4c44-821b-9bf1dd52fb7d", "outputId": "a50ab0fd-4f4e-4493-b506-41d3f7f08966" }, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "" ] }, + "execution_count": 55, "metadata": {}, - "execution_count": 55 + "output_type": "execute_result" } ], "source": [ @@ -715,16 +700,16 @@ "cell_type": "code", "execution_count": 56, "metadata": { - "id": "34331897-23f5-4850-8974-67e522e956dc", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "34331897-23f5-4850-8974-67e522e956dc", "outputId": "9d2b0ba5-97a2-46bf-c9d3-e023afbd3122" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "key: third_question, input: 1000.0 output: 4962.61962890625\n", "key: second_question, input: 108.0 output: 538.472412109375\n", @@ -761,7 +746,7 @@ "id": "53ee7f24-5625-475a-b8cc-9c031591f304" }, "source": [ - "#### Use a CSV file as the source", + "#### Use a CSV file as the source\n", "Follow these steps to use a CSV file as your source." ] }, @@ -776,6 +761,11 @@ }, { "cell_type": "code", + "execution_count": 62, + "metadata": { + "id": "exAZjP7cYAFv" + }, + "outputs": [], "source": [ "# creates a CSV file with the values.\n", "csv_values = [(\"first_question\", 105.00),\n", @@ -791,27 +781,22 @@ " writer.writerow(row)\n", "\n", "assert os.path.exists(input_csv_file) == True" - ], - "metadata": { - "id": "exAZjP7cYAFv" - }, - "execution_count": 62, - "outputs": [] + ] }, { "cell_type": "code", "execution_count": 66, "metadata": { - "id": "9a054c2d-4d84-4b37-b067-1dda5347e776", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "9a054c2d-4d84-4b37-b067-1dda5347e776", "outputId": "2f2ea8b7-b425-48ae-e857-fe214c7eced2" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "key: first_question, input: 105.0 output: 523.5929565429688\n", "key: second_question, input: 108.0 output: 538.472412109375\n", @@ -890,16 +875,16 @@ "cell_type": "code", "execution_count": 68, "metadata": { - "id": "629d070e-9902-42c9-a1e7-56c3d1864f13", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "629d070e-9902-42c9-a1e7-56c3d1864f13", "outputId": "0b4d7f3c-4696-422f-b031-ee5a03e90e03" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "key: third_question * 10, input: 1000.0 output: 9889.59765625\n", "key: second_question * 10, input: 108.0 output: 1075.4891357421875\n", @@ -966,16 +951,16 @@ "cell_type": "code", "execution_count": 69, "metadata": { - "id": "8db9d649-5549-4b58-a9ad-7b8592c2bcbf", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "8db9d649-5549-4b58-a9ad-7b8592c2bcbf", "outputId": "328ba32b-40d4-445b-8b4e-5568258b8a26" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "key: original input is `third_question tensor([1000.])`, input: 4962.61962890625 output: 49045.37890625\n", "key: original input is `second_question tensor([108.])`, input: 538.472412109375 output: 5329.11083984375\n", @@ -1015,5 +1000,20 @@ " inference_result | beam.Map(print)" ] } - ] + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/examples/notebooks/beam-ml/run_inference_sklearn.ipynb b/examples/notebooks/beam-ml/run_inference_sklearn.ipynb index cf896a18981a..1b76f76df292 100644 --- a/examples/notebooks/beam-ml/run_inference_sklearn.ipynb +++ b/examples/notebooks/beam-ml/run_inference_sklearn.ipynb @@ -1,22 +1,13 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "collapsed_sections": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "C1rAsD2L-hSO" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -36,13 +27,7 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "cellView": "form", - "id": "C1rAsD2L-hSO" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -87,24 +72,20 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "zzwnMzzgdyPB" + }, "source": [ "## Before you begin\n", "Complete the following setup steps:\n", "1. Install dependencies for Apache Beam.\n", "1. Authenticate with Google Cloud.\n", "1. Specify your project and bucket. You use the project and bucket to save and load models." - ], - "metadata": { - "id": "zzwnMzzgdyPB" - } + ] }, { "cell_type": "code", - "source": [ - "!pip install google-api-core --quiet\n", - "!pip install google-cloud-pubsub google-cloud-bigquery-storage --quiet\n", - "!pip install apache-beam[gcp,dataframe] --quiet" - ], + "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -112,8 +93,12 @@ "id": "6vlKcT-Wev20", "outputId": "336e8afc-6716-41dd-a438-500353189c62" }, - "execution_count": 1, - "outputs": [] + "outputs": [], + "source": [ + "!pip install google-api-core --quiet\n", + "!pip install google-cloud-pubsub google-cloud-bigquery-storage --quiet\n", + "!pip install apache-beam[gcp,dataframe] --quiet" + ] }, { "cell_type": "markdown", @@ -128,15 +113,15 @@ }, { "cell_type": "code", - "source": [ - "from google.colab import auth\n", - "auth.authenticate_user()" - ], + "execution_count": 2, "metadata": { "id": "V0E35R5Ka2cE" }, - "execution_count": 2, - "outputs": [] + "outputs": [], + "source": [ + "from google.colab import auth\n", + "auth.authenticate_user()" + ] }, { "cell_type": "code", @@ -174,8 +159,8 @@ "import os\n", "\n", "# Constants\n", - "project = \"\"\n", - "bucket = \"\" \n", + "project = \"\" # @param {type:'string'}\n", + "bucket = \"\" # @param {type:'string'}\n", "\n", "# To avoid warnings, set the project.\n", "os.environ['GOOGLE_CLOUD_PROJECT'] = project\n" @@ -240,20 +225,18 @@ }, { "cell_type": "code", - "source": [ - "%pip install --upgrade google-cloud-bigquery --quiet" - ], + "execution_count": 9, "metadata": { "id": "AEGaqpMVqgRP" }, - "execution_count": 9, - "outputs": [] + "outputs": [], + "source": [ + "%pip install --upgrade google-cloud-bigquery --quiet" + ] }, { "cell_type": "code", - "source": [ - "!gcloud config set project $project" - ], + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -261,19 +244,41 @@ "id": "xq5AKtRrqlUx", "outputId": "fba8fb42-4958-451a-8aaa-9a838052a2f8" }, - "execution_count": 10, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Updated property [core/project].\n" ] } + ], + "source": [ + "!gcloud config set project $project" ] }, { "cell_type": "code", + "execution_count": 22, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "QCIjN__rpoVF", + "outputId": "0ded224f-2272-482e-80f5-bb2d21b6f5d8" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Populated BigQuery table\n", "\n", @@ -306,42 +311,22 @@ "\n", "create_job = client.query(query)\n", "create_job.result()" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "QCIjN__rpoVF", - "outputId": "0ded224f-2272-482e-80f5-bb2d21b6f5d8" - }, - "execution_count": 22, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ] - }, - "metadata": {}, - "execution_count": 22 - } ] }, { "cell_type": "code", "execution_count": 23, "metadata": { - "id": "50a648a3-794a-4286-ab2b-fc0458db04ca", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "50a648a3-794a-4286-ab2b-fc0458db04ca", "outputId": "8eab34b4-dcc7-4df1-ec0e-8c86a34d31c6" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "PredictionResult(example=[1000.0], inference=array([5000.]))\n", "PredictionResult(example=[1013.0], inference=array([5065.]))\n", @@ -388,16 +373,16 @@ "cell_type": "code", "execution_count": 25, "metadata": { - "id": "c212916d-b517-4589-ad15-a3a1df926fb3", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "c212916d-b517-4589-ad15-a3a1df926fb3", "outputId": "61db2d76-4dfa-4b38-cf9a-645790b4c5aa" }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "('third_example', PredictionResult(example=[1000.0], inference=array([5000.])))\n", "('fourth_example', PredictionResult(example=[1013.0], inference=array([5065.])))\n", @@ -424,17 +409,41 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "JQ4zvlwsRK1W" + }, "source": [ "## Run multiple models\n", "\n", "This code creates a pipeline that takes two RunInference transforms with different models and then combines the output." - ], - "metadata": { - "id": "JQ4zvlwsRK1W" - } + ] }, { "cell_type": "code", + "execution_count": 86, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0qMlX6SeR68D", + "outputId": "5e4a0852-3761-47da-aa08-0386fd524a78" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "key = third_example * 10, example = 1000.0 -> predictions 10000.0\n", + "key = fourth_example * 10, example = 1013.0 -> predictions 10130.0\n", + "key = second_example * 10, example = 108.0 -> predictions 1080.0\n", + "key = first_example * 10, example = 105.0 -> predictions 1050.0\n", + "key = third_example * 5, example = 1000.0 -> predictions 5000.0\n", + "key = fourth_example * 5, example = 1013.0 -> predictions 5065.0\n", + "key = second_example * 5, example = 108.0 -> predictions 540.0\n", + "key = first_example * 5, example = 105.0 -> predictions 525.0\n" + ] + } + ], "source": [ "from typing import Tuple\n", "\n", @@ -464,31 +473,22 @@ " _ = ((five_times, ten_times) | \"Flattened\" >> beam.Flatten()\n", " | \"format output\" >> beam.Map(format_output)\n", " | \"Print\" >> beam.Map(print))\n" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "0qMlX6SeR68D", - "outputId": "5e4a0852-3761-47da-aa08-0386fd524a78" - }, - "execution_count": 86, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "key = third_example * 10, example = 1000.0 -> predictions 10000.0\n", - "key = fourth_example * 10, example = 1013.0 -> predictions 10130.0\n", - "key = second_example * 10, example = 108.0 -> predictions 1080.0\n", - "key = first_example * 10, example = 105.0 -> predictions 1050.0\n", - "key = third_example * 5, example = 1000.0 -> predictions 5000.0\n", - "key = fourth_example * 5, example = 1013.0 -> predictions 5065.0\n", - "key = second_example * 5, example = 108.0 -> predictions 540.0\n", - "key = first_example * 5, example = 105.0 -> predictions 525.0\n" - ] - } ] } - ] + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/examples/notebooks/beam-ml/run_inference_tensorflow.ipynb b/examples/notebooks/beam-ml/run_inference_tensorflow.ipynb index ad5bb671cce2..c15e9b21ecf9 100644 --- a/examples/notebooks/beam-ml/run_inference_tensorflow.ipynb +++ b/examples/notebooks/beam-ml/run_inference_tensorflow.ipynb @@ -168,8 +168,8 @@ "from apache_beam.ml.inference.tensorflow_inference import TFModelHandlerTensor\n", "from apache_beam.options.pipeline_options import PipelineOptions\n", "\n", - "project = \"PROJECT_ID\"\n", - "bucket = \"BUCKET_NAME\"\n", + "project = \"PROJECT_ID\" # @param {type:'string'}\n", + "bucket = \"BUCKET_NAME\" # @param {type:'string'}\n", "\n", "save_model_dir_multiply = f'gs://{bucket}/tf-inference/model/multiply_five/v1/'\n", "save_weights_dir_multiply = f'gs://{bucket}/tf-inference/weights/multiply_five/v1/'\n" diff --git a/examples/notebooks/beam-ml/run_inference_tensorflow_with_tfx.ipynb b/examples/notebooks/beam-ml/run_inference_tensorflow_with_tfx.ipynb index 9a9c6f5d6e92..2c2f6460651b 100644 --- a/examples/notebooks/beam-ml/run_inference_tensorflow_with_tfx.ipynb +++ b/examples/notebooks/beam-ml/run_inference_tensorflow_with_tfx.ipynb @@ -1,32 +1,13 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "collapsed_sections": [ - "X80jy3FqHjK4", - "40qtP6zJuMXm", - "YzvZWEv-1oiK", - "rIwD_qEpX7Gu", - "O_a0-4Gb19cy", - "G-sAu3cf31f3", - "r4dpR6dQ4JwX", - "P2UMmbNW4YQV" - ] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - }, - "accelerator": "GPU" - }, "cells": [ { "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "fFjof1NgAJwu" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -46,13 +27,7 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "cellView": "form", - "id": "fFjof1NgAJwu" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -74,6 +49,9 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "HrCtxslBGK8Z" + }, "source": [ "This notebook demonstrates how to use the Apache Beam [RunInference](https://beam.apache.org/releases/pydoc/current/apache_beam.ml.inference.base.html#apache_beam.ml.inference.base.RunInference) transform with TensorFlow and [TFX Basic Shared Libraries](https://github.com/tensorflow/tfx-bsl) (`tfx-bsl`).\n", "\n", @@ -89,69 +67,69 @@ "- Use the `tfx-bsl` model handler with the example data, and get a prediction inside an Apache Beam pipeline.\n", "\n", "For more information about using RunInference, see [Get started with AI/ML pipelines](https://beam.apache.org/documentation/ml/overview/) in the Apache Beam documentation." - ], - "metadata": { - "id": "HrCtxslBGK8Z" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "HrCtxslBGK8A" + }, "source": [ "## Before you begin\n", "Set up your environment and download dependencies." - ], - "metadata": { - "id": "HrCtxslBGK8A" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "HrCtxslBGK8A" + }, "source": [ "### Import `tfx-bsl`\n", "First, import `tfx-bsl`.\n", "Creating a model handler is supported in `tfx-bsl` versions 1.10 and later." - ], - "metadata": { - "id": "HrCtxslBGK8A" - } + ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "jBakpNZnAhqk" }, + "outputs": [], "source": [ "!pip install tfx_bsl==1.10.0 --quiet\n", "!pip install protobuf --quiet\n", "!pip install apache_beam --quiet" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "X80jy3FqHjK4" + }, "source": [ "### Authenticate with Google Cloud\n", "This notebook relies on saving your model to Google Cloud. To use your Google Cloud account, authenticate this notebook." - ], - "metadata": { - "id": "X80jy3FqHjK4" - } + ] }, { "cell_type": "code", + "execution_count": 2, "metadata": { "id": "Kz9sccyGBqz3" }, + "outputs": [], "source": [ "from google.colab import auth\n", "auth.authenticate_user()" - ], - "execution_count": 2, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "40qtP6zJuMXm" + }, "source": [ "### Import dependencies and set up your bucket\n", "Use the following code to import dependencies and to set up your Google Cloud Storage bucket.\n", @@ -159,16 +137,15 @@ "Replace `PROJECT_ID` and `BUCKET_NAME` with the ID of your project and the name of your bucket.\n", "\n", "**Important**: If an error occurs, restart your runtime." - ], - "metadata": { - "id": "40qtP6zJuMXm" - } + ] }, { "cell_type": "code", + "execution_count": 12, "metadata": { "id": "eEle839_Akqx" }, + "outputs": [], "source": [ "import argparse\n", "\n", @@ -190,24 +167,22 @@ "\n", "from apache_beam.options.pipeline_options import PipelineOptions\n", "\n", - "project = \"PROJECT_ID\"\n", - "bucket = \"BUCKET_NAME\"\n", + "project = \"PROJECT_ID\" # @param {type:'string'}\n", + "bucket = \"BUCKET_NAME\" # @param {type:'string'}\n", "\n", "save_model_dir_multiply = f'gs://{bucket}/tfx-inference/model/multiply_five/v1/'\n" - ], - "execution_count": 12, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "YzvZWEv-1oiK" + }, "source": [ "## Create and test a simple model\n", "\n", "This section creates and tests a model that predicts the 5 times multiplication table." - ], - "metadata": { - "id": "YzvZWEv-1oiK" - } + ] }, { "cell_type": "markdown", @@ -221,6 +196,7 @@ }, { "cell_type": "code", + "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -228,26 +204,10 @@ "id": "SH7iq3zeBBJ-", "outputId": "c5adb7ec-285b-401e-f9be-1e9b83c6d0ba" }, - "source": [ - "# Create training data that represents the 5 times multiplication table for the numbers 0 to 99.\n", - "# x is the data and y is the labels.\n", - "x = numpy.arange(0, 100) # Examples\n", - "y = x * 5 # Labels\n", - "\n", - "# Build a simple linear regression model.\n", - "# Note that the model has a shape of (1) for its input layer and expects a single int64 value.\n", - "input_layer = keras.layers.Input(shape=(1), dtype=tf.float32, name='x')\n", - "output_layer= keras.layers.Dense(1)(input_layer)\n", - "\n", - "model = keras.Model(input_layer, output_layer)\n", - "model.compile(optimizer=tf.optimizers.Adam(), loss='mean_absolute_error')\n", - "model.summary()" - ], - "execution_count": 4, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Model: \"model\"\n", "_________________________________________________________________\n", @@ -264,21 +224,37 @@ "_________________________________________________________________\n" ] } + ], + "source": [ + "# Create training data that represents the 5 times multiplication table for the numbers 0 to 99.\n", + "# x is the data and y is the labels.\n", + "x = numpy.arange(0, 100) # Examples\n", + "y = x * 5 # Labels\n", + "\n", + "# Build a simple linear regression model.\n", + "# Note that the model has a shape of (1) for its input layer and expects a single int64 value.\n", + "input_layer = keras.layers.Input(shape=(1), dtype=tf.float32, name='x')\n", + "output_layer= keras.layers.Dense(1)(input_layer)\n", + "\n", + "model = keras.Model(input_layer, output_layer)\n", + "model.compile(optimizer=tf.optimizers.Adam(), loss='mean_absolute_error')\n", + "model.summary()" ] }, { "cell_type": "markdown", + "metadata": { + "id": "O_a0-4Gb19cy" + }, "source": [ "### Test the model\n", "\n", "This step tests the model that you created." - ], - "metadata": { - "id": "O_a0-4Gb19cy" - } + ] }, { "cell_type": "code", + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -286,20 +262,10 @@ "id": "5XkIYXhJBFmS", "outputId": "e3bb5079-5cb8-4fe4-eb8d-d3d13d5f9f0c" }, - "source": [ - "model.fit(x, y, epochs=500, verbose=0)\n", - "test_examples =[20, 40, 60, 90]\n", - "value_to_predict = numpy.array(test_examples, dtype=numpy.float32)\n", - "predictions = model.predict(value_to_predict)\n", - "\n", - "print('Test Examples ' + str(test_examples))\n", - "print('Predictions ' + str(predictions))" - ], - "execution_count": 6, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "1/1 [==============================] - 0s 94ms/step\n", "Test Examples [20, 40, 60, 90]\n", @@ -309,10 +275,22 @@ " [34.41496 ]]\n" ] } + ], + "source": [ + "model.fit(x, y, epochs=500, verbose=0)\n", + "test_examples =[20, 40, 60, 90]\n", + "value_to_predict = numpy.array(test_examples, dtype=numpy.float32)\n", + "predictions = model.predict(value_to_predict)\n", + "\n", + "print('Test Examples ' + str(test_examples))\n", + "print('Predictions ' + str(predictions))" ] }, { "cell_type": "markdown", + "metadata": { + "id": "dEmleqiH3t71" + }, "source": [ "## RunInference with Tensorflow using `tfx-bsl`\n", "In versions 1.10.0 and later of `tfx-bsl`, you can\n", @@ -321,16 +299,15 @@ "### Populate the data in a TensorFlow proto\n", "\n", "Tensorflow data uses protos. If you are loading from a file, helpers exist for this step. Because this example uses generated data, this code populates a proto." - ], - "metadata": { - "id": "dEmleqiH3t71" - } + ] }, { "cell_type": "code", + "execution_count": 7, "metadata": { "id": "XvKc9kQilPjx" }, + "outputs": [], "source": [ "# This example shows a proto that converts the samples and labels into\n", "# tensors usable by TensorFlow.\n", @@ -371,23 +348,22 @@ " for i in value_to_predict:\n", " example = ExampleProcessor().create_example(feature=i)\n", " writer.write(example.SerializeToString())" - ], - "execution_count": 7, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "G-sAu3cf31f3" + }, "source": [ "### Fit the model\n", "\n", "This step builds a model. Because RunInference requires pretrained models, this segment builds a usable model." - ], - "metadata": { - "id": "G-sAu3cf31f3" - } + ] }, { "cell_type": "code", + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -395,6 +371,18 @@ "id": "AnbrxXPKeAOQ", "outputId": "42439aac-3a10-4e86-829f-44332aad6173" }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "RAW_DATA_TRAIN_SPEC = {\n", "'x': tf.io.FixedLenFeature([], tf.float32),\n", @@ -408,37 +396,26 @@ "dataset = dataset.repeat()\n", "\n", "model.fit(dataset, epochs=5000, steps_per_epoch=1, verbose=0)" - ], - "execution_count": 8, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ] - }, - "metadata": {}, - "execution_count": 8 - } ] }, { "cell_type": "markdown", + "metadata": { + "id": "r4dpR6dQ4JwX" + }, "source": [ "### Save the model\n", "\n", "This step shows how to save your model." - ], - "metadata": { - "id": "r4dpR6dQ4JwX" - } + ] }, { "cell_type": "code", + "execution_count": 9, "metadata": { "id": "fYvrIYO3qiJx" }, + "outputs": [], "source": [ "RAW_DATA_PREDICT_SPEC = {\n", "'x': tf.io.FixedLenFeature([], tf.float32),\n", @@ -461,25 +438,24 @@ "# programs that consume SavedModels, such as serving APIs.\n", "# See https://www.tensorflow.org/api_docs/python/tf/saved_model/save\n", "tf.keras.models.save_model(model, save_model_dir_multiply, signatures=signature)" - ], - "execution_count": 9, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "P2UMmbNW4YQV" + }, "source": [ "## Run the pipeline\n", "Use the following code to run the pipeline.\n", "\n", "* `FormatOutput` demonstrates how to extract values from the output protos.\n", "* `CreateModelHandler` demonstrates the model handler that needs to be passed into the Apache Beam RunInference API." - ], - "metadata": { - "id": "P2UMmbNW4YQV" - } + ] }, { "cell_type": "code", + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -488,72 +464,24 @@ "id": "PzjmXM_KvqHY", "outputId": "0aa60bef-52a0-4ce2-d228-3fac977d59e0" }, - "source": [ - "from tfx_bsl.public.beam.run_inference import CreateModelHandler\n", - "\n", - "class FormatOutput(beam.DoFn):\n", - " def process(self, element: prediction_log_pb2.PredictionLog):\n", - " predict_log = element.predict_log\n", - " input_value = tf.train.Example.FromString(predict_log.request.inputs['examples'].string_val[0])\n", - " input_float_value = input_value.features.feature['x'].float_list.value[0]\n", - " output_value = predict_log.response.outputs\n", - " output_float_value = output_value['output_0'].float_val[0]\n", - " yield (f\"example is {input_float_value:.2f} prediction is {output_float_value:.2f}\")\n", - "\n", - "tfexample_beam_record = tfx_bsl.public.tfxio.TFExampleRecord(file_pattern=predict_values_five_times_table)\n", - "saved_model_spec = model_spec_pb2.SavedModelSpec(model_path=save_model_dir_multiply)\n", - "inference_spec_type = model_spec_pb2.InferenceSpecType(saved_model_spec=saved_model_spec)\n", - "model_handler = CreateModelHandler(inference_spec_type)\n", - "with beam.Pipeline() as p:\n", - " _ = (p | tfexample_beam_record.RawRecordBeamSource()\n", - " | RunInference(model_handler)\n", - " | beam.ParDo(FormatOutput())\n", - " | beam.Map(print)\n", - " )" - ], - "execution_count": 10, "outputs": [ { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "WARNING:apache_beam.runners.interactive.interactive_environment:Dependencies required for Interactive Beam PCollection visualization are not available, please use: `pip install apache-beam[interactive]` to install necessary dependencies to enable all data visualization features.\n" ] }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stderr", + "output_type": "stream", "text": [ "WARNING:tensorflow:From /usr/local/lib/python3.9/dist-packages/tfx_bsl/beam/run_inference.py:615: load (from tensorflow.python.saved_model.loader_impl) is deprecated and will be removed in a future version.\n", "Instructions for updating:\n", @@ -562,8 +490,8 @@ ] }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "example is 20.00 prediction is 104.36\n", "example is 40.00 prediction is 202.62\n", @@ -571,10 +499,36 @@ "example is 90.00 prediction is 448.26\n" ] } + ], + "source": [ + "from tfx_bsl.public.beam.run_inference import CreateModelHandler\n", + "\n", + "class FormatOutput(beam.DoFn):\n", + " def process(self, element: prediction_log_pb2.PredictionLog):\n", + " predict_log = element.predict_log\n", + " input_value = tf.train.Example.FromString(predict_log.request.inputs['examples'].string_val[0])\n", + " input_float_value = input_value.features.feature['x'].float_list.value[0]\n", + " output_value = predict_log.response.outputs\n", + " output_float_value = output_value['output_0'].float_val[0]\n", + " yield (f\"example is {input_float_value:.2f} prediction is {output_float_value:.2f}\")\n", + "\n", + "tfexample_beam_record = tfx_bsl.public.tfxio.TFExampleRecord(file_pattern=predict_values_five_times_table)\n", + "saved_model_spec = model_spec_pb2.SavedModelSpec(model_path=save_model_dir_multiply)\n", + "inference_spec_type = model_spec_pb2.InferenceSpecType(saved_model_spec=saved_model_spec)\n", + "model_handler = CreateModelHandler(inference_spec_type)\n", + "with beam.Pipeline() as p:\n", + " _ = (p | tfexample_beam_record.RawRecordBeamSource()\n", + " | RunInference(model_handler)\n", + " | beam.ParDo(FormatOutput())\n", + " | beam.Map(print)\n", + " )" ] }, { "cell_type": "markdown", + "metadata": { + "id": "IXikjkGdHm9n" + }, "source": [ "## Use `KeyedModelHandler` with `tfx-bsl`\n", "\n", @@ -584,13 +538,30 @@ "* If you don't know whether keys are associated with your examples, use `beam.MaybeKeyedModelHandler`.\n", "\n", "In addition to demonstrating how to use a keyed model handler, this step demonstrates how to use `tfx-bsl` examples." - ], - "metadata": { - "id": "IXikjkGdHm9n" - } + ] }, { "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "KPtE3fmdJQry", + "outputId": "c33558fc-fb12-4c20-b828-b5520721f279" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "key 5.0 : example is 5.00 prediction is 30.67\n", + "key 50.0 : example is 50.00 prediction is 251.75\n", + "key 40.0 : example is 40.00 prediction is 202.62\n", + "key 100.0 : example is 100.00 prediction is 497.38\n" + ] + } + ], "source": [ "from apache_beam.ml.inference.base import KeyedModelHandler\n", "from google.protobuf import text_format\n", @@ -632,27 +603,32 @@ " | beam.ParDo(FormatOutputKeyed())\n", " | beam.Map(print)\n", " )" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "KPtE3fmdJQry", - "outputId": "c33558fc-fb12-4c20-b828-b5520721f279" - }, - "execution_count": 11, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "key 5.0 : example is 5.00 prediction is 30.67\n", - "key 50.0 : example is 50.00 prediction is 251.75\n", - "key 40.0 : example is 40.00 prediction is 202.62\n", - "key 100.0 : example is 100.00 prediction is 497.38\n" - ] - } ] } - ] -} \ No newline at end of file + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [ + "X80jy3FqHjK4", + "40qtP6zJuMXm", + "YzvZWEv-1oiK", + "rIwD_qEpX7Gu", + "O_a0-4Gb19cy", + "G-sAu3cf31f3", + "r4dpR6dQ4JwX", + "P2UMmbNW4YQV" + ], + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/notebooks/beam-ml/run_inference_vertex_ai.ipynb b/examples/notebooks/beam-ml/run_inference_vertex_ai.ipynb index 46bfc0f2fc00..2ab45e0491a7 100644 --- a/examples/notebooks/beam-ml/run_inference_vertex_ai.ipynb +++ b/examples/notebooks/beam-ml/run_inference_vertex_ai.ipynb @@ -151,6 +151,17 @@ "Replace `PROJECT_ID`, `LOCATION_NAME`, and `ENDPOINT_ID` with the ID of your project, the GCP region where your model is deployed, and the ID of your Vertex AI endpoint." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_ID = \"\" # @param {type:'string'}\n", + "LOCATION_NAME = \"\" # @param {type:'string'}\n", + "ENDPOINT_ID = \"> beam.Create([IMG_URL])\n", " | beam.Map(lambda img_name: (img_name, download_image(img_name)))\n", diff --git a/examples/notebooks/beam-ml/run_inference_vllm.ipynb b/examples/notebooks/beam-ml/run_inference_vllm.ipynb index 13b4a915c087..c4803eccebff 100644 --- a/examples/notebooks/beam-ml/run_inference_vllm.ipynb +++ b/examples/notebooks/beam-ml/run_inference_vllm.ipynb @@ -1,24 +1,12 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "gpuType": "T4", - "toc_visible": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - }, - "accelerator": "GPU" - }, "cells": [ { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "OsFaZscKSPvo" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -38,15 +26,13 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "id": "OsFaZscKSPvo" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "NrHRIznKp3nS" + }, "source": [ "# Run ML inference by using vLLM on GPUs\n", "\n", @@ -58,13 +44,13 @@ " View source on GitHub\n", " \n", "" - ], - "metadata": { - "id": "NrHRIznKp3nS" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "H0ZFs9rDvtJm" + }, "source": [ "[vLLM](https://github.com/vllm-project/vllm) is a fast and user-friendly library for LLM inference and serving. vLLM optimizes LLM inference with mechanisms like PagedAttention for memory management and continuous batching for increasing throughput. For popular models, vLLM has been shown to increase throughput by a multiple of 2 to 4. With Apache Beam, you can serve models with vLLM and scale that serving with just a few lines of code.\n", "\n", @@ -75,13 +61,13 @@ "* remotely with the Dataflow runner\n", "\n", "It also shows how to swap in a different model without modifying your pipeline structure by changing the configuration." - ], - "metadata": { - "id": "H0ZFs9rDvtJm" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "6x41tnbTvQM1" + }, "source": [ "## Requirements\n", "\n", @@ -91,21 +77,18 @@ "\n", "- a computer with Docker installed\n", "- a [Google Cloud](https://cloud.google.com/) account" - ], - "metadata": { - "id": "6x41tnbTvQM1" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "8PSjyDIavRcn" + }, "source": [ "## Install dependencies\n", "\n", "Before creating your pipeline, download and install the dependencies required to develop with Apache Beam and vLLM. vLLM is supported in Apache Beam versions 2.60.0 and later." - ], - "metadata": { - "id": "8PSjyDIavRcn" - } + ] }, { "cell_type": "code", @@ -123,30 +106,33 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "3xz8zuA7vcS4" + }, "source": [ "## Run locally without Apache Beam\n", "\n", "In this section, you run a vLLM server without using Apache Beam. Use the `facebook/opt-125m` model. This model is small enough to fit in Colab memory and doesn't require any extra authentication.\n", "\n", "First, start the vLLM server. This step might take a minute or two, because the model needs to download before vLLM starts running inference." - ], - "metadata": { - "id": "3xz8zuA7vcS4" - } + ] }, { "cell_type": "code", - "source": [ - "! python -m vllm.entrypoints.openai.api_server --model facebook/opt-125m" - ], + "execution_count": null, "metadata": { "id": "GbJGzINNt5sG" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "! python -m vllm.entrypoints.openai.api_server --model facebook/opt-125m" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "n35LXTS3uzIC" + }, "source": [ "Next, while the vLLM server is running, open a separate terminal to communicate with the vLLM serving process. To open a terminal in Colab, in the sidebar, click **Terminal**. In the terminal, run the following commands.\n", "\n", @@ -169,26 +155,28 @@ "```\n", "\n", "This code runs against the server running in the cell. You can experiment with different prompts." - ], - "metadata": { - "id": "n35LXTS3uzIC" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "Hbxi83BfwbBa" + }, "source": [ "## Run locally with Apache Beam\n", "\n", "In this section, you set up an Apache Beam pipeline to run a job with an embedded vLLM instance.\n", "\n", "First, define the `VllmCompletionsModelHandler` object. This configuration object gives Apache Beam the information that it needs to create a dedicated vLLM process in the middle of the pipeline. Apache Beam then provides examples to the pipeline. No additional code is needed." - ], - "metadata": { - "id": "Hbxi83BfwbBa" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sUqjOzw3wpI4" + }, + "outputs": [], "source": [ "from apache_beam.ml.inference.base import RunInference\n", "from apache_beam.ml.inference.vllm_inference import VLLMCompletionsModelHandler\n", @@ -196,24 +184,24 @@ "import apache_beam as beam\n", "\n", "model_handler = VLLMCompletionsModelHandler('facebook/opt-125m')" - ], - "metadata": { - "id": "sUqjOzw3wpI4" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Next, define examples to run inference against, and define a helper function to print out the inference results." - ], "metadata": { "id": "N06lXRKRxCz5" - } + }, + "source": [ + "Next, define examples to run inference against, and define a helper function to print out the inference results." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3a1PznmtxNR_" + }, + "outputs": [], "source": [ "class FormatOutput(beam.DoFn):\n", " def process(self, element, *args, **kwargs):\n", @@ -226,26 +214,26 @@ " \"The future of AI is\",\n", " \"Emperor penguins are\",\n", "]" - ], - "metadata": { - "id": "3a1PznmtxNR_" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "Njl0QfrLxQ0m" + }, "source": [ "Finally, run the pipeline.\n", "\n", "This step might take a minute or two, because the model needs to download before Apache Beam can start running inference." - ], - "metadata": { - "id": "Njl0QfrLxQ0m" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9yXbzV0ZmZcJ" + }, + "outputs": [], "source": [ "with beam.Pipeline() as p:\n", " _ = (p | beam.Create(prompts) # Create a PCollection of the prompts.\n", @@ -253,26 +241,24 @@ " | beam.ParDo(FormatOutput()) # Format the output.\n", " | beam.Map(print) # Print the formatted output.\n", " )" - ], - "metadata": { - "id": "9yXbzV0ZmZcJ" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "Jv7be6Pk9Hlx" + }, "source": [ "## Run remotely on Dataflow\n", "\n", "After you validate that the pipeline can run against a vLLM locally, you can productionalize the workflow on a remote runner. This notebook runs the pipeline on the Dataflow runner." - ], - "metadata": { - "id": "Jv7be6Pk9Hlx" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "J1LMrl1Yy6QB" + }, "source": [ "### Build a Docker image\n", "\n", @@ -284,24 +270,26 @@ "\n", "- The Python version in the following cell matches the Python version defined in the Dockerfile.\n", "- The Apache Beam version defined in your dependencies matches the Apache Beam version defined in the Dockerfile." - ], - "metadata": { - "id": "J1LMrl1Yy6QB" - } + ] }, { "cell_type": "code", - "source": [ - "!python --version" - ], + "execution_count": null, "metadata": { "id": "jCQ6-D55gqfl" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "!python --version" + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7QyNq_gygHLO" + }, + "outputs": [], "source": [ "cell_str='''\n", "FROM nvidia/cuda:12.4.1-devel-ubuntu22.04\n", @@ -338,15 +326,13 @@ "\n", "with open('VllmDockerfile', 'w') as f:\n", " f.write(cell_str)" - ], - "metadata": { - "id": "7QyNq_gygHLO" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "zWma0YetiEn5" + }, "source": [ "After you save the Dockerfile, build and push your Docker image. Because Docker is not accessible from Colab, you need to complete this step in a separate environment.\n", "\n", @@ -358,13 +344,13 @@ " docker build -t \":\" -f VllmDockerfile ./\n", " docker image push \":\"\n", " ```" - ], - "metadata": { - "id": "zWma0YetiEn5" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "NjZyRjte0g0Q" + }, "source": [ "### Define and run the pipeline\n", "\n", @@ -378,13 +364,15 @@ "- ``: the name of the Google Cloud project that you created your bucket and Artifact Registry repository in.\n", "\n", "This workflow uses the following Dataflow service option: `worker_accelerator=type:nvidia-tesla-t4;count:1;install-nvidia-driver:5xx`. When you use this service option, Dataflow to installs a T4 GPU that uses a `5xx` series Nvidia driver on each worker machine. The 5xx driver is required to run vLLM jobs." - ], - "metadata": { - "id": "NjZyRjte0g0Q" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "kXy9FRYVCSjq" + }, + "outputs": [], "source": [ "\n", "from apache_beam.options.pipeline_options import GoogleCloudOptions\n", @@ -396,9 +384,12 @@ "\n", "options = PipelineOptions()\n", "\n", - "BUCKET_NAME = '' # Replace with your bucket name.\n", - "CONTAINER_IMAGE = ':' # Replace with the image repository and tag from the previous step.\n", - "PROJECT_NAME = '' # Replace with your GCP project\n", + "# Replace with your bucket name.\n", + "BUCKET_NAME = '' # @param {type:'string'}\n", + "# Replace with the image repository and tag from the previous step.\n", + "CONTAINER_IMAGE = ':' # @param {type:'string'}\n", + "# Replace with your GCP project\n", + "PROJECT_NAME = '' # @param {type:'string'}\n", "\n", "options.view_as(GoogleCloudOptions).project = PROJECT_NAME\n", "\n", @@ -430,50 +421,50 @@ "options.view_as(WorkerOptions).machine_type = \"n1-standard-4\"\n", "\n", "options.view_as(WorkerOptions).sdk_container_image = CONTAINER_IMAGE" - ], - "metadata": { - "id": "kXy9FRYVCSjq" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Next, authenticate Colab so that it can to submit a job on your behalf." - ], "metadata": { "id": "xPhe597P1-QJ" - } + }, + "source": [ + "Next, authenticate Colab so that it can to submit a job on your behalf." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Xkf6yIVlFB8-" + }, + "outputs": [], "source": [ "def auth_to_colab():\n", " from google.colab import auth\n", " auth.authenticate_user()\n", "\n", "auth_to_colab()" - ], - "metadata": { - "id": "Xkf6yIVlFB8-" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "MJtEI6Ux2eza" + }, "source": [ "Finally, run the pipeline on Dataflow. The pipeline definition is almost exactly the same as the definition used for local execution. The pipeline options are the only change to the pipeline.\n", "\n", "The following code creates a Dataflow job in your project. You can view the results in Colab or in the Google Cloud console. Creating a Dataflow job and downloading the model might take a few minutes. After the job starts performing inference, it quickly runs through the inputs." - ], - "metadata": { - "id": "MJtEI6Ux2eza" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8gjDdru_9Dii" + }, + "outputs": [], "source": [ "import logging\n", "from apache_beam.ml.inference.base import RunInference\n", @@ -503,15 +494,13 @@ " | beam.ParDo(FormatOutput()) # Format the output.\n", " | beam.Map(logging.info) # Print the formatted output.\n", " )" - ], - "metadata": { - "id": "8gjDdru_9Dii" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "22cEHPCc28fH" + }, "source": [ "## Run vLLM with a Gemma model\n", "\n", @@ -526,55 +515,57 @@ "When you complete these steps, the following message appears on the model card page: `You have been granted access to this model`.\n", "\n", "Next, sign in to your account from this notebook by running the following code and then following the prompts." - ], - "metadata": { - "id": "22cEHPCc28fH" - } + ] }, { "cell_type": "code", - "source": [ - "! huggingface-cli login" - ], + "execution_count": null, "metadata": { "id": "JHwIsFI9kd9j" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "! huggingface-cli login" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "IjX2If8rnCol" + }, "source": [ "Verify that the notebook can now access the Gemma model. Run the following code, which starts a vLLM server to serve the Gemma 2b model. Because the default T4 Colab runtime doesn't support the full data type precision needed to run Gemma models, the `--dtype=half` parameter is required.\n", "\n", "When successful, the following cell runs indefinitely. After it starts the server process, you can shut it down. When the server process starts, the Gemma 2b model is successfully downloaded, and the server is ready to serve traffic." - ], - "metadata": { - "id": "IjX2If8rnCol" - } + ] }, { "cell_type": "code", - "source": [ - "! python -m vllm.entrypoints.openai.api_server --model google/gemma-2b --dtype=half" - ], + "execution_count": null, "metadata": { "id": "LH_oCFWMiwFs" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "! python -m vllm.entrypoints.openai.api_server --model google/gemma-2b --dtype=half" + ] }, { "cell_type": "markdown", - "source": [ - "To run the pipeline in Apache Beam, run the following code. Update the `VLLMCompletionsModelHandler` object with the new parameters, which match the command from the previous cell. Reuse all of the pipeline logic from the previous pipelines." - ], "metadata": { "id": "31BmdDUAn-SW" - } + }, + "source": [ + "To run the pipeline in Apache Beam, run the following code. Update the `VLLMCompletionsModelHandler` object with the new parameters, which match the command from the previous cell. Reuse all of the pipeline logic from the previous pipelines." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "DyC2ikXg237p" + }, + "outputs": [], "source": [ "model_handler = VLLMCompletionsModelHandler('google/gemma-2b', vllm_server_kwargs={'dtype': 'half'})\n", "\n", @@ -584,15 +575,13 @@ " | beam.ParDo(FormatOutput()) # Format the output.\n", " | beam.Map(print) # Print the formatted output.\n", " )" - ], - "metadata": { - "id": "DyC2ikXg237p" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "C6OYfub6ovFK" + }, "source": [ "### Run Gemma on Dataflow\n", "\n", @@ -607,10 +596,24 @@ "2. Set pipeline options. You can reuse the options defined in this notebook. Replace the Docker image location with your new Docker image.\n", "3. Run the pipeline. Copy the pipeline that you ran on Dataflow, and replace the pipeline options with the pipeline options that you just defined.\n", "\n" - ], - "metadata": { - "id": "C6OYfub6ovFK" - } + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" } - ] + }, + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/examples/notebooks/beam-ml/vertex_ai_feature_store_enrichment.ipynb b/examples/notebooks/beam-ml/vertex_ai_feature_store_enrichment.ipynb index ebfcca34b94c..03feb96cbf68 100644 --- a/examples/notebooks/beam-ml/vertex_ai_feature_store_enrichment.ipynb +++ b/examples/notebooks/beam-ml/vertex_ai_feature_store_enrichment.ipynb @@ -197,8 +197,8 @@ }, "outputs": [], "source": [ - "PROJECT_ID = \"\"\n", - "LOCATION = \"\"" + "PROJECT_ID = \"\" # @param {type:'string'}\n", + "LOCATION = \"\" # @param {type:'string'}" ] }, { @@ -1790,10 +1790,10 @@ "outputs": [], "source": [ "# Replace with the name of your Pub/Sub topic.\n", - "TOPIC = \" \"\n", + "TOPIC = \"\" # @param {type:'string'}\n", "\n", "# Replace with the subscription path for your topic.\n", - "SUBSCRIPTION = \"\"" + "SUBSCRIPTION = \"\" # @param {type:'string'}" ] }, { diff --git a/examples/notebooks/healthcare/beam_nlp.ipynb b/examples/notebooks/healthcare/beam_nlp.ipynb index c2061bc4d75f..bbcbb6254024 100644 --- a/examples/notebooks/healthcare/beam_nlp.ipynb +++ b/examples/notebooks/healthcare/beam_nlp.ipynb @@ -1,25 +1,10 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "include_colab_link": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", "metadata": { - "id": "view-in-github", - "colab_type": "text" + "colab_type": "text", + "id": "view-in-github" }, "source": [ "\"Open" @@ -27,6 +12,12 @@ }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "lBuUTzxD2mvJ" + }, + "outputs": [], "source": [ "# @title ###### Licensed to the Apache Software Foundation (ASF), Version 2.0 (the \"License\")\n", "\n", @@ -46,16 +37,13 @@ "# KIND, either express or implied. See the License for the\n", "# specific language governing permissions and limitations\n", "# under the License" - ], - "metadata": { - "id": "lBuUTzxD2mvJ", - "cellView": "form" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "nEUAYCTx4Ijj" + }, "source": [ "# **Natural Language Processing Pipeline**\n", "\n", @@ -70,101 +58,103 @@ "For details about Apache Beam pipelines, including PTransforms and PCollections, visit the [Beam Programming Guide](https://beam.apache.org/documentation/programming-guide/).\n", "\n", "You'll be able to use this notebook to explore the data in each PCollection." - ], - "metadata": { - "id": "nEUAYCTx4Ijj" - } + ] }, { "cell_type": "markdown", - "source": [ - "First, lets install the necessary packages." - ], "metadata": { "id": "ZLBB0PTG5CHw" - } + }, + "source": [ + "First, lets install the necessary packages." + ] }, { "cell_type": "code", - "source": [ - "!pip install apache-beam[gcp]" - ], + "execution_count": null, "metadata": { "id": "O7hq2sse8K4u" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "!pip install apache-beam[gcp]" + ] }, { "cell_type": "markdown", - "source": [ - " **GCP Setup**" - ], "metadata": { "id": "5vQDhIv0E-LR" - } + }, + "source": [ + " **GCP Setup**" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "DGYiBYfxsSCw" + }, "source": [ "1. Authenticate your notebook by `gcloud auth application-default login` in the Colab terminal.\n", "\n", "2. Run `gcloud config set project `" - ], - "metadata": { - "id": "DGYiBYfxsSCw" - } + ] }, { "cell_type": "markdown", + "metadata": { + "id": "D7lJqW2PRFcN" + }, "source": [ "Set the variables in the next cell based upon your project and preferences. The files referred to in this notebook nlpsample*.csv are in the format with one\n", "blurb of clinical note.\n", "\n", "Note that below, **us-central1** is hardcoded as the location. This is because of the limited number of [locations](https://cloud.google.com/healthcare-api/docs/how-tos/nlp) the API currently supports." - ], - "metadata": { - "id": "D7lJqW2PRFcN" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "s9lhe5CZ5F3o" + }, + "outputs": [], "source": [ - "DATASET=\"\"\n", - "TEMP_LOCATION=\"\"\n", - "PROJECT=''\n", + "DATASET=\"\" # @param {type:'string'}\n", + "TEMP_LOCATION=\"\" # @param {type:'string'}\n", + "PROJECT=''# @param {type:'string'}\n", "LOCATION='us-central1'\n", "URL=f'https://healthcare.googleapis.com/v1/projects/{PROJECT}/locations/{LOCATION}/services/nlp:analyzeEntities'\n", "NLP_SERVICE=f'projects/{PROJECT}/locations/{LOCATION}/services/nlp'" - ], - "metadata": { - "id": "s9lhe5CZ5F3o" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "Then, download [this raw CSV file](https://github.com/socd06/medical-nlp/blob/master/data/test.csv), and then upload it into Colab. You should be able to view this file (*test.csv*) in the \"Files\" tab in Colab after uploading." - ], "metadata": { "id": "1IArtEm8QuCR" - } + }, + "source": [ + "Then, download [this raw CSV file](https://github.com/socd06/medical-nlp/blob/master/data/test.csv), and then upload it into Colab. You should be able to view this file (*test.csv*) in the \"Files\" tab in Colab after uploading." + ] }, { "cell_type": "markdown", + "metadata": { + "id": "DI_Qkyn75LO-" + }, "source": [ "**BigQuery Setup**\n", "\n", "We will be using BigQuery to warehouse the structured data revealed in the output of the Healthcare NLP API. For this purpose, we create 3 tables to organize the data. Specifically, these will be table entities, table relations, and table entity mentions, which are all outputs of interest from the Healthcare NLP API." - ], - "metadata": { - "id": "DI_Qkyn75LO-" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bZDqtFVE5Wd_" + }, + "outputs": [], "source": [ "from google.cloud import bigquery\n", "\n", @@ -198,15 +188,15 @@ "print(\n", " \"Created table {}.{}.{}\".format(table.project, table.dataset_id, table.table_id)\n", ")" - ], - "metadata": { - "id": "bZDqtFVE5Wd_" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YK-G7uV5APuP" + }, + "outputs": [], "source": [ "from google.cloud import bigquery\n", "\n", @@ -240,15 +230,15 @@ ")\n", "\n", "\n" - ], - "metadata": { - "id": "YK-G7uV5APuP" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "R9IHgZKoAQWj" + }, + "outputs": [], "source": [ "from google.cloud import bigquery\n", "\n", @@ -324,26 +314,26 @@ "print(\n", " \"Created table {}.{}.{}\".format(table.project, table.dataset_id, table.table_id)\n", ")" - ], - "metadata": { - "id": "R9IHgZKoAQWj" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", + "metadata": { + "id": "jc_iS_BP5aS4" + }, "source": [ "**Pipeline Setup**\n", "\n", "We will use InteractiveRunner in this notebook." - ], - "metadata": { - "id": "jc_iS_BP5aS4" - } + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "07ct6kf55ihP" + }, + "outputs": [], "source": [ "# Python's regular expression library\n", "import re\n", @@ -365,24 +355,24 @@ " job_name=\"my-healthcare-nlp-job\",\n", " temp_location=TEMP_LOCATION,\n", " region=LOCATION)" - ], - "metadata": { - "id": "07ct6kf55ihP" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "The following defines a `PTransform` named `ReadLinesFromText`, that extracts lines from a file." - ], "metadata": { "id": "dO1A9_WK5lb4" - } + }, + "source": [ + "The following defines a `PTransform` named `ReadLinesFromText`, that extracts lines from a file." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "t5iDRKMK5n_B" + }, + "outputs": [], "source": [ "class ReadLinesFromText(beam.PTransform):\n", "\n", @@ -392,74 +382,73 @@ " def expand(self, pcoll):\n", " return (pcoll.pipeline\n", " | beam.io.ReadFromText(self._file_pattern))" - ], - "metadata": { - "id": "t5iDRKMK5n_B" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "The following sets up an Apache Beam pipeline with the *Interactive Runner*. The *Interactive Runner* is the runner suitable for running in notebooks. A runner is an execution engine for Apache Beam pipelines." - ], "metadata": { "id": "HI_HVB185sMQ" - } + }, + "source": [ + "The following sets up an Apache Beam pipeline with the *Interactive Runner*. The *Interactive Runner* is the runner suitable for running in notebooks. A runner is an execution engine for Apache Beam pipelines." + ] }, { "cell_type": "code", - "source": [ - "p = beam.Pipeline(options = options)" - ], + "execution_count": null, "metadata": { "id": "7osCZ1om5ql0" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [ + "p = beam.Pipeline(options = options)" + ] }, { "cell_type": "markdown", + "metadata": { + "id": "EaF8NfC_521y" + }, "source": [ "The following sets up a PTransform that extracts words from a Google Cloud Storage file that contains lines with each line containing a In our example, each line is a medical notes excerpt that will be passed through the Healthcare NLP API\n", "\n", "**\"|\"** is an overloaded operator that applies a PTransform to a PCollection to produce a new PCollection. Together with |, >> allows you to optionally name a PTransform.\n", "\n", "Usage:[PCollection] | [PTransform], **or** [PCollection] | [name] >> [PTransform]" - ], - "metadata": { - "id": "EaF8NfC_521y" - } + ] }, { "cell_type": "code", - "source": [ - "lines = p | 'read' >> ReadLinesFromText(\"test.csv\")" - ], + "execution_count": null, "metadata": { - "id": "2APAh6XQ6NYd", "colab": { "base_uri": "https://localhost:8080/", "height": 72 }, + "id": "2APAh6XQ6NYd", "outputId": "033c5110-fd5a-4da0-b59b-801a1ce9d3b1" }, - "execution_count": null, - "outputs": [ + "outputs": [], + "source": [ + "lines = p | 'read' >> ReadLinesFromText(\"test.csv\")" ] }, { "cell_type": "markdown", - "source": [ - "We then write a **DoFn** that will invoke the [NLP API](https://cloud.google.com/healthcare-api/docs/how-tos/nlp)." - ], "metadata": { "id": "vM_FbhkbGI-E" - } + }, + "source": [ + "We then write a **DoFn** that will invoke the [NLP API](https://cloud.google.com/healthcare-api/docs/how-tos/nlp)." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3ZJ-0dex9WE5" + }, + "outputs": [], "source": [ "class InvokeNLP(beam.DoFn):\n", "\n", @@ -486,24 +475,24 @@ " pcoll\n", " | \"Invoke NLP API\" >> beam.ParDo(InvokeNLP())\n", " )" - ], - "metadata": { - "id": "3ZJ-0dex9WE5" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "From our elements, being processed, we will get the entity mentions, relationships, and entities respectively." - ], "metadata": { "id": "TeYxIlNgGdK0" - } + }, + "source": [ + "From our elements, being processed, we will get the entity mentions, relationships, and entities respectively." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3KZgUv3d6haf" + }, + "outputs": [], "source": [ "import json\n", "from apache_beam import pvalue\n", @@ -529,15 +518,15 @@ " for e in element['entityMentions']:\n", " e['id'] = element['id']\n", " yield e\n" - ], - "metadata": { - "id": "3KZgUv3d6haf" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "OkxgB2a-6iYN" + }, + "outputs": [], "source": [ "from apache_beam.io.gcp.internal.clients import bigquery\n", "\n", @@ -550,24 +539,24 @@ "nlp_annotations = (lines\n", " | \"Analyze\" >> AnalyzeLines()\n", " )\n" - ], - "metadata": { - "id": "OkxgB2a-6iYN" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "We then write these results to [BigQuery](https://cloud.google.com/bigquery), a cloud data warehouse." - ], "metadata": { "id": "iTh65CXIGoQn" - } + }, + "source": [ + "We then write these results to [BigQuery](https://cloud.google.com/bigquery), a cloud data warehouse." + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Q9GIyLeS6oAe" + }, + "outputs": [], "source": [ "resultsEntities = ( nlp_annotations\n", " | \"Break\" >> beam.ParDo(breakUpEntities())\n", @@ -576,15 +565,15 @@ " write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,\n", " create_disposition=beam.io.BigQueryDisposition.CREATE_NEVER)\n", " )" - ], - "metadata": { - "id": "Q9GIyLeS6oAe" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yOlHfkcT6s4y" + }, + "outputs": [], "source": [ "table_spec = bigquery.TableReference(\n", " projectId=PROJECT,\n", @@ -598,15 +587,15 @@ " write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,\n", " create_disposition=beam.io.BigQueryDisposition.CREATE_NEVER)\n", " )" - ], - "metadata": { - "id": "yOlHfkcT6s4y" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a6QxxnY890Za" + }, + "outputs": [], "source": [ "table_spec = bigquery.TableReference(\n", " projectId=PROJECT,\n", @@ -620,43 +609,31 @@ " write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,\n", " create_disposition=beam.io.BigQueryDisposition.CREATE_NEVER)\n", " )" - ], - "metadata": { - "id": "a6QxxnY890Za" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", - "source": [ - "You can see the job graph for the pipeline by doing:" - ], "metadata": { "id": "6rP2nO6Z60bt" - } + }, + "source": [ + "You can see the job graph for the pipeline by doing:" + ] }, { "cell_type": "code", - "source": [ - "ib.show_graph(p)" - ], + "execution_count": null, "metadata": { - "id": "zQB5h1Zq6x8d", "colab": { "base_uri": "https://localhost:8080/", "height": 806 }, + "id": "zQB5h1Zq6x8d", "outputId": "7885e493-fee8-402e-baf2-cbbf406a3eb9" }, - "execution_count": null, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", " \n", @@ -665,16 +642,16 @@ " Processing... show_graph\n", " \n", " " + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "text/plain": [ - "" - ], "text/html": [ "\n", "\n", "\n", "\n" + ], + "text/plain": [ + "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { - "application/javascript": [ - "\n", - " if (typeof window.interactive_beam_jquery == 'undefined') {\n", - " var jqueryScript = document.createElement('script');\n", - " jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n", - " jqueryScript.type = 'text/javascript';\n", - " jqueryScript.onload = function() {\n", - " var datatableScript = document.createElement('script');\n", - " datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n", - " datatableScript.type = 'text/javascript';\n", - " datatableScript.onload = function() {\n", - " window.interactive_beam_jquery = jQuery.noConflict(true);\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_fa6997b180fa86966dd888a7d59a34f7\").remove();\n", - " });\n", - " }\n", - " document.head.appendChild(datatableScript);\n", - " };\n", - " document.head.appendChild(jqueryScript);\n", - " } else {\n", - " window.interactive_beam_jquery(document).ready(function($){\n", - " \n", - " $(\"#progress_indicator_fa6997b180fa86966dd888a7d59a34f7\").remove();\n", - " });\n", - " }" - ] + "application/javascript": "\n if (typeof window.interactive_beam_jquery == 'undefined') {\n var jqueryScript = document.createElement('script');\n jqueryScript.src = 'https://code.jquery.com/jquery-3.4.1.slim.min.js';\n jqueryScript.type = 'text/javascript';\n jqueryScript.onload = function() {\n var datatableScript = document.createElement('script');\n datatableScript.src = 'https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js';\n datatableScript.type = 'text/javascript';\n datatableScript.onload = function() {\n window.interactive_beam_jquery = jQuery.noConflict(true);\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_fa6997b180fa86966dd888a7d59a34f7\").remove();\n });\n }\n document.head.appendChild(datatableScript);\n };\n document.head.appendChild(jqueryScript);\n } else {\n window.interactive_beam_jquery(document).ready(function($){\n \n $(\"#progress_indicator_fa6997b180fa86966dd888a7d59a34f7\").remove();\n });\n }" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } + ], + "source": [ + "ib.show_graph(p)" ] } - ] + ], + "metadata": { + "colab": { + "include_colab_link": true, + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } From 4073aba0ca8237441041dd910190c08d3f617c88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:57:27 -0500 Subject: [PATCH 002/135] Bump org.sonarqube from 3.0 to 6.0.0.5145 (#33174) Bumps org.sonarqube from 3.0 to 6.0.0.5145. --- updated-dependencies: - dependency-name: org.sonarqube dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index d96e77a4c78c..cd2309c73bb4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ plugins { // Enable gradle-based release management id("net.researchgate.release") version "2.8.1" id("org.apache.beam.module") - id("org.sonarqube") version "3.0" + id("org.sonarqube") version "6.0.0.5145" } /*************************************************************************************************/ From b70375db84a7ff04a6be3aea8d5ae30f4d7cdbe1 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 22 Nov 2024 13:09:58 -0500 Subject: [PATCH 003/135] Revert "Bump org.sonarqube from 3.0 to 6.0.0.5145 (#33174)" (#33193) This reverts commit 4073aba0ca8237441041dd910190c08d3f617c88. --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index cd2309c73bb4..d96e77a4c78c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ plugins { // Enable gradle-based release management id("net.researchgate.release") version "2.8.1" id("org.apache.beam.module") - id("org.sonarqube") version "6.0.0.5145" + id("org.sonarqube") version "3.0" } /*************************************************************************************************/ From d342dd3755c5a55d697bb9fa05b70f5c426bb000 Mon Sep 17 00:00:00 2001 From: liferoad Date: Fri, 22 Nov 2024 14:55:38 -0500 Subject: [PATCH 004/135] Remove the default machine types (#33191) From https://github.com/apache/beam/issues/30507#issuecomment-2494385957, try to use the default machine types for Flink with more memory. --- .test-infra/dataproc/flink_cluster.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.test-infra/dataproc/flink_cluster.sh b/.test-infra/dataproc/flink_cluster.sh index 759d7a6fcc38..aeb5ec19441f 100755 --- a/.test-infra/dataproc/flink_cluster.sh +++ b/.test-infra/dataproc/flink_cluster.sh @@ -131,10 +131,8 @@ function create_cluster() { # Docker init action restarts yarn so we need to start yarn session after this restart happens. # This is why flink init action is invoked last. - # TODO(11/11/2022) remove --worker-machine-type and --master-machine-type once N2 CPUs quota relaxed - # Dataproc 2.1 uses n2-standard-2 by default but there is N2 CPUs=24 quota limit gcloud dataproc clusters create $CLUSTER_NAME --region=$GCLOUD_REGION --num-workers=$FLINK_NUM_WORKERS --public-ip-address \ - --master-machine-type=n1-standard-2 --worker-machine-type=n1-standard-2 --metadata "${metadata}", \ + --metadata "${metadata}", \ --image-version=$image_version --zone=$GCLOUD_ZONE --optional-components=FLINK,DOCKER --quiet } From 09c1c9e9f23ed39b75885b68955960e44bee23de Mon Sep 17 00:00:00 2001 From: Damon Date: Fri, 22 Nov 2024 16:19:19 -0800 Subject: [PATCH 005/135] Enable Java SDK Distroless container image variants. (#33173) * Enable Java SDK Distroless container image variant * Add LANG environment and /usr/lib/locale * Use examples tests instead --- .../trigger_files/beam_PostCommit_Java.json | 5 +- .../beam_PostCommit_Java_DataflowV2.json | 2 +- .../google-cloud-dataflow-java/build.gradle | 63 +++++++++++++++++++ sdks/java/container/Dockerfile-distroless | 42 +++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 sdks/java/container/Dockerfile-distroless diff --git a/.github/trigger_files/beam_PostCommit_Java.json b/.github/trigger_files/beam_PostCommit_Java.json index 9e26dfeeb6e6..920c8d132e4a 100644 --- a/.github/trigger_files/beam_PostCommit_Java.json +++ b/.github/trigger_files/beam_PostCommit_Java.json @@ -1 +1,4 @@ -{} \ No newline at end of file +{ + "comment": "Modify this file in a trivial way to cause this test suite to run", + "modification": 1 +} \ No newline at end of file diff --git a/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json b/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json index 1efc8e9e4405..3f63c0c9975f 100644 --- a/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json +++ b/.github/trigger_files/beam_PostCommit_Java_DataflowV2.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 1 + "modification": 2 } diff --git a/runners/google-cloud-dataflow-java/build.gradle b/runners/google-cloud-dataflow-java/build.gradle index 4906d9cf9cb8..3a337684bf18 100644 --- a/runners/google-cloud-dataflow-java/build.gradle +++ b/runners/google-cloud-dataflow-java/build.gradle @@ -273,6 +273,69 @@ def createRunnerV2ValidatesRunnerTest = { Map args -> } } +tasks.register('examplesJavaRunnerV2IntegrationTestDistroless', Test.class) { + group = "verification" + dependsOn 'buildAndPushDistrolessContainerImage' + def javaVer = project.findProperty('testJavaVersion') + def repository = "us.gcr.io/apache-beam-testing/${System.getenv('USER')}" + def tag = project.findProperty('dockerTag') + def imageURL = "${repository}/beam_${javaVer}_sdk_distroless:${tag}" + def pipelineOptions = [ + "--runner=TestDataflowRunner", + "--project=${gcpProject}", + "--region=${gcpRegion}", + "--tempRoot=${dataflowValidatesTempRoot}", + "--sdkContainerImage=${imageURL}", + "--experiments=use_unified_worker,use_runner_v2", + "--firestoreDb=${firestoreDb}", + ] + systemProperty "beamTestPipelineOptions", JsonOutput.toJson(pipelineOptions) + + include '**/*IT.class' + + maxParallelForks 4 + classpath = configurations.examplesJavaIntegrationTest + testClassesDirs = files(project(":examples:java").sourceSets.test.output.classesDirs) + useJUnit { } +} + +tasks.register('buildAndPushDistrolessContainerImage', Task.class) { + // Only Java 17 and 21 are supported. + // See https://github.com/GoogleContainerTools/distroless/tree/main/java#image-contents. + def allowed = ["java17", "java21"] + doLast { + def javaVer = project.findProperty('testJavaVersion') + if (!allowed.contains(javaVer)) { + throw new GradleException("testJavaVersion must be one of ${allowed}, got: ${javaVer}") + } + if (!project.hasProperty('dockerTag')) { + throw new GradleException("dockerTag is missing but required") + } + def repository = "us.gcr.io/apache-beam-testing/${System.getenv('USER')}" + def tag = project.findProperty('dockerTag') + def imageURL = "${repository}/beam_${javaVer}_sdk_distroless:${tag}" + exec { + executable 'docker' + workingDir rootDir + args = [ + 'buildx', + 'build', + '-t', + imageURL, + '-f', + 'sdks/java/container/Dockerfile-distroless', + "--build-arg=BEAM_BASE=gcr.io/apache-beam-testing/beam-sdk/beam_${javaVer}_sdk", + "--build-arg=DISTROLESS_BASE=gcr.io/distroless/${javaVer}-debian12", + '.' + ] + } + exec { + executable 'docker' + args = ['push', imageURL] + } + } +} + // Push docker images to a container registry for use within tests. // NB: Tasks which consume docker images from the registry should depend on this // task directly ('dependsOn buildAndPushDockerJavaContainer'). This ensures the correct diff --git a/sdks/java/container/Dockerfile-distroless b/sdks/java/container/Dockerfile-distroless new file mode 100644 index 000000000000..328c4dc6a7b3 --- /dev/null +++ b/sdks/java/container/Dockerfile-distroless @@ -0,0 +1,42 @@ +############################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +############################################################################### + +# ARG BEAM_BASE is the Beam SDK container image built using sdks/python/container/Dockerfile. +ARG BEAM_BASE + +# ARG DISTROLESS_BASE is the distroless container image URL. For available distroless Java images, +# see https://github.com/GoogleContainerTools/distroless/tree/main?tab=readme-ov-file#what-images-are-available. +# Only Java versions 17 and 21 are supported. +ARG DISTROLESS_BASE +FROM ${BEAM_BASE} AS base +ARG TARGETARCH +ENV LANG C.UTF-8 + +LABEL Author="Apache Beam " + +RUN if [ -z "${TARGETARCH}" ]; then echo "fatal: TARGETARCH not set; run as docker buildx build or use --build-arg=TARGETARCH=amd64|arm64" >&2; exit 1; fi + +FROM ${DISTROLESS_BASE}:latest-${TARGETARCH} AS distroless + +COPY --from=base /opt /opt + +# Along with the LANG environment variable above, prevents internally discovered failing bugs related to Dataflow Flex +# template character encodings. +COPY --from=base /usr/lib/locale /usr/lib/locale + +ENTRYPOINT ["/opt/apache/beam/boot"] From e5bd69db5bf9258021bb6c7a80e84441f0159c2c Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Sat, 23 Nov 2024 21:32:32 -0500 Subject: [PATCH 006/135] Remove leftover print statement in SpannerIO (#33200) --- .../java/org/apache/beam/sdk/io/gcp/spanner/ReadOperation.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/ReadOperation.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/ReadOperation.java index 2b9f24f09541..933394982e30 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/ReadOperation.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/ReadOperation.java @@ -135,7 +135,6 @@ String tryGetTableName() { return getTable(); } else if (getQuery() != null) { String query = getQuery().getSql(); - System.err.println(query); Matcher matcher = queryPattern.matcher(query); if (matcher.find()) { return matcher.group("table"); From 60f87930a70b50f4978dc8abe7dd4724ad9d383e Mon Sep 17 00:00:00 2001 From: liferoad Date: Mon, 25 Nov 2024 09:43:44 -0500 Subject: [PATCH 007/135] Use --enable-component-gateway when creating the flink cluster (#33198) * Use --enable-component-gateway when creating the flink cluster * Update flink_cluster.sh --- .test-infra/dataproc/flink_cluster.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.test-infra/dataproc/flink_cluster.sh b/.test-infra/dataproc/flink_cluster.sh index aeb5ec19441f..24d51ac13bde 100755 --- a/.test-infra/dataproc/flink_cluster.sh +++ b/.test-infra/dataproc/flink_cluster.sh @@ -131,8 +131,10 @@ function create_cluster() { # Docker init action restarts yarn so we need to start yarn session after this restart happens. # This is why flink init action is invoked last. - gcloud dataproc clusters create $CLUSTER_NAME --region=$GCLOUD_REGION --num-workers=$FLINK_NUM_WORKERS --public-ip-address \ - --metadata "${metadata}", \ + # TODO(11/22/2024) remove --worker-machine-type and --master-machine-type once N2 CPUs quota relaxed + # Dataproc 2.1 uses n2-standard-2 by default but there is N2 CPUs=24 quota limit for this project + gcloud dataproc clusters create $CLUSTER_NAME --enable-component-gateway --region=$GCLOUD_REGION --num-workers=$FLINK_NUM_WORKERS --public-ip-address \ + --master-machine-type=n1-standard-2 --worker-machine-type=n1-standard-2 --metadata "${metadata}", \ --image-version=$image_version --zone=$GCLOUD_ZONE --optional-components=FLINK,DOCKER --quiet } From 78630d15445fdf54935d6ba99c2fd9d0930b6af8 Mon Sep 17 00:00:00 2001 From: liferoad Date: Mon, 25 Nov 2024 09:45:14 -0500 Subject: [PATCH 008/135] Add a new precommit workflow to test flink container (#33206) * Add a new precommit to test finl container * Changed trigger file for Flink container workflow * updated the timeout * only allow maual trigger to test * fixed the PR check * fixed the workflow checks --- .../beam_PreCommit_Flink_Container.json | 4 + .../beam_PreCommit_Flink_Container.yml | 155 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 .github/trigger_files/beam_PreCommit_Flink_Container.json create mode 100644 .github/workflows/beam_PreCommit_Flink_Container.yml diff --git a/.github/trigger_files/beam_PreCommit_Flink_Container.json b/.github/trigger_files/beam_PreCommit_Flink_Container.json new file mode 100644 index 000000000000..b75e2800330d --- /dev/null +++ b/.github/trigger_files/beam_PreCommit_Flink_Container.json @@ -0,0 +1,4 @@ +{ + "https://github.com/apache/beam/pull/33206": "testing the new flink container workflow" +} + \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Flink_Container.yml b/.github/workflows/beam_PreCommit_Flink_Container.yml new file mode 100644 index 000000000000..7b82469d2b81 --- /dev/null +++ b/.github/workflows/beam_PreCommit_Flink_Container.yml @@ -0,0 +1,155 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +name: PreCommit Flink Container + +on: + pull_request_target: + paths: + - '.github/trigger_files/beam_PreCommit_Flink_Container.json' + - 'release/trigger_all_tests.json' + workflow_dispatch: + +# Setting explicit permissions for the action to avoid the default permissions which are `write-all` +permissions: + actions: write + pull-requests: read + checks: read + contents: read + deployments: read + id-token: none + issues: read + discussions: read + packages: read + pages: read + repository-projects: read + security-events: read + statuses: read + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.sha || github.head_ref || github.ref }}' + cancel-in-progress: true + +env: + DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} + INFLUXDB_USER: ${{ secrets.INFLUXDB_USER }} + INFLUXDB_USER_PASSWORD: ${{ secrets.INFLUXDB_USER_PASSWORD }} + GCLOUD_ZONE: us-central1-a + CLUSTER_NAME: beam-precommit-flink-container-${{ github.run_id }} + GCS_BUCKET: gs://beam-flink-cluster + FLINK_DOWNLOAD_URL: https://archive.apache.org/dist/flink/flink-1.17.0/flink-1.17.0-bin-scala_2.12.tgz + HADOOP_DOWNLOAD_URL: https://repo.maven.apache.org/maven2/org/apache/flink/flink-shaded-hadoop-2-uber/2.8.3-10.0/flink-shaded-hadoop-2-uber-2.8.3-10.0.jar + FLINK_TASKMANAGER_SLOTS: 1 + DETACHED_MODE: true + HARNESS_IMAGES_TO_PULL: gcr.io/apache-beam-testing/beam-sdk/beam_go_sdk:latest + JOB_SERVER_IMAGE: gcr.io/apache-beam-testing/beam_portability/beam_flink1.17_job_server:latest + ARTIFACTS_DIR: gs://beam-flink-cluster/beam-precommit-flink-container-${{ github.run_id }} + +jobs: + beam_PreCommit_Flink_Container: + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'push' || + github.event_name == 'schedule' || + (github.event_name == 'pull_request_target' && + github.base_ref == 'master' && + github.event.pull_request.draft == false) || + github.event.comment.body == 'Run Flink Container PreCommit' + runs-on: [self-hosted, ubuntu-20.04, main] + timeout-minutes: 45 + name: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + strategy: + matrix: + job_name: ["beam_PreCommit_Flink_Container"] + job_phrase: ["Run Flink Container PreCommit"] + steps: + - uses: actions/checkout@v4 + - name: Setup repository + uses: ./.github/actions/setup-action + with: + comment_phrase: ${{ matrix.job_phrase }} + github_token: ${{ secrets.GITHUB_TOKEN }} + github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + - name: Start Flink with parallelism 1 + env: + FLINK_NUM_WORKERS: 1 + run: | + cd ${{ github.workspace }}/.test-infra/dataproc; ./flink_cluster.sh create + # Run a simple Go Combine load test to verify the Flink container + - name: Run Flink Container Test with Go Combine + timeout-minutes: 10 + uses: ./.github/actions/gradle-command-self-hosted-action + with: + gradle-command: :sdks:go:test:load:run + arguments: | + -PloadTest.mainClass=combine \ + -Prunner=PortableRunner \ + -PloadTest.args=" + --runner=FlinkRunner \ + --job_endpoint=localhost:8099 \ + --environment_type=DOCKER \ + --environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_go_sdk:latest \ + --parallelism=1 \ + --input_options=\"{\"num_records\":200,\"key_size\":1,\"value_size\":9}\" + --fanout=1 \ + --top_count=10 \ + --iterations=1" + + # Run a Python Combine load test to verify the Flink container + - name: Run Flink Container Test with Python Combine + timeout-minutes: 10 + uses: ./.github/actions/gradle-command-self-hosted-action + with: + gradle-command: :sdks:python:test:load:run + arguments: | + -PloadTest.mainClass=apache_beam.testing.load_tests.combine_test \ + -Prunner=FlinkRunner \ + -PloadTest.args=" + --runner=PortableRunner \ + --job_endpoint=localhost:8099 \ + --environment_type=DOCKER \ + --environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_python3.9_sdk:latest \ + --parallelism=1 \ + --input_options=\"{\"num_records\":200,\"key_size\":1,\"value_size\":9,\"algorithm\":\"lcg\"}\" \ + --fanout=1 \ + --top_count=10 \ + --iterations=1" + + # Run a Java Combine load test to verify the Flink container + - name: Run Flink Container Test with Java Combine + timeout-minutes: 10 + uses: ./.github/actions/gradle-command-self-hosted-action + with: + gradle-command: :sdks:java:testing:load-tests:run + arguments: | + -PloadTest.mainClass=org.apache.beam.sdk.loadtests.CombineLoadTest \ + -Prunner=:runners:flink:1.17 \ + -PloadTest.args=" + --runner=FlinkRunner \ + --environment_type=DOCKER \ + --environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_java11_sdk:latest \ + --parallelism=1 \ + --sourceOptions={\"numRecords\":200,\"keySizeBytes\":1,\"valueSizeBytes\":9} \ + --fanout=1 \ + --iterations=1 \ + --topCount=10" + + - name: Teardown Flink + if: always() + run: | + ${{ github.workspace }}/.test-infra/dataproc/flink_cluster.sh delete \ No newline at end of file From 9bdc9a5a0bff4adc711e3eee91e0063606024ed3 Mon Sep 17 00:00:00 2001 From: Kenn Knowles Date: Mon, 25 Nov 2024 13:27:42 -0500 Subject: [PATCH 009/135] Remove sickbay GitHub Actions (#33214) --- .github/workflows/README.md | 2 - .../beam_PostCommit_Java_Sickbay.yml | 93 ---------------- .../beam_PostCommit_Sickbay_Python.yml | 105 ------------------ 3 files changed, 200 deletions(-) delete mode 100644 .github/workflows/beam_PostCommit_Java_Sickbay.yml delete mode 100644 .github/workflows/beam_PostCommit_Sickbay_Python.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 971bfd857b27..206364f416f7 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -331,7 +331,6 @@ PostCommit Jobs run in a schedule against master branch and generally do not get | [ PostCommit Java SingleStoreIO IT ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_SingleStoreIO_IT.yml) | N/A |`beam_PostCommit_Java_SingleStoreIO_IT.json`| [![.github/workflows/beam_PostCommit_Java_SingleStoreIO_IT.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_SingleStoreIO_IT.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_SingleStoreIO_IT.yml?query=event%3Aschedule) | | [ PostCommit Java PVR Spark3 Streaming ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml) | N/A |`beam_PostCommit_Java_PVR_Spark3_Streaming.json`| [![.github/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml?query=event%3Aschedule) | | [ PostCommit Java PVR Spark Batch ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_PVR_Spark_Batch.yml) | N/A |`beam_PostCommit_Java_PVR_Spark_Batch.json`| [![.github/workflows/beam_PostCommit_Java_PVR_Spark_Batch.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_PVR_Spark_Batch.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_PVR_Spark_Batch.yml?query=event%3Aschedule) | -| [ PostCommit Java Sickbay ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Sickbay.yml) | N/A |`beam_PostCommit_Java_Sickbay.json`| [![.github/workflows/beam_PostCommit_Java_Sickbay.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Sickbay.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Sickbay.yml?query=event%3Aschedule) | | [ PostCommit Java Tpcds Dataflow ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Dataflow.yml) | N/A |`beam_PostCommit_Java_Tpcds_Dataflow.json`| [![.github/workflows/beam_PostCommit_Java_Tpcds_Dataflow.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Dataflow.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Dataflow.yml?query=event%3Aschedule) | | [ PostCommit Java Tpcds Flink ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Flink.yml) | N/A |`beam_PostCommit_Java_Tpcds_Flink.json`| [![.github/workflows/beam_PostCommit_Java_Tpcds_Flink.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Flink.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Flink.yml?query=event%3Aschedule) | | [ PostCommit Java Tpcds Spark ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Spark.yml) | N/A |`beam_PostCommit_Java_Tpcds_Spark.json`| [![.github/workflows/beam_PostCommit_Java_Tpcds_Spark.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Spark.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Java_Tpcds_Spark.yml?query=event%3Aschedule) | @@ -372,7 +371,6 @@ PostCommit Jobs run in a schedule against master branch and generally do not get | [ PostCommit Python Xlang Gcp Dataflow ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml) | N/A |`beam_PostCommit_Python_Xlang_Gcp_Dataflow.json`| [![.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml?query=event%3Aschedule) | | [ PostCommit Python Xlang Gcp Direct ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml) | N/A |`beam_PostCommit_Python_Xlang_Gcp_Direct.json`| [![.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml?query=event%3Aschedule) | | [ PostCommit Python Xlang IO Dataflow ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml) | N/A |`beam_PostCommit_Python_Xlang_IO_Dataflow.json`| [![.github/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml?query=event%3Aschedule) | -| [ PostCommit Sickbay Python ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Sickbay_Python.yml) | ['3.8','3.9','3.10','3.11'] |`beam_PostCommit_Sickbay_Python.json`| [![.github/workflows/beam_PostCommit_Sickbay_Python.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Sickbay_Python.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Sickbay_Python.yml?query=event%3Aschedule) | | [ PostCommit SQL ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_SQL.yml) | N/A |`beam_PostCommit_SQL.json`| [![.github/workflows/beam_PostCommit_SQL.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_SQL.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_SQL.yml?query=event%3Aschedule) | | [ PostCommit TransformService Direct ](https://github.com/apache/beam/actions/workflows/beam_PostCommit_TransformService_Direct.yml) | N/A |`beam_PostCommit_TransformService_Direct.json`| [![.github/workflows/beam_PostCommit_TransformService_Direct.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_TransformService_Direct.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_TransformService_Direct.yml?query=event%3Aschedule) | [ PostCommit Website Test](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Website_Test.yml) | N/A |`beam_PostCommit_Website_Test.json`| [![.github/workflows/beam_PostCommit_Website_Test.yml](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Website_Test.yml/badge.svg?event=schedule)](https://github.com/apache/beam/actions/workflows/beam_PostCommit_Website_Test.yml?query=event%3Aschedule) | diff --git a/.github/workflows/beam_PostCommit_Java_Sickbay.yml b/.github/workflows/beam_PostCommit_Java_Sickbay.yml deleted file mode 100644 index 95c36fc863cf..000000000000 --- a/.github/workflows/beam_PostCommit_Java_Sickbay.yml +++ /dev/null @@ -1,93 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -name: PostCommit Java Sickbay - -on: - schedule: - - cron: '30 4/6 * * *' - pull_request_target: - paths: ['.github/trigger_files/beam_PostCommit_Java_Sickbay.json'] - workflow_dispatch: - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.sha || github.head_ref || github.ref }}-${{ github.event.schedule || github.event.comment.id || github.event.sender.login }}' - cancel-in-progress: true - -#Setting explicit permissions for the action to avoid the default permissions which are `write-all` in case of pull_request_target event -permissions: - actions: write - pull-requests: write - checks: write - contents: read - deployments: read - id-token: none - issues: write - discussions: read - packages: read - pages: read - repository-projects: read - security-events: read - statuses: read - -env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} - -jobs: - beam_PostCommit_Java_Sickbay: - name: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) - runs-on: [self-hosted, ubuntu-20.04, main] - timeout-minutes: 120 - strategy: - matrix: - job_name: [beam_PostCommit_Java_Sickbay] - job_phrase: [Run Java Sickbay] - if: | - github.event_name == 'workflow_dispatch' || - github.event_name == 'pull_request_target' || - (github.event_name == 'schedule' && github.repository == 'apache/beam') || - github.event.comment.body == 'Run Java Sickbay' - steps: - - uses: actions/checkout@v4 - - name: Setup repository - uses: ./.github/actions/setup-action - with: - comment_phrase: ${{ matrix.job_phrase }} - github_token: ${{ secrets.GITHUB_TOKEN }} - github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) - - name: Setup environment - uses: ./.github/actions/setup-environment-action - - name: run PostCommit Java Sickbay script - uses: ./.github/actions/gradle-command-self-hosted-action - with: - gradle-command: :javaPostCommitSickbay - - name: Archive JUnit Test Results - uses: actions/upload-artifact@v4 - if: ${{ !success() }} - with: - name: JUnit Test Results - path: "**/build/reports/tests/" - - name: Publish JUnit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - commit: '${{ env.prsha || env.GITHUB_SHA }}' - comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Sickbay_Python.yml b/.github/workflows/beam_PostCommit_Sickbay_Python.yml deleted file mode 100644 index 6d253e03723d..000000000000 --- a/.github/workflows/beam_PostCommit_Sickbay_Python.yml +++ /dev/null @@ -1,105 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -name: PostCommit Sickbay Python - -on: - pull_request_target: - paths: ['.github/trigger_files/beam_PostCommit_Sickbay_Python.json'] - workflow_dispatch: - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.sha || github.head_ref || github.ref }}-${{ github.event.schedule || github.event.comment.id || github.event.sender.login }}' - cancel-in-progress: true - -#Setting explicit permissions for the action to avoid the default permissions which are `write-all` in case of pull_request_target event -permissions: - actions: write - pull-requests: write - checks: write - contents: read - deployments: read - id-token: none - issues: write - discussions: read - packages: read - pages: read - repository-projects: read - security-events: read - statuses: read - -env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} - GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} - GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} - -jobs: - beam_PostCommit_Sickbay_Python: - name: ${{ matrix.job_name }} (${{ matrix.job_phrase_1 }} ${{ matrix.python_version }} ${{ matrix.job_phrase_2 }}) - runs-on: [self-hosted, ubuntu-20.04, main] - timeout-minutes: 180 - strategy: - fail-fast: false - matrix: - job_name: [beam_PostCommit_Sickbay_Python] - job_phrase_1: [Run Python] - job_phrase_2: [PostCommit Sickbay] - python_version: ['3.9', '3.10', '3.11', '3.12'] - if: | - github.event_name == 'workflow_dispatch' || - github.event_name == 'pull_request_target' || - (github.event_name == 'schedule' && github.repository == 'apache/beam') || - (startswith(github.event.comment.body, 'Run Python') && - endswith(github.event.comment.body, 'PostCommit Sickbay')) - steps: - - uses: actions/checkout@v4 - - name: Setup repository - uses: ./.github/actions/setup-action - with: - comment_phrase: ${{ matrix.job_phrase_1 }} ${{ matrix.python_version }} ${{ matrix.job_phrase_2 }} - github_token: ${{ secrets.GITHUB_TOKEN }} - github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase_1 }} ${{ matrix.python_version }} ${{ matrix.job_phrase_2 }}) - - name: Setup environment - uses: ./.github/actions/setup-environment-action - with: - python-version: ${{ matrix.python_version }} - - name: Set PY_VER_CLEAN - id: set_py_ver_clean - run: | - PY_VER=${{ matrix.python_version }} - PY_VER_CLEAN=${PY_VER//.} - echo "py_ver_clean=$PY_VER_CLEAN" >> $GITHUB_OUTPUT - - name: run PostCommit Python ${{ matrix.python_version }} script - uses: ./.github/actions/gradle-command-self-hosted-action - with: - gradle-command: :sdks:python:test-suites:dataflow:py${{steps.set_py_ver_clean.outputs.py_ver_clean}}:postCommitSickbay - arguments: | - -PpythonVersion=${{ matrix.python_version }} \ - - name: Archive Python Test Results - uses: actions/upload-artifact@v4 - if: failure() - with: - name: Python Test Results - path: '**/pytest*.xml' - - name: Publish Python Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - commit: '${{ env.prsha || env.GITHUB_SHA }}' - comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' From 8f75c61cfd9809d105cd865037b2ae3f076d503d Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 25 Nov 2024 11:54:12 -0800 Subject: [PATCH 010/135] More complete error message for StripErrorMetadata. (#33110) * More complete error message for StripErrorMetadata. * Update sdks/python/apache_beam/yaml/yaml_mapping.py Co-authored-by: Danny McCormick * fix formatting, paren --------- Co-authored-by: Danny McCormick --- sdks/python/apache_beam/yaml/yaml_mapping.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/yaml/yaml_mapping.py b/sdks/python/apache_beam/yaml/yaml_mapping.py index 3bef1a0a1101..9f92f59f42b6 100644 --- a/sdks/python/apache_beam/yaml/yaml_mapping.py +++ b/sdks/python/apache_beam/yaml/yaml_mapping.py @@ -467,7 +467,9 @@ def expand(self, pcoll): break else: raise ValueError( - f"No field name matches one of {self._ERROR_FIELD_NAMES}") + 'The input to this transform does not appear to be an error ' + + "output. Expected a schema'd input with a field named " + + ' or '.join(repr(fld) for fld in self._ERROR_FIELD_NAMES)) if fld is None: # This handles with_exception_handling() that returns bare tuples. From 2e4417f89c3d0bb7b349880233cf525715e95792 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 25 Nov 2024 15:01:50 -0500 Subject: [PATCH 011/135] Update website for 2.61.0 release (#33117) * Update website for 2.61.0 release * Update CHANGES.md * Update beam-2.61.0.md * Update downloads.md * Update CHANGES.md * Update beam-2.61.0.md --- CHANGES.md | 18 +---- website/www/site/config.toml | 2 +- .../www/site/content/en/blog/beam-2.61.0.md | 73 +++++++++++++++++++ .../site/content/en/get-started/downloads.md | 14 +++- 4 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 website/www/site/content/en/blog/beam-2.61.0.md diff --git a/CHANGES.md b/CHANGES.md index 979cbbd67329..7a5e89425650 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -88,12 +88,10 @@ * ([#X](https://github.com/apache/beam/issues/X)). -# [2.61.0] - Unreleased +# [2.61.0] - 2024-11-25 ## Highlights -* New highly anticipated feature X added to Python SDK ([#X](https://github.com/apache/beam/issues/X)). -* New highly anticipated feature Y added to Java SDK ([#Y](https://github.com/apache/beam/issues/Y)). * [Python] Introduce Managed Transforms API ([#31495](https://github.com/apache/beam/pull/31495)) * Flink 1.19 support added ([#32648](https://github.com/apache/beam/pull/32648)) @@ -111,36 +109,22 @@ ## New Features / Improvements * Added support for read with metadata in MqttIO (Java) ([#32195](https://github.com/apache/beam/issues/32195)) -* X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). * Added support for processing events which use a global sequence to "ordered" extension (Java) [#32540](https://github.com/apache/beam/pull/32540) * Add new meta-transform FlattenWith and Tee that allow one to introduce branching without breaking the linear/chaining style of pipeline construction. -## Breaking Changes - -* X behavior was changed ([#X](https://github.com/apache/beam/issues/X)). - ## Deprecations * Removed support for Flink 1.15 and 1.16 * Removed support for Python 3.8 -* X behavior is deprecated and will be removed in X versions ([#X](https://github.com/apache/beam/issues/X)). ## Bugfixes -* Fixed X (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). * (Java) Fixed tearDown not invoked when DoFn throws on Portable Runners ([#18592](https://github.com/apache/beam/issues/18592), [#31381](https://github.com/apache/beam/issues/31381)). * (Java) Fixed protobuf error with MapState.remove() in Dataflow Streaming Java Legacy Runner without Streaming Engine ([#32892](https://github.com/apache/beam/issues/32892)). * Adding flag to support conditionally disabling auto-commit in JdbcIO ReadFn ([#31111](https://github.com/apache/beam/issues/31111)) * (Python) Fixed BigQuery Enrichment bug that can lead to multiple conditions returning duplicate rows, batching returning incorrect results and conditions not scoped by row during batching ([#32780](https://github.com/apache/beam/pull/32780)). -## Security Fixes -* Fixed (CVE-YYYY-NNNN)[https://www.cve.org/CVERecord?id=CVE-YYYY-NNNN] (Java/Python/Go) ([#X](https://github.com/apache/beam/issues/X)). - -## Known Issues - -* ([#X](https://github.com/apache/beam/issues/X)). - # [2.60.0] - 2024-10-17 ## Highlights diff --git a/website/www/site/config.toml b/website/www/site/config.toml index d769f8434a7f..0ed8aef2a906 100644 --- a/website/www/site/config.toml +++ b/website/www/site/config.toml @@ -104,7 +104,7 @@ github_project_repo = "https://github.com/apache/beam" [params] description = "Apache Beam is an open source, unified model and set of language-specific SDKs for defining and executing data processing workflows, and also data ingestion and integration flows, supporting Enterprise Integration Patterns (EIPs) and Domain Specific Languages (DSLs). Dataflow pipelines simplify the mechanics of large-scale batch and streaming data processing and can run on a number of runtimes like Apache Flink, Apache Spark, and Google Cloud Dataflow (a cloud service). Beam also brings DSL in different languages, allowing users to easily implement their data integration processes." -release_latest = "2.60.0" +release_latest = "2.61.0" # The repository and branch where the files live in Github or Colab. This is used # to serve and stage from your local branch, but publish to the master branch. # e.g. https://github.com/{{< param branch_repo >}}/path/to/notebook.ipynb diff --git a/website/www/site/content/en/blog/beam-2.61.0.md b/website/www/site/content/en/blog/beam-2.61.0.md new file mode 100644 index 000000000000..6f0404af7846 --- /dev/null +++ b/website/www/site/content/en/blog/beam-2.61.0.md @@ -0,0 +1,73 @@ +--- +title: "Apache Beam 2.61.0" +date: 2024-11-25 15:00:00 -0500 +categories: + - blog + - release +authors: + - damccorm +--- + + +We are happy to present the new 2.61.0 release of Beam. +This release includes both improvements and new functionality. +See the [download page](/get-started/downloads/#2610-2024-11-25) for this release. + + + +For more information on changes in 2.61.0, check out the [detailed release notes](https://github.com/apache/beam/milestone/25). + +## Highlights + +* [Python] Introduce Managed Transforms API ([#31495](https://github.com/apache/beam/pull/31495)) +* Flink 1.19 support added ([#32648](https://github.com/apache/beam/pull/32648)) + +## I/Os + +* [Managed Iceberg] Support creating tables if needed ([#32686](https://github.com/apache/beam/pull/32686)) +* [Managed Iceberg] Now available in Python SDK ([#31495](https://github.com/apache/beam/pull/31495)) +* [Managed Iceberg] Add support for TIMESTAMP, TIME, and DATE types ([#32688](https://github.com/apache/beam/pull/32688)) +* BigQuery CDC writes are now available in Python SDK, only supported when using StorageWrite API at least once mode ([#32527](https://github.com/apache/beam/issues/32527)) +* [Managed Iceberg] Allow updating table partition specs during pipeline runtime ([#32879](https://github.com/apache/beam/pull/32879)) +* Added BigQueryIO as a Managed IO ([#31486](https://github.com/apache/beam/pull/31486)) +* Support for writing to [Solace messages queues](https://solace.com/) (`SolaceIO.Write`) added (Java) ([#31905](https://github.com/apache/beam/issues/31905)). + +## New Features / Improvements + +* Added support for read with metadata in MqttIO (Java) ([#32195](https://github.com/apache/beam/issues/32195)) +* Added support for processing events which use a global sequence to "ordered" extension (Java) [#32540](https://github.com/apache/beam/pull/32540) +* Add new meta-transform FlattenWith and Tee that allow one to introduce branching + without breaking the linear/chaining style of pipeline construction. + +## Deprecations + +* Removed support for Flink 1.15 and 1.16 +* Removed support for Python 3.8 + +## Bugfixes + +* (Java) Fixed tearDown not invoked when DoFn throws on Portable Runners ([#18592](https://github.com/apache/beam/issues/18592), [#31381](https://github.com/apache/beam/issues/31381)). +* (Java) Fixed protobuf error with MapState.remove() in Dataflow Streaming Java Legacy Runner without Streaming Engine ([#32892](https://github.com/apache/beam/issues/32892)). +* Adding flag to support conditionally disabling auto-commit in JdbcIO ReadFn ([#31111](https://github.com/apache/beam/issues/31111)) + +## Known Issues + +N/A + +For the most up to date list of known issues, see https://github.com/apache/beam/blob/master/CHANGES.md + +## List of Contributors + +According to git shortlog, the following people contributed to the 2.60.0 release. Thank you to all contributors! + +Ahmed Abualsaud, Ahmet Altay, Arun Pandian, Ayush Pandey, Chamikara Jayalath, Chris Ashcraft, Christoph Grotz, DKPHUONG, Damon, Danny Mccormick, Dmitry Ulyumdzhiev, Ferran Fernández Garrido, Hai Joey Tran, Hyeonho Kim, Idan Attias, Israel Herraiz, Jack McCluskey, Jan Lukavský, Jeff Kinard, Jeremy Edwards, Joey Tran, Kenneth Knowles, Maciej Szwaja, Manit Gupta, Mattie Fu, Michel Davit, Minbo Bae, Mohamed Awnallah, Naireen Hussain, Rebecca Szper, Reeba Qureshi, Reuven Lax, Robert Bradshaw, Robert Burke, S. Veyrié, Sam Whittle, Sergei Lilichenko, Shunping Huang, Steven van Rossum, Tan Le, Thiago Nunes, Vitaly Terentyev, Vlado Djerek, Yi Hu, claudevdm, fozzie15, johnjcasey, kushmiD, liferoad, martin trieu, pablo rodriguez defino, razvanculea, s21lee, tvalentyn, twosom diff --git a/website/www/site/content/en/get-started/downloads.md b/website/www/site/content/en/get-started/downloads.md index ff432996578d..dea5dc314b17 100644 --- a/website/www/site/content/en/get-started/downloads.md +++ b/website/www/site/content/en/get-started/downloads.md @@ -96,11 +96,19 @@ versions denoted `0.x.y`. ## Releases +### 2.61.0 (2024-11-25) + +Official [source code download](https://downloads.apache.org/beam/2.61.0/apache-beam-2.61.0-source-release.zip). +[SHA-512](https://downloads.apache.org/beam/2.61.0/apache-beam-2.61.0-source-release.zip.sha512). +[signature](https://downloads.apache.org/beam/2.61.0/apache-beam-2.61.0-source-release.zip.asc). + +[Release notes](https://github.com/apache/beam/releases/tag/v2.61.0) + ### 2.60.0 (2024-10-17) -Official [source code download](https://downloads.apache.org/beam/2.60.0/apache-beam-2.60.0-source-release.zip). -[SHA-512](https://downloads.apache.org/beam/2.60.0/apache-beam-2.60.0-source-release.zip.sha512). -[signature](https://downloads.apache.org/beam/2.60.0/apache-beam-2.60.0-source-release.zip.asc). +Official [source code download](https://archive.apache.org/dist/beam/2.60.0/apache-beam-2.60.0-source-release.zip). +[SHA-512](https://archive.apache.org/dist/beam/2.60.0/apache-beam-2.60.0-source-release.zip.sha512). +[signature](https://archive.apache.org/dist/beam/2.60.0/apache-beam-2.60.0-source-release.zip.asc). [Release notes](https://github.com/apache/beam/releases/tag/v2.60.0) From c2f56866e636e2b7da554b9c5d1e25982d7a39c1 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Fri, 22 Nov 2024 10:12:31 -0800 Subject: [PATCH 012/135] Move MetricAggregator to its only use in BundleBasedDirectRunner. --- .../apache_beam/internal/metrics/cells.py | 27 +-- sdks/python/apache_beam/metrics/cells.py | 160 +++++------------- .../runners/direct/direct_metrics.py | 73 +++++++- 3 files changed, 109 insertions(+), 151 deletions(-) diff --git a/sdks/python/apache_beam/internal/metrics/cells.py b/sdks/python/apache_beam/internal/metrics/cells.py index c7b546258a70..9a28ba46447a 100644 --- a/sdks/python/apache_beam/internal/metrics/cells.py +++ b/sdks/python/apache_beam/internal/metrics/cells.py @@ -28,7 +28,6 @@ from typing import TYPE_CHECKING from typing import Optional -from apache_beam.metrics.cells import MetricAggregator from apache_beam.metrics.cells import MetricCell from apache_beam.metrics.cells import MetricCellFactory from apache_beam.utils.histogram import Histogram @@ -50,10 +49,10 @@ class HistogramCell(MetricCell): """ def __init__(self, bucket_type): self._bucket_type = bucket_type - self.data = HistogramAggregator(bucket_type).identity_element() + self.data = HistogramData.identity_element(bucket_type) def reset(self): - self.data = HistogramAggregator(self._bucket_type).identity_element() + self.data = HistogramData.identity_element(self._bucket_type) def combine(self, other: 'HistogramCell') -> 'HistogramCell': result = HistogramCell(self._bucket_type) @@ -148,22 +147,6 @@ def combine(self, other: Optional['HistogramData']) -> 'HistogramData': return HistogramData(self.histogram.combine(other.histogram)) - -class HistogramAggregator(MetricAggregator): - """For internal use only; no backwards-compatibility guarantees. - - Aggregator for Histogram metric data during pipeline execution. - - Values aggregated should be ``HistogramData`` objects. - """ - def __init__(self, bucket_type: 'BucketType') -> None: - self._bucket_type = bucket_type - - def identity_element(self) -> HistogramData: - return HistogramData(Histogram(self._bucket_type)) - - def combine(self, x: HistogramData, y: HistogramData) -> HistogramData: - return x.combine(y) - - def result(self, x: HistogramData) -> HistogramResult: - return HistogramResult(x.get_cumulative()) + @staticmethod + def identity_element(bucket_type) -> HistogramData: + return HistogramData(Histogram(bucket_type)) diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index 63fc9f3f7cc9..2d4aba50f972 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -43,11 +43,7 @@ class fake_cython: globals()['cython'] = fake_cython __all__ = [ - 'MetricAggregator', - 'MetricCell', - 'MetricCellFactory', - 'DistributionResult', - 'GaugeResult' + 'MetricCell', 'MetricCellFactory', 'DistributionResult', 'GaugeResult' ] _LOGGER = logging.getLogger(__name__) @@ -110,11 +106,11 @@ class CounterCell(MetricCell): """ def __init__(self, *args): super().__init__(*args) - self.value = CounterAggregator.identity_element() + self.value = 0 def reset(self): # type: () -> None - self.value = CounterAggregator.identity_element() + self.value = 0 def combine(self, other): # type: (CounterCell) -> CounterCell @@ -175,11 +171,11 @@ class DistributionCell(MetricCell): """ def __init__(self, *args): super().__init__(*args) - self.data = DistributionAggregator.identity_element() + self.data = DistributionData.identity_element() def reset(self): # type: () -> None - self.data = DistributionAggregator.identity_element() + self.data = DistributionData.identity_element() def combine(self, other): # type: (DistributionCell) -> DistributionCell @@ -234,10 +230,10 @@ class GaugeCell(MetricCell): """ def __init__(self, *args): super().__init__(*args) - self.data = GaugeAggregator.identity_element() + self.data = GaugeData.identity_element() def reset(self): - self.data = GaugeAggregator.identity_element() + self.data = GaugeData.identity_element() def combine(self, other): # type: (GaugeCell) -> GaugeCell @@ -284,7 +280,7 @@ class StringSetCell(MetricCell): """ def __init__(self, *args): super().__init__(*args) - self.data = StringSetAggregator.identity_element() + self.data = StringSetData.identity_element() def add(self, value): self.update(value) @@ -308,9 +304,8 @@ def get_cumulative(self): def combine(self, other): # type: (StringSetCell) -> StringSetCell - combined = StringSetAggregator().combine(self.data, other.data) result = StringSetCell() - result.data = combined + result.data = self.data.combine(other.data) return result def to_runner_api_monitoring_info_impl(self, name, transform_id): @@ -324,7 +319,7 @@ def to_runner_api_monitoring_info_impl(self, name, transform_id): def reset(self): # type: () -> None - self.data = StringSetAggregator.identity_element() + self.data = StringSetData.identity_element() class DistributionResult(object): @@ -449,6 +444,10 @@ def get_cumulative(self): # type: () -> GaugeData return GaugeData(self.value, timestamp=self.timestamp) + def get_result(self): + # type: () -> GaugeResult + return GaugeResult(self.get_cumulative()) + def combine(self, other): # type: (Optional[GaugeData]) -> GaugeData if other is None: @@ -464,6 +463,11 @@ def singleton(value, timestamp=None): # type: (Optional[int], Optional[int]) -> GaugeData return GaugeData(value, timestamp=timestamp) + @staticmethod + def identity_element(): + # type: () -> GaugeData + return GaugeData(0, timestamp=0) + class DistributionData(object): """For internal use only; no backwards-compatibility guarantees. @@ -510,6 +514,9 @@ def get_cumulative(self): # type: () -> DistributionData return DistributionData(self.sum, self.count, self.min, self.max) + def get_result(self): + return DistributionResult(self.get_cumulative()) + def combine(self, other): # type: (Optional[DistributionData]) -> DistributionData if other is None: @@ -526,6 +533,11 @@ def singleton(value): # type: (int) -> DistributionData return DistributionData(value, 1, value, value) + @staticmethod + def identity_element(): + # type: () -> DistributionData + return DistributionData(0, 0, 2**63 - 1, -2**63) + class StringSetData(object): """For internal use only; no backwards-compatibility guarantees. @@ -568,6 +580,9 @@ def __repr__(self) -> str: def get_cumulative(self) -> "StringSetData": return StringSetData(set(self.string_set), self.string_size) + def get_result(self) -> set[str]: + return set(self.string_set) + def add(self, *strings): """ Add strings into this StringSetData and return the result StringSetData. @@ -585,6 +600,11 @@ def combine(self, other: "StringSetData") -> "StringSetData": if other is None: return self + if not other.string_set: + return self + elif not self.string_set: + return other + combined = set(self.string_set) string_size = self.add_until_capacity( combined, self.string_size, other.string_set) @@ -614,113 +634,9 @@ def add_until_capacity( return current_size @staticmethod - def singleton(value): - # type: (int) -> DistributionData - return DistributionData(value, 1, value, value) - - -class MetricAggregator(object): - """For internal use only; no backwards-compatibility guarantees. - - Base interface for aggregating metric data during pipeline execution.""" - def identity_element(self): - # type: () -> Any - - """Returns the identical element of an Aggregation. - - For the identity element, it must hold that - Aggregator.combine(any_element, identity_element) == any_element. - """ - raise NotImplementedError - - def combine(self, x, y): - # type: (Any, Any) -> Any - raise NotImplementedError - - def result(self, x): - # type: (Any) -> Any - raise NotImplementedError - - -class CounterAggregator(MetricAggregator): - """For internal use only; no backwards-compatibility guarantees. - - Aggregator for Counter metric data during pipeline execution. - - Values aggregated should be ``int`` objects. - """ - @staticmethod - def identity_element(): - # type: () -> int - return 0 - - def combine(self, x, y): - # type: (SupportsInt, SupportsInt) -> int - return int(x) + int(y) - - def result(self, x): - # type: (SupportsInt) -> int - return int(x) - - -class DistributionAggregator(MetricAggregator): - """For internal use only; no backwards-compatibility guarantees. - - Aggregator for Distribution metric data during pipeline execution. + def singleton(value: str) -> "StringSetData": + return StringSetData({value}) - Values aggregated should be ``DistributionData`` objects. - """ @staticmethod - def identity_element(): - # type: () -> DistributionData - return DistributionData(0, 0, 2**63 - 1, -2**63) - - def combine(self, x, y): - # type: (DistributionData, DistributionData) -> DistributionData - return x.combine(y) - - def result(self, x): - # type: (DistributionData) -> DistributionResult - return DistributionResult(x.get_cumulative()) - - -class GaugeAggregator(MetricAggregator): - """For internal use only; no backwards-compatibility guarantees. - - Aggregator for Gauge metric data during pipeline execution. - - Values aggregated should be ``GaugeData`` objects. - """ - @staticmethod - def identity_element(): - # type: () -> GaugeData - return GaugeData(0, timestamp=0) - - def combine(self, x, y): - # type: (GaugeData, GaugeData) -> GaugeData - result = x.combine(y) - return result - - def result(self, x): - # type: (GaugeData) -> GaugeResult - return GaugeResult(x.get_cumulative()) - - -class StringSetAggregator(MetricAggregator): - @staticmethod - def identity_element(): - # type: () -> StringSetData + def identity_element() -> "StringSetData": return StringSetData() - - def combine(self, x, y): - # type: (StringSetData, StringSetData) -> StringSetData - if len(x.string_set) == 0: - return y - elif len(y.string_set) == 0: - return x - else: - return x.combine(y) - - def result(self, x): - # type: (StringSetData) -> set - return set(x.string_set) diff --git a/sdks/python/apache_beam/runners/direct/direct_metrics.py b/sdks/python/apache_beam/runners/direct/direct_metrics.py index f715ce3bf521..693c0a64538e 100644 --- a/sdks/python/apache_beam/runners/direct/direct_metrics.py +++ b/sdks/python/apache_beam/runners/direct/direct_metrics.py @@ -25,22 +25,81 @@ import threading from collections import defaultdict -from apache_beam.metrics.cells import CounterAggregator -from apache_beam.metrics.cells import DistributionAggregator -from apache_beam.metrics.cells import GaugeAggregator -from apache_beam.metrics.cells import StringSetAggregator +from apache_beam.metrics.cells import DistributionData +from apache_beam.metrics.cells import GaugeData +from apache_beam.metrics.cells import StringSetData from apache_beam.metrics.execution import MetricKey from apache_beam.metrics.execution import MetricResult from apache_beam.metrics.metric import MetricResults +class MetricAggregator(object): + """For internal use only; no backwards-compatibility guarantees. + + Base interface for aggregating metric data during pipeline execution.""" + def identity_element(self): + # type: () -> Any + + """Returns the identical element of an Aggregation. + + For the identity element, it must hold that + Aggregator.combine(any_element, identity_element) == any_element. + """ + raise NotImplementedError + + def combine(self, x, y): + # type: (Any, Any) -> Any + raise NotImplementedError + + def result(self, x): + # type: (Any) -> Any + raise NotImplementedError + + +class CounterAggregator(MetricAggregator): + """For internal use only; no backwards-compatibility guarantees. + + Aggregator for Counter metric data during pipeline execution. + + Values aggregated should be ``int`` objects. + """ + @staticmethod + def identity_element(): + # type: () -> int + return 0 + + def combine(self, x, y): + # type: (SupportsInt, SupportsInt) -> int + return int(x) + int(y) + + def result(self, x): + # type: (SupportsInt) -> int + return int(x) + + +class GenericAggregator(MetricAggregator): + def __init__(self, data_class): + self._data_class = data_class + + def identity_element(self): + return self._data_class.identity_element() + + def combine(self, x, y): + return x.combine(y) + + def result(self, x): + return x.get_result() + + class DirectMetrics(MetricResults): def __init__(self): self._counters = defaultdict(lambda: DirectMetric(CounterAggregator())) self._distributions = defaultdict( - lambda: DirectMetric(DistributionAggregator())) - self._gauges = defaultdict(lambda: DirectMetric(GaugeAggregator())) - self._string_sets = defaultdict(lambda: DirectMetric(StringSetAggregator())) + lambda: DirectMetric(GenericAggregator(DistributionData))) + self._gauges = defaultdict( + lambda: DirectMetric(GenericAggregator(GuageData))) + self._string_sets = defaultdict( + lambda: DirectMetric(GenericAggregator(StringSetData))) def _apply_operation(self, bundle, updates, op): for k, v in updates.counters.items(): From aa939c17616d3f596a7374952bccad00d4620caf Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Fri, 22 Nov 2024 10:12:53 -0800 Subject: [PATCH 013/135] Consolidate implementation of metric cells. --- sdks/python/apache_beam/metrics/cells.pxd | 13 +++- sdks/python/apache_beam/metrics/cells.py | 94 +++++++++++------------ 2 files changed, 52 insertions(+), 55 deletions(-) diff --git a/sdks/python/apache_beam/metrics/cells.pxd b/sdks/python/apache_beam/metrics/cells.pxd index 98bb5eff0977..807aff7fe76a 100644 --- a/sdks/python/apache_beam/metrics/cells.pxd +++ b/sdks/python/apache_beam/metrics/cells.pxd @@ -33,6 +33,7 @@ cdef class CounterCell(MetricCell): cpdef bint update(self, value) except -1 +# Not using AbstractMetricCell so that data can be typed. cdef class DistributionCell(MetricCell): cdef readonly DistributionData data @@ -40,14 +41,18 @@ cdef class DistributionCell(MetricCell): cdef inline bint _update(self, value) except -1 -cdef class GaugeCell(MetricCell): +cdef class AbstractMetricCell(MetricCell): + cdef readonly object data_class cdef readonly object data + cdef bint _update_locked(self, value) except -1 -cdef class StringSetCell(MetricCell): - cdef readonly object data +cdef class GaugeCell(AbstractMetricCell): + pass - cdef inline bint _update(self, value) except -1 + +cdef class StringSetCell(AbstractMetricCell): + pass cdef class DistributionData(object): diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index 2d4aba50f972..ca74d50d1c85 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -217,47 +217,65 @@ def to_runner_api_monitoring_info_impl(self, name, transform_id): ptransform=transform_id) -class GaugeCell(MetricCell): +class AbstractMetricCell(MetricCell): """For internal use only; no backwards-compatibility guarantees. - Tracks the current value and delta for a gauge metric. - - Each cell tracks the state of a metric independently per context per bundle. - Therefore, each metric has a different cell in each bundle, that is later - aggregated. + Tracks the current value and delta for a metric with a data class. This class is thread safe. """ - def __init__(self, *args): - super().__init__(*args) - self.data = GaugeData.identity_element() + def __init__(self, data_class): + super().__init__() + self.data_class = data_class + self.data = self.data_class.identity_element() def reset(self): - self.data = GaugeData.identity_element() + self.data = self.data_class.identity_element() - def combine(self, other): - # type: (GaugeCell) -> GaugeCell - result = GaugeCell() + def combine(self, other: 'AbstractMetricCell') -> 'AbstractMetricCell': + result = type(self)() result.data = self.data.combine(other.data) return result def set(self, value): - self.update(value) + with self._lock: + self._update_locked(value) def update(self, value): - # type: (SupportsInt) -> None - value = int(value) with self._lock: - # Set the value directly without checking timestamp, because - # this value is naturally the latest value. - self.data.value = value - self.data.timestamp = time.time() + self._update_locked(value) + + def _update_locked(self, value): + raise NotImplementedError(type(self)) def get_cumulative(self): - # type: () -> GaugeData with self._lock: return self.data.get_cumulative() + def to_runner_api_monitoring_info_impl(self, name, transform_id): + raise NotImplementedError(type(self)) + + +class GaugeCell(AbstractMetricCell): + """For internal use only; no backwards-compatibility guarantees. + + Tracks the current value and delta for a gauge metric. + + Each cell tracks the state of a metric independently per context per bundle. + Therefore, each metric has a different cell in each bundle, that is later + aggregated. + + This class is thread safe. + """ + def __init__(self): + super().__init__(GaugeData) + + def _update_locked(self, value): + # Set the value directly without checking timestamp, because + # this value is naturally the latest value. + self.data.value = int(value) + self.data.timestamp = time.time() + def to_runner_api_monitoring_info_impl(self, name, transform_id): from apache_beam.metrics import monitoring_infos return monitoring_infos.int64_user_gauge( @@ -267,7 +285,7 @@ def to_runner_api_monitoring_info_impl(self, name, transform_id): ptransform=transform_id) -class StringSetCell(MetricCell): +class StringSetCell(AbstractMetricCell): """For internal use only; no backwards-compatibility guarantees. Tracks the current value for a StringSet metric. @@ -278,49 +296,23 @@ class StringSetCell(MetricCell): This class is thread safe. """ - def __init__(self, *args): - super().__init__(*args) - self.data = StringSetData.identity_element() + def __init__(self): + super().__init__(StringSetData) def add(self, value): self.update(value) - def update(self, value): - # type: (str) -> None - if cython.compiled: - # We will hold the GIL throughout the entire _update. - self._update(value) - else: - with self._lock: - self._update(value) - - def _update(self, value): + def _update_locked(self, value): self.data.add(value) - def get_cumulative(self): - # type: () -> StringSetData - with self._lock: - return self.data.get_cumulative() - - def combine(self, other): - # type: (StringSetCell) -> StringSetCell - result = StringSetCell() - result.data = self.data.combine(other.data) - return result - def to_runner_api_monitoring_info_impl(self, name, transform_id): from apache_beam.metrics import monitoring_infos - return monitoring_infos.user_set_string( name.namespace, name.name, self.get_cumulative(), ptransform=transform_id) - def reset(self): - # type: () -> None - self.data = StringSetData.identity_element() - class DistributionResult(object): """The result of a Distribution metric.""" From ea93ce51c781b8e03e937daa7f12ed9741f34c97 Mon Sep 17 00:00:00 2001 From: liferoad Date: Mon, 25 Nov 2024 16:04:41 -0500 Subject: [PATCH 014/135] Fixed the new flink container precommit (#33217) * Fixed the new flink container precommit * trigger it * tried to trigger the workflow * at least 2 workers * trigger it --- .../beam_PreCommit_Flink_Container.json | 4 +-- .../beam_PreCommit_Flink_Container.yml | 25 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/trigger_files/beam_PreCommit_Flink_Container.json b/.github/trigger_files/beam_PreCommit_Flink_Container.json index b75e2800330d..3f63c0c9975f 100644 --- a/.github/trigger_files/beam_PreCommit_Flink_Container.json +++ b/.github/trigger_files/beam_PreCommit_Flink_Container.json @@ -1,4 +1,4 @@ { - "https://github.com/apache/beam/pull/33206": "testing the new flink container workflow" + "comment": "Modify this file in a trivial way to cause this test suite to run", + "modification": 2 } - \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Flink_Container.yml b/.github/workflows/beam_PreCommit_Flink_Container.yml index 7b82469d2b81..42a402add966 100644 --- a/.github/workflows/beam_PreCommit_Flink_Container.yml +++ b/.github/workflows/beam_PreCommit_Flink_Container.yml @@ -18,8 +18,23 @@ name: PreCommit Flink Container on: pull_request_target: paths: + - 'model/**' + - 'sdks/python/**' + - 'release/**' + - 'sdks/java/io/kafka/**' + - 'runners/core-construction-java/**' + - 'runners/core-java/**' + - 'runners/extensions-java/**' + - 'runners/flink/**' + - 'runners/java-fn-execution/**' + - 'runners/reference/**' - '.github/trigger_files/beam_PreCommit_Flink_Container.json' - 'release/trigger_all_tests.json' + push: + branches: ['master', 'release-*'] + tags: 'v*' + schedule: + - cron: '0 */6 * * *' workflow_dispatch: # Setting explicit permissions for the action to avoid the default permissions which are `write-all` @@ -66,9 +81,7 @@ jobs: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.event_name == 'schedule' || - (github.event_name == 'pull_request_target' && - github.base_ref == 'master' && - github.event.pull_request.draft == false) || + github.event_name == 'pull_request_target' || github.event.comment.body == 'Run Flink Container PreCommit' runs-on: [self-hosted, ubuntu-20.04, main] timeout-minutes: 45 @@ -85,9 +98,9 @@ jobs: comment_phrase: ${{ matrix.job_phrase }} github_token: ${{ secrets.GITHUB_TOKEN }} github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) - - name: Start Flink with parallelism 1 + - name: Start Flink with 2 workers env: - FLINK_NUM_WORKERS: 1 + FLINK_NUM_WORKERS: 2 run: | cd ${{ github.workspace }}/.test-infra/dataproc; ./flink_cluster.sh create # Run a simple Go Combine load test to verify the Flink container @@ -109,7 +122,7 @@ jobs: --fanout=1 \ --top_count=10 \ --iterations=1" - + # Run a Python Combine load test to verify the Flink container - name: Run Flink Container Test with Python Combine timeout-minutes: 10 From c0ab7e5ac303575dbc7a79b92c87282183ef6f69 Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Mon, 25 Nov 2024 17:04:23 -0500 Subject: [PATCH 015/135] ZetaSQL test Java version fixes (#33213) * Align SDK container version with pipeline submission env * Disable ZetaSQL test on Java8 --- ...ommit_XVR_PythonUsingJavaSQL_Dataflow.json | 1 + .../beam_PostCommit_XVR_Samza.json | 1 + CHANGES.md | 1 + .../google-cloud-dataflow-java/build.gradle | 4 +++- .../python/apache_beam/transforms/sql_test.py | 20 +++++++++++++++++++ 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .github/trigger_files/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.json create mode 100644 .github/trigger_files/beam_PostCommit_XVR_Samza.json diff --git a/.github/trigger_files/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.json b/.github/trigger_files/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/.github/trigger_files/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.github/trigger_files/beam_PostCommit_XVR_Samza.json b/.github/trigger_files/beam_PostCommit_XVR_Samza.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/.github/trigger_files/beam_PostCommit_XVR_Samza.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 7a5e89425650..654512c3a4e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -70,6 +70,7 @@ ## Breaking Changes +* Upgraded ZetaSQL to 2024.11.1 ([#32902](https://github.com/apache/beam/pull/32902)). Java11+ is now needed if Beam's ZetaSQL component is used. * X behavior was changed ([#X](https://github.com/apache/beam/issues/X)). ## Deprecations diff --git a/runners/google-cloud-dataflow-java/build.gradle b/runners/google-cloud-dataflow-java/build.gradle index 3a337684bf18..811a3c15f836 100644 --- a/runners/google-cloud-dataflow-java/build.gradle +++ b/runners/google-cloud-dataflow-java/build.gradle @@ -16,6 +16,8 @@ * limitations under the License. */ +import static org.apache.beam.gradle.BeamModulePlugin.getSupportedJavaVersion + import groovy.json.JsonOutput plugins { id 'org.apache.beam.module' } @@ -341,7 +343,7 @@ tasks.register('buildAndPushDistrolessContainerImage', Task.class) { // task directly ('dependsOn buildAndPushDockerJavaContainer'). This ensures the correct // task ordering such that the registry doesn't get cleaned up prior to task completion. def buildAndPushDockerJavaContainer = tasks.register("buildAndPushDockerJavaContainer") { - def javaVer = "java8" + def javaVer = getSupportedJavaVersion() if(project.hasProperty('testJavaVersion')) { javaVer = "java${project.getProperty('testJavaVersion')}" } diff --git a/sdks/python/apache_beam/transforms/sql_test.py b/sdks/python/apache_beam/transforms/sql_test.py index 854aec078ce5..a7da253c4617 100644 --- a/sdks/python/apache_beam/transforms/sql_test.py +++ b/sdks/python/apache_beam/transforms/sql_test.py @@ -20,6 +20,7 @@ # pytype: skip-file import logging +import subprocess import typing import unittest @@ -69,6 +70,22 @@ class SqlTransformTest(unittest.TestCase): """ _multiprocess_can_split_ = True + @staticmethod + def _disable_zetasql_test(): + # disable if run on Java8 which is no longer supported by ZetaSQL + try: + result = subprocess.run(['java', '-version'], + check=True, + capture_output=True, + text=True) + version_line = result.stderr.splitlines()[0] + version = version_line.split()[2].strip('\"') + if version.startswith("1."): + return True + return False + except: # pylint: disable=bare-except + return False + def test_generate_data(self): with TestPipeline() as p: out = p | SqlTransform( @@ -150,6 +167,9 @@ def test_row(self): assert_that(out, equal_to([(1, 1), (4, 1), (100, 2)])) def test_zetasql_generate_data(self): + if self._disable_zetasql_test(): + raise unittest.SkipTest("ZetaSQL tests need Java11+") + with TestPipeline() as p: out = p | SqlTransform( """SELECT From 3c9a60bb3bed74af81f9cf0c0922e7c7eebe3f61 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 25 Nov 2024 12:05:33 -0800 Subject: [PATCH 016/135] Type hints. --- sdks/python/apache_beam/internal/metrics/cells.py | 2 +- sdks/python/apache_beam/metrics/cells.pxd | 2 +- sdks/python/apache_beam/metrics/cells.py | 6 ++---- sdks/python/apache_beam/runners/direct/direct_metrics.py | 4 +++- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sdks/python/apache_beam/internal/metrics/cells.py b/sdks/python/apache_beam/internal/metrics/cells.py index 9a28ba46447a..989dc7183045 100644 --- a/sdks/python/apache_beam/internal/metrics/cells.py +++ b/sdks/python/apache_beam/internal/metrics/cells.py @@ -148,5 +148,5 @@ def combine(self, other: Optional['HistogramData']) -> 'HistogramData': return HistogramData(self.histogram.combine(other.histogram)) @staticmethod - def identity_element(bucket_type) -> HistogramData: + def identity_element(bucket_type) -> 'HistogramData': return HistogramData(Histogram(bucket_type)) diff --git a/sdks/python/apache_beam/metrics/cells.pxd b/sdks/python/apache_beam/metrics/cells.pxd index 807aff7fe76a..c583dabeb0c0 100644 --- a/sdks/python/apache_beam/metrics/cells.pxd +++ b/sdks/python/apache_beam/metrics/cells.pxd @@ -43,7 +43,7 @@ cdef class DistributionCell(MetricCell): cdef class AbstractMetricCell(MetricCell): cdef readonly object data_class - cdef readonly object data + cdef public object data cdef bint _update_locked(self, value) except -1 diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index ca74d50d1c85..10ac7b3a1e69 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -27,11 +27,9 @@ import threading import time from datetime import datetime -from typing import Any from typing import Iterable from typing import Optional from typing import Set -from typing import SupportsInt try: import cython @@ -233,7 +231,7 @@ def reset(self): self.data = self.data_class.identity_element() def combine(self, other: 'AbstractMetricCell') -> 'AbstractMetricCell': - result = type(self)() + result = type(self)() # type: ignore[call-arg] result.data = self.data.combine(other.data) return result @@ -506,7 +504,7 @@ def get_cumulative(self): # type: () -> DistributionData return DistributionData(self.sum, self.count, self.min, self.max) - def get_result(self): + def get_result(self) -> DistributionResult: return DistributionResult(self.get_cumulative()) def combine(self, other): diff --git a/sdks/python/apache_beam/runners/direct/direct_metrics.py b/sdks/python/apache_beam/runners/direct/direct_metrics.py index 693c0a64538e..d20849d769af 100644 --- a/sdks/python/apache_beam/runners/direct/direct_metrics.py +++ b/sdks/python/apache_beam/runners/direct/direct_metrics.py @@ -24,6 +24,8 @@ import threading from collections import defaultdict +from typing import Any +from typing import SupportsInt from apache_beam.metrics.cells import DistributionData from apache_beam.metrics.cells import GaugeData @@ -97,7 +99,7 @@ def __init__(self): self._distributions = defaultdict( lambda: DirectMetric(GenericAggregator(DistributionData))) self._gauges = defaultdict( - lambda: DirectMetric(GenericAggregator(GuageData))) + lambda: DirectMetric(GenericAggregator(GaugeData))) self._string_sets = defaultdict( lambda: DirectMetric(GenericAggregator(StringSetData))) From ad8545cbb33e10e6ed48aae24358e30b6ffe3fa7 Mon Sep 17 00:00:00 2001 From: liferoad Date: Mon, 25 Nov 2024 21:41:12 -0500 Subject: [PATCH 017/135] [Accenture Baltics] Case Study (#33215) * [Accenture Baltics] Case Study * changed the date * changed the date * Fixed the captions * Removed the captions * removed the link --- .../en/case-studies/accenture_baltics.md | 105 ++++++++++++++++++ website/www/site/data/en/quotes.yaml | 5 + .../accenture/Jana_Polianskaja_sm.jpg | Bin 0 -> 284152 bytes .../case-study/accenture/dataflow_grafana.jpg | Bin 0 -> 153028 bytes .../accenture/dataflow_pipelines.png | Bin 0 -> 105348 bytes 5 files changed, 110 insertions(+) create mode 100644 website/www/site/content/en/case-studies/accenture_baltics.md create mode 100644 website/www/site/static/images/case-study/accenture/Jana_Polianskaja_sm.jpg create mode 100644 website/www/site/static/images/case-study/accenture/dataflow_grafana.jpg create mode 100644 website/www/site/static/images/case-study/accenture/dataflow_pipelines.png diff --git a/website/www/site/content/en/case-studies/accenture_baltics.md b/website/www/site/content/en/case-studies/accenture_baltics.md new file mode 100644 index 000000000000..98f9d9a8a687 --- /dev/null +++ b/website/www/site/content/en/case-studies/accenture_baltics.md @@ -0,0 +1,105 @@ +--- +title: "Accenture Baltics' Journey with Apache Beam to Streamlined Data Workflows for a Sustainable Energy Leader" +name: "Accenture Baltics" +icon: /images/logos/powered-by/accenture.png +hasNav: true +category: study +cardTitle: "Accenture Baltics' Journey with Apache Beam" +cardDescription: "Accenture Baltics uses Apache Beam on Google Cloud to build a robust data processing infrastructure for a sustainable energy leader.They use Beam to democratize data access, process data in real-time, and handle complex ETL tasks." +authorName: "Jana Polianskaja" +authorPosition: "Data Engineer @ Accenture Baltics" +authorImg: /images/case-study/accenture/Jana_Polianskaja_sm.jpg +publishDate: 2024-11-25T00:12:00+00:00 +--- + + +
+
+ +
+
+

+ “Apache Beam empowers team members who don’t have data engineering backgrounds to directly access and analyze BigQuery data by using SQL. The data scientists, the finance department, and production optimization teams all benefit from improved data accessibility, which gives them immediate access to critical information for faster analysis and decision-making.” +

+
+
+ +
+
+
+ Jana Polianskaja +
+
+ Data Engineer @ Accenture Baltics +
+
+
+
+
+ + +
+ +# Accenture Baltics' Journey with Apache Beam to Streamlined Data Workflows for a Sustainable Energy Leader + +## Background + +Accenture Baltics, a branch of the global professional services company Accenture, leverages its expertise across various industries to provide consulting, technology, and outsourcing solutions to clients worldwide. A specific project at Accenture Baltics highlights the effective implementation of Apache Beam to support a client who is a global leader in sustainable energy and uses Google Cloud. + +## Journey to Apache Beam + +The team responsible for transforming, curating, and preparing data, including transactional, analytics, and sensor data, for data scientists and other teams has been using Dataflow with Apache Beam for about five years. Dataflow with Beam is a natural choice for both streaming and batch data processing. For our workloads, we typically use the following configurations: worker machine types are `n1-standard-2` or `n1-standard-4`, and the maximum number of workers varies up to five, using the Dataflow runner. + +As an example, a streaming pipeline ingests transaction data from Pub/Sub, performs basic ETL and data cleaning, and outputs the results to BigQuery. A separate batch Dataflow pipeline evaluates a binary classification model, reading input and writing results to Google Cloud Storage. The following diagram shows a workflow that uses Pub/Sub to feed Dataflow pipelines across three Google Cloud projects. It also shows how Dataflow, Composer, Cloud Storage, BigQuery, and Grafana integrate into the architecture. + +
+ + Diagram of Accenture Baltics' Dataflow pipeline architecture + +
+ +## Use Cases + +Apache Beam is an invaluable tool for our use cases, particularly in the following areas: + +* **Democratizing data access:** Beam empowers team members without data engineering backgrounds to directly access and analyze BigQuery data using their SQL skills. The data scientists, the finance department, and production optimization teams all benefit from improved data accessibility, gaining immediate access to critical information for faster analysis and decision-making. +* **Real-time data processing:** Beam excels at ingesting and processing data in real time from sources like Pub/Sub. +* **ETL (extract, transform, load):** Beam effectively manages the full spectrum of data transformation and cleaning tasks, even when dealing with complex data structures. +* **Data routing and partitioning:** Beam enables sophisticated data routing and partitioning strategies. For example, it can automatically route failed transactions to a separate BigQuery table for further analysis. +* **Data deduplication and error handling:** Beam has been instrumental in tackling challenging tasks like deduplicating Pub/Sub messages and implementing robust error handling, such as for JSON parsing, that are crucial for maintaining data integrity and pipeline reliability. + +We also utilize Grafana (shown in below) with custom notification emails and tickets for comprehensive monitoring of our Beam pipelines. Notifications are generated from Google’s Cloud Logging and Cloud Monitoring services to ensure we stay informed about the performance and health of our pipelines. The seamless integration of Airflow with Dataflow and Beam further enhances our workflow, allowing us to effortlessly use operators such as `DataflowCreatePythonJobOperator` and `BeamRunPythonPipelineOperator` in [Airflow 2](https://airflow.apache.org/docs/apache-airflow-providers-google/stable/_api/airflow/providers/google/cloud/operators/dataflow/index.html). + +
+ + scheme + +
+ +## Results + +Our data processing infrastructure uses 12 distinct pipelines to manage and transform data across various projects within the organization. These pipelines are divided into two primary categories: + +* **Streaming pipelines:** These pipelines are designed to handle real-time or near real-time data streams. In our current setup, these pipelines process an average of 10,000 messages per second from Pub/Sub and about 200,000 rows per hour to BigQuery, ensuring that time-sensitive data is ingested and processed with minimal latency. +* **Batch pipelines:** These pipelines are optimized for processing large volumes of data in scheduled batches. Our current batch pipelines handle approximately two gigabytes of data per month, transforming and loading this data into our data warehouse for further analysis and reporting. + +Apache Beam has proven to be a highly effective solution for orchestrating and managing the complex data pipelines required by the client. By leveraging the capabilities of Dataflow, a fully managed service for executing Beam pipelines, we have successfully addressed and fulfilled the client's specific data processing needs. This powerful combination has enabled us to achieve scalability, reliability, and efficiency in handling large volumes of data, ensuring timely and accurate delivery of insights to the client. + +*Check out [my Medium blog](https://medium.com/@jana_om)\! I usually post about using Beam/Dataflow as an ETL tool with Python and how it works with other data engineering tools. My focus is on building projects that are easy to understand and learn from, especially if you want to get some hands-on experience with Beam.* + + +{{< case_study_feedback "AccentureBalticsStreaming" >}} +
+
diff --git a/website/www/site/data/en/quotes.yaml b/website/www/site/data/en/quotes.yaml index 4139d855fff5..db45c4344346 100644 --- a/website/www/site/data/en/quotes.yaml +++ b/website/www/site/data/en/quotes.yaml @@ -76,6 +76,11 @@ logoUrl: /images/logos/powered-by/yelp.png linkUrl: case-studies/yelp_streaming/index.html linkText: Learn more +- text: Accenture Baltics uses Apache Beam on Google Cloud to build a robust data processing infrastructure for a sustainable energy leader.They use Beam to democratize data access, process data in real-time, and handle complex ETL tasks. + icon: icons/quote-icon.svg + logoUrl: /images/logos/powered-by/accenture.png + linkUrl: case-studies/accenture_baltics/index.html + linkText: Learn more - text: Have a story to share? Your logo could be here. icon: icons/quote-icon.svg logoUrl: images/logos/powered-by/blank.jpg diff --git a/website/www/site/static/images/case-study/accenture/Jana_Polianskaja_sm.jpg b/website/www/site/static/images/case-study/accenture/Jana_Polianskaja_sm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1fb63edd227fd2831cda1fb83dc3dab873a49ac0 GIT binary patch literal 284152 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
(t?uDJ;JMeF<~bjqNT8>TDM{oX}n&S)z&; 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�Qoe;bjU50vFH0 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�%*+84(@HXtEoBn99&M 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%0DBXQkGB@y)Vo;W<> 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 ji*t0tj;LetX$cTZu#g8wC&2G3p5K2yW;& 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 literal 0 HcmV?d00001 diff --git a/website/www/site/static/images/case-study/accenture/dataflow_grafana.jpg b/website/www/site/static/images/case-study/accenture/dataflow_grafana.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a7471c1798fc177edb19e4a0c7e78f7adcf9652 GIT binary patch literal 153028 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 literal 0 HcmV?d00001 diff --git a/website/www/site/static/images/case-study/accenture/dataflow_pipelines.png b/website/www/site/static/images/case-study/accenture/dataflow_pipelines.png new file mode 100644 index 0000000000000000000000000000000000000000..423044910e36ebd67f58545c3e2634e08838ed68 GIT binary patch literal 105348 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∓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

F?yCOF`cYj{Nc-DIo1+i}bNVm`Sm|+ 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�)ju4}LW1yn+Tf=CbwK}3)s86*fu&N)jE$skD1 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%0DeCDsri%m9)0AD_1#- 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πwOCzV2Jm9X?FTMYM#v>@ zYO_;9nhQCDu!soE!T>x2$(jV|B*7QZ&k6Z`Q1`>_{nGa-KuN( ziF|79Rr#m;ii7ph2NE%7P}3gB({(DH>zZ5>+)-$w)@w|^>Ue&L4HZI!td1skd*()g z(|ULXss&GrP*fU7sp788je~M&%}GF>)&DwT_P>I*!SDVp6Nz-VJDGm;KH#mPvLM8C zm(g)gGlGv>*IC1lsh-%^n- Jk~Dqze*jMoSLpx% literal 0 HcmV?d00001 From aa21e4a84b3594e9ddd6ddb436c9628cb6660552 Mon Sep 17 00:00:00 2001 From: martin trieu Date: Tue, 26 Nov 2024 02:51:57 -0600 Subject: [PATCH 018/135] Integrate direct path with StreamingDataflowWorker code path (#32778) --- .../DataflowStreamingPipelineOptions.java | 15 +- .../worker/StreamingDataflowWorker.java | 594 +++++++++++------- .../FanOutStreamingEngineWorkerHarness.java | 1 + .../harness/SingleSourceWorkerHarness.java | 13 +- .../harness/StreamingWorkerHarness.java | 3 +- .../harness/StreamingWorkerStatusPages.java | 2 +- .../StreamingWorkerStatusReporter.java | 131 ++-- .../client/throttling/ThrottleTimer.java | 3 +- .../throttling/ThrottledTimeTracker.java | 32 + .../refresh/StreamPoolHeartbeatSender.java | 4 +- .../worker/StreamingDataflowWorkerTest.java | 42 +- .../SingleSourceWorkerHarnessTest.java | 2 + .../StreamingWorkerStatusReporterTest.java | 53 +- .../StreamPoolHeartbeatSenderTest.java | 6 +- 14 files changed, 561 insertions(+), 340 deletions(-) create mode 100644 runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottledTimeTracker.java diff --git a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowStreamingPipelineOptions.java b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowStreamingPipelineOptions.java index 6a0208f1447f..61c38dde2b42 100644 --- a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowStreamingPipelineOptions.java +++ b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowStreamingPipelineOptions.java @@ -20,6 +20,7 @@ import org.apache.beam.sdk.options.Default; import org.apache.beam.sdk.options.DefaultValueFactory; import org.apache.beam.sdk.options.Description; +import org.apache.beam.sdk.options.ExperimentalOptions; import org.apache.beam.sdk.options.Hidden; import org.apache.beam.sdk.options.PipelineOptions; import org.joda.time.Duration; @@ -219,10 +220,8 @@ public interface DataflowStreamingPipelineOptions extends PipelineOptions { void setWindmillServiceStreamMaxBackoffMillis(int value); - @Description( - "If true, Dataflow streaming pipeline will be running in direct path mode." - + " VMs must have IPv6 enabled for this to work.") - @Default.Boolean(false) + @Description("Enables direct path mode for streaming engine.") + @Default.InstanceFactory(EnableWindmillServiceDirectPathFactory.class) boolean getIsWindmillServiceDirectPathEnabled(); void setIsWindmillServiceDirectPathEnabled(boolean isWindmillServiceDirectPathEnabled); @@ -300,4 +299,12 @@ public Integer create(PipelineOptions options) { return streamingOptions.isEnableStreamingEngine() ? Integer.MAX_VALUE : 1; } } + + /** EnableStreamingEngine defaults to false unless one of the experiment is set. */ + class EnableWindmillServiceDirectPathFactory implements DefaultValueFactory { + @Override + public Boolean create(PipelineOptions options) { + return ExperimentalOptions.hasExperiment(options, "enable_windmill_service_direct_path"); + } + } } diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java index 6ce60283735f..088a28e9b2db 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java @@ -17,11 +17,11 @@ */ package org.apache.beam.runners.dataflow.worker; -import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument; +import static org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.WindmillChannelFactory.remoteChannel; -import com.google.api.services.dataflow.model.CounterUpdate; import com.google.api.services.dataflow.model.MapTask; import com.google.auto.value.AutoValue; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -33,24 +33,28 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import javax.annotation.Nullable; import org.apache.beam.runners.core.metrics.MetricsLogger; import org.apache.beam.runners.dataflow.DataflowRunner; import org.apache.beam.runners.dataflow.options.DataflowWorkerHarnessOptions; -import org.apache.beam.runners.dataflow.worker.counters.DataflowCounterUpdateExtractor; import org.apache.beam.runners.dataflow.worker.status.DebugCapture; import org.apache.beam.runners.dataflow.worker.status.WorkerStatusPages; import org.apache.beam.runners.dataflow.worker.streaming.ComputationState; import org.apache.beam.runners.dataflow.worker.streaming.ComputationStateCache; import org.apache.beam.runners.dataflow.worker.streaming.StageInfo; +import org.apache.beam.runners.dataflow.worker.streaming.WeightedSemaphore; import org.apache.beam.runners.dataflow.worker.streaming.WorkHeartbeatResponseProcessor; import org.apache.beam.runners.dataflow.worker.streaming.config.ComputationConfig; import org.apache.beam.runners.dataflow.worker.streaming.config.FixedGlobalConfigHandle; import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingApplianceComputationConfigFetcher; import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingEngineComputationConfigFetcher; import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingGlobalConfig; +import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingGlobalConfigHandle; import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingGlobalConfigHandleImpl; +import org.apache.beam.runners.dataflow.worker.streaming.harness.FanOutStreamingEngineWorkerHarness; import org.apache.beam.runners.dataflow.worker.streaming.harness.SingleSourceWorkerHarness; import org.apache.beam.runners.dataflow.worker.streaming.harness.SingleSourceWorkerHarness.GetWorkSender; import org.apache.beam.runners.dataflow.worker.streaming.harness.StreamingCounters; @@ -59,12 +63,15 @@ import org.apache.beam.runners.dataflow.worker.streaming.harness.StreamingWorkerStatusReporter; import org.apache.beam.runners.dataflow.worker.util.BoundedQueueExecutor; import org.apache.beam.runners.dataflow.worker.util.MemoryMonitor; +import org.apache.beam.runners.dataflow.worker.windmill.ApplianceWindmillClient; import org.apache.beam.runners.dataflow.worker.windmill.Windmill; import org.apache.beam.runners.dataflow.worker.windmill.Windmill.JobHeader; import org.apache.beam.runners.dataflow.worker.windmill.WindmillServerStub; import org.apache.beam.runners.dataflow.worker.windmill.appliance.JniWindmillApplianceServer; +import org.apache.beam.runners.dataflow.worker.windmill.client.CloseableStream; import org.apache.beam.runners.dataflow.worker.windmill.client.WindmillStream.GetDataStream; import org.apache.beam.runners.dataflow.worker.windmill.client.WindmillStreamPool; +import org.apache.beam.runners.dataflow.worker.windmill.client.commits.Commit; import org.apache.beam.runners.dataflow.worker.windmill.client.commits.Commits; import org.apache.beam.runners.dataflow.worker.windmill.client.commits.CompleteCommit; import org.apache.beam.runners.dataflow.worker.windmill.client.commits.StreamingApplianceWorkCommitter; @@ -78,8 +85,16 @@ import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.GrpcDispatcherClient; import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.GrpcWindmillServer; import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.GrpcWindmillStreamFactory; +import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.ChannelCache; +import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.ChannelCachingRemoteStubFactory; +import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.ChannelCachingStubFactory; +import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.IsolationChannel; +import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.WindmillStubFactoryFactory; import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.WindmillStubFactoryFactoryImpl; +import org.apache.beam.runners.dataflow.worker.windmill.client.throttling.ThrottledTimeTracker; import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillStateCache; +import org.apache.beam.runners.dataflow.worker.windmill.work.budget.GetWorkBudget; +import org.apache.beam.runners.dataflow.worker.windmill.work.budget.GetWorkBudgetDistributors; import org.apache.beam.runners.dataflow.worker.windmill.work.processing.StreamingWorkScheduler; import org.apache.beam.runners.dataflow.worker.windmill.work.processing.failures.FailureTracker; import org.apache.beam.runners.dataflow.worker.windmill.work.processing.failures.StreamingApplianceFailureTracker; @@ -89,6 +104,7 @@ import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.ApplianceHeartbeatSender; import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.HeartbeatSender; import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.StreamPoolHeartbeatSender; +import org.apache.beam.sdk.annotations.Internal; import org.apache.beam.sdk.fn.IdGenerator; import org.apache.beam.sdk.fn.IdGenerators; import org.apache.beam.sdk.fn.JvmInitializers; @@ -98,18 +114,25 @@ import org.apache.beam.sdk.metrics.MetricsEnvironment; import org.apache.beam.sdk.util.construction.CoderTranslation; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.CacheStats; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableSet; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.net.HostAndPort; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.util.concurrent.ThreadFactoryBuilder; import org.joda.time.Duration; import org.joda.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Implements a Streaming Dataflow worker. */ +/** + * For internal use only. + * + *

Implements a Streaming Dataflow worker. + */ @SuppressWarnings({ "nullness" // TODO(https://github.com/apache/beam/issues/20497) }) +@Internal public final class StreamingDataflowWorker { /** @@ -142,6 +165,10 @@ public final class StreamingDataflowWorker { private static final int DEFAULT_STATUS_PORT = 8081; private static final Random CLIENT_ID_GENERATOR = new Random(); private static final String CHANNELZ_PATH = "/channelz"; + private static final String BEAM_FN_API_EXPERIMENT = "beam_fn_api"; + private static final String ENABLE_IPV6_EXPERIMENT = "enable_private_ipv6_google_access"; + private static final String STREAMING_ENGINE_USE_JOB_SETTINGS_FOR_HEARTBEAT_POOL_EXPERIMENT = + "streaming_engine_use_job_settings_for_heartbeat_pool"; private final WindmillStateCache stateCache; private final StreamingWorkerStatusPages statusPages; @@ -155,9 +182,8 @@ public final class StreamingDataflowWorker { private final ReaderCache readerCache; private final DataflowExecutionStateSampler sampler = DataflowExecutionStateSampler.instance(); private final ActiveWorkRefresher activeWorkRefresher; - private final WorkCommitter workCommitter; private final StreamingWorkerStatusReporter workerStatusReporter; - private final StreamingCounters streamingCounters; + private final int numCommitThreads; private StreamingDataflowWorker( WindmillServerStub windmillServer, @@ -170,17 +196,17 @@ private StreamingDataflowWorker( DataflowWorkerHarnessOptions options, HotKeyLogger hotKeyLogger, Supplier clock, - StreamingWorkerStatusReporter workerStatusReporter, + StreamingWorkerStatusReporterFactory streamingWorkerStatusReporterFactory, FailureTracker failureTracker, WorkFailureProcessor workFailureProcessor, StreamingCounters streamingCounters, MemoryMonitor memoryMonitor, GrpcWindmillStreamFactory windmillStreamFactory, ScheduledExecutorService activeWorkRefreshExecutorFn, - ConcurrentMap stageInfoMap) { + ConcurrentMap stageInfoMap, + @Nullable GrpcDispatcherClient dispatcherClient) { // Register standard file systems. FileSystems.setDefaultPipelineOptions(options); - this.configFetcher = configFetcher; this.computationStateCache = computationStateCache; this.stateCache = windmillStateCache; @@ -189,35 +215,13 @@ private StreamingDataflowWorker( Duration.standardSeconds(options.getReaderCacheTimeoutSec()), Executors.newCachedThreadPool()); this.options = options; - - boolean windmillServiceEnabled = options.isEnableStreamingEngine(); - - int numCommitThreads = 1; - if (windmillServiceEnabled && options.getWindmillServiceCommitThreads() > 0) { - numCommitThreads = options.getWindmillServiceCommitThreads(); - } - - this.workCommitter = - windmillServiceEnabled - ? StreamingEngineWorkCommitter.builder() - .setCommitByteSemaphore(Commits.maxCommitByteSemaphore()) - .setCommitWorkStreamFactory( - WindmillStreamPool.create( - numCommitThreads, - COMMIT_STREAM_TIMEOUT, - windmillServer::commitWorkStream) - ::getCloseableStream) - .setNumCommitSenders(numCommitThreads) - .setOnCommitComplete(this::onCompleteCommit) - .build() - : StreamingApplianceWorkCommitter.create( - windmillServer::commitWork, this::onCompleteCommit); - this.workUnitExecutor = workUnitExecutor; - - this.workerStatusReporter = workerStatusReporter; - this.streamingCounters = streamingCounters; this.memoryMonitor = BackgroundMemoryMonitor.create(memoryMonitor); + this.numCommitThreads = + options.isEnableStreamingEngine() + ? Math.max(options.getWindmillServiceCommitThreads(), 1) + : 1; + StreamingWorkScheduler streamingWorkScheduler = StreamingWorkScheduler.create( options, @@ -234,107 +238,200 @@ private StreamingDataflowWorker( ID_GENERATOR, configFetcher.getGlobalConfigHandle(), stageInfoMap); - ThrottlingGetDataMetricTracker getDataMetricTracker = new ThrottlingGetDataMetricTracker(memoryMonitor); - WorkerStatusPages workerStatusPages = - WorkerStatusPages.create(DEFAULT_STATUS_PORT, memoryMonitor); - StreamingWorkerStatusPages.Builder statusPagesBuilder = StreamingWorkerStatusPages.builder(); - int stuckCommitDurationMillis; - GetDataClient getDataClient; - HeartbeatSender heartbeatSender; - if (windmillServiceEnabled) { - WindmillStreamPool getDataStreamPool = - WindmillStreamPool.create( - Math.max(1, options.getWindmillGetDataStreamCount()), - GET_DATA_STREAM_TIMEOUT, - windmillServer::getDataStream); - getDataClient = new StreamPoolGetDataClient(getDataMetricTracker, getDataStreamPool); - if (options.getUseSeparateWindmillHeartbeatStreams() != null) { + // Status page members. Different implementations on whether the harness is streaming engine + // direct path, streaming engine cloud path, or streaming appliance. + @Nullable ChannelzServlet channelzServlet = null; + Consumer getDataStatusProvider; + Supplier currentActiveCommitBytesProvider; + if (isDirectPathPipeline(options)) { + WeightedSemaphore maxCommitByteSemaphore = Commits.maxCommitByteSemaphore(); + FanOutStreamingEngineWorkerHarness fanOutStreamingEngineWorkerHarness = + FanOutStreamingEngineWorkerHarness.create( + createJobHeader(options, clientId), + GetWorkBudget.builder() + .setItems(chooseMaxBundlesOutstanding(options)) + .setBytes(MAX_GET_WORK_FETCH_BYTES) + .build(), + windmillStreamFactory, + (workItem, watermarks, processingContext, getWorkStreamLatencies) -> + computationStateCache + .get(processingContext.computationId()) + .ifPresent( + computationState -> { + memoryMonitor.waitForResources("GetWork"); + streamingWorkScheduler.scheduleWork( + computationState, + workItem, + watermarks, + processingContext, + getWorkStreamLatencies); + }), + createFanOutStubFactory(options), + GetWorkBudgetDistributors.distributeEvenly(), + Preconditions.checkNotNull(dispatcherClient), + commitWorkStream -> + StreamingEngineWorkCommitter.builder() + // Share the commitByteSemaphore across all created workCommitters. + .setCommitByteSemaphore(maxCommitByteSemaphore) + .setBackendWorkerToken(commitWorkStream.backendWorkerToken()) + .setOnCommitComplete(this::onCompleteCommit) + .setNumCommitSenders(Math.max(options.getWindmillServiceCommitThreads(), 1)) + .setCommitWorkStreamFactory( + () -> CloseableStream.create(commitWorkStream, () -> {})) + .build(), + getDataMetricTracker); + getDataStatusProvider = getDataMetricTracker::printHtml; + currentActiveCommitBytesProvider = + fanOutStreamingEngineWorkerHarness::currentActiveCommitBytes; + channelzServlet = + createChannelzServlet( + options, fanOutStreamingEngineWorkerHarness::currentWindmillEndpoints); + this.streamingWorkerHarness = fanOutStreamingEngineWorkerHarness; + } else { + // Non-direct path pipelines. + Windmill.GetWorkRequest request = + Windmill.GetWorkRequest.newBuilder() + .setClientId(clientId) + .setMaxItems(chooseMaxBundlesOutstanding(options)) + .setMaxBytes(MAX_GET_WORK_FETCH_BYTES) + .build(); + GetDataClient getDataClient; + HeartbeatSender heartbeatSender; + WorkCommitter workCommitter; + GetWorkSender getWorkSender; + if (options.isEnableStreamingEngine()) { + WindmillStreamPool getDataStreamPool = + WindmillStreamPool.create( + Math.max(1, options.getWindmillGetDataStreamCount()), + GET_DATA_STREAM_TIMEOUT, + windmillServer::getDataStream); + getDataClient = new StreamPoolGetDataClient(getDataMetricTracker, getDataStreamPool); heartbeatSender = - StreamPoolHeartbeatSender.Create( - Boolean.TRUE.equals(options.getUseSeparateWindmillHeartbeatStreams()) - ? separateHeartbeatPool(windmillServer) - : getDataStreamPool); - + createStreamingEngineHeartbeatSender( + options, windmillServer, getDataStreamPool, configFetcher.getGlobalConfigHandle()); + channelzServlet = + createChannelzServlet(options, windmillServer::getWindmillServiceEndpoints); + workCommitter = + StreamingEngineWorkCommitter.builder() + .setCommitWorkStreamFactory( + WindmillStreamPool.create( + numCommitThreads, + COMMIT_STREAM_TIMEOUT, + windmillServer::commitWorkStream) + ::getCloseableStream) + .setCommitByteSemaphore(Commits.maxCommitByteSemaphore()) + .setNumCommitSenders(numCommitThreads) + .setOnCommitComplete(this::onCompleteCommit) + .build(); + getWorkSender = + GetWorkSender.forStreamingEngine( + receiver -> windmillServer.getWorkStream(request, receiver)); } else { - heartbeatSender = - StreamPoolHeartbeatSender.Create( - separateHeartbeatPool(windmillServer), - getDataStreamPool, - configFetcher.getGlobalConfigHandle()); + getDataClient = new ApplianceGetDataClient(windmillServer, getDataMetricTracker); + heartbeatSender = new ApplianceHeartbeatSender(windmillServer::getData); + workCommitter = + StreamingApplianceWorkCommitter.create( + windmillServer::commitWork, this::onCompleteCommit); + getWorkSender = GetWorkSender.forAppliance(() -> windmillServer.getWork(request)); } - stuckCommitDurationMillis = - options.getStuckCommitDurationMillis() > 0 ? options.getStuckCommitDurationMillis() : 0; - statusPagesBuilder - .setDebugCapture( - new DebugCapture.Manager(options, workerStatusPages.getDebugCapturePages())) - .setChannelzServlet( - new ChannelzServlet( - CHANNELZ_PATH, options, windmillServer::getWindmillServiceEndpoints)) - .setWindmillStreamFactory(windmillStreamFactory); - } else { - getDataClient = new ApplianceGetDataClient(windmillServer, getDataMetricTracker); - heartbeatSender = new ApplianceHeartbeatSender(windmillServer::getData); - stuckCommitDurationMillis = 0; + getDataStatusProvider = getDataClient::printHtml; + currentActiveCommitBytesProvider = workCommitter::currentActiveCommitBytes; + + this.streamingWorkerHarness = + SingleSourceWorkerHarness.builder() + .setStreamingWorkScheduler(streamingWorkScheduler) + .setWorkCommitter(workCommitter) + .setGetDataClient(getDataClient) + .setComputationStateFetcher(this.computationStateCache::get) + .setWaitForResources(() -> memoryMonitor.waitForResources("GetWork")) + .setHeartbeatSender(heartbeatSender) + .setThrottledTimeTracker(windmillServer::getAndResetThrottleTime) + .setGetWorkSender(getWorkSender) + .build(); } + this.workerStatusReporter = + streamingWorkerStatusReporterFactory.createStatusReporter(streamingWorkerHarness); this.activeWorkRefresher = new ActiveWorkRefresher( clock, options.getActiveWorkRefreshPeriodMillis(), - stuckCommitDurationMillis, + options.isEnableStreamingEngine() + ? Math.max(options.getStuckCommitDurationMillis(), 0) + : 0, computationStateCache::getAllPresentComputations, sampler, activeWorkRefreshExecutorFn, getDataMetricTracker::trackHeartbeats); this.statusPages = - statusPagesBuilder + createStatusPageBuilder(options, windmillStreamFactory, memoryMonitor) .setClock(clock) .setClientId(clientId) .setIsRunning(running) - .setStatusPages(workerStatusPages) .setStateCache(stateCache) .setComputationStateCache(this.computationStateCache) - .setCurrentActiveCommitBytes(workCommitter::currentActiveCommitBytes) - .setGetDataStatusProvider(getDataClient::printHtml) .setWorkUnitExecutor(workUnitExecutor) .setGlobalConfigHandle(configFetcher.getGlobalConfigHandle()) + .setChannelzServlet(channelzServlet) + .setGetDataStatusProvider(getDataStatusProvider) + .setCurrentActiveCommitBytes(currentActiveCommitBytesProvider) .build(); - Windmill.GetWorkRequest request = - Windmill.GetWorkRequest.newBuilder() - .setClientId(clientId) - .setMaxItems(chooseMaximumBundlesOutstanding()) - .setMaxBytes(MAX_GET_WORK_FETCH_BYTES) - .build(); - - this.streamingWorkerHarness = - SingleSourceWorkerHarness.builder() - .setStreamingWorkScheduler(streamingWorkScheduler) - .setWorkCommitter(workCommitter) - .setGetDataClient(getDataClient) - .setComputationStateFetcher(this.computationStateCache::get) - .setWaitForResources(() -> memoryMonitor.waitForResources("GetWork")) - .setHeartbeatSender(heartbeatSender) - .setGetWorkSender( - windmillServiceEnabled - ? GetWorkSender.forStreamingEngine( - receiver -> windmillServer.getWorkStream(request, receiver)) - : GetWorkSender.forAppliance(() -> windmillServer.getWork(request))) - .build(); - - LOG.debug("windmillServiceEnabled: {}", windmillServiceEnabled); + LOG.debug("isDirectPathEnabled: {}", options.getIsWindmillServiceDirectPathEnabled()); + LOG.debug("windmillServiceEnabled: {}", options.isEnableStreamingEngine()); LOG.debug("WindmillServiceEndpoint: {}", options.getWindmillServiceEndpoint()); LOG.debug("WindmillServicePort: {}", options.getWindmillServicePort()); LOG.debug("LocalWindmillHostport: {}", options.getLocalWindmillHostport()); } - private static WindmillStreamPool separateHeartbeatPool( - WindmillServerStub windmillServer) { - return WindmillStreamPool.create(1, GET_DATA_STREAM_TIMEOUT, windmillServer::getDataStream); + private static StreamingWorkerStatusPages.Builder createStatusPageBuilder( + DataflowWorkerHarnessOptions options, + GrpcWindmillStreamFactory windmillStreamFactory, + MemoryMonitor memoryMonitor) { + WorkerStatusPages workerStatusPages = + WorkerStatusPages.create(DEFAULT_STATUS_PORT, memoryMonitor); + + StreamingWorkerStatusPages.Builder streamingStatusPages = + StreamingWorkerStatusPages.builder().setStatusPages(workerStatusPages); + + return options.isEnableStreamingEngine() + ? streamingStatusPages + .setDebugCapture( + new DebugCapture.Manager(options, workerStatusPages.getDebugCapturePages())) + .setWindmillStreamFactory(windmillStreamFactory) + : streamingStatusPages; + } + + private static ChannelzServlet createChannelzServlet( + DataflowWorkerHarnessOptions options, + Supplier> windmillEndpointProvider) { + return new ChannelzServlet(CHANNELZ_PATH, options, windmillEndpointProvider); + } + + private static HeartbeatSender createStreamingEngineHeartbeatSender( + DataflowWorkerHarnessOptions options, + WindmillServerStub windmillClient, + WindmillStreamPool getDataStreamPool, + StreamingGlobalConfigHandle globalConfigHandle) { + // Experiment gates the logic till backend changes are rollback safe + if (!DataflowRunner.hasExperiment( + options, STREAMING_ENGINE_USE_JOB_SETTINGS_FOR_HEARTBEAT_POOL_EXPERIMENT) + || options.getUseSeparateWindmillHeartbeatStreams() != null) { + return StreamPoolHeartbeatSender.create( + Boolean.TRUE.equals(options.getUseSeparateWindmillHeartbeatStreams()) + ? WindmillStreamPool.create(1, GET_DATA_STREAM_TIMEOUT, windmillClient::getDataStream) + : getDataStreamPool); + + } else { + return StreamPoolHeartbeatSender.create( + WindmillStreamPool.create(1, GET_DATA_STREAM_TIMEOUT, windmillClient::getDataStream), + getDataStreamPool, + globalConfigHandle); + } } public static StreamingDataflowWorker fromOptions(DataflowWorkerHarnessOptions options) { @@ -387,17 +484,21 @@ public static StreamingDataflowWorker fromOptions(DataflowWorkerHarnessOptions o failureTracker, () -> Optional.ofNullable(memoryMonitor.tryToDumpHeap()), clock); - StreamingWorkerStatusReporter workerStatusReporter = - StreamingWorkerStatusReporter.create( - dataflowServiceClient, - windmillServer::getAndResetThrottleTime, - stageInfo::values, - failureTracker, - streamingCounters, - memoryMonitor, - workExecutor, - options.getWindmillHarnessUpdateReportingPeriod().getMillis(), - options.getPerWorkerMetricsUpdateReportingPeriodMillis()); + StreamingWorkerStatusReporterFactory workerStatusReporterFactory = + throttleTimeSupplier -> + StreamingWorkerStatusReporter.builder() + .setDataflowServiceClient(dataflowServiceClient) + .setWindmillQuotaThrottleTime(throttleTimeSupplier) + .setAllStageInfo(stageInfo::values) + .setFailureTracker(failureTracker) + .setStreamingCounters(streamingCounters) + .setMemoryMonitor(memoryMonitor) + .setWorkExecutor(workExecutor) + .setWindmillHarnessUpdateReportingPeriodMillis( + options.getWindmillHarnessUpdateReportingPeriod().getMillis()) + .setPerWorkerMetricsUpdateReportingPeriodMillis( + options.getPerWorkerMetricsUpdateReportingPeriodMillis()) + .build(); return new StreamingDataflowWorker( windmillServer, @@ -410,7 +511,7 @@ public static StreamingDataflowWorker fromOptions(DataflowWorkerHarnessOptions o options, new HotKeyLogger(), clock, - workerStatusReporter, + workerStatusReporterFactory, failureTracker, workFailureProcessor, streamingCounters, @@ -418,7 +519,8 @@ public static StreamingDataflowWorker fromOptions(DataflowWorkerHarnessOptions o configFetcherComputationStateCacheAndWindmillClient.windmillStreamFactory(), Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("RefreshWork").build()), - stageInfo); + stageInfo, + configFetcherComputationStateCacheAndWindmillClient.windmillDispatcherClient()); } /** @@ -433,53 +535,121 @@ public static StreamingDataflowWorker fromOptions(DataflowWorkerHarnessOptions o WorkUnitClient dataflowServiceClient, GrpcWindmillStreamFactory.Builder windmillStreamFactoryBuilder, Function computationStateCacheFactory) { - ComputationConfig.Fetcher configFetcher; - WindmillServerStub windmillServer; - ComputationStateCache computationStateCache; - GrpcWindmillStreamFactory windmillStreamFactory; if (options.isEnableStreamingEngine()) { GrpcDispatcherClient dispatcherClient = GrpcDispatcherClient.create(options, new WindmillStubFactoryFactoryImpl(options)); - configFetcher = + ComputationConfig.Fetcher configFetcher = StreamingEngineComputationConfigFetcher.create( options.getGlobalConfigRefreshPeriod().getMillis(), dataflowServiceClient); configFetcher.getGlobalConfigHandle().registerConfigObserver(dispatcherClient::onJobConfig); - computationStateCache = computationStateCacheFactory.apply(configFetcher); - windmillStreamFactory = + ComputationStateCache computationStateCache = + computationStateCacheFactory.apply(configFetcher); + GrpcWindmillStreamFactory windmillStreamFactory = windmillStreamFactoryBuilder .setProcessHeartbeatResponses( new WorkHeartbeatResponseProcessor(computationStateCache::get)) .setHealthCheckIntervalMillis( options.getWindmillServiceStreamingRpcHealthCheckPeriodMs()) .build(); - windmillServer = GrpcWindmillServer.create(options, windmillStreamFactory, dispatcherClient); - } else { - if (options.getWindmillServiceEndpoint() != null - || options.getLocalWindmillHostport().startsWith("grpc:")) { - windmillStreamFactory = - windmillStreamFactoryBuilder - .setHealthCheckIntervalMillis( - options.getWindmillServiceStreamingRpcHealthCheckPeriodMs()) - .build(); - windmillServer = - GrpcWindmillServer.create( - options, - windmillStreamFactory, - GrpcDispatcherClient.create(options, new WindmillStubFactoryFactoryImpl(options))); - } else { - windmillStreamFactory = windmillStreamFactoryBuilder.build(); - windmillServer = new JniWindmillApplianceServer(options.getLocalWindmillHostport()); + return ConfigFetcherComputationStateCacheAndWindmillClient.builder() + .setWindmillDispatcherClient(dispatcherClient) + .setConfigFetcher(configFetcher) + .setComputationStateCache(computationStateCache) + .setWindmillStreamFactory(windmillStreamFactory) + .setWindmillServer( + GrpcWindmillServer.create(options, windmillStreamFactory, dispatcherClient)) + .build(); + } + + // Build with local Windmill client. + if (options.getWindmillServiceEndpoint() != null + || options.getLocalWindmillHostport().startsWith("grpc:")) { + GrpcDispatcherClient dispatcherClient = + GrpcDispatcherClient.create(options, new WindmillStubFactoryFactoryImpl(options)); + GrpcWindmillStreamFactory windmillStreamFactory = + windmillStreamFactoryBuilder + .setHealthCheckIntervalMillis( + options.getWindmillServiceStreamingRpcHealthCheckPeriodMs()) + .build(); + GrpcWindmillServer windmillServer = + GrpcWindmillServer.create(options, windmillStreamFactory, dispatcherClient); + ComputationConfig.Fetcher configFetcher = + createApplianceComputationConfigFetcher(windmillServer); + return ConfigFetcherComputationStateCacheAndWindmillClient.builder() + .setWindmillDispatcherClient(dispatcherClient) + .setWindmillServer(windmillServer) + .setWindmillStreamFactory(windmillStreamFactory) + .setConfigFetcher(configFetcher) + .setComputationStateCache(computationStateCacheFactory.apply(configFetcher)) + .build(); + } + + WindmillServerStub windmillServer = + new JniWindmillApplianceServer(options.getLocalWindmillHostport()); + ComputationConfig.Fetcher configFetcher = + createApplianceComputationConfigFetcher(windmillServer); + return ConfigFetcherComputationStateCacheAndWindmillClient.builder() + .setWindmillStreamFactory(windmillStreamFactoryBuilder.build()) + .setWindmillServer(windmillServer) + .setConfigFetcher(configFetcher) + .setComputationStateCache(computationStateCacheFactory.apply(configFetcher)) + .build(); + } + + private static StreamingApplianceComputationConfigFetcher createApplianceComputationConfigFetcher( + ApplianceWindmillClient windmillClient) { + return new StreamingApplianceComputationConfigFetcher( + windmillClient::getConfig, + new FixedGlobalConfigHandle(StreamingGlobalConfig.builder().build())); + } + + private static boolean isDirectPathPipeline(DataflowWorkerHarnessOptions options) { + if (options.isEnableStreamingEngine() && options.getIsWindmillServiceDirectPathEnabled()) { + boolean isIpV6Enabled = + Optional.ofNullable(options.getDataflowServiceOptions()) + .map(serviceOptions -> serviceOptions.contains(ENABLE_IPV6_EXPERIMENT)) + .orElse(false); + + if (isIpV6Enabled) { + return true; } - configFetcher = - new StreamingApplianceComputationConfigFetcher( - windmillServer::getConfig, - new FixedGlobalConfigHandle(StreamingGlobalConfig.builder().build())); - computationStateCache = computationStateCacheFactory.apply(configFetcher); + LOG.warn( + "DirectPath is currently only supported with IPv6 networking stack. This requires setting " + + "\"enable_private_ipv6_google_access\" in experimental pipeline options. " + + "For information on how to set experimental pipeline options see " + + "https://cloud.google.com/dataflow/docs/guides/setting-pipeline-options#experimental. " + + "Defaulting to CloudPath."); } - return ConfigFetcherComputationStateCacheAndWindmillClient.create( - configFetcher, computationStateCache, windmillServer, windmillStreamFactory); + return false; + } + + private static void validateWorkerOptions(DataflowWorkerHarnessOptions options) { + Preconditions.checkArgument( + options.isStreaming(), + "%s instantiated with options indicating batch use", + StreamingDataflowWorker.class.getName()); + + Preconditions.checkArgument( + !DataflowRunner.hasExperiment(options, BEAM_FN_API_EXPERIMENT), + "%s cannot be main() class with beam_fn_api enabled", + StreamingDataflowWorker.class.getSimpleName()); + } + + private static ChannelCachingStubFactory createFanOutStubFactory( + DataflowWorkerHarnessOptions workerOptions) { + return ChannelCachingRemoteStubFactory.create( + workerOptions.getGcpCredential(), + ChannelCache.create( + serviceAddress -> + // IsolationChannel will create and manage separate RPC channels to the same + // serviceAddress. + IsolationChannel.create( + () -> + remoteChannel( + serviceAddress, + workerOptions.getWindmillServiceRpcChannelAliveTimeoutSec())))); } @VisibleForTesting @@ -495,7 +665,9 @@ static StreamingDataflowWorker forTesting( Supplier clock, Function executorSupplier, StreamingGlobalConfigHandleImpl globalConfigHandle, - int localRetryTimeoutMs) { + int localRetryTimeoutMs, + StreamingCounters streamingCounters, + WindmillStubFactoryFactory stubFactory) { ConcurrentMap stageInfo = new ConcurrentHashMap<>(); BoundedQueueExecutor workExecutor = createWorkUnitExecutor(options); WindmillStateCache stateCache = @@ -538,7 +710,6 @@ static StreamingDataflowWorker forTesting( stateNameMap, stateCache.forComputation(mapTask.getStageName()))); MemoryMonitor memoryMonitor = MemoryMonitor.fromOptions(options); - StreamingCounters streamingCounters = StreamingCounters.create(); FailureTracker failureTracker = options.isEnableStreamingEngine() ? StreamingEngineFailureTracker.create( @@ -554,19 +725,23 @@ static StreamingDataflowWorker forTesting( () -> Optional.ofNullable(memoryMonitor.tryToDumpHeap()), clock, localRetryTimeoutMs); - StreamingWorkerStatusReporter workerStatusReporter = - StreamingWorkerStatusReporter.forTesting( - publishCounters, - workUnitClient, - windmillServer::getAndResetThrottleTime, - stageInfo::values, - failureTracker, - streamingCounters, - memoryMonitor, - workExecutor, - executorSupplier, - options.getWindmillHarnessUpdateReportingPeriod().getMillis(), - options.getPerWorkerMetricsUpdateReportingPeriodMillis()); + StreamingWorkerStatusReporterFactory workerStatusReporterFactory = + throttleTimeSupplier -> + StreamingWorkerStatusReporter.builder() + .setPublishCounters(publishCounters) + .setDataflowServiceClient(workUnitClient) + .setWindmillQuotaThrottleTime(throttleTimeSupplier) + .setAllStageInfo(stageInfo::values) + .setFailureTracker(failureTracker) + .setStreamingCounters(streamingCounters) + .setMemoryMonitor(memoryMonitor) + .setWorkExecutor(workExecutor) + .setExecutorFactory(executorSupplier) + .setWindmillHarnessUpdateReportingPeriodMillis( + options.getWindmillHarnessUpdateReportingPeriod().getMillis()) + .setPerWorkerMetricsUpdateReportingPeriodMillis( + options.getPerWorkerMetricsUpdateReportingPeriodMillis()) + .build(); GrpcWindmillStreamFactory.Builder windmillStreamFactory = createGrpcwindmillStreamFactoryBuilder(options, 1) @@ -584,7 +759,7 @@ static StreamingDataflowWorker forTesting( options, hotKeyLogger, clock, - workerStatusReporter, + workerStatusReporterFactory, failureTracker, workFailureProcessor, streamingCounters, @@ -596,7 +771,8 @@ static StreamingDataflowWorker forTesting( .build() : windmillStreamFactory.build(), executorSupplier.apply("RefreshWork"), - stageInfo); + stageInfo, + GrpcDispatcherClient.create(options, stubFactory)); } private static GrpcWindmillStreamFactory.Builder createGrpcwindmillStreamFactoryBuilder( @@ -605,13 +781,7 @@ private static GrpcWindmillStreamFactory.Builder createGrpcwindmillStreamFactory !options.isEnableStreamingEngine() && options.getLocalWindmillHostport() != null ? GrpcWindmillServer.LOCALHOST_MAX_BACKOFF : Duration.millis(options.getWindmillServiceStreamMaxBackoffMillis()); - return GrpcWindmillStreamFactory.of( - JobHeader.newBuilder() - .setJobId(options.getJobId()) - .setProjectId(options.getProject()) - .setWorkerId(options.getWorkerId()) - .setClientId(clientId) - .build()) + return GrpcWindmillStreamFactory.of(createJobHeader(options, clientId)) .setWindmillMessagesBetweenIsReadyChecks(options.getWindmillMessagesBetweenIsReadyChecks()) .setMaxBackOffSupplier(() -> maxBackoff) .setLogEveryNStreamFailures(options.getWindmillServiceStreamingLogEveryNStreamFailures()) @@ -622,6 +792,15 @@ private static GrpcWindmillStreamFactory.Builder createGrpcwindmillStreamFactory options, "streaming_engine_disable_new_heartbeat_requests")); } + private static JobHeader createJobHeader(DataflowWorkerHarnessOptions options, long clientId) { + return JobHeader.newBuilder() + .setJobId(options.getJobId()) + .setProjectId(options.getProject()) + .setWorkerId(options.getWorkerId()) + .setClientId(clientId) + .build(); + } + private static BoundedQueueExecutor createWorkUnitExecutor(DataflowWorkerHarnessOptions options) { return new BoundedQueueExecutor( chooseMaxThreads(options), @@ -640,15 +819,7 @@ public static void main(String[] args) throws Exception { DataflowWorkerHarnessHelper.initializeGlobalStateAndPipelineOptions( StreamingDataflowWorker.class, DataflowWorkerHarnessOptions.class); DataflowWorkerHarnessHelper.configureLogging(options); - checkArgument( - options.isStreaming(), - "%s instantiated with options indicating batch use", - StreamingDataflowWorker.class.getName()); - - checkArgument( - !DataflowRunner.hasExperiment(options, "beam_fn_api"), - "%s cannot be main() class with beam_fn_api enabled", - StreamingDataflowWorker.class.getSimpleName()); + validateWorkerOptions(options); CoderTranslation.verifyModelCodersRegistered(); @@ -705,21 +876,6 @@ void reportPeriodicWorkerUpdatesForTest() { workerStatusReporter.reportPeriodicWorkerUpdates(); } - private int chooseMaximumNumberOfThreads() { - if (options.getNumberOfWorkerHarnessThreads() != 0) { - return options.getNumberOfWorkerHarnessThreads(); - } - return MAX_PROCESSING_THREADS; - } - - private int chooseMaximumBundlesOutstanding() { - int maxBundles = options.getMaxBundlesFromWindmillOutstanding(); - if (maxBundles > 0) { - return maxBundles; - } - return chooseMaximumNumberOfThreads() + 100; - } - @VisibleForTesting public boolean workExecutorIsEmpty() { return workUnitExecutor.executorQueueIsEmpty(); @@ -727,7 +883,7 @@ public boolean workExecutorIsEmpty() { @VisibleForTesting int numCommitThreads() { - return workCommitter.parallelism(); + return numCommitThreads; } @VisibleForTesting @@ -740,7 +896,6 @@ ComputationStateCache getComputationStateCache() { return computationStateCache; } - @SuppressWarnings("FutureReturnValueIgnored") public void start() { running.set(true); configFetcher.start(); @@ -791,27 +946,17 @@ private void onCompleteCommit(CompleteCommit completeCommit) { completeCommit.shardedKey(), completeCommit.workId())); } - @VisibleForTesting - public Iterable buildCounters() { - return Iterables.concat( - streamingCounters - .pendingDeltaCounters() - .extractModifiedDeltaUpdates(DataflowCounterUpdateExtractor.INSTANCE), - streamingCounters - .pendingCumulativeCounters() - .extractUpdates(false, DataflowCounterUpdateExtractor.INSTANCE)); + @FunctionalInterface + private interface StreamingWorkerStatusReporterFactory { + StreamingWorkerStatusReporter createStatusReporter(ThrottledTimeTracker throttledTimeTracker); } @AutoValue abstract static class ConfigFetcherComputationStateCacheAndWindmillClient { - private static ConfigFetcherComputationStateCacheAndWindmillClient create( - ComputationConfig.Fetcher configFetcher, - ComputationStateCache computationStateCache, - WindmillServerStub windmillServer, - GrpcWindmillStreamFactory windmillStreamFactory) { - return new AutoValue_StreamingDataflowWorker_ConfigFetcherComputationStateCacheAndWindmillClient( - configFetcher, computationStateCache, windmillServer, windmillStreamFactory); + private static Builder builder() { + return new AutoValue_StreamingDataflowWorker_ConfigFetcherComputationStateCacheAndWindmillClient + .Builder(); } abstract ComputationConfig.Fetcher configFetcher(); @@ -821,6 +966,23 @@ private static ConfigFetcherComputationStateCacheAndWindmillClient create( abstract WindmillServerStub windmillServer(); abstract GrpcWindmillStreamFactory windmillStreamFactory(); + + abstract @Nullable GrpcDispatcherClient windmillDispatcherClient(); + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setConfigFetcher(ComputationConfig.Fetcher value); + + abstract Builder setComputationStateCache(ComputationStateCache value); + + abstract Builder setWindmillServer(WindmillServerStub value); + + abstract Builder setWindmillStreamFactory(GrpcWindmillStreamFactory value); + + abstract Builder setWindmillDispatcherClient(GrpcDispatcherClient value); + + abstract ConfigFetcherComputationStateCacheAndWindmillClient build(); + } } /** diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java index 1ef1691b0817..4c73e8c7f61c 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java @@ -371,6 +371,7 @@ private void closeStreamSender(Endpoint endpoint, StreamSender sender) { } /** Add up all the throttle times of all streams including GetWorkerMetadataStream. */ + @Override public long getAndResetThrottleTime() { return backends.get().windmillStreams().values().stream() .map(WindmillStreamSender::getAndResetThrottleTime) diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarness.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarness.java index 9716b834cac4..65203288e169 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarness.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarness.java @@ -37,6 +37,7 @@ import org.apache.beam.runners.dataflow.worker.windmill.client.WindmillStream; import org.apache.beam.runners.dataflow.worker.windmill.client.commits.WorkCommitter; import org.apache.beam.runners.dataflow.worker.windmill.client.getdata.GetDataClient; +import org.apache.beam.runners.dataflow.worker.windmill.client.throttling.ThrottledTimeTracker; import org.apache.beam.runners.dataflow.worker.windmill.work.WorkItemReceiver; import org.apache.beam.runners.dataflow.worker.windmill.work.processing.StreamingWorkScheduler; import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.HeartbeatSender; @@ -66,6 +67,7 @@ public final class SingleSourceWorkerHarness implements StreamingWorkerHarness { private final Function> computationStateFetcher; private final ExecutorService workProviderExecutor; private final GetWorkSender getWorkSender; + private final ThrottledTimeTracker throttledTimeTracker; SingleSourceWorkerHarness( WorkCommitter workCommitter, @@ -74,7 +76,8 @@ public final class SingleSourceWorkerHarness implements StreamingWorkerHarness { StreamingWorkScheduler streamingWorkScheduler, Runnable waitForResources, Function> computationStateFetcher, - GetWorkSender getWorkSender) { + GetWorkSender getWorkSender, + ThrottledTimeTracker throttledTimeTracker) { this.workCommitter = workCommitter; this.getDataClient = getDataClient; this.heartbeatSender = heartbeatSender; @@ -90,6 +93,7 @@ public final class SingleSourceWorkerHarness implements StreamingWorkerHarness { .build()); this.isRunning = new AtomicBoolean(false); this.getWorkSender = getWorkSender; + this.throttledTimeTracker = throttledTimeTracker; } public static SingleSourceWorkerHarness.Builder builder() { @@ -144,6 +148,11 @@ public void shutdown() { workCommitter.stop(); } + @Override + public long getAndResetThrottleTime() { + return throttledTimeTracker.getAndResetThrottleTime(); + } + private void streamingEngineDispatchLoop( Function getWorkStreamFactory) { while (isRunning.get()) { @@ -254,6 +263,8 @@ Builder setComputationStateFetcher( Builder setGetWorkSender(GetWorkSender getWorkSender); + Builder setThrottledTimeTracker(ThrottledTimeTracker throttledTimeTracker); + SingleSourceWorkerHarness build(); } diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerHarness.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerHarness.java index c1b4570e2260..731a5a4b1b51 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerHarness.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerHarness.java @@ -17,11 +17,12 @@ */ package org.apache.beam.runners.dataflow.worker.streaming.harness; +import org.apache.beam.runners.dataflow.worker.windmill.client.throttling.ThrottledTimeTracker; import org.apache.beam.sdk.annotations.Internal; /** Provides an interface to start streaming worker processing. */ @Internal -public interface StreamingWorkerHarness { +public interface StreamingWorkerHarness extends ThrottledTimeTracker { void start(); void shutdown(); diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusPages.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusPages.java index 6981312eff1d..ddfc6809231a 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusPages.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusPages.java @@ -258,7 +258,7 @@ public interface Builder { Builder setDebugCapture(DebugCapture.Manager debugCapture); - Builder setChannelzServlet(ChannelzServlet channelzServlet); + Builder setChannelzServlet(@Nullable ChannelzServlet channelzServlet); Builder setStateCache(WindmillStateCache stateCache); diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporter.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporter.java index ba77d8e1ce26..3557f0d193c5 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporter.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporter.java @@ -27,6 +27,7 @@ import com.google.api.services.dataflow.model.WorkItemStatus; import com.google.api.services.dataflow.model.WorkerMessage; import com.google.api.services.dataflow.model.WorkerMessageResponse; +import com.google.auto.value.AutoBuilder; import java.io.IOException; import java.math.RoundingMode; import java.util.ArrayList; @@ -51,6 +52,7 @@ import org.apache.beam.runners.dataflow.worker.streaming.StageInfo; import org.apache.beam.runners.dataflow.worker.util.BoundedQueueExecutor; import org.apache.beam.runners.dataflow.worker.util.MemoryMonitor; +import org.apache.beam.runners.dataflow.worker.windmill.client.throttling.ThrottledTimeTracker; import org.apache.beam.runners.dataflow.worker.windmill.work.processing.failures.FailureTracker; import org.apache.beam.sdk.annotations.Internal; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; @@ -78,7 +80,7 @@ public final class StreamingWorkerStatusReporter { private final int initialMaxThreadCount; private final int initialMaxBundlesOutstanding; private final WorkUnitClient dataflowServiceClient; - private final Supplier windmillQuotaThrottleTime; + private final ThrottledTimeTracker windmillQuotaThrottleTime; private final Supplier> allStageInfo; private final FailureTracker failureTracker; private final StreamingCounters streamingCounters; @@ -97,10 +99,10 @@ public final class StreamingWorkerStatusReporter { // Used to track the number of WorkerMessages that have been sent without PerWorkerMetrics. private final AtomicLong workerMessagesIndex; - private StreamingWorkerStatusReporter( + StreamingWorkerStatusReporter( boolean publishCounters, WorkUnitClient dataflowServiceClient, - Supplier windmillQuotaThrottleTime, + ThrottledTimeTracker windmillQuotaThrottleTime, Supplier> allStageInfo, FailureTracker failureTracker, StreamingCounters streamingCounters, @@ -131,57 +133,13 @@ private StreamingWorkerStatusReporter( this.workerMessagesIndex = new AtomicLong(); } - public static StreamingWorkerStatusReporter create( - WorkUnitClient workUnitClient, - Supplier windmillQuotaThrottleTime, - Supplier> allStageInfo, - FailureTracker failureTracker, - StreamingCounters streamingCounters, - MemoryMonitor memoryMonitor, - BoundedQueueExecutor workExecutor, - long windmillHarnessUpdateReportingPeriodMillis, - long perWorkerMetricsUpdateReportingPeriodMillis) { - return new StreamingWorkerStatusReporter( - /* publishCounters= */ true, - workUnitClient, - windmillQuotaThrottleTime, - allStageInfo, - failureTracker, - streamingCounters, - memoryMonitor, - workExecutor, - threadName -> - Executors.newSingleThreadScheduledExecutor( - new ThreadFactoryBuilder().setNameFormat(threadName).build()), - windmillHarnessUpdateReportingPeriodMillis, - perWorkerMetricsUpdateReportingPeriodMillis); - } - - @VisibleForTesting - public static StreamingWorkerStatusReporter forTesting( - boolean publishCounters, - WorkUnitClient workUnitClient, - Supplier windmillQuotaThrottleTime, - Supplier> allStageInfo, - FailureTracker failureTracker, - StreamingCounters streamingCounters, - MemoryMonitor memoryMonitor, - BoundedQueueExecutor workExecutor, - Function executorFactory, - long windmillHarnessUpdateReportingPeriodMillis, - long perWorkerMetricsUpdateReportingPeriodMillis) { - return new StreamingWorkerStatusReporter( - publishCounters, - workUnitClient, - windmillQuotaThrottleTime, - allStageInfo, - failureTracker, - streamingCounters, - memoryMonitor, - workExecutor, - executorFactory, - windmillHarnessUpdateReportingPeriodMillis, - perWorkerMetricsUpdateReportingPeriodMillis); + public static Builder builder() { + return new AutoBuilder_StreamingWorkerStatusReporter_Builder() + .setPublishCounters(true) + .setExecutorFactory( + threadName -> + Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder().setNameFormat(threadName).build())); } /** @@ -228,6 +186,22 @@ private static void shutdownExecutor(ScheduledExecutorService executor) { } } + // Calculates the PerWorkerMetrics reporting frequency, ensuring alignment with the + // WorkerMessages RPC schedule. The desired reporting period + // (perWorkerMetricsUpdateReportingPeriodMillis) is adjusted to the nearest multiple + // of the RPC interval (windmillHarnessUpdateReportingPeriodMillis). + private static long getPerWorkerMetricsUpdateFrequency( + long windmillHarnessUpdateReportingPeriodMillis, + long perWorkerMetricsUpdateReportingPeriodMillis) { + if (windmillHarnessUpdateReportingPeriodMillis == 0) { + return 0; + } + return LongMath.divide( + perWorkerMetricsUpdateReportingPeriodMillis, + windmillHarnessUpdateReportingPeriodMillis, + RoundingMode.CEILING); + } + @SuppressWarnings("FutureReturnValueIgnored") public void start() { reportHarnessStartup(); @@ -276,27 +250,13 @@ private void reportHarnessStartup() { } } - // Calculates the PerWorkerMetrics reporting frequency, ensuring alignment with the - // WorkerMessages RPC schedule. The desired reporting period - // (perWorkerMetricsUpdateReportingPeriodMillis) is adjusted to the nearest multiple - // of the RPC interval (windmillHarnessUpdateReportingPeriodMillis). - private static long getPerWorkerMetricsUpdateFrequency( - long windmillHarnessUpdateReportingPeriodMillis, - long perWorkerMetricsUpdateReportingPeriodMillis) { - if (windmillHarnessUpdateReportingPeriodMillis == 0) { - return 0; - } - return LongMath.divide( - perWorkerMetricsUpdateReportingPeriodMillis, - windmillHarnessUpdateReportingPeriodMillis, - RoundingMode.CEILING); - } - /** Sends counter updates to Dataflow backend. */ private void sendWorkerUpdatesToDataflowService( CounterSet deltaCounters, CounterSet cumulativeCounters) throws IOException { // Throttle time is tracked by the windmillServer but is reported to DFE here. - streamingCounters.windmillQuotaThrottling().addValue(windmillQuotaThrottleTime.get()); + streamingCounters + .windmillQuotaThrottling() + .addValue(windmillQuotaThrottleTime.getAndResetThrottleTime()); if (memoryMonitor.isThrashing()) { streamingCounters.memoryThrashing().addValue(1); } @@ -496,4 +456,33 @@ private void updateThreadMetrics() { .maxOutstandingBundles() .addValue((long) workExecutor.maximumElementsOutstanding()); } + + @AutoBuilder + public interface Builder { + Builder setPublishCounters(boolean publishCounters); + + Builder setDataflowServiceClient(WorkUnitClient dataflowServiceClient); + + Builder setWindmillQuotaThrottleTime(ThrottledTimeTracker windmillQuotaThrottledTimeTracker); + + Builder setAllStageInfo(Supplier> allStageInfo); + + Builder setFailureTracker(FailureTracker failureTracker); + + Builder setStreamingCounters(StreamingCounters streamingCounters); + + Builder setMemoryMonitor(MemoryMonitor memoryMonitor); + + Builder setWorkExecutor(BoundedQueueExecutor workExecutor); + + Builder setExecutorFactory(Function executorFactory); + + Builder setWindmillHarnessUpdateReportingPeriodMillis( + long windmillHarnessUpdateReportingPeriodMillis); + + Builder setPerWorkerMetricsUpdateReportingPeriodMillis( + long perWorkerMetricsUpdateReportingPeriodMillis); + + StreamingWorkerStatusReporter build(); + } } diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottleTimer.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottleTimer.java index f660112721ba..fdcb0339d23d 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottleTimer.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottleTimer.java @@ -25,7 +25,7 @@ * CommitWork are both blocked for x, totalTime will be 2x. However, if 2 GetWork streams are both * blocked for x totalTime will be x. All methods are thread safe. */ -public final class ThrottleTimer { +public final class ThrottleTimer implements ThrottledTimeTracker { // This is -1 if not currently being throttled or the time in // milliseconds when throttling for this type started. private long startTime = -1; @@ -56,6 +56,7 @@ public synchronized boolean throttled() { } /** Returns the combined total of all throttle times and resets those times to 0. */ + @Override public synchronized long getAndResetThrottleTime() { if (throttled()) { stop(); diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottledTimeTracker.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottledTimeTracker.java new file mode 100644 index 000000000000..9bb8fb0a7b5f --- /dev/null +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/throttling/ThrottledTimeTracker.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.beam.runners.dataflow.worker.windmill.client.throttling; + +import org.apache.beam.sdk.annotations.Internal; + +/** + * Tracks time spent in a throttled state due to {@code Status.RESOURCE_EXHAUSTED} errors returned + * from gRPC calls. + */ +@Internal +@FunctionalInterface +public interface ThrottledTimeTracker { + + /** Returns the combined total of all throttle times and resets those times to 0. */ + long getAndResetThrottleTime(); +} diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSender.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSender.java index fa36b11ffe55..f54091dc2b95 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSender.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSender.java @@ -42,7 +42,7 @@ private StreamPoolHeartbeatSender( this.heartbeatStreamPool.set(heartbeatStreamPool); } - public static StreamPoolHeartbeatSender Create( + public static StreamPoolHeartbeatSender create( @Nonnull WindmillStreamPool heartbeatStreamPool) { return new StreamPoolHeartbeatSender(heartbeatStreamPool); } @@ -55,7 +55,7 @@ public static StreamPoolHeartbeatSender Create( * enabled. * @param getDataPool stream to use when using separate streams for heartbeat is disabled. */ - public static StreamPoolHeartbeatSender Create( + public static StreamPoolHeartbeatSender create( @Nonnull WindmillStreamPool dedicatedHeartbeatPool, @Nonnull WindmillStreamPool getDataPool, @Nonnull StreamingGlobalConfigHandle configHandle) { diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorkerTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorkerTest.java index dadf02171235..6eeb7bd6bbfc 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorkerTest.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorkerTest.java @@ -96,6 +96,7 @@ import org.apache.beam.runners.dataflow.util.CloudObjects; import org.apache.beam.runners.dataflow.util.PropertyNames; import org.apache.beam.runners.dataflow.util.Structs; +import org.apache.beam.runners.dataflow.worker.counters.DataflowCounterUpdateExtractor; import org.apache.beam.runners.dataflow.worker.streaming.ComputationState; import org.apache.beam.runners.dataflow.worker.streaming.ComputationStateCache; import org.apache.beam.runners.dataflow.worker.streaming.ExecutableWork; @@ -104,6 +105,7 @@ import org.apache.beam.runners.dataflow.worker.streaming.Work; import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingGlobalConfig; import org.apache.beam.runners.dataflow.worker.streaming.config.StreamingGlobalConfigHandleImpl; +import org.apache.beam.runners.dataflow.worker.streaming.harness.StreamingCounters; import org.apache.beam.runners.dataflow.worker.testing.RestoreDataflowLoggingMDC; import org.apache.beam.runners.dataflow.worker.testing.TestCountingSource; import org.apache.beam.runners.dataflow.worker.util.BoundedQueueExecutor; @@ -129,6 +131,9 @@ import org.apache.beam.runners.dataflow.worker.windmill.Windmill.WatermarkHold; import org.apache.beam.runners.dataflow.worker.windmill.Windmill.WorkItemCommitRequest; import org.apache.beam.runners.dataflow.worker.windmill.client.getdata.FakeGetDataClient; +import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.WindmillChannelFactory; +import org.apache.beam.runners.dataflow.worker.windmill.testing.FakeWindmillStubFactory; +import org.apache.beam.runners.dataflow.worker.windmill.testing.FakeWindmillStubFactoryFactory; import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.HeartbeatSender; import org.apache.beam.sdk.coders.Coder; import org.apache.beam.sdk.coders.Coder.Context; @@ -178,6 +183,7 @@ import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.CacheStats; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Lists; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.primitives.UnsignedLong; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -285,6 +291,7 @@ public Long get() { private final FakeWindmillServer server = new FakeWindmillServer( errorCollector, computationId -> computationStateCache.get(computationId)); + private StreamingCounters streamingCounters; public StreamingDataflowWorkerTest(Boolean streamingEngine) { this.streamingEngine = streamingEngine; @@ -304,9 +311,20 @@ private static CounterUpdate getCounter(Iterable counters, String return null; } + private Iterable buildCounters() { + return Iterables.concat( + streamingCounters + .pendingDeltaCounters() + .extractModifiedDeltaUpdates(DataflowCounterUpdateExtractor.INSTANCE), + streamingCounters + .pendingCumulativeCounters() + .extractUpdates(false, DataflowCounterUpdateExtractor.INSTANCE)); + } + @Before public void setUp() { server.clearCommitsReceived(); + streamingCounters = StreamingCounters.create(); } @After @@ -856,7 +874,13 @@ private StreamingDataflowWorker makeWorker( streamingDataflowWorkerTestParams.clock(), streamingDataflowWorkerTestParams.executorSupplier(), mockGlobalConfigHandle, - streamingDataflowWorkerTestParams.localRetryTimeoutMs()); + streamingDataflowWorkerTestParams.localRetryTimeoutMs(), + streamingCounters, + new FakeWindmillStubFactoryFactory( + new FakeWindmillStubFactory( + () -> + WindmillChannelFactory.inProcessChannel( + "StreamingDataflowWorkerTestChannel")))); this.computationStateCache = worker.getComputationStateCache(); return worker; } @@ -1715,7 +1739,7 @@ public void testMergeWindows() throws Exception { intervalWindowBytes(WINDOW_AT_ZERO))); Map result = server.waitForAndGetCommits(1); - Iterable counters = worker.buildCounters(); + Iterable counters = buildCounters(); // These tags and data are opaque strings and this is a change detector test. // The "/u" indicates the user's namespace, versus "/s" for system namespace @@ -1836,7 +1860,7 @@ public void testMergeWindows() throws Exception { expectedBytesRead += dataBuilder.build().getSerializedSize(); result = server.waitForAndGetCommits(1); - counters = worker.buildCounters(); + counters = buildCounters(); actualOutput = result.get(2L); assertEquals(1, actualOutput.getOutputMessagesCount()); @@ -2004,7 +2028,7 @@ public void testMergeWindowsCaching() throws Exception { intervalWindowBytes(WINDOW_AT_ZERO))); Map result = server.waitForAndGetCommits(1); - Iterable counters = worker.buildCounters(); + Iterable counters = buildCounters(); // These tags and data are opaque strings and this is a change detector test. // The "/u" indicates the user's namespace, versus "/s" for system namespace @@ -2125,7 +2149,7 @@ public void testMergeWindowsCaching() throws Exception { expectedBytesRead += dataBuilder.build().getSerializedSize(); result = server.waitForAndGetCommits(1); - counters = worker.buildCounters(); + counters = buildCounters(); actualOutput = result.get(2L); assertEquals(1, actualOutput.getOutputMessagesCount()); @@ -2430,7 +2454,7 @@ public void testUnboundedSources() throws Exception { null)); Map result = server.waitForAndGetCommits(1); - Iterable counters = worker.buildCounters(); + Iterable counters = buildCounters(); Windmill.WorkItemCommitRequest commit = result.get(1L); UnsignedLong finalizeId = UnsignedLong.fromLongBits(commit.getSourceStateUpdates().getFinalizeIds(0)); @@ -2492,7 +2516,7 @@ public void testUnboundedSources() throws Exception { null)); result = server.waitForAndGetCommits(1); - counters = worker.buildCounters(); + counters = buildCounters(); commit = result.get(2L); finalizeId = UnsignedLong.fromLongBits(commit.getSourceStateUpdates().getFinalizeIds(0)); @@ -2540,7 +2564,7 @@ public void testUnboundedSources() throws Exception { null)); result = server.waitForAndGetCommits(1); - counters = worker.buildCounters(); + counters = buildCounters(); commit = result.get(3L); finalizeId = UnsignedLong.fromLongBits(commit.getSourceStateUpdates().getFinalizeIds(0)); @@ -2710,7 +2734,7 @@ public void testUnboundedSourceWorkRetry() throws Exception { server.whenGetWorkCalled().thenReturn(work); Map result = server.waitForAndGetCommits(1); - Iterable counters = worker.buildCounters(); + Iterable counters = buildCounters(); Windmill.WorkItemCommitRequest commit = result.get(1L); UnsignedLong finalizeId = UnsignedLong.fromLongBits(commit.getSourceStateUpdates().getFinalizeIds(0)); diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarnessTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarnessTest.java index 5a2df4baae61..4df3bf7cd823 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarnessTest.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/SingleSourceWorkerHarnessTest.java @@ -60,6 +60,8 @@ private SingleSourceWorkerHarness createWorkerHarness( .setWaitForResources(waitForResources) .setStreamingWorkScheduler(streamingWorkScheduler) .setComputationStateFetcher(computationStateFetcher) + // no-op throttle time supplier. + .setThrottledTimeTracker(() -> 0L) .setGetWorkSender(getWorkSender) .build(); } diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporterTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporterTest.java index 7e65a495638f..f348e4cf1bdb 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporterTest.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/StreamingWorkerStatusReporterTest.java @@ -39,14 +39,15 @@ @RunWith(JUnit4.class) public class StreamingWorkerStatusReporterTest { - private final long DEFAULT_WINDMILL_QUOTA_THROTTLE_TIME = 1000; - private final long DEFAULT_HARNESS_REPORTING_PERIOD = 10000; - private final long DEFAULT_PER_WORKER_METRICS_PERIOD = 30000; + private static final long DEFAULT_WINDMILL_QUOTA_THROTTLE_TIME = 1000; + private static final long DEFAULT_HARNESS_REPORTING_PERIOD = 10000; + private static final long DEFAULT_PER_WORKER_METRICS_PERIOD = 30000; private BoundedQueueExecutor mockExecutor; private WorkUnitClient mockWorkUnitClient; private FailureTracker mockFailureTracker; private MemoryMonitor mockMemoryMonitor; + private StreamingWorkerStatusReporter reporter; @Before public void setUp() { @@ -54,23 +55,11 @@ public void setUp() { this.mockWorkUnitClient = mock(WorkUnitClient.class); this.mockFailureTracker = mock(FailureTracker.class); this.mockMemoryMonitor = mock(MemoryMonitor.class); + this.reporter = buildWorkerStatusReporterForTest(); } @Test public void testOverrideMaximumThreadCount() throws Exception { - StreamingWorkerStatusReporter reporter = - StreamingWorkerStatusReporter.forTesting( - true, - mockWorkUnitClient, - () -> DEFAULT_WINDMILL_QUOTA_THROTTLE_TIME, - () -> Collections.emptyList(), - mockFailureTracker, - StreamingCounters.create(), - mockMemoryMonitor, - mockExecutor, - (threadName) -> Executors.newSingleThreadScheduledExecutor(), - DEFAULT_HARNESS_REPORTING_PERIOD, - DEFAULT_PER_WORKER_METRICS_PERIOD); StreamingScalingReportResponse streamingScalingReportResponse = new StreamingScalingReportResponse().setMaximumThreadCount(10); WorkerMessageResponse workerMessageResponse = @@ -84,23 +73,25 @@ public void testOverrideMaximumThreadCount() throws Exception { @Test public void testHandleEmptyWorkerMessageResponse() throws Exception { - StreamingWorkerStatusReporter reporter = - StreamingWorkerStatusReporter.forTesting( - true, - mockWorkUnitClient, - () -> DEFAULT_WINDMILL_QUOTA_THROTTLE_TIME, - () -> Collections.emptyList(), - mockFailureTracker, - StreamingCounters.create(), - mockMemoryMonitor, - mockExecutor, - (threadName) -> Executors.newSingleThreadScheduledExecutor(), - DEFAULT_HARNESS_REPORTING_PERIOD, - DEFAULT_PER_WORKER_METRICS_PERIOD); - WorkerMessageResponse workerMessageResponse = new WorkerMessageResponse(); when(mockWorkUnitClient.reportWorkerMessage(any())) - .thenReturn(Collections.singletonList(workerMessageResponse)); + .thenReturn(Collections.singletonList(new WorkerMessageResponse())); reporter.reportPeriodicWorkerMessage(); verify(mockExecutor, Mockito.times(0)).setMaximumPoolSize(anyInt(), anyInt()); } + + private StreamingWorkerStatusReporter buildWorkerStatusReporterForTest() { + return StreamingWorkerStatusReporter.builder() + .setPublishCounters(true) + .setDataflowServiceClient(mockWorkUnitClient) + .setWindmillQuotaThrottleTime(() -> DEFAULT_WINDMILL_QUOTA_THROTTLE_TIME) + .setAllStageInfo(Collections::emptyList) + .setFailureTracker(mockFailureTracker) + .setStreamingCounters(StreamingCounters.create()) + .setMemoryMonitor(mockMemoryMonitor) + .setWorkExecutor(mockExecutor) + .setExecutorFactory((threadName) -> Executors.newSingleThreadScheduledExecutor()) + .setWindmillHarnessUpdateReportingPeriodMillis(DEFAULT_HARNESS_REPORTING_PERIOD) + .setPerWorkerMetricsUpdateReportingPeriodMillis(DEFAULT_PER_WORKER_METRICS_PERIOD) + .build(); + } } diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSenderTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSenderTest.java index ed915088d0a6..acbb3aebbcf5 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSenderTest.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/work/refresh/StreamPoolHeartbeatSenderTest.java @@ -39,7 +39,7 @@ public class StreamPoolHeartbeatSenderTest { public void sendsHeartbeatsOnStream() { FakeWindmillServer server = new FakeWindmillServer(new ErrorCollector(), c -> Optional.empty()); StreamPoolHeartbeatSender heartbeatSender = - StreamPoolHeartbeatSender.Create( + StreamPoolHeartbeatSender.create( WindmillStreamPool.create(1, Duration.standardSeconds(10), server::getDataStream)); Heartbeats.Builder heartbeatsBuilder = Heartbeats.builder(); heartbeatsBuilder @@ -59,7 +59,7 @@ public void sendsHeartbeatsOnDedicatedStream() { FakeGlobalConfigHandle configHandle = new FakeGlobalConfigHandle(getGlobalConfig(/*useSeparateHeartbeatStreams=*/ true)); StreamPoolHeartbeatSender heartbeatSender = - StreamPoolHeartbeatSender.Create( + StreamPoolHeartbeatSender.create( WindmillStreamPool.create( 1, Duration.standardSeconds(10), dedicatedServer::getDataStream), WindmillStreamPool.create( @@ -104,7 +104,7 @@ public void sendsHeartbeatsOnGetDataStream() { FakeGlobalConfigHandle configHandle = new FakeGlobalConfigHandle(getGlobalConfig(/*useSeparateHeartbeatStreams=*/ false)); StreamPoolHeartbeatSender heartbeatSender = - StreamPoolHeartbeatSender.Create( + StreamPoolHeartbeatSender.create( WindmillStreamPool.create( 1, Duration.standardSeconds(10), dedicatedServer::getDataStream), WindmillStreamPool.create( From d745198af5b84fd00b247e73a44605d41cc6ad57 Mon Sep 17 00:00:00 2001 From: scwhittle Date: Tue, 26 Nov 2024 13:07:22 +0100 Subject: [PATCH 019/135] Change the Java sdk harness cache timeout for bundle processors to be an hour for streaming pipelines instead of 1 minute. (#33175) * Change the cache timeout for bundle processors to be an hour for streaming pipelines instead of 1 minute. Use a hidden option so that it can be controlled further if desired. --- .../beam/sdk/options/SdkHarnessOptions.java | 18 +++++++++ .../harness/control/ProcessBundleHandler.java | 38 ++++++++++--------- .../control/ProcessBundleHandlerTest.java | 37 ++++++++++-------- 3 files changed, 59 insertions(+), 34 deletions(-) diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/options/SdkHarnessOptions.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/options/SdkHarnessOptions.java index 6e5843f533db..78ea34503e54 100644 --- a/sdks/java/core/src/main/java/org/apache/beam/sdk/options/SdkHarnessOptions.java +++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/options/SdkHarnessOptions.java @@ -20,6 +20,7 @@ import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkNotNull; import com.fasterxml.jackson.annotation.JsonCreator; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -386,4 +387,21 @@ static List getConfiguredLoggerFromOptions(SdkHarnessOptions loggingOpti } return configuredLoggers; } + + @Hidden + @Description( + "Timeout used for cache of bundle processors. Defaults to a minute for batch and an hour for streaming.") + @Default.InstanceFactory(BundleProcessorCacheTimeoutFactory.class) + Duration getBundleProcessorCacheTimeout(); + + void setBundleProcessorCacheTimeout(Duration duration); + + class BundleProcessorCacheTimeoutFactory implements DefaultValueFactory { + @Override + public Duration create(PipelineOptions options) { + return options.as(StreamingOptions.class).isStreaming() + ? Duration.ofHours(1) + : Duration.ofMinutes(1); + } + } } diff --git a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java index 0d517503b12d..300796ac6f12 100644 --- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java +++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java @@ -83,6 +83,7 @@ import org.apache.beam.sdk.metrics.MetricsEnvironment; import org.apache.beam.sdk.metrics.MetricsEnvironment.MetricsEnvironmentState; import org.apache.beam.sdk.options.PipelineOptions; +import org.apache.beam.sdk.options.SdkHarnessOptions; import org.apache.beam.sdk.transforms.DoFn.BundleFinalizer; import org.apache.beam.sdk.util.WindowedValue; import org.apache.beam.sdk.util.common.ReflectHelpers; @@ -188,7 +189,8 @@ public ProcessBundleHandler( executionStateSampler, REGISTERED_RUNNER_FACTORIES, processWideCache, - new BundleProcessorCache(), + new BundleProcessorCache( + options.as(SdkHarnessOptions.class).getBundleProcessorCacheTimeout()), dataSampler); } @@ -927,25 +929,25 @@ public int hashCode() { return super.hashCode(); } - BundleProcessorCache() { - this.cachedBundleProcessors = + BundleProcessorCache(Duration timeout) { + CacheBuilder> builder = CacheBuilder.newBuilder() - .expireAfterAccess(Duration.ofMinutes(1L)) .removalListener( - removalNotification -> { - ((ConcurrentLinkedQueue) removalNotification.getValue()) - .forEach( - bundleProcessor -> { - bundleProcessor.shutdown(); - }); - }) - .build( - new CacheLoader>() { - @Override - public ConcurrentLinkedQueue load(String s) throws Exception { - return new ConcurrentLinkedQueue<>(); - } - }); + removalNotification -> + removalNotification + .getValue() + .forEach(bundleProcessor -> bundleProcessor.shutdown())); + if (timeout.compareTo(Duration.ZERO) > 0) { + builder = builder.expireAfterAccess(timeout); + } + this.cachedBundleProcessors = + builder.build( + new CacheLoader>() { + @Override + public ConcurrentLinkedQueue load(String s) throws Exception { + return new ConcurrentLinkedQueue<>(); + } + }); // We specifically use a weak hash map so that references will automatically go out of scope // and not need to be freed explicitly from the cache. this.activeBundleProcessors = Collections.synchronizedMap(new WeakHashMap<>()); diff --git a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java index 95b404aa6203..a69ea5338dc3 100644 --- a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java +++ b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java @@ -48,6 +48,7 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -354,6 +355,10 @@ void reset() throws Exception { private static class TestBundleProcessorCache extends BundleProcessorCache { + TestBundleProcessorCache() { + super(Duration.ZERO); + } + @Override BundleProcessor get( InstructionRequest processBundleRequest, @@ -376,7 +381,7 @@ public void testTrySplitBeforeBundleDoesNotFail() { executionStateSampler, ImmutableMap.of(), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); BeamFnApi.InstructionResponse response = @@ -407,7 +412,7 @@ public void testProgressBeforeBundleDoesNotFail() throws Exception { executionStateSampler, ImmutableMap.of(), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); handler.progress( @@ -487,7 +492,7 @@ public void testOrderOfStartAndFinishCalls() throws Exception { DATA_INPUT_URN, startFinishRecorder, DATA_OUTPUT_URN, startFinishRecorder), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); handler.processBundle( @@ -592,7 +597,7 @@ public void testOrderOfSetupTeardownCalls() throws Exception { executionStateSampler, urnToPTransformRunnerFactoryMap, Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); handler.processBundle( @@ -699,7 +704,7 @@ private static InstructionRequest processBundleRequestFor( public void testBundleProcessorIsFoundWhenActive() { BundleProcessor bundleProcessor = mock(BundleProcessor.class); when(bundleProcessor.getInstructionId()).thenReturn("known"); - BundleProcessorCache cache = new BundleProcessorCache(); + BundleProcessorCache cache = new BundleProcessorCache(Duration.ZERO); // Check that an unknown bundle processor is not found assertNull(cache.find("unknown")); @@ -811,7 +816,7 @@ public void testCreatingPTransformExceptionsArePropagated() throws Exception { throw new IllegalStateException("TestException"); }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); assertThrows( "TestException", @@ -862,7 +867,7 @@ public void testBundleFinalizationIsPropagated() throws Exception { return null; }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); BeamFnApi.InstructionResponse.Builder response = handler.processBundle( @@ -916,7 +921,7 @@ public void testPTransformStartExceptionsArePropagated() { return null; }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); assertThrows( "TestException", @@ -1094,7 +1099,7 @@ public void onCompleted() {} executionStateSampler, urnToPTransformRunnerFactoryMap, Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); } @@ -1427,7 +1432,7 @@ public void testInstructionIsUnregisteredFromBeamFnDataClientOnSuccess() throws return null; }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); handler.processBundle( BeamFnApi.InstructionRequest.newBuilder() @@ -1500,7 +1505,7 @@ public void testDataProcessingExceptionsArePropagated() throws Exception { return null; }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); assertThrows( "TestException", @@ -1551,7 +1556,7 @@ public void testPTransformFinishExceptionsArePropagated() throws Exception { return null; }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); assertThrows( "TestException", @@ -1647,7 +1652,7 @@ private void doStateCalls(BeamFnStateClient beamFnStateClient) { } }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); handler.processBundle( BeamFnApi.InstructionRequest.newBuilder() @@ -1698,7 +1703,7 @@ private void doStateCalls(BeamFnStateClient beamFnStateClient) { } }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); assertThrows( "State API calls are unsupported", @@ -1787,7 +1792,7 @@ public void reset() { return null; }; - BundleProcessorCache bundleProcessorCache = new BundleProcessorCache(); + BundleProcessorCache bundleProcessorCache = new BundleProcessorCache(Duration.ZERO); ProcessBundleHandler handler = new ProcessBundleHandler( PipelineOptionsFactory.create(), @@ -1930,7 +1935,7 @@ public Object createRunnerForPTransform(Context context) throws IOException { } }), Caches.noop(), - new BundleProcessorCache(), + new BundleProcessorCache(Duration.ZERO), null /* dataSampler */); assertThrows( "Timers are unsupported", From ddfd1a2270f1801216cf7b5fa9d65b2125964a15 Mon Sep 17 00:00:00 2001 From: liferoad Date: Tue, 26 Nov 2024 08:50:37 -0500 Subject: [PATCH 020/135] Fixed beam_PreCommit_Flink_Container.yml (#33219) * Fixed beam_PreCommit_Flink_Container.yml * Update beam_PreCommit_Flink_Container.yml * Update beam_PreCommit_Flink_Container.yml * refactored the options * added test type * fixed the python gradle * Added the python version * Fixed the java test * fixed java options * fixed options * fixed the options * fixed the job name --- .../beam_PreCommit_Flink_Container.yml | 51 ++++++++----------- .../go_Combine_Flink_Batch_small.txt | 24 +++++++++ .../java_Combine_Flink_Batch_small.txt | 25 +++++++++ .../python_Combine_Flink_Batch_small.txt | 23 +++++++++ 4 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/flink-tests-pipeline-options/go_Combine_Flink_Batch_small.txt create mode 100644 .github/workflows/flink-tests-pipeline-options/java_Combine_Flink_Batch_small.txt create mode 100644 .github/workflows/flink-tests-pipeline-options/python_Combine_Flink_Batch_small.txt diff --git a/.github/workflows/beam_PreCommit_Flink_Container.yml b/.github/workflows/beam_PreCommit_Flink_Container.yml index 42a402add966..77ddcdf18788 100644 --- a/.github/workflows/beam_PreCommit_Flink_Container.yml +++ b/.github/workflows/beam_PreCommit_Flink_Container.yml @@ -98,6 +98,21 @@ jobs: comment_phrase: ${{ matrix.job_phrase }} github_token: ${{ secrets.GITHUB_TOKEN }} github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + - name: Setup environment + uses: ./.github/actions/setup-environment-action + with: + python-version: default + - name: Prepare test arguments + uses: ./.github/actions/test-arguments-action + with: + test-type: precommit + test-language: go,python,java + argument-file-paths: | + ${{ github.workspace }}/.github/workflows/flink-tests-pipeline-options/go_Combine_Flink_Batch_small.txt + ${{ github.workspace }}/.github/workflows/flink-tests-pipeline-options/python_Combine_Flink_Batch_small.txt + ${{ github.workspace }}/.github/workflows/flink-tests-pipeline-options/java_Combine_Flink_Batch_small.txt + - name: get current time + run: echo "NOW_UTC=$(date '+%m%d%H%M%S' --utc)" >> $GITHUB_ENV - name: Start Flink with 2 workers env: FLINK_NUM_WORKERS: 2 @@ -112,36 +127,18 @@ jobs: arguments: | -PloadTest.mainClass=combine \ -Prunner=PortableRunner \ - -PloadTest.args=" - --runner=FlinkRunner \ - --job_endpoint=localhost:8099 \ - --environment_type=DOCKER \ - --environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_go_sdk:latest \ - --parallelism=1 \ - --input_options=\"{\"num_records\":200,\"key_size\":1,\"value_size\":9}\" - --fanout=1 \ - --top_count=10 \ - --iterations=1" + '-PloadTest.args=${{ env.beam_PreCommit_Flink_Container_test_arguments_1 }} --job_name=flink-tests-go-${{env.NOW_UTC}}' # Run a Python Combine load test to verify the Flink container - name: Run Flink Container Test with Python Combine timeout-minutes: 10 uses: ./.github/actions/gradle-command-self-hosted-action with: - gradle-command: :sdks:python:test:load:run + gradle-command: :sdks:python:apache_beam:testing:load_tests:run arguments: | -PloadTest.mainClass=apache_beam.testing.load_tests.combine_test \ -Prunner=FlinkRunner \ - -PloadTest.args=" - --runner=PortableRunner \ - --job_endpoint=localhost:8099 \ - --environment_type=DOCKER \ - --environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_python3.9_sdk:latest \ - --parallelism=1 \ - --input_options=\"{\"num_records\":200,\"key_size\":1,\"value_size\":9,\"algorithm\":\"lcg\"}\" \ - --fanout=1 \ - --top_count=10 \ - --iterations=1" + '-PloadTest.args=${{ env.beam_PreCommit_Flink_Container_test_arguments_2 }} --job_name=flink-tests-python-${{env.NOW_UTC}}' # Run a Java Combine load test to verify the Flink container - name: Run Flink Container Test with Java Combine @@ -152,17 +149,9 @@ jobs: arguments: | -PloadTest.mainClass=org.apache.beam.sdk.loadtests.CombineLoadTest \ -Prunner=:runners:flink:1.17 \ - -PloadTest.args=" - --runner=FlinkRunner \ - --environment_type=DOCKER \ - --environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_java11_sdk:latest \ - --parallelism=1 \ - --sourceOptions={\"numRecords\":200,\"keySizeBytes\":1,\"valueSizeBytes\":9} \ - --fanout=1 \ - --iterations=1 \ - --topCount=10" + '-PloadTest.args=${{ env.beam_PreCommit_Flink_Container_test_arguments_3 }} --jobName=flink-tests-java11-${{env.NOW_UTC}}' - name: Teardown Flink if: always() run: | - ${{ github.workspace }}/.test-infra/dataproc/flink_cluster.sh delete \ No newline at end of file + ${{ github.workspace }}/.test-infra/dataproc/flink_cluster.sh delete diff --git a/.github/workflows/flink-tests-pipeline-options/go_Combine_Flink_Batch_small.txt b/.github/workflows/flink-tests-pipeline-options/go_Combine_Flink_Batch_small.txt new file mode 100644 index 000000000000..6b44f53886b2 --- /dev/null +++ b/.github/workflows/flink-tests-pipeline-options/go_Combine_Flink_Batch_small.txt @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--input_options=''{\"num_records\":200,\"key_size\":1,\"value_size\":9}'' +--fanout=1 +--top_count=10 +--parallelism=2 +--endpoint=localhost:8099 +--environment_type=DOCKER +--environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_go_sdk:latest +--runner=FlinkRunner \ No newline at end of file diff --git a/.github/workflows/flink-tests-pipeline-options/java_Combine_Flink_Batch_small.txt b/.github/workflows/flink-tests-pipeline-options/java_Combine_Flink_Batch_small.txt new file mode 100644 index 000000000000..e792682bfbc4 --- /dev/null +++ b/.github/workflows/flink-tests-pipeline-options/java_Combine_Flink_Batch_small.txt @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--sourceOptions={"numRecords":200,"keySizeBytes":1,"valueSizeBytes":9} +--fanout=1 +--iterations=1 +--topCount=10 +--parallelism=2 +--jobEndpoint=localhost:8099 +--defaultEnvironmentType=DOCKER +--defaultEnvironmentConfig=gcr.io/apache-beam-testing/beam-sdk/beam_java11_sdk:latest +--runner=FlinkRunner \ No newline at end of file diff --git a/.github/workflows/flink-tests-pipeline-options/python_Combine_Flink_Batch_small.txt b/.github/workflows/flink-tests-pipeline-options/python_Combine_Flink_Batch_small.txt new file mode 100644 index 000000000000..5522a8f9b823 --- /dev/null +++ b/.github/workflows/flink-tests-pipeline-options/python_Combine_Flink_Batch_small.txt @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--input_options=''{\\"num_records\\":200,\\"key_size\\":1,\\"value_size\\":9,\\"algorithm\\":\\"lcg\\"}'' +--parallelism=2 +--job_endpoint=localhost:8099 +--environment_type=DOCKER +--environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_python3.9_sdk:latest +--top_count=10 +--runner=PortableRunner \ No newline at end of file From aeefa4f6433a84206cf45085c7476f36a96f611d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=2E=20Veyri=C3=A9?= Date: Tue, 26 Nov 2024 16:16:32 +0100 Subject: [PATCH 021/135] Make the warning about JSON BigQuery column type more precise (#33121) --- .../java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java index 84bf90bd4121..9a7f3a05556c 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java @@ -3747,7 +3747,7 @@ private WriteResult continueExpandTyped( if (rowWriterFactory.getOutputType() == OutputType.JsonTableRow) { LOG.warn( "Found JSON type in TableSchema for 'FILE_LOADS' write method. \n" - + "Make sure the TableRow value is a parsed JSON to ensure the read as a " + + "Make sure the TableRow value is a Jackson JsonNode to ensure the read as a " + "JSON type. Otherwise it will read as a raw (escaped) string.\n" + "See https://cloud.google.com/bigquery/docs/loading-data-cloud-storage-json#limitations " + "for limitations."); From 5f5978178d21065562964973b4a94a6c9578185e Mon Sep 17 00:00:00 2001 From: liferoad Date: Tue, 26 Nov 2024 11:40:20 -0500 Subject: [PATCH 022/135] updated the pic (#33222) --- .../accenture/Jana_Polianskaja_sm.jpg | Bin 284152 -> 244421 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/www/site/static/images/case-study/accenture/Jana_Polianskaja_sm.jpg b/website/www/site/static/images/case-study/accenture/Jana_Polianskaja_sm.jpg index 1fb63edd227fd2831cda1fb83dc3dab873a49ac0..49ce23e5d7cdcc5632cee468e2b2f320a4bb830c 100644 GIT binary patch literal 244421 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 z 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

xoT99 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 XQh_4w{rab_S9lm&|9|WE!m74{ zKmcP`fcn}aWGI#(nSheW?sy8=j#O@NV?ug*?pV}@vYL*HDQ>yuU?~WCeEucN)oEuA zfK8tOi0OFud%>wZ_(wSuMfzN}|I9bn#G_T#kCuk|?SR+|{5r6JP(^>+ z)7_~zAjWc`iK57c-V+v+Oqhja)dCK+%Gk^6@%(BO#}XXxhXzup#d6me>H@btV*)&u za#dgA>uy}NrA(_w2EHD)nv@;|1!i$b5-@Hs1w}8H4fJ58lh|?)CxBTP1=~h>pojI$ z95;$CK8;-szt{z4f*5e3t$-nAK6)5nAz21u0wm>>MSK##SX^}mmSYJs!BUkN5;zdvrIkrBZKs{r{>EJMY!942A*`$gW}T2~z+t%0V!3$>DB5DvdWic5DCS;7 zyuMgjr^nsML^{YQnDGAnfhwK#jWeQ*)n=ot-l{rmxc-EEI|B&=d4^?{{IArNDFCql z-Je>1yn3H0@N9Ic!PJ9cK`xVPdf0S20Y?Zd_!TZvGK>mX2hSXSI#9YqI<;{x#~ zupP!Pu|}b`2LB(Gb+3=J)k3Kso%uR-#EL0+xf?$+*ycSs1O^|9<}c%dsSK|7!h~G& z;>PUz#9iGk9fmE=20<~6dKp-`Pa<`}>88d)iHT4uATQDZ-b!TLkt&R|oZ z6|U|M<;a?Y@xVL)n7~^zIp|Vv1bdQ0fcO4!j%-ebZmhCoee8H?vZRQ&8LKg!ZBIoU#GikSX)E zNYhlNHg<+$;n||cAXG>o^390Oa(}RGaT<-h+M`F6Y!f6Aoi>!1%MQs^H7T&ufIe>3 zD@dmKRV-qI7q0dY(MKNVRWf-sSyx17m zMq_awpjt4Ax}J-_6+y7)?sJ;#uaql&^X;9osK)tkZglVKb^)vie~u>!?5-*0i5Y#} zx`sIkK4m6fC(DX)FxQ96V{3XJH}g(fZumLSCca&^Wvit9UIWIaZlvmaq$)6He%IiS zYupP$N>_{(`N>b-L&o7KT7+c)BKn;F}68z|x{^~!{ z%L#X}yxh=jme1X7UOu{BYF;GQ0CSA}-2RH3_J*EsW0a1>BJXhh`@mBgX$386YP1va z%Z&I%S#*7K7Pk%izO>kfL4;eiuMaz4&GWq!UB=W5)867-O=;hq9Nyg4Q^G5Y43V+S z2jPcpU+G^3)Y`{wyZg?*EbwH7`CfTh*vm6wjmZnmKDwqE)VXRwQSi{zq`oxn1T^NX zb70U|D_9)y-yb-45*uDw(EFH)`=(R(sHEUdmTpmcRz-_-QgQnVrP2Yd^T}{efi#%* zN`!m?mzg4KXf8Q^LN%C8Qn~k?m;{(ZKuY|+a7|avoG?>CTYB}r-U1Y?;~-2GcOBC* zBYyuo``ukN3vRqXfZ#sdO*%J0SZ$SLT8wda5Oh?sDx@Lt^LCv+tZ@@{;F4a#gUwcM z;KxYIxSbZ>n|Br;H0QAkpiin$;6Yn8VcomB27BB0@%v1liXGjj@~wAoupT`XbDj9w zk-Tyj%PD#6`=+W9m_=5s_*d6=-zJoru(TR^UW_=x7XhuJk~&f{dND-pp~-n|k4sm^ zhv%^~PCEl!cHX@9@vrF$a|1^Vvavxg0&7>dotQc=Hg%(Xh@?|V#$R&J1?~^qD?G?Y zJh27~n2HK0C!bq(61QnoVED2MFqU(O(wMRW*|>1rZ12o!_-cWGRWOTvkTJ+t+myPl zB};k5kUs)8@MBt6?^`UsGWN^@NYcudNR#?=1}A7~HgtF9WWU4jUnBOuY7LW`NX18j zmwZJrsU{Xq+9q{3B4+&~=*5tF-IJM@#t#@XyN9OVA*m)#uiUNq?oO8ZTzOD_y30n| zr^q)o={2a>s4p^)*- z2IU>ZgX~#oe)~9C6IUk}$g_~Q=&?sMjBO%enL6+EZL!l;Fs@|yNO%3Ux82?!fMdAH zd0=LSNl^0uVuw}Xac0WcN_73ADz>vve_aNsx57OlVn(z+yeYqB{Rc%-iYLi8ma04$ z{U?<>Xzw96be-(OStsAF(*w~vkBy)nbqX43YMI{W2^C-TD%ETuwfTYq4?wr)dGHOh z>kgfVEx1=3e=}lj8)-rw27)Zat^f4oimNU{BZXB;oPdhRbd#5B|5o~>E$hhRjvCsp z{RcqeQbhy}HN~t2Z#^Nc!1E)0$ITSjkM88SW!y250?^$b-Rhg$yXWGf0%+Jlcq*H<@GD0- z6>|aw8hTLs6RYUHI+UE;&ycu7b6t~^akEFLEQbCh`kw?? zGwxv}BR2_d=*%}ZrpO;FX5w%(m=`^IS^If9JqH8Fxk(EoG4Sv}2 zp<(3pEat=^6uaQm?zHm6FN(+FLd;hHpRP3}Sl1>hBKSTkvAj z&C(!&nbndO7L5@|+Am;|N3e#U?I+W732f^W)@L_NLb$4qw4 zcwgA156tL~sdofHgdzgeOt|ziN|z4Dc(*_^ z?zCZkVPlW$rB^==W?b=rYFuiRd#;|ecBx{`b(p3HTNia?&H9SNBiP_%B9wU&kzFQ5Wve(YowI3PUI z2Cuy!4j2vEnTZjCsHqmj5-ya1iQJ3<7z(WBxTXU3v=T)dv)j zoLKIpkz1w>v`q9JAX=iiwOQV>BS7DCqb%uEG!K-WvTKaS|&XrwC$9{jW-b7|bZ$mr*&FbWmdV&8S-G7$=f%at?`T%p%)^2C zkmGd@jUKR&8TOk|CucMAGc`uwCBJ=CiZ?n!UE%rq-evsN#l4aYQxAJ>jLz$AjYQbP z3nnkYsO|_L0`(@@3BXg<6G|G?`rQ~SS&Rkt!WjVZn-^!O6>qK%UuA+ny8H)Fq?O&f zBy2)$9J6fMBVd6`YhsfkK*fY9LC>LzBn0T8M#EgOtH((;R7n$zLho>GPZfAo3*^{suc$-MXF2ruB| zybOGi_&zu(s9e%fL3Py2z!G&j8TpD`sTWZm)2cje5jl3rBM`^YEwHZv2v!WRk}KD| zj$Lx*f->xJu+R3GjxiUu1h)ujts(dlwVFB<&_{=p`r2T7qTi5$#&A)%q(fV$#tpnc zF%5)aggPg2`AWrdOaA>~6FNKO`vx8cTyRWIGDA{U`JSq?Kp1pE*8#oYJw9k_BktLz zWaqa@3l^@STVPG>*$jdb$6Sv};y_fiPFTA6rh-}G(#-!D2x#ex2f?lJ&{VVZiXT#; z)dh*2uE8%G2bC5tpwUWe4Do%@Pk6sBZW}CWTy6hWm+K2%12+u<7UkvhR#n3$~bzhr~{sU-REqopNpy_4zc>a%=cUZZvq-#8C z!;KyVphm7P!3EcQx_><)%^Euow!;Rn;ch!e&9$?VEx^gY4DMYKJ!#)S7d>5z9}E10 zj3P5*snGuFMR0xY(Bo1D7;?zHL+*{XjN~Rd%C^2h(4@k9B2|noEv@#7D2_Zd<*6mY&)I?NaPhFyhNoh+! zTkU}vq187kgE?zQ1nMUr5mW0zrcXjokT*z{f+#ID0|#>?a&i%$fW4dSG$#De;611O z2xK>r$|5$oNCJD>{S@9O;tqQ)J=uc01527BWfToA?P3KZQG@nefi0Amzd6wm6&Jf~ zL}x6~7@m+3NXF}$>z9~o5!~8lKs+AQ5&E1y@1xGv%l z=a>(Vb}7^R-qkR}GHv?T*K z)Fy0d)(zIAWv(Ejm(d;|N6-q1T7PkAhF#G3W188oPuGnWEk+Qnot$N0Jhb>3)l@3_ zFf7>ppk|SmXBMdqR+O|44W;}OwiznUbsNdj#NYfeZOCz?(>V0*jI;6yi@kEqe`+)8 zr01aqrL(8}52~&kNuzO|-W$tD@hFpga@qM;GzF9@$gu9)AEsS2tc>xj#N| zy)q*ZXmxu$VSCf)?IH>D&Ps?yhD9h$<>XrJmz4KF-Z*;+Ed9JPv-N)d2r7Y>6`vJ1 zL=97qP3&ZeT=iAhOWM9NW%y|Mai$@TGD+(=1rDVJlUbzr;k&B1CP~{kcK>*Ek3hff z_r-Hdw#V=WWB!9)pIxK%$1s0g+g{piH?g42=2Rrk@V2eEiBDRnsoG%Hp#pYe(jAzGL^8s04-O%ZDIt>B%Qf$B|c^(eHO3!6kXO8hD)tTY@f z$u|~*O(Y^dgIj@pQnEzY)ku+$#Ec6x?b=we@rsRP)0}8s^VBXQzYtZYg=i30iXUB* zqR8`0oOQNDRyOYR3+;5Y^f#y})ACoH z_Ybt)7;NoeLmxM;3h_T4=#1jh(0A2;r|0gk3a$W$qNApjcAx%_&&sb5!&EIXs|=3g z?_WWIx?;g|qz4hpfin+7KI1UI_0fJ>%2Mf(P_PlxJv(U}LjZo?x&|!kvLw!@yu|M6 zgR%96ii>P}R_tCB3QWH;G#h#BE% zas^V81Mf4!_I231gBF=}E~hnCMXrQy?c7u%6ApBNl zk^mzx;{~`hZ&?C{_`&}(q;n0p+fg}yV+(V--l2EPbre6sDHzJu)GXX;OYtsieHbjm ztdsyzj=f99EVg_A9Un%>6D4=ijory}qL|g=AWTTQ_|<&$OPgLCJKfjDm(oIZo~kZ=cgDjj@L4o8Y#ZXoiSVF69$n_`*$raiHo1!81-h|A#8cGAzRduQr}o~SDDe1! ztSoSA6Rd-K`|h!jgKOXvkNurLq&hA?m^PmZeUuhzgJGf(w0DoJ_>Db!HJAYO^yk6< z0;Cv4+}I0@s%2tm6n-?Hdf;?_KpK8pW(8_avHAdvUcywG9g9Ej~ z_g(EKXw*`V`?2D>lckAW^FcqSHQ@?!)^W4A2`W0I-)A%R0uiJg0`4^cGqznW_m+P3 zip5OSM(V+YljD|eP#*B6C$fRwn=w+tEW~x(XVEF>4=HxgKQ6!$c~NX?ozte2b_gVF zY0TqAGPv?78eaBj@2`yxuNc_v>9h8sjsqND`RHzi*NbBZ^491fl~~~Itp7g z(AxVtMJblkG5cQI0<)@|`4s7fGHh6oVVXy5n#ORcKc)8oPy`eG_|J9Hf?RqHaz%TM z|Bp@G{dpJ_fTQHP)sk?JYfMWr(kdB#)X2m9rJ*=GH2`&AS->(>V|*8rJtco7Vx$6| zsx~0~gI5S)QjaTpK|?O8@R8V=22@8^k$Y)zq?-?(aV%umM8+jEm05_;HI`vW>tH+# zcH_>FaPV&F)Q70TY)K_Zj>sfwyo&aoi7<}Dghh~6eco8fVwA3?x-LG&SZ5rG9-1#H zt7(~JzP|x6J#CF7YH^>UnlqQ!U^NPyJT)F=Pk~5ngbBWRnk|)4w9z*p{q}vJ*#f8P zh)nY|wJHE;weBu=JNqS@qwnZUYepRE!X|n{TlyHuhal6yjj`T^&i>!p;IBgsO1Q?v zS!@#B-?}Q+8rU#oM??_u?`c*KLs-rBafds_hNb^G^xV;gWa~3DLM-L63qh*FRXw!d zulE-lMT&V#p4)~4$&+OnVOzabw@%>FI*@@uN!QXy8gp7uk zui=WbO|PmRXPs9&zgr{C+zP~92-z`m%ebkwQJnF$U; zC*-y5GSGp!QEwGSzga>wvDX9Vx4WL;oj(_Rb?Ptubwgt%v-ZE;ljH`~)066nLF_YO zg@Jp^uFOIP&`J92@>aT*?iDs-A=EURH{4gJvap*Pq;5;>JEpqLQ8g*CS zklPTF%J9#}J@+A*w(|?3yP0;vyf8;Gu^J7#B}fW_TPZlO$p(ywD+|JE>BB}Q zt(`BgosoNj6 zHmRxs`(9%{{S5kU$nRTn>#`DOgd`DuS?=?+<_|N{oQ)rX_&jX5tE)Sn9&rQZ`FQ{L z*&6lO%`73l2BGCHY{Mex^)Ou@XPqXC-|~Mp;&I*kVpyx#WDP~7l_)c$1s&i67w)S- zReR5+1?fonxWYzMu7b_R|JEIC;usg>Ms&j-Z^(UmxH!Xu(3t7@p3Qx*$B0xud8t<>4p{wDw`fHsABt_>Q+d;Z)#)r%AC@l>Y1f<0j3@7 zErO_8+PTF3Q)MQZ)I%vvD>htMewbPyyO%s25@lG^`a<6KJ`j|qu2Dje(7XC zsQgnj*F?gaf6!Eoi0Jx|2HF5E%_8!iG&TI9rDqI*^XLB~>C3~KI=8RAx3{&{0qaDI zLh1xn1gf;pwUM3R(;%sgEFeibvubh$_duQ9Ce9fT<1i zH{X~Sn_j<%3Ja`|yXWL%WLC3QF=e?NS(*Mxykf@98cOAb6PoAJTi|Ibp<~aBOM2*V zPNKSKgL&Po*O#ib@XDR_mhL+5MRn00unkO4S|J$PGr`3P+Hod>^8pKAh0p>#KMeP^M}s`|AT`cJ>pScaQunvV+AMs&pEy?R!Uwr^WTYGn_PE=A_akv0Pth)!hK_Ec{d7Z=j;S(9yMd#$7`AX^a*Ne z?0C_v*5aaN=B1d5A(1O~gr6p#iRL34x;>lyuV>%r;$xLN3k%x?NC`ffS#rFgFO)Zs zb14AXub|Z_ew1&CH|hA^z%NzYnpn*MO6s1{48z929}21UJQ-~6U>)){iGBBl(E@9P zBGoU^P_^XV&uL#gBaX#!qJJ+tc|0e+r*#M|pDrRsvKUH$yWG# zU~lWG0f++r5&!5+bmWC)k_lq3-FYBeCWk-Mp$1v6pNbH{VWG|wgUNH};A8_MGpI;SP|inb_5%_`(B{yi9;Dj(tfw^A8E#j0{k}=1a<>fv)tO$S=utlu2K?27 zD*D`;(L@vnn?4T~Fos}ggIirZ7!YZFR5Nah%~U1@v77!I^s2=3&# zj-#D#@GFdQOW@D1jC*jTT^2K{CiR%#EbRK0%O0ts_49go%Gk!4SFx?u0_Dz}w70ja zWcc6ubVMLo@OHk<9?meH0e3929FfOQ##|0EqZY>ks1@`HUo6%49#bb`rYN8$DLWkV zpIG`SJ`KF!fcW?d~w2+RCbEVbs!A733$7Zm7^)@7B z`w4daZWZ%~5e6&XV%M;L^Hl7a0ABx%du=sk6#aQuG-9>3@VUn%&Z68z zmnF{m|5l)nPG@ydUp+pB&mP7r7m7G&!cElMZ4eR+;cj~LAkD}3VD)e=;~bm>-DPax z+^5iXsl%hcOJ_2jWz4hDClOfNq6&R5&5RUtFzyu9MY4uql@e<-Tm|t@UDFKXqv-eC zJLulk*d+iD^Q(>(9|p!T8Vl@Kstds+jRh50a#OKiNLnj)Qxr2AGX0fu5YCSbNBLZb zEGk<}r$pYe^*pbboS)ju&m%vN^g?cUD$EdAW_>JVX=w~0eT54`4!VBK7RebQ5heVr z{Kl}5>}xSp6#{ zf)<}ILj0=IV0CO*8-_`kl!L+Na?VXm#{wyXQwD26kL~h-|joQn-gOJrHb(Gpg~u7-v=(Y|wu?OmjR0 z8{w$AbmsynGQlo8y;q={W$pT{H;Ll8D|y;&k0GRV?{=U`i+bq67Zc-1*O{h0p3Pp4 zS?ciA)jzWF3;p%q?9|v-et{UV|QfcmDK$j=011TUs%Mf|ig*5Gru3MXdvk;+WQUpY!)exAfc{Q&u>_^xhPMY zist+}Gjf$8a^qmHi9_^dTQF@Czc&u594tEy#y8pfrN-^(K9^!e2;4@hrqw+U7jHo# zY?{r7b?QIq{OAe7hJ6?_ZY$$xHJG4A72cUv8fEPUIN62&Xr8mIc|bkChM=m~ECf#b zQR8myjTg&^0}5uj{ls%(kA=g#m@+EmxtF@Qlc1cW1HNq$ATPa;EcCH^kID0}C{S+} zyOnu@x)EyQKl)u2k*p%@N8(tk(Soqp-_%%#t3HJ3DAgt#t-z$J;FhZ_bJGSM*lgGo zpXPrOo!`-g`MmD0dkX4HDgJPzfC4*<<6a84?T+38Dt~4xfT@?>U!Auc7YXLw<~LDy zvQ;rHUIj(qh8ah1#MO$+#zl~B9`-e|b-v;x3@PfMuvyM(U%5npTLuzfEsemGopYY! zHJtUOmGwXyUEC;Y)L3ofPo=1J3YJF4gQptpb4tPAvkUOtxVVcLT{=AN`a3f00)i@n0#U87D4$`qOHi+_K zw(Orejx-U1UMgyvD_-iQi3fs90eXS@Zpq?xBHHQj?s7^v4df5+83vclK=b(Dwa_Yz z#-a9$lwr#j4%*AzM7zcGgrnD{GezB0Hh&5vi#6kC3C7{1VeNg>xxn#QxbR}DviR`s_?F~f z--hHEQNdGG#-+D7r!5X_4>ejm>M4qTVEvvWiWx4H26l@Ne;+-Nb?BW{4z5j<*L-d* zoiuK_zmMhq>}sN@!9LZ>h=YDJP~L;~c)LLSG*iv`2NV)t0StH%ljFnFO;fU&?ys)* zjs04qw~&QLy}=2^N4LqDELxXg2NvB;jt z+k|;VUSQTeEO}2ii1|xS*E^rkMlUrJ{M<5&silYbNa~(T=pOv~4pS^i>kw_035iVe zdkRa!z=%FzI$^=a$?Fo7gLP%|MmVKo3r&T2?Ph2Y!=cJ6gikBf+=o-+!Zv!y6h|Rs z)pSoNRDi%YH#1hTqlKNpiyb`M#oN)YskD4W#Ui-(Tn~_zBQ3yu{i7cXiK!se^@8K) z1~|uKX`M-yO_arcjkpzN{?MyhJ8ZZQ7ilHPX35-!sKKcJ`mD6QLJG%8M?bGq?o5l8 z9%uILr$hTyKD=6ESwFg#_!MF3Y(UPqJR|arscdzyj zlts9KY>Q=@(kOC{$#4{jqS7>iCsJsBzCnC3fNp+9rt#_<=|tGAU>$s=mt}v^bm>|Q z7cbvtG9kTTU^7isl_Ef-`o%Tz*bUcbq6YjdNFqsoOJv7WnS8~K`*)-!!4=-^6sBsWCCn+Amx50HJ zEu;rYOA?Ob7h1&i5!<|Nf+hrOp!NXN23T`wwUM7ne=UE8H*N)7mI~m0AdRcA$QKMx zjlu1VHWLD6+yGNLF3i*tR_oRT++2F&8*VKt#V5(KI!X4-*;&4RjzfW0lN)ez=9Qi* z8obPv#L1`!C8lVO^aMtb==e!85}6F}$|9e`;(`Od70Czc)&BJVAWm zkw_q5DgLxq^vHAW4Ei>h6fhfJR&FfHUt*6OP>=Q#%3=R0wU+S(Wa3STU?^mQ#A4Y$ z6~*{tCO6W|ot4_%q(@N7?1rfqGZGs91-CJZvKMb+eH6`mCZz((kwA~d1!uruN zn%1w@UH+Q_P=-K(z59Z;yydSAlN&D)9ip+{wxD%>EetDm!d(B%nlu}NbWc8?W-529 z-$B1nj;0UjmtN;y>%{=Un~gxwp4-2i3%ow4*ap@d*Z;$ke+Y$p*Rt&azLuK54}q3y;FrInvh?mv zL$Nt8zuP!holUfvV1|ZH54aS=?^3~3=B3i#5&J(;T^%0wr~i+wty;1gpn^=8FL@U; zIDY-p5<5#%E%TDc$MkmoaZ)`YYx$q_G--1a{uUc&y?xM@Sjg<=zKf+QB0e7fy*38B zKw!M5+*$4Dud_Z!RTyn^!rHfzx3AznViHU_yTr;ZvpZ<*NW){^zzprKgi;A2DZfla zS*bWVVRcd4s3~=rh19{PXhqJCG3oyy^?*ny=2pdOs_Lxho%l+my@D&_IQLqfIKrNt z@QV(RQliWA#!Azud1b>c!|@{1#*feWJ%l=CHt*^NFGu<7;+t9pbF7?p>0?0hY> z-k!_7V={}enu(Al#X@w~rfi~XUl@o&n z^Nh$r{XvuT%Z%g-WqMwXdl|kR+Uxr2##PxG+u_|i`%_yb=H{wKm%8~3#Rs`uYGbrK z;2RXix>CQ#itO85Wc%mLmHq9_CJOR3=le33<#&uzSvGN(lhv~w1{pof(%?yJ{X@Mb z1x6}X|L@xrPLnTw|6h21qL35SnPp?wbCP2{W#pZe)f>q$tWMk6=Q)Ptq6_sg24QW@ z7px3BgC)0_yH7qH=})TPwLHK)<^R}1&viMsaA%vBC(>nJ_6~&W!i}xbH~WU1#*K5r zoyCYCKj0EsYF4_NFy6(v9ZLW7WA52ucCG&%LHs6bugIdQ%Ay92=V1HOX3st3MWWx> z;Is*`|A;$$sLqAEK3+-Nn{Ux6*eNwSK>E$)d6}x_s7K0aO16Dh%L0sWjTyscVX{y~ zjGJjFK~|E_WqM8H=3y6R@8>rRe=@vr6{M@Ko)@-}7g!d( zR?6%?i)>!}F7C3}^E$JXA#vMz-RrV`>R)+qO8*hUDv21v8_F5(yQ;znjAyI>BAO^# zWIRF-3awpeDRq20Cf>g!u5bZ!R4#7bb$d;dtfH#mn!P@Q4Y*Fsx~#(d%|X$(jDC%g zd(yi-jwogJx2>dEb)aqYa!6<^Znh#Z z#IB)7FKd7Q<~g?L?cs57GVLb}vAcsLVd8!KG_;_)bW&`1csI=I1{?|p$;M_AIHPD0u*L*v+c%(KyJ6sfPq8CF-+ zu*Rs3=f^?Kd|FwZVKHgAPc$tO*2k}6}iE1nO5Nn1jecL7j7{2C-xKMU#+gp8(zAS$f_-*xw3x<{1yo$ zh(7o-_AZm_%+jEaVY@uDk1XGS5a7)vaE8vg{V|(;ySyRTh8T5xj}i85aGZLvDH5A- zAR9eYc}U2MJvAGozJgixldR}9J$6!J)mtnk$(nLq!ng92`DDF?U^IeJ4ryZdIcnNo ziS29l#zr;7H0p7ORKyiQqjJMDBUJ4xC;S}eu*6O&Z;y_b;QlppMUU~M#aa4PaOaPr zWw>j|^ZHj#;?2e>`>G5ee-o2}KOqTgl>Tm(+HeCzv4Q&%%S{$4->b&^x-QdyWrE;)P=t2e|oQ%Xx>)noHX#I{lAJf z-L8c6DmQ~NwjiJA>bhqj%h>3;P5!XDZO&l+I-qV|c&Zez74v&~fyJ7V!GZ&q_uy^r zZzAkn$_v*ge66Qr_0Lo3vG>~G5_x2{ib_g*dE<>4%{2+F^kLi>LZ@4!buR?JY}(Ca zM|f>1l9iOQ)bn8xNhIp*mC8NvyftL@8wp{g~BSqJCa3&v5 zvIDK!bHS88#zYRF4FxWaFt=I#mf`V1wHtjvTdXHujKn)K1jF)_wn7j8&T1`?788_U zIC%q0l0MG%qOQ1%-}9%ax71kK{1k`Qx}2v+ndlk+gS;$=pRjB}v8w{_``Kx{Gc?7P zTDv2AL9KQc8AooFllx3O`Hy1Q9~oEDXU^XVn-8729cF{!KDLg9%`r2XF)095pG{>i z8zG7SJ}8l0e2kcz5Lt8Z*O}w18Q%gi@<2l&5ciI|NuOqi-^{x?0(Y1d=97>B_EEM& z4HR?qK=;pLRQ!3}>HhbI>&*|iMwt}}ZYW(kXm8g--tYC9+A$UuVp;+$*-1K8mKVr- ztD9T|uN`xS1KB@<>krfd5N_}_C1Xd5ZxqE;&3k>XkVD7$R3w34qR5JsZ)_|Z!G(umCgA?{9$w01ze+KK{r;HusBy|@*b=u z!)_|at7$Us?11d3YSDQmR)4NareoG?7W)jBJfMy@{r+o;(#S7UZ&5dCN(@4d7BO;r z^$G2kZ7em@|H!scY$^aJFq)M;Cm6CAa*2A<_N#a1{j9savlZAAQz`8@2>|J&gHhe` zJJU1Ln?p^ghuwPgF#uOjeTO%zCjG;EtUVPeUe+=MWEN0gnzV!FR|QblZ^N&y0R>Az zs9q}7WCGkZPy9w2FG4n5fV8=3#RvW3@`Tpv4ABgh_n?W61zjxpZvY=j3Yu)8-eS_I zG9W;`I-Ab<;%@e#O4<(37}Udaz)kvj-Qi)rSiWEJt$tF^BHB~N;?8!P4^o+v8e^WU z3+Q4;RL5Rb3+6cl2LwPr-QK@iSbADxcyAwHDcgW)RkwB5B&hfC-LcTn%S5k&0jx0a z9qM7vg4IuVx{Pg5Z+>}-e_E)>r7Mn1e2~7D`^q+u-az4*^csJOSv_5y4%Q2}?79sH z#4%XYQKYVEDTbQYF{AR6z9a?|<7Z#YK1@QVrkOZcBh|KURBG9=LC!g&B1Shyh&=`m zd?@U?3Hc>NQc$Ql2!!>b3)QzuJL75Jm*;cD?o)Tixtwoo@6jZEnp6WM}HKMdAzPCAQoC~6{0sc+nv ztnfJ?x~iT&rn^#h5;30k8^bq=NoELcL0Rfg9P|3vm${j!vG6S92Lf zLbhnL2Q#;wKl6E=q%$wqepF#`*FiffJc;zmiu9P};}5!BTH&bTWrIHCBJP~r0cq%0H#Rk0Ifi+neXzlQ3p{t4Ar@6o+d^Ve$sj_~qi_GxR)cAnQk@&(mNw$iW& zUx?Yr0GmeLx|_{unIYy{F@3zg>8^71ZSh=GHZpFjY->d=c+$9Q6(=37 zg&cYcuJ#Ry*fNODR_zDo{nNzkt*1L@vZ4ZUG{eI)!6D|5$t&JSLb~;HdyLR1>3?e| zG2)VmxhU&u)J*#*kSTT|59|-@92h@3Kw?9{D$}(#>}Lxgec!Y(rpI<{D)b zXQ`efWDaNYIWrgciub9l44zcT^>M1js0&n|ZbeTUiSW_9v61##GJhqd>ufjw2rUc}B>W8;|TLD{`mM7fhy z)2Z;_9`{%ls`T=IRSB@>3oYsHenQ!156uRjHnB2c@i5S&T!s!&!{@QJZ!1|h2z!l! z{tKUp)v?~!IANe8#`o!-=^ry67^3ZoIW4`!71*Qa!B(>nQ-z^QX%EIROa(d(99i3~ zp(`)#@2Dxe5z=g!*nfC`Tz1& zg?XOn?3li$Z?_D zJge?=-1C3YxH2O7eHPAY`6?&|VFgO?K;F%NU?10nhQDM(njVneV+Q;(EN=)Vh}eD; zg8Z)o-4?c<*}E&tmHt@`Oqk``@#g6F8e!VrP+G86&Ut*w6Y^V_eCO4-2Cxozn`TM1 z=c=*iOEOv(19}Pq&QHzH!%6GysiP&P!{(xMzi$VnwFJu6r){WkvDN;X&V4YbXpS~- zWRHV1)P70Z@DO7`{}cPmVRhQoH!REckB4`3`B$f@_pXhaz5D9XeYnclRsM?d>#Np; z)!J^F?82nI!lLD1R2bj9LMbPI00;tdO0jc>15!S$N>HzUEaR&8gEwWZXF};~fp^Fd zX7m?2QnDxI4VRm=DZ@K!&FM_kB_C&({k2AqtcFb>_b$JOg7`Qk$pTPMDr_zJC%yIJU%jx*KjqMvs zvY3@3q~9&p;(@<$=sDQ&BZZTQeczy~2fe@#qV30L?3h~X6sON0R3wUXq5%!trDf}Z zA6~QHYW9f^EhdX)F6UsTGEN3&7sC(?z2{rbhIhV+W}KAkQ+o|re`RQ+p6a%8?a%8# zgaecM+Pst?t-J0mdaDL(6noIc;=fkk?VzE0=Gl&nHIKdb(yi*;1*FG<>kxGE>+bJl zv0b!lr=OobS}o2UQg)>*!T^-^+b1dQwfH?JP=i5QEwMsLdRs?0PE%Q-fAe56I zad{?dFc7)^QSz{Xd&wf}51F;&Q-TQD-TyX6;IKXuaQRwqKfV?o8@<({sAq3o?St+K zi9t3#O|;J~d(>k*GSL>e%-%({vOXfCWsvunVHzhg$W{Kipq?7g*B+2QU24=KZB?(_ z-&I#camo|rbW7c5l$JBrBwdsjMmS=T-?D1B_zI9cf_gYQ1#*{{mfoVit+F+*4TE`d zyOt+t+*qi2*#j?j!o^ycoLkZ1GQ@~Mz5M<#?U89FWo{+Wk;YPN2mM1dC+=r3rNVYT zs%J0OPKFpDm4?EDoORH9{kCX zvIOMdB>2kl3g6M@6_r~k0xxJERywPGkT_Zs<_McPMOan&-Q(L~QxFWi^?BOIgvu*Y z!Npp|1i*V_zSW1fyd93qN0w2WLX^{v;9(!BaStMj4Z}wW66;?%U?l&HioN-k(fp3+ zqnX)O?a-d5faDa=1Xv1RVCp}NBOk?FM2Q@{8TFLCte2sUjaao@o-0Fka?DkGWxJvv zw+fi|SQSl8y>3CC=)5jc7tbF|QHrpNHH+34%Y~{KPo(FUQtn3Y$%%qm=V;I4`*EmY zl__Qp`8G~@WZX85Z{41%=Bj1?2P`npLNUWJL&a?MvR7vgQP~73fb47GX&JG6 zTA@2Z*J<&Va~(lh3rq%UAeUGgT(%LB%emWYho z<)xE*gzF#cAfHy~)bDTXuR@l}B%WAB@GEoZC+V594X-I9hJmT<5uM%TEV^qqR-QKQ zfKeMisNdLlt0i_K0`3X{UBgXW8eRj3VP6GBu*Sou1Y~-|Gz8aIaOZB6xXP@9?=_-~ z>CzF0qG#oa+B8sb#gs%M^UqxU{?uKvgkWlQCJ`=f|3M-iTBP{90>@0$XM5C|g)DSI z02k`CiEc78*{#p(0&LM*Z1St3<((S;%jg{8JO+j+B&Wt2w~B#uL|xi+do`L7^Ld@h zJ9ITp_4|02d@AZ8atQa)qGXW8&@Eu4%~hch${pWIOCp2JofA;qtwZe5h##xj$wQb`1IT=S)UG6>)lY~Zv)^<}>52T}(_WQN zolZZGZ4*lDE~ojm@#nD7$R&7*15LMeAo^VLALte`g%X>OXwjx&51WHzY)h=vJ60Fo zDyXK{3;#P7po1uPF0t(5__;X}_nk=epXy`soy9i9VymZYQ78MS3boGe5}YC11l=&I zs4X1X|NrNvRWtac$s%+~{nhX4#u4jpA`^o)vgdX)6#g>(#mzbwjt~`WDgH&^R$&$V znuaAgGI^Y6WuRO7>1kx{sDbr-__r-ybQ+QW&1@OUi~cK?@=e2JY{LFKt1c%d-_cWF z@plJBJvn>XJS@ZP;|k=LmdU;|X%p8c^%fhyRXHvxaea*rT0nVikN8`tqgf}qI(7id zWmM$FGt~~b#7Xn<$|o~f&PVmSGIrG~S#J3OtEI=*B#KU6@>rhn*bGt^#S5O!c+JXG zf9Q9fH3^SSw!T-*hCr;t+$26IO35oDb-R5VpkaM12bao~eregZ`#yqmr;3AQi z6FUjOWvcLHQV!3?Jz{L_9q!;l`X3k^jjk`mp)Ip z*y0kQ#@B<}6lRih#%Ign#LD=h8?I#?xG+Z)2lT;&i0qp04wx&9>T6O`@O+h%Q2 zyQZw>#TZV#BL!mi*rBPZlKjGEskf2riIaR2I%Gy8qS;FWB+y>7xDIh`tWa+rd!cOE zy`ww*rIa?-pGvuGlr5vZR_&ztYi1GzZY09=2yh0h(m?GUt*}5H(GYMLz7E^X(Nq2V zGR5)ntQz@RqzxxEUOeDrnnTcCYgqFrq(DmMWRNk+WHg#HC|F&T2C%0YfI{VrO z;hkI1AFox3eBsBQzyhO(*PDX=eM-YF!yJkXthcTIi7kI;=_VUcuYRgv$Yu7g`}Qea z78muadnQF?%V={D_J1Hs&(9fFcrNOjiSX98^h}(!Yqq3g6v3oj_;*4EcC{syz;tH^ zfZv|7w)+)S4yWE-_&<6KhKjvACnHK05=yGTriqiz~09 zbf(CtW;qB71sPq?D)iS}+z;7}@~4f6@-MzZs1>d#2ouFV%r>5~x)PL0(FF<)PS*AE zuuWwU3lqCKSAmVQzz$o^pSzkO7Y>=!1)I-r@GsD7RfF+?zqhGSWRtw!Om9?n#rwnD z-pUUR$mjv$2RZVy(trj)2)lE{U9P9r{XPO}?CAfIK2_9-$SZ8<>a`kfSB42noS*0| z?9rhFsUH^=O7D{SxEjf#7WwfeU;=Z{g`B>X-6z3|E5(0782wME4@h8gW-*A|94P0f zf-U;8KWh-**fg1G=JSB`A>Kfh)gMrfTDnQZHrTTSMoS$(DM;sa-hqNNM0Rb}P1B*`0kb;Z6 zX1T>o@9}{blg#LLj}`rgB^kX@-9}Hv!iOc=6ZGLpEEs3J2r&RpcAfWA3v13Z+lvwV zwQr<0yin*Df3-cmNwLE=*o0cHP094uq2>2H#*q)c6|CBWBwM{T_QdCIl|~I9r&P!e zHGhuw0p0N+BqtF&)^~h?+>KX4!qpOLle$%wSUwMG`?p5Qg)&cvO+Q;!pcaA{?pSa3 zjiWc>_@#ogxi2Vc2r>klM_(AxRBzZ^=2YD~k>Eeh{UxFwfyc7pQ;se7{IZ*P37E(LX0 zcS*-t?DJ{!;(C}CWT4zmiYx#Eg$XKqlRBj0hSCY_xry%!qMU!YJhN(^8IpDjDWWVu z+fhRIZ_3t-`>9$KuAS`T6_dmPc63>{j&v}?{hgA(7Vl63E-48F^YdqbZRV+{bLR^P zU8*>9Wan6~wEtE}3Unn=iw6LB{chiBN0X&((_8Kbkxk>XR)P18q-h-Zux8ytMW5H5 zcWA*KMW+t|Lp9)Hbw>X0&O;2q+EdVg%LK@R>+3YNA?6i0Ux@oS*8SKm9%OMuR;ajB z!r_jQ#jWTGZuB$cKj13#us5zLxxILn=39x&#wC~I7IM|#u|`nnM;ZI8bIjBAmIzs} zgGiIG`dPbbFd?d?wsm)%$Dm*pO#ydTm=limcD7}hJi}(qAqUS5&_Z?NNlEun;AE7 zEj$%i=;rX)M5j#UTM6sMAfXP{Skf^2kU464{w%Vr$5!4#v>V`u4#%E4#8`r~TU*u8P6}jZDrw&(0FTA^cLw@cJVXki8jMr_JWEr=nRJaNz;X?&TweITd z3Nt6|4T@i$dkkkJ8yp_)5l~uW##Ndy7Y-n9=||A-2`^SC{W3$wuXyRMfc{!pT&OoF z88hlkBDb3$daWqEJY4NGr*lAo3esOy85$t3rZa6pHqgcW+50Q7-rUhc7Ra&pu7%Tr zl;*2Cz~>UaC{(T$q*z0v6^fo5n^@3yt)6)e9(!ocl?Dx=8_Muoq5Gdt>+IWp5&asqEjuJRb0zXJ*kxM% z)}kD{;(%57?jNvXM+QRHsAi^eQNgF)d+xv=_w+%&4Dh5K9)o+kipXi@(BLVtUi=07 zFnFIf4$AKh9LzLdmU_jeIb^mgsV$>pq}pdTn9-qz>dHp`41qWBbC*s*+AuN+AMCp4 zmPTPbA8@nRC%v3t!!G>mrLiuR<2IaBcq}pHd|C$!JkLQJH4P8h{4=-YaR)TrggZar zUC1TX-U3NCM_)_evLX8sd$=5uqSowJZe1XtDIqDY)M)!_)dpu*9C z{QJ)%<;DKw@)yM^Ri~XE+}Ltx+`qftFsws*6)~^0C?)N1zC{hQF6Wm$+Fz=ubNkZX zuc8BYP^fu+X@OSMqWmPxf5uGA%-&fnH(Wdyk*IdK}YVUto`(nEy`wH>~dBfp*7Hlt*$yKGsd&Xrw%(=`RJEPF8i$;pm~DbTYrnR zt9IbVQCq$8lh+qYLWTu@xUH+M6WVi~=(FE7W*6*P`H`E!oNg3)U89%8v(@?NfY2v@ zPiUj1C7p-_{F86NNTt4`7ghbO^@QO4Wcj_`Y_{zcQ+UkDMX;CU=*1n*U3t!~#0sbw zAFVf8rRM27(?YGanArLFTja3~g=N>hLU-`24WjH^IE@MRmeLwA+cH zFRs9jlLel4{Q13rw0RRb9Qj?N%Al?NP00$+y?~gqc(H}&6FnGus+h{J zuJ(&iQ60y6fYYNAfCzhLxlwbqZJgC0#tlxDa<-&6#2>nCqp|vzFuz6=!d|21f;Di; zFKLU`>hKJ2#xMgrNRP_QwE1|fW%WS7=D^tEfZ$Io(Y2aqhc16!cQH`1-zigUIc6iE zm)IuEOA$$Djq;?1{4dMOZufTS*IbOKoBnsvU#o&e|I*k~czIg=jmUelbLDW#80b^$ zGdbP7qAwnaSR+mJH)mp=t1|dN{Yk)-Yus}rws^euaLiXbX6`q=7@k2LeMg%4E$3%3 z%gfXKl6uBauxjS;#*c2xk zDI_JS=-FejpVu9$sf%9Ji!9rCBE*m;BX+eeXhqu$RggdwKjJwCBZOGD%8S@Gs3-yHnk&LUw!PLT+>W*!jJOE3-}@|an=L-=1Kv8RBt)spOuivz)T z-l3lu6YT%Q=8@-PS{RW_)&HBD95SJ?kSY4en+&teARTO9|H^G(bdZQm}3x z4%4lE4B$uZ)C6z_u6aQLIaM~1VttP!P<#lX zsA~ruiprqDvLDe?hX0}OjM{8jczcQ6S8qOdj08@Hnwm>xb(1;ewc`CSGO6i5zA1m6o2su?Oapb z=HIX>9PpK13N+GdqbJ(`=Jx7ESwdv-Q{qd1qrjG-jGwxz6J$j5G^YbT1pBHalDB?# z{;{H`yypA2sx!Q!eo5-LYAyqWeql;hwX<#fG|#OpFv%@*_BNYb3sj@CYKSr9j26sASi*t-%eaAy9~p`%ICrJ23&Lg6Un2Drk7PVk zL$!HU!Ws`|FIuapZLLP>>~<7wW2WZ=UONK2cxn58WJ(nUkfnyY!>ve%MsgDZf*Xfu z#;Zj!765;F|5Mk75Lh(~ka1(z5cBa%(QmKUif!N+#i=#j4gdq|UFSVUUY5<-7b0Ps z?>Cu6AzTqM0Jp^JWc)mCQ~^m2k1hNeUy!GqS-l3`7|G36neYj#KV}SCyz2ZuB2KqU zX^vBhVlsusp!m>76f@vKFDyL!>Cnh9hj??fd0?R%%S6X+K*=*29WHFuB19BN=$vJ@ zpiLXR#&d{JDO+|cZhue%C=}Ll1^*7dIK`^FXr~DV7eQhXC)%!_$Uuvvg^|G+(2+pn!7X*%(oZ-l z5i+JwbM%`+7*1S7d|PA(l#A}NjUhUaaaN7P}Wvt)#`IDTWBw{NLd9rq@aAql34=Z$Hy}96YaGqwyBh}G5fm@uphrNq-}O4 z`RDq>6j=NszlO2l+W6)3x_9sj?H@Rv3O2-Afbkj+HUqGO&rV(n4v+r4&KUJtsD|(L zLJ{;$?G|KVx0a=GY<93Y`q!G`;#jFRwl#i4X>G;ztI0OM1C$dxyEf>Ab^Fboev|6{o@@|7W&Z|ug1MNL1@7* z<5ZnA0pdHZp^(MSIM4A|-E;g0jsS&XAF*Mw?2_B;Zz!K$)1SzM+n;@9)pTJlrCotX zz~vlvPoxB+g!G#{*RPJfMC}a`A_*?lrhtb3!9C;e^4ush)SrhgPM2uK_71o%@)7!D)V{gA zR(ZPlakHbpQCjJN3u~*Xo!4?}`b%XC8wted|8Z)9gEobT|MfGST(;XSDUIYnxYY4#CN_+%dIrF$`1iarQcyBDaPX zL)y1M#w1^#5(V)oU$jfzw@t$1f_IbSTbXJ}q%G0@_wLi7xq%ys0%q_Ik=Ul2v$sLW zCH}6=x3*dom`#K6%h&uIKE#L=(`s(^$|z+93v8gWbN8`mzxH2d-#PeL*nNZtHYGOg zO-?+E!vxN}3;DyjWRHd3Dc<2H{8xDtPFY4=+p62<>tVZll9Gd$&x1NMCscj-@y*V3 z*4Ms1l7g(mKi0WE33Yw)@htzRU|GMFAZ~7vJ)pb#ssBh{@vL;~QEB2yoxLS77I)ZF zT{2>Gpw9OGBiO#ebo{yRWbvIfcMs`j6l_;)X^U*V5yL!?K`mUkZEB-aZ-N)<&uo0G zbz*&IrjXiexw{Z5`H`T+aQHP zjV_rPpg#WF?;FN zM0^9n?ry@y4%b*+TnPG?ljP%St##Swb@z+Do4n1rm1!?q$$0UL!}=F7Ji6RKJ1g6z-_$pvS9^B>!?Y5u*EBJy$qRT z50^|VKA1;`N`rR2kA+v$r(iGlpc^+YnNM`2*c5_k&~qxKVvvA5{;E}xJjMJfMa&I%L+arKInAXZV*6@uyLx160beMD{#hM=DWBgy2$?ZzMc)qU(N_vLns zs$3~43SA4t6qOkMyqU3pO?>ztPJ+c?MH+_*BeWNP10^2jf$mBDE^9hT_=Qck3HI2m zZ3eDRJ3Lk9;|eZWFX!lUylM!xp!Sh!W8i=4?B9l&6@=Y#P`1V4;wOa{NO@%jHWxGW zZ>O4gG=8XbXw5I1N$)j9+f6GQ$qO&bcuHKBch%o9HQPIsO8u^IQp|hQUp8x7bjyy+ zRc1^rJOeH%<9q!Vy$2s0iuokmcuCD;XKA|}+Y}V-8Ce2*{7pm(ESXwlIv9I<{i=_D z@t=WR?3rZ8gMQqx*aEDsMJAr9T*~)rH5$6n(~%Rij_84`nsE)Q0jjup#l=mF@tovp zG8avm?#Eu=d}jyN-@dy-y|~~@4>>9SsU&rky@*6O75Nv#7{U{L{cjIjo^QWN<*$;9 zfW)`+Df&qAt*x;u>1f6!f5n34v+)ySqul;`1J$hL%-r5?DT&CDL#@W@)^Sd~#U=-; zl-duU7Q}QO&g1PbUQ)pdi^WR9#!X(N0XrYU1c}9v5QQGrFHJj=Nsrj-RgrFF<)Jid z4&?fL=zm*upnG04i>Xks>xV9_l5;i<-)+@Po2($X#iZ=+_LR3Z8;UImpQJpMFaZZC`f}~H*+7zJVojlI|&>eu~me3l&2a|ehv~epiW#9<&c^%{;G5l`# z6xi@=F7Iw9zQT>i{Mo;$T8sx*Ab>x&651InZ5;I?Aea*RbL|i4sbD8rM%)6b_8awT zyo@r?uOwS?VgRUj*%uQ2A4k_2(A52Q+xl;VudX~a=(h~0xa;0%&XbVT2xaudr1i`0#Nr8 zxc~&Ky?cB+pRE}GjLWY3J37PW2{%Yk=&zmznaG{?>ZhE#!8|}0MEe*4{@?^7U|^fi zcJ&qkj9iL}vQLIvfrL1WzQS7{eya`WYbm4`;A8>D=A(9?BS0{6(QPSh^3YM_+4FmW zaMN1RAzlYv|9aR3Pu}WM^~utU>gC%&JUa%Q)^Md>fcewUI_Ch%kNJ;Knm*~rNEmKmteDnXDn}p^g*KPbNDG<4PQtCbYF~Nw}oy}GGh0;c$De(o2rUq z`ko-m?er{rQmvykjrXu{{H}uR0ye6SDRRrjD>xY%UBZ8=ga=A=RC3Gnc}(?dVYH5) zGVF9sZ#1$hua=)8_MRYq zKyqATAbah`ZDqzc5*@54HF)NMPs@A607n`b=JM7HxU{6G&H2*+TfLZ~cfh|A)I}LN zr@qUtlX7@m44|_6o_0SE>`||!tF7)~&_P62G-IwL7&?}t#thwO^TI?O`>Eab)97`d zj!hNRP(O{Ir%_*XWWN}-u_(pP(Jh~8(-z*_A7gt>sCT_aiF?|KI;uKBO(R38`}NOA z@o%?2ZgvWr^7rq=2p(7soT8gDNR$w`Q+riVQiuv_FhS2;>Irh}2;1a^Qz>~hC}n~4 z77#pd0UyY+p4C1+f_;0swZIhISPBPk( z3V)wld~fe$)o|V{j_?&>l;UeUSTbsl?g{fS6B4gUTIHzF zjfCDUu$yE`y1wX4X_L%zxbm3+iFi@PrvvYV3EKY20KJG}bz8dbZXvexBYgGbnYztB zPU0h@xIq_Nuw!fhf%dE!&@VU5cqh;V50fH6cq&=7+t~YPd^B z5JhnMCZ9T6?)iFPQRG|xlEJ1a?688&V{9b*|ou{6T-SxrsVav|FU z;X8QCxRUM8Ci+T9CN^VdIWPwF!IjW?+4R%d6yLc_EI06C5tx=K^4Y{V=HPzi@r!{s z-w~4vl}&PpTOPmnOMY z_Fg6k7{WfdnVwNnE23Hhag*wz;a7#`4X46CnE*pYHzIEI$n1VWsj!9UXb?J4p>{s{ zVIWon#Yuf(-w^}X50^`@74DqZy^nZ<&qL;Ycmow@=8;Gnsj`)SZzY8IQl` zSe%OY78cVrepDO%^}%spfiFLMR@N9EcU&D|Ltm?#%rblah==K)PZ(M<-4R_zS^6KB z))>EZDcfs}_vZr3{-RK@*G0a5KHSkF)W<2^$3F%2Vq@dR)k0*7GHmqbpEK(BSrjH6 zTX~{za=4Rl+x}q6yv;CQG(CT@2w_W&mShss%GokgB^i$AFC5Zt@?O@kwszi`+*b{q z4RqAKrZTgRRat@SE~>wRYg3x1_7G))plWB6d8Eg7?R`Pq$E=pXLa0}Z6-FHZ{0C@a z<;rZPU1=2G<{Pr_5ecWQXwUVj5k>bvGZTuf{Uz=3>w`lz!7=XE^MRrc0ZBii>e_af z2qjm{cS6p~N~LFVT7;qBZzey%CSJsj@6k?L`?T(7dv)Y9=3%&k$6}v1O|!4^Tx!x` zb+qbWBy&-#?djVTUaB+aP1msL>tfDHh=fB*0KlD|`nq*NbeQMRohL=2)|H3#;L$7P zZ+ut7>4l*))3mTVWzA&wq?!nHU+uc`rEjr;-jy8`wU#6%!4c1Hazw4UznO#4&laTW zvWU4PwxhgEKPXrQ0=%V95Hf-KdWB~J+dQK^>wpnoC3FxJO%_J%#61g5QKv0 zudakL>%ft^60g=GwGRo%v(GxR02i$|rcNd=7)5xri3FzJT^xubh4B z;~2;E*TpfBO<^7?tf@*3y)i__cx}B&c9RaT^a=k=Zp&1gKbwADRw#OVuwpg5RHXgh zm9@7V{Q5^4G3`QlAv8{i{Lpc*`d!SLd*(F7Px(Xs{v7&Vo|vR8=(#NFw=J_AYa}hw z3M0rMP?4+cMIGRkC^wS+5lsn>C9B)AbZ?SFh!Jwp8yM->O8WnBc+CSjvambz6;yXQ zaSFc~-M}kd)a~5M{h%TV0zOO1ZIayzeqi)_kBRLteatS-l5Qdp;InL4DS@o-=Y|L= z8YF7b?l?YUCdM6qK4gbLKcbIKsWgnr`qs&uJ1cUTgYqK(Au$46!8v7lA@gk+3}Hg5 zD@aLr&M?H1IBp1|66e^-rnMgP^I>sgrlSK7XFt~1Y5u3eQzLx7^K)jYUL>G7nsmkk z?RBi)P*h!dYn<)XHCuO5So-T&bt#T9RI7|Hs@3VoS9}t7JM6P@!?!hcH$|3#E45;z?6Xk=os=OL_+^o4O-G^8iGrzJ_*v=TeAvwB=*}@JP_r%_}Q5V+7!9;)luyI`odnz1J!qx8{x+t zS4ya_-y3ifjbfDU5PJX@9<74oQ0DIs>T>xnt)Q*OMwpJ&_iY91chzn95KkZ}=XJrs z2hPv za?J(->T5@KM=x$0ySwwzS=thILNF<1sspYK*;O-+Di{|P_K0E%ngx%%u02=NW*Ba$O+PJ=#&-h zK)c%mk=KVMgt<;rrYPT8d8&{s9Q!IGkBZw?w zv&+p*VBI~QWe)EuR5EVRgCeVUnNjP|uJwpQxT6}4p1cui4ef^mLVK$R|P?5a5!M)}y)pRG8!;W%)0uUxWDz7u>Th$V4 zq-2l4=6n0;zZxCe{IvIS&oMjd&}l(&%1|4cFA}$+fKs_Smp5+0sBRp9S0iNc{Jox; zTvTF~#Dc)F8NLYWQUsnBF*8Gx%F6cnoZev|l`QNh){dAkr$IeFClu zizD!690z@>GnuJ0$WHpO;j=N3oD>nTkY3bz-mWzTAh940zqP=ddVOpUVN8#%+ghA? zyst+j4go)(RR8Sv8S~i(C8RmpU>KynGe}yptmHtX{W1icLiN%U9g~Fm`LWb;opZwi zBxv?~xE?Pl49^h{PY)#&njq8pohz*)E<(R1=uDQ*U*ONx(br62I5fFN9Z7&A+V#VR zeBybul^~xTNROI&3D0T=Vi$D{zIt;hR!}&%K>N2qfBWaUnMgP3Jvy0+^M;Pr$xMvQ zP}poAp6RB#CVpNU5Q!|)&D3|Mk8XY?%oO1m!1kq)Jk6Cw*T8ez)xHIeg>CTDR`B%W z&KFo_v-G6Qyq3haQUX}0&uyG*U2>lvJH?>t)Ap=7G%voTO;f2Ei_BK$V9X{;=sZD( zDQxW7*f;r?ILHX|ruv5Jj5@u9vMpS4DNe1iNJycwn#?2d@0l;KcdBUL)l`*pqXqgj z)+)xQZ<$n%fX(cd&K1Qe(@*DEV$9?Bvr$c%2BW^V*q|)iS-G2usUkd#U;r7FTNp+D zdRBz}_jfK9moJ}p$@mIs8S0ft5CPTzo5sMM!Y1slsouW3Q>;F zG=sM2?Z5|lv&+%g{|7Ayi6FOVLtc8XyKP>!6%AGmZ z*^Ra@5gjLs9WG!bH*Q;{f0x`9x~3ct2jj!W zM$Of-B5Jy0W`k|{YLeyti%SNwr~TNT14(2WahPfOUNmny^t+GJtI#T4y|$VNbnCLF zX7A(+W>QGX)JxY<+CzBhsex=inyjIz)jHt9Ky7t8Sh=09l7`(*HZ^n}*l7Fup5EnH z=g~JXExcm;x-E=4edhzr=g0MPo+_NY!X^7nm5 z<}XY`6Z-m8X23;x>hC2ke!5@YeO*H-&P4;@N^fuCEa~_4yn;FWpjDb^t{UOM0c{{C z)^6Z0aQ+f)#ND}^qONP7OO7={%dO?q@ED7~QIj ztB1&Pqj)qBdkt>tQV?LI_5pe*(6eaJt4I3RPy5z&@4ZgE@we(;N{_q7sjDtbL)@xP z@GSRV;Ta0O%GSdC0g^jjv3hb}b=p=KlqAZy*g=u*gnE0hQBChWt@mn*N8f>ryT2r4 z>H!CgjCL2%L`eGS83_WPd?K~t3p1qO(WeRupjL*j)>euMuvo6Rhw z9!_ITC0(A%ctV@1vevi>QmZ0A>$v^IbN^$kPP%WsI`L>;vD3ZB4aQRdFjvF|~oz+#!V*Q6uge zGfarTetVV2Z1Iraf6-+qHm{QoQ^KD1l8O_bbZviA%1a6GWQxOnudZ7kKz{sDbhd{% zWv5|u;l$le_CNdX8-ilt_0EfJl7doV$D7=Uox$y6?7#qu=kF1PO*?%uaHrGTAZDrU z^oalMP-cej#~_LE>u&ScX@JTP^0Gx1ENhnaup`+`8KUfOQ#U?Qx`lLY^d^B_hc6fj zv9a^P`GWZ$b?_NJ)!PQJL&$LM{La5VZLaJZT_-N8XMFfu&-`#i`HC}^+BxD2N}n%z zqmJHIJKN7@3?hNzm#@lXyL-Wnm>5jVCQ< zguPGH{jDiFm;Hu1cQRn@0dlovhEzTP(kpcNo4CJxY3~^krhkGH9jcB5xWk&o)L)Za zEHzzS8-_S=Q}JA>=*aoX>-i~{h!`F!jQh&Y@RGe});&;EOruHur2yX;2E!)Y7!7XQ zC&=82Wtm?0|N1I70xu!oR?9UEa6cmffaQ+i1uTD`>9OxO?}S!9d7}iyrKs;rthk+( zdSHjnJZ3HWIDlw*HvBB1nx|&`zszL$WPNByhQNUPF8;V>1p97%nL_U0R{ zh|5D3kA|A#t4=TtKt?N@l_FMi+Bb$|&kUWSv`I`gAl}*lgL^7R4S>FTk@`fQ7`oAO z_>9=cyGstvSL`{Gpk(k&Vj%Sh9%D8BLO z#M#=UE&s+-Q|BjOO@OwEk9pr<>c|~flw*gi9;iMna6hC08rTE>z{~dhMEp^um9oe0 z1bS=}9GP-{jC>10yp!y9Dj@!}+xY<^CL!XnEy95-o-5>U7$1#W7?ABTHP_GLjwJ3> ziqX-?KUeAasw!pBFbBCzy#=7&7G8F7=+fZOtX3LNIe0N98a{ez3Gpp&2;&iZ^*izp zf+#tu5$a)69xMS&>D^+@>S3;`BDT9Hy6l_99TJUw7Ia3!gE^*}`@H}Rs>`T-wv0lI zcMph}1TUFNJMhx4(A=$s-&OtpPztfZJ55;A`eoQ{Cz$n0KuUb)&J?wwRsB8N+FabX zvpZ1DT4^6&n;EeHV`0oTb>j)8Hie`j z9&aei*bgQ#w#dJD(}M@+k%=i1bG1jiw>J;)PTxLIqBHh`t96KKOWo#oKOyHg`v!2c zfK&H`n`9Crv*Q|ctynbNcuu56BawpD(E@d6$vs&4?Z?)kxS7Mn*OJE8LSM_(OKuD! zj7vY#Gljl6_Q2}DkF@*k45#LZB-jG&^{N$1?hY&8q^x`-iZ|#3`IOdOQs;EXTJVB$ zyuas`9xg@B(7VG%URWQjf8WdgrV%8(H}|xsplbeh)@aktW}9Xt(hAaQ(-&Jx-11*# zgDu|7PdRx7-phuEPX+uu?S0t+LQD+*Bf0+@4H;#SK@Y zr$6rQm4hpO^!aKs1e3lA;TF7kkmMu{W@-7Fv z8Dt#D+T5*HuFs~3vfL{`vU?i>F&D@I}9+W)tBz0Y)#Ji>0%+r0t2b?BA<7t;z5CuKi*}VFj52vEkG$|(V%IzR7 zxOA@O%FpTpE@*EeJ&mG~2Cwc*v$6e&EDY6@sFD*_7<$=P)^%oZA`^ehdV zN;uv5+z&4i4RSQb?{=E3oKHcOWuL*`pu(o^wHfSL89nhr3Ufub{a63M+FENA;3wt0 zKHN>zibMBE49B8AsuiW3@+j@iqQI{-yU>6~=pBD@f;4a`JYuW2G}KZhF@ ze$fRn#?pgmQOqENu}hTnL+Zl`*t?EPJGsNg*JzT4cAQpbQ1dT}{#6(e76!s4WcTH^ExcM=Bkwz{bOGPJRBgXeeCcKR=~0*$RPdYR zUU5%us|D}M&h>&$h1<`!e#rAJPpc0@###ei&aV%u3*x{my_S9JH#)*lGd=iPC_pBE9}Hzu_Q&7&P_D74~3-fD7^Cq|AQ&^Ujx4D9{jCs{e*kGfHlX=~wK+I1ahB-E)HO+%PH_ohjop;aX99TWfv#uqe@8xC_*S~VCDKZU#fpu(pR8ve*)&%yyv{Piv?rRbkaJn&l3O{_uqVH( zy`P+5dHnZLUGE#OfIztHPp57UC=4}=Z(3ATKj?&0rPM&H14?86JSqMfcY z72K7+(F?vkz2k|`;3+&%Ig+uzPKfu->yw~0VJMK3?@{GOxSs(I`|{ptDq^G4F_Gop z{P$5{=v*=??kb_$6D%d^1hwsl+nSVmZ&NHlBw` zZ1%I&Z#?YHx>bc70DDGy5PCiw(5|eTTCOKmhNiZ<&J_@m)|+)muX8P$4~h>4xFv8; zUalF(9kAh_wXpWPl`s92wG|Edk$mFn{rC@X=us9Y$21TwZdNA(nUPqkaSF ziE{_iait>V=jY4W8>p@VjUfjFUANzgFdYn^l=M!sv5sjc*|rQ{m<-$t%pFHPfzJqJ zH5#ANbg1hK>c>**gQyLQjWESS+}yu7#wrT*9Wh9khY1>YhBbz_P^|OpLv33%V&jtr zGk@)%k*{2iK|MU%TTO>czVez%*BX@?y^q+~enImw2dpxu^h^T_Nu~^x-AH<~&?t<{ z%PthYOnt}CPgo({S~j(6D>b%}oB!vi5{K&#kca#)0wOlGX3d&9O+R_mV z=N`JZge%Mou=%))s!uBVyf{+e1LhnT`P|{+2cq)hDl)C3_tBc6+XY_!+P}=^Uz$)h zCqx;euIsGnKadVI`=g|vKH%3ndYxLLVzgOGa%*Rio6mDX0+;i*{~z<*ss!#t5m+0f zwy0TfI^s{aO~FyBZT+h7R0u^~DDCT>&Gw{#hrETq@_1xd?ITUsssXBD>i5XTC7m*_dmQeJZp<1tnY%}`yu(|PG7l}+(U+;t64C2H zAuH`cKh|maRcGkC(Q5G^%QhzJ|4sMyS z`l0JE@ zDRyFgK0$m-w9C!}Q)SCv@1j-*^tF9Iv*ux0g-0YQ&$L`N|VX%p(5 zAar|m+iyVj^sQf0*6<|Yp}O9okBG8HlFF66zL>GK{$+Pm$7Oi5vsWM&L;%qp)~wwz zP$l(&5BbkjnKfJ#<3y~|GkQk-IMh|pJGZaH=4C$j!vOH8JlMYcY#(*`OL@8$GCL>M zb(^Udx9*I|gjRnM{148}Kpn+rZcP}p)bDJ@0yxPAk_ zqlsSz#j={yA{?!fR8ZO0rl_f}O#zqA8EtnWUFs);`EKyEa+FzS!JRAdbIab~h|4=JtSKSSopMvz-m!tgo%AilS{++Pm_V5!B@qHi5L|5;L9DaCy$IS$exs(nZcGbv| zV*f7%%LVZtvmd3;^h|PKF z2~4bVC8vOhhTlxg1F|_E_T_wP6g;dv63RmkfjglNr&hO~rzx^TLKtpK-+~68esLcB z_I$46oFybN_L&&6Ra)5w9?}Y(sC9wMD2M!vO-v!iB?+Wjt2)^Jh7i$OXG0$n(zjo_ z4JU2<-63h|=8Z#FH~1}R7e_eOs?y3Kr-PrkyEYij)JVN>p@HffjSC_dk z_bNx`z*^qyd-ZR-!~Z`6)%o9=N*7t=GT9!Z1U^i0KlE-ptm*{d%H z`}Yd8qZFwkUt8`FO^XBY;l8=F7ahdk{@3&&>dKUK8}-=g6j9S8Lm^1A&dhNs?ZGhd;;7Usu0U{z+IbMxlqNZmwDEkk!Kg|n~B4av)IV5!%XDYB^5N(*+; zA`}Qj)y~ywrLpRg^y>B>`z#TG=UT-3BQQn>l3|j1O~pF$kMeuTE49IfTe80QXE!c5 z{-%luAz#f7pzeDQ3UGD)7gs29@aV-Kiy>h|AqD(`m2l#E6WZmY1a`V9Nr% zohBRN({7hh^XtCDTZg$HZB6Pd5j;mF6}jo11yla0q^{8{bINZ>;UJH8JoqarsrX!_5+oyDhs9migc0$ zMcFx84BhWG{9di@%U~A?F5K$x)|S23#%s;mHLp>Zc!r*C9QZ=n&jk>jH5GY z`oLNFyOQKb*j!LU`FXn*=rnM$-P~*Mb|&XkgDnJ%g7?F$ABgK;2ddw@`_L5^b-b0P z8E)nD*Xitc&t~trJEu*h z+)odaWiiG4a=+Hi5ej6^%c3jOyzy-yoEx?x7njN;m#6WyFtx-W?EtOA6@R4oWY|Og zU3oQi%USVw0rG!Y8NhI>JKuIOJ)>iv z88FaM-=#~9R}i?p@Y-iwo27&Ll2T}RATfB;ee2BZzcuL=6&+no?MhE(*69zBQ2E|4 zn|@GmuJk)#Zqxed(3BLk2m?Kx5WYuxq#{}0O}~s`d$`rt{gbd;Id{+H9;-|A!A$zT z<*bh|jd@}%;7u9#bfvDoNQ+$eu&Kr+_N@$ZrRC2K^CISFuGqs z>+W3oRG7rL(la1$f+_U6o5avvjh;y74kqbh2h!z@!#{gPrfz0t*9g zh9Fa|1iQ1>C9Ao;+)CTGvvL_LBaom2@50D=3=nt;Mbq@p6_@z6S`vAxEiiryta3Y} zC*|DI!d|nZxawYR1sW%$2|yF!OZD&B7=ED8O-YT!hM7|yQvH{|Aqz-FjUheHRJ=T0 z5uP<0$A&kJf-##U<=L7H zi<1dOoi)&=fb{3jLUIsdeBYtj)M@Te;7a7!Y@M~n771LimTX6TXfCdKrDr&PqkYy1 zNbf27;hbcsELs6xf3Xg)f3<%EZqb*F7oY`nG^8!y1QUHopYqySy->s)M%(qq2y&4G z6HIhaPPGo@R6c0LWtU%tFnbtFlUrUXNEpnkmLm6^Sr`@S1dh52@Kic+)2#1BuQFIJ z3pB4G-##GeaT>)ywj29^$R|yt*6p{gCz+wjQr=fiFftwz5~jQr-aD;m6rOJl6JANi z$+sF+Z6qJMm_BDoq8Kaygz?@bbqL!(MnIuq`eA2Yfs1+d_q%xZfLXrv#6E{>#yA_` zt_+{!^3I}R)sBHSXEa-0vOegjaZ1VF;R!g|s_83;=T(nCBwq4qbIP=IXYm}lpyM`(PJ|3P4L8jNna3W; zxvJ%iaqu^pC2d$mV1n=gZBRZ&TA);NP2V1xw&vbiTOQMRO49fMh_yat^1*uffz4}C z-vgF+X3EV#;D~+>IG|7zsMk;fc3T3rrUPdaI*K zN%Nv6%9Oz01(Ttogh=Jj)?8nrGLeUtU>*OKGnXNUnez*2*wZ;(!z!d7!FzFfj-wr+*}m9RC(+ zw|XFASEsktI45Mki%PA$cR1i^GhI_16)iy3S7)=`Rybyk5QKtR(=o|09iFbrJVx9O zj*3A4VwL)Si;d|A>-<_943tOfRZPyH>v7&D(NG$lq@Ps+jS>~}-@rkc2FByfR9`9c zl?yNHJ+g8cSZe*)%`vCnhU!K`3W`<3HEqjE%X^W`P_?jvJ{f@UZKgB$r!MsnX?lwf znOEqYzYXCYjA~bzi+53~#3AD+FJw(1BW@@gXNnH?S;gf)dtcwqd=8v!WIT~e+0%Qe za*^Et@mCU!T0sLap4n@k9Us@&^HbgZp#`h3?d6lm3UgJ3sIIbCb6?;yc{h8#yT>+Z zV@;^AFN)(jYo78;%(=?}kRPam-=Sf}3IzHnR|*za_(K)bMN-?3mVxxZRaoZB@ENeR(#^BFZJdlNZ_-^R ztJi(msk%?2mE2EPALH!J!+hA^48jMF*`x5x;k}{=o82iiS0-}lR_cfB?MuhOt2z6E z9yn0GUF=l8?pWIRJGhL-x8tSz&`l}wrM|cW_b5An$u=E~PTsG6Db-V^V$XfAEVsF* zt8`Ny8#wbAsSb}qO96Hu@7UX0v`rL?aBZ`hfbS~r<#t7wV!vcUc!nc$p0LnuFWnSwC@j}K7n3$fV( z1Dy1%oX{csPhIEQc8vL3F%2>>g3^07gwY13ddZy<7{ndg4;L~*D<{aUXqTYMI~S*Q z>j2u@Ghb<{AnuLVtp&3%zZ*<(e<$<-<_}ZvLu|Sa+rEvWcNl3u_VbkL-G zDrRP2(TP5kCB^0EotK55goQAL8QocUNF`Tqc?$H4xOZ3;U(5tz!3@z3vmk zUJkB;V_Gt%VRxWXnL3*p*g%Q4%`ke$+_>5vxOn`<<+c2oedxxrVh$EDzpc&-Q@#@Y zHIT9F*1%^xZ_spXloYo_hr>82tzm=XKtiD^1BRJ`kMSN?*j?;|PJN$(TOYSLZ!emA zu2(r52T+CqwjQ&(War=p@B7Y)*s)f3%46V?bF}XT4|~Qa&qNppw^%WJq?wt1>LSeH z0@&3Ba^;z>V7Cb4OIY9ZPI=J^&&)TvLlFHd{e!-5qbKHrhPUxMu%j9}zB>@)SYm#%Pi)5-yU3F3Z96+f~ z`9fW^x`YUQItfYKuT2{Ji?Hd0kQaSw^x~A+TK4q5#j$7S<)YUsE}DjoX#?im(S}&G zHjgigwP~IDYtCAT%odRp!7F(YS`SwsFPXeB8_P)~trcZXta(n~(JqVFX=VnR(ARNq ztM95Yo$IFMzA!qL_O~x%El@u$ZQQWyjZji%R9XMDnOR>Fs^vPNDqFt69zG5m)7ze~ zcKnK8yU@l$iMbMF>PW&KPu}qx;ta9#b&s0G>Crf#~!>nvb&DC zP})&ARUBS|qGFwGG9amw-A^AuHeNS1o- zZL$wL_heWivU}8Z&iqJzUA6QQWV&{w?8M>HU-zp1nw?BmoLO4;-JmlCCZ~^TjfsE}pKd9j98!bOqxAa+r+drO)dp=)Q+uxo`9s_&9UJRQ zcdXLNx63P^Y5z4uhcjgaY0t0??I_A0aq zMn!K*x$zr_sJU+4uo^pc;)=?+!mpicIFpXOctJiM&Rp;+U2lj z>Auja>}dQZb24aKUN~B%PmZuMeA5e?dE}`vJU79-AGs!rq_!FTgS?rlk#b?O0!%#I z&fOLAcYUjMz4vGTx|D=di|KDIton>o)ED_1L&y|}cU_AdBg`{3fq$V%8p-{TI^CzM zJyCC91xY_jwf#u#?pdZLfZt#;)mK_j8ro)^`RpDpN@5x(5?b(Tk5 zC&@=-0RV|qde_cfUMcF$zZ~C^R_NC~3PHHZGnskU24V=2^k96&QO&~-*1V#%0>g=p z#ybj?A1aWX{vBrs4R6Tyc^6K7Rp zmRZ~mMd$Y>+~@NuQ7HpE9LgdYPnDadJkvubkA`hL-JRFizin*xa_&iiax4VB%N{Z+ zwQ{7l_4GJlLb}!?Sn9IuPwFn{+E0J4p_Vb=RIaR)g8q886NDY}nl`gL?oBAVM`yEM zmYY9EnoDh~Q>J4ccMXJ*Y^0|V`>A7!?d0$cf150ntb3dciokIi8e4yRIS%F5AqNI9 z#@OpvVeY^^VFmv%l4tYNX5?Rp^C&^*=a$I|SprkqEvn!0aRhlwxFqyFIO zF;|aN!AqFJW#`=ID@`0Tj;Wd^sNT$G$sD%cF;|@p}L@&Dq5VJ}W|A9C~a!c1@8Bpg^$P(|CmHfU9Ei z=;XdKsTsE|;IBC9DVu)#Hs|jqZw`fBuOo4#c(;&zH}c-~Il$INHxY_({cp&7Zu178 z0jV#zoTT~}ZbDpD$&8S0TIqj~)y>sT&3phg<&1BBwDY>gI=+?Ko67XPoi~Wtn zZ7g5@P(j^gYVc!~(v!Eu=?@(C7|`QLE7ZN}+{x?^ARWAQ9M4kk`?+h)Y%hT;P%|Or;Px3kyH{!w7@FKN zshZh)7--g2!|9~`H9cgF3rA4gVbTaPOvrvpKEj#b2W=YeNa;OoLdA@iRx%Pb4Ljwv zder*Hmk(yQBbOV5J$iJxh*N!s=TQ+)y~hm(t3y*u8hiLc)I^MTpFP-pJ2coXVMO)yz5{A-N(=cr{j6 zYT8m~*Y``)t`C9M&>9}8&bSJfNiTL(l{#h9Su&u7`A|6J=Wj3)mxS+`Vmc%R+)LIp zmN&}QgGK5#D|oa%(`B8FE?iFEwy73N$IqGx_M^ZENG8kd7aNjplAR88R|Gf@S;r`r zUMSxIFx`+?EF?5i(YUspN2OgINY+0M#ZN4MH#Y51nhWZglbOS-Sn=qtxGs7TZ91SzvTzMy<3?XGm2M#M z(IJNM~|g-bx(fTqpB z^t_iKYMNm7-$y6#*uC&z=<1x1NqeX`DdYx9p*l@~>MypC)yv0W3CEy9%(>o`o)q6< zr~fhR;pNO*v7??!0vue*wSPlG1o^;oJ8@%xJQBsBFGz|w`YQ%BCwi?4E3un>TCggY zsozc_Z`0+s@wNrg1wEt+1@g~KB^uZJ=$qidcFQy@MZSORJ`9{tFEq8QCH9km1D&hs zKQ;CaZPLj_YUN=moXYa2f)Kr)^?`_T`NZQa2b+go2Zp=uI3^#H^c*XSyFrjZqSrw4 zKvg6FxmpD>T0r|3W(W_Bv_Ny&Y9KLKqhc)Jg+FL8-u7X8*!F`^L>rd2CRuz}m2l5M z$>bCs6lV8Kv_1IF{5l=c8Uc!Tw7a+^OA4i}v2LM)`vJZ3@S_t*&mn#CSqk>DD;}dI zj^TSMGcJ~=VHKB|cAtTd#}t8M(enYytC^_HkPSc04v`;5l?czz#+Ey{_892}|IG~- zUSO+-)z85!GO%)8RZY{%?7}u^!vzxw@qA&{+b4nY`?BSS-@=X(V1YjR+s57YzmIV0 zg=c{cr1wbmj43LFqcBx{yTtY|RlOo>{dYB5u#G68FlpH2nuvb8ht^%3MrLLDzNIX# z%BhUtmV){;#Pge|7j|D6>vEn%e@L+J<5-VxC?aVP8;EuAnxZi`-i)NYW7N?Fw$p2hO?s z_zt8T^OCvh#=(XQxfupas(u;J7tA~6vfpN~zj1Aw&p$F~+dnt{-b=SSQi{(XwHEHlzAXKI(5z=_SbyRa z_L-yt`U87zhdp}pZ#|r7fg~sx)z^k@@>2st#=oR$s9vqR=-rx4b z@g8-Im8*MM>4E=}!ls5>En>6Nze{UqZ;}UI&634sCjM#d*Zz@-q4m0zwLV$vkb_TM zUfG(?$sddJ6ataxag8+FTvCj2QLk_pL>HWpZqcI;tVJ#3`B3>_(Y;5NJwKr(r=x#_ zI1FyLDD};FW`H{#Mwr5ox@xw8BmH%~6zHM!OYN&kPyH0%3h0Uuw{D@*P1NZl1BEwN znHtsfr{Y3ws7~0p-Px}1E>tUKAAyZH@e5CDlVgXDTm{P_tdTE9P)!-b99Semo=HEu%V1mORD_t`!zLO)be*aRoG$Ph{r zFVyYyAJhv#v~pf9D*Qy80qy`kJD}O*60mG_Pp6F|7m^@jiDb2i56=}qcdN$Egs^Ot zXjY!y&9-+bxPYlIJNX7qNk?UByZ>xc)`9DY z%+IPkxt6ef=9gLMdtRZ7TLTU-&zqMiD~n7F$-*2US^EFpOxw z&*E)JYp{>i<&m`3>@E+Ebzq~sWs&1!Y`>PNFBHDZynC$f67rM?b*B7mnZdub-aB=;%MRVrrYqdqEe<*Zat9RihR_E3xHpX|W zj?V;4G0T2Bh|to@t>48t4u!TvRLNI*d`5r94&cqZZs~YF^6V!r;W4BrIP1#&yaIbJH4Z?G9U*P?*mD_65!B_}741Eh;8bR_4Ct{9(-Dic%qbo#D1= z&sR%dmHC2tiWmuQ^rQGc-x5?SAGE3bk&>oI;F^;lSRwcLwfpOE8dDocsGZ*Ms^_q@zCbIQbacSk=?2Yz=3i$4cT66T-;)G)dGvAqX)vS8%z6%pN;` zox{RMfu2pHZAv;=P=D$e5w+P1SnW}rD&9M=*cR(&8pR|{J&=)*%v)?EMl-EcuZP+k zx18g;;&vB#reKEk>Fme7OJu97Nac!dVCpdPCDFrjNjPWzqKA57eXuZ_7UX|_Dy(dU zQGJrBA07GWVRp{JXOKF=m#-&6&UppQci0dXkO5%M3vWDa)%z0yWPrOgoVuM$!EX!2 z`{_x*B+y;HX>KJoi zXYHp4M19(2pqJzIs?*VR#w%)XB+ix>;4$-P#c(gu!u`t8IJG*-7!kxRio$lAT-y3l z+Y$TE-G<`7!RgGU)N@6nF*?~^%$Pi08XMIAqAbJU?ZuSUtqIj}%lj67l!nGjEw7kk z=gy_ay~r!3E<5gFf$$t;)C(3fW$y@w&?b#K5`Z+(SZ&SKtLC@Uj`%yE!|0L#{n(xI zLpx=fI5XYJy~yc2ZFfrQnqsBv`itT=^gBjM>Dy)&XS8`T!mxdJh_ZGYsaD#q+L{-+ zOD@mMYF)Dwms6Jqm35-&+9Wdvvi2*Gv7wXXq#=v%+w#Vc%We^QV|v*H-n0Mr5sV6g zypbix;d$`=U-_S#44wlu5+ZFX1yc?O5v<^5@l)Jt0X-{kq~&NUT0UOQ z-Oa90zC!3?mU5>>!*VxKhQLBdg90WOQ74ur-F;2p+9;k7s8>a)-SdG)?VT7jj)rQk zOIq%E<%fi`h8)y%8FuCN|BrKCsLR^hwBr|bq3x?lYO(?7l@6p+g@mSH_##MKz*OO}J0?yeKypwV+wxT{0dD)x}uDeA8HqetB3 zhw|HcQD|GpUQel<&ddIfr0))E>Tcif_H9+DS|>tU2Urn!RYpbNR8bKTqq1bC7G;W2 z0U?m&ysZOAAq4~kgp?v6Ga@65K&qg~jO?ArNZ1J^WRc|b_wfDwgV*Ji3zD4Ac<$%9 z$BP|0=HSe-{KC-k^5H?U1tVZZ9+TaD<-R@FG*QG989w^MeQ0*AzbrBvUr)ncy`;wL5uiGvpdwpw=N2&fA~h#ZM^j)* zN$Z=`WZOxhX6S;it?wmG4~B2v*#*_oiocm{-fZSYYi7HS5|wr2&qDDZq-jilUJ$Xt zHk>$hvIcLQH@^dIZOFC1ToXGMPp&qnENezDB>Y%1`G-f1{T`wOz84N6>bri=qyzKU zltsljSFkFoB*hVEeUg6S5A3c<;OVS-`o|h3(HAy&QU0!Nwf4pK&IqVL`F3RAI+ME; zAh%av=)3Q~M#kWhI@uguB6`i|gh_k&}Yq+-hj0(Q9B&q_{tGb_?tHKTzf>barzxRvc_pTYk{6v+T*QUoY3 zyyF^?l8h_g0~l<$WZ16Ps@PT@{>J{hPA`L0xw&(V7o_=NYmVX8wbQ<%#5FYHhLG1c zk{0NnNz;F-z>l(<_x^vy!s+|OkF?2>dfr*qV(oBbLNDe(tA15|^V_>w?{8nuYj%68 zVoa>L%O2akcqq6JWhrf<6q&0r6n8@#zlC~Hg&HvyeeQ%`RVBSoqLyodHJ7*;I%6KjN-j;6_ zwQqt=iI~PI{q@!~FWNr*;i(c_h!*L%e;$5))XitwO!iguu^U~Rb?o{cblEdTIk&QF z5qWe#x}Y}<0GlUp6We7e;&il^k61Jj3V5aL1%k};y?WEQeLcP^PAX3A@cfY~TT_=b zk>wyigj_R=CNPJU!-lj)-UBJJb}oJ}2)8t4fF1FUi$S&WbL>cY7Ecpg{Re(vvnb{; z(IMzUL2uB@)#i$n~F8u|X4@fhIY?2Sl_f%49tnC2V z1U(pg;CYD{tVq4S+dxUV(#w5bJ5l`ojkfA3sxYexOTxE{*|($@M9RE{_5U&63+s%x z;4Iu*aLe2E)ZH!zXLXBJ%l&bWPx5XW`Hna^c{yB?Z|w8qw!QA%!}xFCUP*8%{D|nM z2Z8;5P?(cdVq?5Nh5UHwI1nkj1&m@VdlMnIezT$zl?8uYXttErqtI@l$HKi3)Z=%* z-n*(x1#_5hcR~6ua4Y(sas#Q(k(*bvJca)#tz2wK5IsDIl5Y*d!IX=Ni#`;C7T8w8;ZDk zl&NXU43A>m7fsXuaXAd?*SPYE|B%~lUXpoH+$y34zDVoEwiSY~mk>ECEYRQaRV%!=X#TX|{k!L3QeW5~5GW(o)b zi(_$ePmr#ri29P)>HL1Hjz|8NVG&zj0f+x_2upyd&P=QJOn(r_IU<;~dRCCk-){Nb zhq%%rq1+<8Qr^A*yj6T=T@Yqs&SIRe0M*W&HfesWt?y|#cI{4IDNMVI%xu1sjmad? zCru8oyQvyRe>#7mX>Nnt2G9+BkZN`kba`3Q)4at-Zs`>vo~&;u`&r)ar9sMim#N3eX>5}sEh=L_p=6~dcjxN^WOb( z)`bG9a65bIZ#Tmi==0&{z2~AC6~oUFNk>ZKSIfWS<`n=jlGEq9L6WLS!2~So5u1ME zYRo+t@)c@Z_Fu6e)Sk(3%+JV+k=hp%o!UwyjVtT=;rbumV%FF+IW%Hx^oTU0Sy_%n zoYFia?qQR3{T6X>1J8N~y5<1w(>^1ZZKePwsW)tqu6+tVzY*XVJvVIDj#y~(`xEAN ztm~$(IL7yWcyZG$_g2llo>z9Nrec`xA8NQc*`!%%=rJM}hUs0k&Gu3{0ne@&pZS$R zzYng@FB;*300v#PwQf=)sMvtxT^>PTG2-7|kaN=8Ugo*jfbRN|3EGwa5ZKtZJ1ZLA zlZ(&zNugNI(OvGmdC7)Yr$?;Yc{eB`IjRYyqO~Eo#?X`4E7rNA1w-AU>B&PY)zYt0 z7$kPn|4l9`ByKSxEQ~?uHaj!MI2(5=+6*R(9bB? zD_a*^b-fZi#I@Fb%HHFayVx7Qf zJMY`{0`TT*L3WsV(q|mO09=^YPye?YgXK&KH(1&4nwPF(bFV{-_UZ+dTZ*A32^oke zjGyLStsNajQnXd!i$6vAe>k7(lt5sjDMyBd$)Mo%kY~uG62s-mv2L(a^O#tY}AQFN8rHc zgCR$_sT<=ruK|WOj8~8!Fs;Ep)qV$$Qj`bX%1f(mR{rpt%MfxwZSfVVaOQwiw13q_I_0Sw z9MEyO-3wO5E7ypUVNDYZpLLovEuUlF&x)Vut>IR=%VDnSz}& zEq-kxiz4A>E}nxw{CX$*HDR}cI;*SG5p#*S9L$ODD$n&lTwe896=7>n+$0}DR}R1h zBOXFvelS?Lra@MdoWf|3d3P)6|InMTh@KUS<{wtHwXJ_f*_CJ@%tzz?Ia0Jh{c)z| zdfhji%WM?(&zYg{bxJLh#*NCMnEzrbzh$2KrYNk$#@Y{OJal^OG!}uE>(&3Ed2SkF z;h^|!DqvsVOHBd#3f7Z4+EB3m_&ewT^g%_0(-O{Tn$0h0|F0l(jGZ%TTX1U0bDlQE zrO#W3yCnz{;k~+x$DJA64ht3 z9)l5r@LKNh&^u$}>&WkTlgYojpVjU0aeWV&??^{d(CJ(*%w%h$(fHZSl+g8@4F9lL z0s5t_zu+w{Lwy?p>p@>_+2MB&VQP}WhfioP@1mJ+V}F>0i*5Bi@93b*(ut!Q1s2s* zrCDH$kZO(w(JjxuKT*M2ehlk?Os5SUrpYa0+{t}@v>?a`)gPy)Bp5c!^@d*EXGDb~^E!(${^h;Qvb=`;0GNMB zi1Mz=bA|94_MPswL0LO)5`2~z`p)nT1VnGhkD|W6^O5}kye(dMHPqENsl!)}Wh-vs zbQF&I6`IjDRH31UD~Kwb^U|7?Go)|1HgGl|(ysbR1ya z;Kk0pw*Il(W{|nb!GBUzY^3Gc9084qC`$?MQ$?lD9HNg0n^y1gsJx^X22c?j?fWS% z@zQFXWj0kay*sGZ5vg&C3O{23fqc2 zA2UY#kYQGq+rpRM_(|XPn6=JsmHNO0u&dhhq19~cxkq6w5h*S$yw<04&m)7M^djN9 zJCaFj9_uFe!5gh^5!apT$2{!_rz7bOVrKut zxh+*Z0zQTjx=K+wn9uHOax-9xUoaVG#Uw3PY}H-cwg-+bu1o9t!mrMHG`r=e3SC1f z1&C!0-a@0jS>U5n55|l>>1$R>y%LY+tKNmMY@F(@9dpOrIIeJmUD{{x(cA>S2PbAt zA#As}(n(GKN=@6eX$czbUh}-(g%xIJB3yEoJ|qu^{k1;U_!5=l;TqxN7`lS2q)i_f z*YH%~024AQ>8b&!PrRAw>s+NRGs$f6&>2tYODx~L|Eg#DMYxSy`kD+Mog!~izi555 zStI*u=iKWl&B8>x$1|zc8xDk}mLvkH@*(9W5Kur^I%8~1E=2V$+p;akO_rm^O?F8P zKa?xUoRMZH`@fV89gk{$>^Yn&SkYx9-B9G0tCTq`4L^{QKF`z^xLQ{*lXka*L&0T% zvK%dB!8nUMd9tepZ?JF>lrCCgWVzUS&itp`WNua$f7O{X%>hDTNWX|w; zA5l;}Y<9zAXT3`kSVz>k27oiC*&dN8{=3$|N?oZidF7dfd7v4YD5LMcB#5iyy;elEeiY&9;y$39@vKmC0m z$g}LiRV{+V9Pc;+mnw`qwWc5-qWi=_pNzcOy26IA>flOdP0{`f2W9>Rb_IQnQN<+h z3aJfjb?%H^(-;ijL^7fit`1-J)+qi|BPaO#&T5umd={WprhX=Q;rbPb; zO7h$y?mv_#%F3Tb^GA9E0%`f)m4QtBI zYbO5vL6!D`SQ%wXX*lu1Y0~BeKdk*L zkuyxNRgb}@dc$nO+#w1Lk6+daQzFCpR>uk`1?>kGu6@)@%<~{-@k5{==TxjsS3GIh z=EO;rv@?INHF8!0*Gxrwt(>Oeu|3>mde;&t`7%3$_Ydez7wp4H;Wtv!o^y|113w-2 zw2pj%@-T=bQ1R|=lZ(=~(p=g``=EzY_F^K+MR_$7;r8L{{g!Na@RHwfidp_!-Vj6Q zVvQ<37YfVd|MO6vuOyPfv0CUd#MS)RedvYX+>V|hug9x6p{b)?`0po@ca}@mzXBmb zY{m6oF=H>HC`QU52B(0TzoWlSIZ-a8gCR_bfoH|jGs!nGkqUUj;g^J_K8KcsL6M=X=eje^znim;2U8an_tLMLH zAlg&%$j|JX#X!QZztPXDcq0U9QdBg<)OLLVc~xf!JqqhMdH~u}!CAZpH};1)C;XDO z%sJgO@|Rc{S@>9vd<={JS$5^l8+kfXUXt-Wobqu*C_De>0 FPWt5+lg%$B8{Hz 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�LB+d1sHEF)hXDp#sQIt}8exN7z5_%mfI7gI^$@Lpsu|yV3q5=C1st7@>g0 zv9DRrZtzk44RK+vFam4B@>!q*f#aE!J$y}M^kJ=1Ve+lIRc9e9Z~TDvT8bR zCqR}u|N9$Fc8OfMkt)PhdyVorLUo2oSg^NCV{*id3L5hNLvOvjzB>((qtzIL;{W+y z!3BVN%uHWRu4;K{7p@@8itD3gzgcWge`T zZl9zs%I_T*UXj91UV{A#6NIq;jW<$VR$AyR$KD=KM*+IDSt~2V^n!*bpXIX?Lv}+I z@-Xe^+43WPkeyZ%ucRX!pZGk2l6pH`y>;`%K^~=GLXOs!G&s~K=Q48+2b>)MLP9=n zviJe09qZp0-T{ilJ9b_3>Z^Gg+Ih|BBswct9h>aPoT=C+AsduM`s@n}CRuGN z@Y*X&dSRn-XZJR*qx0g*pz+jhaw+{aW-zMThTi1CG8!(?wyR;O5cRJ5eVQQ(hpsT} zuE6WeZK?UfQj7I4{N{CfRW%9}H~7d^x8wU++NBV^G@*)y-p*Y&AJkaP!>rL^Mk#A6 z=(xKtQO70z@98|oJ9KU2d%`Sxc!&0i5ucr6kRV;@TudK#rA^dn4FxthW^U_BV}WA z$ofJ|1zb@oJGe-FLC_jPg4Ppyi~R(Rp&GFihD)~dsA=Gonnr+6`v(0Eebc;HM}M#^ zKg~5&c4bD~nap3vT71t+-@tJgt+9CU4+s2x10A^wiw-32$)AR9o~kzOt^13ImePY|u7MSu^>VL1H~C z{X&i8il7c{+)}}hy2lV1K3M)M6b(vmE1n}!4A-=apM!&8aH#Ebre^+WF$6>EIA}yG zE%PDcC23(DK}qX)yQ=Fh_RGVkFP`+`=5G})xgFl`58e51cZrJyY3#k2DcYp!pSNg? z1_GR+*ks}!Bk_gxHp2LQgO67J4bb-Qs&QZ|z$xElz%dydAHU)4=iN3gBo-}_x6=b$kOc{8}PGI{!um+aY z;iCR66)lkbbuE16@~qy~sGU=9>i2a*p~~{V^l}6GC8cznrbU8|X9Nh@>Bj&9b&dto z_x|ywWur}WB&(o_+B-}bBoUT7s;lM^k`Lc9hGyv(QpZd;*gT`Bqv<~*=yph}DYogm z^!!ZdVQ@t8&st^vXN-#rGX8KP77s3570*2r=GtB-&sLMR3TJipSKpNH+cz=0eecG1 z(wtED$|cImhxd77b9D37JT3PIs8T5=nyr@@&RxKK7nGK4yQuS^@cE(|e2KpZU)99P zd%Gg>SajbF#oA24TYhmQ{m}v$Mvvx}^0~)^QB2HAdAEgKD=GNh&xWoVVp{)UaR{7O z@7Pt}--ku9e$Y&1=zhCLAlj8|NHfl*wZ^Mc_h;}qD<`*zNzDHk+S-J^jMgqYO<(RR zI*HZf(S^3o#(TvjAh|4{H5cg~$M2tzho9p)i`l(hBVU&k+Wc%97OiBp_3X$KK9>|a z*;Pn_xcT25dM67{&1#u}+LHx({AEvCrQLbXR+)E`YIEtopZu^E)&HM51}SR>DMVGWxDOfa-%!r3xp{D? z{Q!|@z$>^d2)^LizvKCUD#p^U`F1SJdp-Goi zRj|eyKbDy!BbZ{sYL#X!7PZ%F)ljL1QPB{No_yAUF^i>^(0QhOUD(;o;T6p~9n3!~1^IPXd9g@6B+)s# z!e!QE52T89<>`XHdn}%$t4ejW0RFSY<4Kz!B8^w^1F7Ip4`*(opEg17kwMX49-_#b) z{n-6$XHKN8yAUlCSZUtdfe za4hQ7l=Evp*{+!NQyBIW8qTxVNK#N#&eC}ehTMGIV`mQMWK3S6A3lw#}6P#KMIbWW2C7htB*!Z)Xs5X#KqgO>Qu~ z{9O8m4b(|UDz@p{ZjkQX0|&)0UiDC;G>mq5xsm5M{_iJSD1$gMAgfk;w(6Vw-}0YF z;%&PR&>Pe8hI}3Al>XKdzllb4=*zLLv0%@iz#^;A_fky{%Z@k|tkpRPb-*syb+^s^ zvEwwco&1M;RQ^@$`yU6nBSC~|*^78Mvj1{TVi5)-aMvpEHbfB^Q-R_=7QUi3T^5ICsWviicW3U~aR{w6B-&eq4od12ORh`hW_ zGG@<{XJBAp$eBpc`5M=zO0eP7dY|OpF=NCT@2(7s!`Ym$yRC{B4T1{*j#oA?JVANr z-aTo#PWO19(T*`zo@95T#uiGHlE~{*cD>TOW&M?>melF1bCj0hw-&OP#9bsHD5W_X zOOExbMTumhj+)-&z2?Zg-adtkY5i7}UtA{I{3>jifP=HfA+<#R-%sqBVwpZq%5_tP zuhcHqJ0O3g?Fg_SEExxURO;VDA}o^RP$(=Uk#(06$K>i=J|^jJDn)Bjb65{Q&&GrS za?Ns7*FF48e-9)8%93v#h>XB8A5U!Ix#KilwfD+ktEO&@tv?~B`NP3Hm*J)|nu;-N zE)s5PizZoQvzL1D3vQhiif|L9?{4)BOilip!i^2b7n64A@`R!Vp5_}j%tWSF*Z@?tkq}DivZr$B?}pqE@!@zF={dQejc(`y|)Ox?5=3=x$l5m z(6)&*LHOy-{;RG_B0)U;hKxxhxC1R+hl-GekJ4S)l$Inb^3Z-AMeBV^&;tj0BDI{G)9=RS5 zI#Qusz2jO2d_jzr8t&4UOj#q5kBFG?g{*XRF6^zW65l2#fbCQ6EwSDmUB(I`+T<&O z^I^DzNa(C@y^#RpVq!g$g~*!2A6%|d$sd+YVQK>3FiQN!+7=H2Xtu7eN!WCr=x4FF zO+QR&a7u7erH=xrUL843NT16N4d?{d#k$uYs7P}__4~n*HuY9O7RHf;EsG}-6^0MR zY-TP#Q6Y097TH;-cZ+R<=vK6yUwhc-z*``$Zc(6ty|fK}wH}w^n)Zh3ow*+#jm3!7BSOxwzXu1(;n;BY~kOfe6=HJY&k9ucawx zG>zE{@(u6)_qHuL_V9*l<^(?^)6KR6W=D2L4Okai$?2>Ui5d`rPvoDS%{w21S?$qGjYL7CP&Ho)%hA|lf+Nh9)CX>4 zmR}>GwZoTsEPqtR4B)iqtt^(lXmme?J-XRG%droWw7CHo}ppt&)EHH3+ZmM4#6a zZu#Tjw|teqAfzMZt2-=@5je?}^1KyhT;0SN^nGn!uNul4^Z%-*X{eluVv`5-PoyTF z&e}Mxlqc)?H{W_pu%6ptHg&f|ZyT=-*6Koflh0iPyG?6Pd`z-@N>Vi~f4YbpC#Vmw z*E~nB(H3Md+4E%u6XYR}wnL zd@*C0a=$~C-dLJqx@{n!s+-ti8JL$y-438L5#RRGzv6Z@X5U%51Om(Yf<4%b#M zC90$n{Jh=Lanb9VCYe?imV3lPGux7bg!~U%KKBT%YC><@E!ROm>78rDdDM3V8y8s0 zf}>wteHm~C<+(Mk)D;coWG0Ol|Ay5gSiH?2X_zEQPz%YkDi2Y zT1;J)dZwRZ)^@`%IbLB-0Cd20lNh$dNC_4BRAR2*2|ZnEnxxfS+lKvp}1@ zcwgH7oyR_*KpZsvn{SEnqdomwHV%!{Ik6sZ=&wF2uGTVePJveHnwy&=QkF;+EnM57 zGxK?ej%ewN_fJUIdMrPFQ0hhDSE3Z3dzSaje#|89artczHRQHDd8eyYyj#w>EJ05$ z3BrXbjPOY0v6YD+ZRKxqf6I)Q9y3SDax0E0iBuFJM;3_>Ot)wAyz1&>3C@vs-bnco zI|zfJ5f+=n#@{)Q+R9=T`FI>5w!ngBwo_j|AL$YAp-%^qcsqfz< z->!EP1_i$_-71{3J>$2YMPTKx-j10rb$J7%(z;(}H;^uVxX|u{78i*Q&s^kDR-p5C zV9u6~`D{DbJUL5Ww3ITmA&X5+Fl_6&xNWYlT;*PT<*mO@>ZMTM^N$xU?m-qUxt%~# z&BSX=v*+u>*{X27{GgBRy71V~|IV7(F-f@E-4$raF`2g+4V>)HQzXi3yv+XTp*g)L zmtMX(*?hsv+cCsIIG4Y4k}-IFV~|8g1)<2zapk|=Z3aG^YN$GUb*Hz(L!b&vBB_K! z;u@{cmkAzCbv7Yy0|^e=sqU{F+|$0H*A!DMXSsGI*FEvdsq=%ae6RCsSdjBpD+$KA zNiJ6vrrG3Uw8@_S4a2Qo6 z-uW^%LzGm4-w&H3h+uw=Q@B`;F%{0_Z3tvr8z{~t5*MM9GV6PC=#C4mw2 zdDqSPlFjGHc?{kFoUH+KwADG(TmiE$L|8Pd^O#WKsqF0Ei%h15?^fSCp;-u9A%MSr z$bw%5I4SqKW3%IBscli4>q0060of&fycO48lEmtn%c0X!I#+qUCjrOUH_^BI4uj-esEZ0_QadtpMUO4>X8*30QgTq`~~NEjxpcmra3 zlK*DXPX+#8bclgIR&tA@aS3knxi)e5$i1eOvT@kEth)ALFpKq40*S?)g}JE%H!)(m%iOIAhYcDWu={+L15x#EWOnIa4t<`LkNnWkc&U1(N-OlSO6QP0h>duTDpY+^dIk~60P0A61W$|>F{q1~cXFp)(cv9`qq(QkyKq8R zYWI_&jKk^lQ=Mv^tYJ`GQCEmk3@@K5adk+Awx2^k8%Xm1fW!SP@!MJY(tp#$-J!rC z0-j*bCu<(bQ!j=M*?D%uZnqt+*uuOm=CaP9hwHGLL>;hkz{na)6g=>`ky>J z&xCBJ2qXddV$;RwD{;EEtzm6rwlH?6e!Y3?@BmQ4C$Rv1Bdoy=0 zp3C~k{$g-uF>fe@>nLKj@`zg+0j0*dwOd9sEQG%6Psuc|xM$OpldCYQ$KHb-F1I?r zM^2fDdcyf*S?QPJ{FWi)sc1gT1FwdLx$d)pd^q(QQD2fr)5=9MG-{wM_!vIz!Oag1 z9#gW*ciZn}(W4x}H&fpC>U+tk&5!j1f6fQ|`^oS1_Z29`CU*QTtH*+pW})P%mij+m z#NLIRn^;INSYeC$+3o>l9y3J$9j4ms_sV`z-{t}xr`%hm&3@$bHHq2wDmZp@VcG~r zNa)T0i>SOfV?Q{}dPV>M^NGQIuZ9%A>md63tQrS_<$^-y&iHn}(yGd2Q=Zt4Ezpc! zURZCPx|Eq^dJw=k%CpmN2g5z`d7h_ocq^8g3gHFWjCCFtg_y>!Libm3$tnZs)o@t*`m;+Au1N1Hz%-US{6-TVQWNC#?m!W>#nNVnP7HNPBkEUPBwy+d>{ zjjj2WEL9yWQP5u7e6F}WCVHs1-ma#a`8#DzX5rz^hkVvUN%x+`#zS06nZqes8@`@CJGY-J2sFZ)3U=F>62(V9 z1b&|0XXN*?M~@dxX_u~7UmQ^){;==q)EdUOXpZ}EFiz4gvv%s7*H6QbLg!)fuMbVE zZvDR7fwjXTwY1khSa!wpC|HW{Hs?GKq&kkIfajph^?E*(Hdlw?2^sA;^Vu3hlI*AS z`J33%M3Jtoa~r`I23^veorGUdQM&<5z2@CE6*$OVWOg)(-~g}={QSt%t;x9SRVp1! z)TZplNL@|Af)ONnVKr>Xh|SN4pyE2SwxGW89(IGCgvB_=V4Sck?#85*qy@wP8_`0g z?h9roi^H?``O}nG<~c4n5$jvxDxPakY_2#3;}Z9jdF48tr&*PndEjw+Ri?Cq_ERM+ zXT1o4H+ORMlFhY#H`JE~r*CI?)@SyY*Rks*2}Ie(Z1b`+yjr~C^S%k$1kUQ;Jt!eqq2-hxe{u(?MHE^M_+$ukY}Xrrtu_ z-I~BtYyw(h@q!k%ogJz_ZJ3aAxN?pCnC2p!j+C%i6ulYi&|Gh?6NAuAh7MS~~%+SpP#j|eGGZv&wJjK`90Ot*L9XQIb4^UXNFEh`9`vzeZNQb!ozPJ8Kk*dC}2^y)v?Je59Wom>>^Sli0~ zoaZJrD@q~(!=XdU$=?Wk5V^mKG%ChyEC9ikhh(FoQxy~ukG)SP=YCJL|!ZQ zzeIFr_+o#I6mKpb5+kr(3mG+GM+ryq_Rlp_sb1$?AyYT-Iei&b;cVVvbi4tJ4xY*CciLMjGG-zEhHD+Kl_*cYEU?>l!h9^fqDt za(>T$p%MGoLm)H?W<4WS+_@R-?JT&}lY0YnO*uDL;73%>|M3JXX3nQh_UA_0Fmjai zxs5D_^9O3w$|fi98OTf$tiCY%yWq^P_W#zdK+~c7Vj(3{8gy zjghtwVhCgPZQ=s3fo0Un8^G5p`X&51DGl8n)V&5W5q0*+|%%{ zDG=t~zn2lS64hU5w>1fuVZbYKbEp*is=Sg6WWd>2?UMQ*n(5|STWDO;9QPizd#Q4K z<9O+!HzgNHPOL=@W5qi3AFjW{I8Gt=kTk;$nEa-e`0dI1|Hskw2Qr=i|GUrq=&Gw- zb=5`eis(qL@}tD|=~R-mE^6{)yHZI@ONg=U{pl+D#f}c)*hzl>Sn?~3T_H&>)c=i|p$-Dpfx#&jdX4097IdNY5h{A0)RKmZBI|opArG@zNCyt!z8*n`rbpUFik|R`~NZ11b z*qIcOIY4U@JoA@1KAcD@+e>(HUi)dfap`>-)hYgG7&V={YWTtEQHy4t?D*}ksJzI9 zvIkpLU_knbn{2%^d#jsIAmtR~p+%l-!F|4(Q$iv}_=T+Lx@37H*)PT0gYi4dW{Yn_ zcvkv(e}p1`267($UN7JId*4%w)anCP|ex>T@PI(ymP>H z3f@}VF#}{Gvt{vtr#gQN&|rT0VXE+%0_hSQp>71}pZ8M%BZ~YOKl>qn4=329ZBA(j zx+SAofAU9NZ+7m#YH&7nGt5qfC#Sr#Qqhf_J@Xv1%zX*&tqIV%#&gS9aDdZ7im4lK zbY^YX!HnO~yI59CrP#kDI{Rgu-5PaZTrr8e7Q!-(GM_v~KQQjz9!9p>!rt8<=2;9B zQIQkaJLbHSmi;zuxE0%9l;?LC6E~+lfHrQt6ZrQMu_F=P0#lUAY8E87VxLa4h<8(2 zNUjL7Fu<=k2j5dkhfG?VBkiA&*Tpqo1Bnn!VJu@ z{Qh6>46C7fERmvj=Laew+YZ!Wg#bGEX#Ok}UuLw$eGM$AjGvtDwx#WXrcO>|(Y5jmU%EQ`J0&&Iv@E0pfso#*o^4~}%80_Ykayn-pqJc{pfT#WY*=JX$ z>{m#g>cX>U*MYj#LJ1*bQgv2&%KMoPn))0ZK(eaq$Pr;3uk{-LL4NOGB#8Vpc8oj+ zc6AR;v4}_(4~kpUjD!dMKjObc?T-A-zDLcSRPA$8Hs`eM8sK#wZ7R08|m?K!Ux zaaTqbQA9IR`$-lxBeG!C)e!3wUVyDU;W@$1SlDPd&w}#sdRzq;tHDiGHO^wFe&7D= z9FRE~%X_j4T5_KUdQRj5e3{c-uB}uv>{FEe_b2D={!G2p3FvxVPg_;M^d>S$mGLe( z&riN%g#WqY=KI!O`-hJWw@=7zh$-TAS7gYu6$<+b&B8Q$GyT`KB{D}30mO!5fgn)fA%@r=4=aj&!Wo1+>$k9?DEs&?Uc_YC^{``Kk(`si(?tD>4O{D!j!UzGAcSH}(=w6Lt_O<|4t_$6pKHSg9EM(d_Z{xgO>2~HT0KjQo7fgi7p zyo%gniR)6pNdU#&9kesGHNa-)?loPiSRBk_+irW#|4im|Z$?YlDo94=b|Ww>lH$#Y zAFmtoPYqxbmi3{@D%e77^r;`?(v>P%; zP2ccmUESc3Y*?L89Odo2zI&Xy=!@eI)G&rMM=2Y^SV~(N^$u#}#mGP1-j126ZoZ_j z7(AA0A)H|8_=5rkC;OA@5CN>Cr}Z-;Xrtk}w06`+O&t-;@J2W@m2nOi21e6@74+(; z7~)|>r4=q9(p3*@$A)+6x2qjSrG^JRF37UA$J;XC6TgcOdaLK1NUn*-g$aGNWtk&u zz-O=_@ehCsvKiq{kF3r;tEjAAcD-TYVc!Od5rWKCT7Og%sC-)V>fXWFisPw9@~-aj zW|TNpUNOX{qR8$_A72-Fk^Lmd|2Q0Ugq5XWJ%!wFPpvIi#{`L^Ek`!jZ!zX^1=pFK ziC82YouiKC8yd?F_clg?==q^B&B?SB3%X>`c?|gvwWxX+y)#VKp9)a6wDrG@t$l}d z0WDG((nVo^#j$qy9HM!sg{nu}(#_v2F?4{|0RKq?$DTn<4xPv8cUfjVI>%FNOE^ej z0p?K)8_3plZX5+Hy4l~*OM8k@&w19m2H<=DJg|MaD`$#=iiJG`=gLz;%JHQDC;sZz z4|;|eSwivOeb4ul5Z1QDz;Cw~cW3jY!svOvUDx1hHq=z1WUD4+i=Ia6L%e6aL&N3dvSlKS|J9WM$~5%N3?cobkSt(|8*LLY{o&)NX!Pc>><-UY{8i`MQO@R$>c`CWre*-YG@(z9z zOu-N0TXgTu+;{(V;)h+==6UnT0t}2kiISbMGdw_?PZ_s?dxqw6F8XMVcSW27&NgxY zthbgv2zw3viu@6ZZami%03V7P(lo^jF(pKOAV>sxl+B*@r)dH=8p!j5lv9B%xi*fv zeZTnF)-toL23*o*eg%%NT%X15+E&79Dk1ERzR(BxjpgBa82J90sv%TJJ8wEyfJmzP zdj8T*obeB_1{7#$X%JH3)yquGJ)#jmhq+&o31Jgui*smpqV`6OtKSIQ?3Tv7LUU!- zGvepl)5%AoB)Fcd=n=jO7UF{E$eRPYl0Xe__d)jPJ;8@)@f!=RlZN*yfupOZ?yd2w zV_I}vFF!YpW#W}g(fQMVwLT2_(TzP^mI}+lUh?HQTNdhd4yrP(iq6IuU$7P}kiMr2?L{m8M3-BR{q z5gmVr1a3$A@C#?6O6@97ni3w(@Txko&Cvhqgc4BRz<*nz!eoiR%W7IKKW@ID*BC-+ zhxluD34?Td=sXbf@J1+;d+Th9FL(67DX~aLSH0`~i}lOL`P}U>0oVvyn^wuw9*OX) z!}sp<_;?X*ls&dEg7MF@>gCNYRDN8cQ&mSuw1f2;oCqHE32jLcapY+9*7B**)jn-4 z*e0OS1uk0@1$yi!s*FR=7svlBAHTii|C~;i5*|PIa`?65!~eU4L9g{1l63fD#c1Pe zp4w^mh6d7Rd3MKjyxyPo_NFLL2~`9d^yqN51mVP%;#601PR zQ(2a@RxH|FFSPuD^060W(@7grK14}o21vwF7ai7JPCsKfx#J0}dIyOg-?FR-DXC0n zO}1pkjHS$}J~oUoAF)V(6(CfLmHGr;r1-UT`m|kZ3Qn}_SoacNkGyynyF7!nz%im# zLkT=8Znaw4;HP8dFclL$_9YSg!)Y{nyD&ra(2yU>9}+3En?{+5#wQ_Ix`r?bCG9; z%Xzl%~on!NcnmhT$*AZm4KUsq{Fo;aUBUrA4%EgV?b^*0v*b!YXAP8h+gS?m+nX%7us^5lZPWMLFjN84Tb z_hJNuL{1NzowifTdqT!hl2^o&(+y2Ng4lJ-;7;j8yirFw`Ok^m!|FQk7e$66-L-dR zN25hV*7PPr_cF<;+&M5}T7exqOKBMIZm1`@`hlQ=;AHyyAj{1O3#>rOxWGAZBOvi! z)|hR?*wpO%5E4ON_?@c+GgD~ANM!(X2O&SOEhROqielZ*x;%B0MqDWpOojfV(S&L7 zP{->@g0gMX^nElzmXrU#+1O1lre ze!ph8SP14QT~?Z0i{B5CB8IEUFB>PEeM+%4xDZ0$&M9kXUU+Mu0#4Dq?|+rp@@k|c z2qXe?*2cXZ*H3krnL~y!B$p9a%lAT`GXsM78)-h?vah0)Sy8|QAdeO_vHzh9cSNP& zQx8wPM+Ul8J@gFC``6t&jzL6+?_wj7bRFLN&P59c|kOfu`?#TxLZ0>yB za>(V~U`bN?>weE}YP`tVhuDjRYU&roUytg3``g47WPmAJ&?g}?mNDDZjo(-rG#P0~YJ$ZI1JL!xizWrjs6?`~-IuiUC z_aMVFqoa$Hq0QdYv47bsxQqESD4V+}#A85r#`jvP!Y1lS)^J_2=_nkP~ z+9ce*(4xLN6f-euojHN58sTk_{639nKP z=1muhiuC?$J(tR0AD;f5*xLxd1`4R1$ncd*Uxe5nAa1cuN8zQ;!N}b%;$-9PYd+_{cJTlSNj(ZzDRp; zu0(xPuVY#y-#6yPUwJFmoW+wiN(G-bqd%Z}Lkonp&^vo}sQ=ixPqzl#N_BUaI>5>3 zuwS*j0By|9PGBz?oG^*3BhU)bdA6y}D!iG?iyS8uLyCb(rGSSvowWd7m8Qv9KAWML zBH3eZ2JM@V_~!ca6!$7*cdtk==xHXY>&e0AV0y zSaDHfn21-teGy$tSlA?#x4+n3<^{<9gejD;Z|oinocKZeR=iEyn>;>X#aN-*6oEsO zVzv|)O|!heEz(M%&)3A4<@pdSV6|(k8|PP(UQ28Bs|y`xlYJ_dF7q9*RBb7ASv{Sd zxpM~J+1T3-BQwbTgx*^I-XF$veL!EI0F~x^r9$Z@HP!Cml|@W@;+l48H)j(fU~?(t zK|6(@OHa_x$y?A)_Nrk1UEXT#mJ!#z6FSpVE-ueqmrY<`wJ605*dYQ>@YNxd$eiET7y?(>cVa7S0Atr^O{|H z#j^hV~yTcx<$jN61>A0?niZ_45z4v&Z|0-(0{0gC;H1-qy@KmbCr?O zSC>+2s6IZT@R1p&DDwj>Pe$ZeuH)T8DLSyoM7{P2Y&F}^_vL|fe2JiJ*4nnop~^E` zH4zU)p%s6|&l95;X34{$Z*)X-9sb~Kno-Rimv)O-MAYm`D1kx=c@E&3+Q~w4O8@QP zNyA*gAS%=@8EV#Bw)ExhbZ302Jr8FflZISD#jQ$nG4Mp11*p zUK37}eMA#0sC4VUJ=zgv&o9KSQDaU^x9Sn!x>8zSRd$*x>{EHw%QizZV&&+t@J&Ac zYc+B_YKG~9re9PH9Q^mQuNubTs=bTA%a-Id7X<%=lk1zQ|FdgIA!JQX=>tZfWzd+h ztbM|WA9-MH88!Gt7t>_Ghqi=ct%$`f=tlb*V6Vz22pxF8Z;euTX*`@WrkMMqoXWy7 zDqqoT95GU7o*$pQv!@?`cPVu_rb!r;_>;9J)uoaA{cbu9E7`!0D4b(Y(*^LTl?YmE zO}lx+TVY+dr7X+K6ebqIBO!{}&5 zf58B@sj`%I4JTq8(b#IuUa4=7tcb+#YprmVWrN(#s`iYr+xJ${Cmv{>)MspRf8$-f z$CbQ#g50ci>Che-Aw)8NWPKRGN|bhBe;dR5`&jKpr`*2j+LeMrZuA8L+Ye#^FM_9& zk&}2^(J%TPcZ{cmTx;v^;RzuUFelTaqil_%#piLIgnP^%;j>%^x>mR=z$-vHWzUF( z9(1%Tub{hPdbp`>Cp8MJwgU(#e1KG=SuuKXQ{cV1TbAxnwU2&Px2Y)0GnPjj+)-%D}MQ zqcbV^_Vo@&za*%%HSzMA8qQC-N7;Cp;K)i%l*Mts0=l`ZMpRhWGyO+n$eOyvIp~-j z%;yi*Eh$rmTz{#1tH)^JD{+4-dL{R`EwRm7th{4vv7NVPUaieXW*-p&F+6l!^TiOa z-omiXElLyK+B@Cq1Q5USFm4le48}Jh{0FxO?xDX38~C43)f#nKLav!JIRu2J}lv;>GGg+bYbbxD8mf9T3y%FCc$+*yQY{ax0KlP=I|Og^L#t0 z@B#V_+8JkXUPTzErJ$az(S(;9{n^&eD0>;R1uFuq@Oc}wWyipKGsVFC_{7oVH^aF1 zs9zF1gOzSR>f;K45@?LjrRzm^692GzmG$UUz?4EzaxSUBDp)?YI0_=SOGEP-%**~= zZB@r&vvs9Kyi;IF*}er}xu1Fz0yN2>QB0QBF%tgthR7&x<{K)Fh0FYlpC(=$t+E$J8x|te(mqfExb+2jQ>S zbCa3{*w?Axg}~1UyY6zCZXtj1iSxM*Rf|dJ;ExbA&j;!OQKdDIk5`TKYOi>xNW zlW|ST;PVdc{tKN(3EhWdZME=LLXUlH*SPQLH@?MD%xG9X?zec<(qk8o#$16bqhZex zQ-q&fb-5wR=kSeabE0cyPjxf$k{eKLn3bM1S&kaXgi$th_{V1hA=Wpm%=_%Iva<%K z3F>6p3tL$ROae44vP0k1#00&l)RGSn%nkxzxaf}t!(5GeIBw;0%@klB&IV=O`XyZz z8I@UwWOI8X8tj_TmK)5SFDlwcPd%es7$$BP$cq*wg60pJ_{DY7cx|a6R5V3NVOYgRnx7|6HT4 z`0y$&z>qrAw}{h~pmPWEyj_QNa)Y2Z$fw>rgZ}=aI^KjUsGEE)*iq6J;+$Sd*ai@{ zXAlP=iq%eQ+-;S>IjB~#h2&QY3Ty*w?YRLGBW@z3Kp2E*OOhYLsJPzGZOHyBdna9V z=+<^4XR3AsaxOCH4+g|Q`RH$VA0eF9C6WOS~l3EsJhii`9 zhxNl>=J!bM-E=AtclS2r(QYzSoKE&ibvbx&f6Kl($TQ>l>2?PYUf&8$&{exiz78xYuOoqZyOP$|#nwl5r)?VsZo znZIaTI6j%a*USxfw_U>Rl~^rO>Vg@k;fnHRecZ=xx;uQeM6}55?T$|cP4m4@6e@FsAByyZ zE-AAp9niC0X)PJYcT^M!;E1xqhpoc0z)+f)4i3rJBA;QmlxH4f&3jC5euR=lh2s0Q z0#tPQ-_Ht#YtETs12vra`{LsVb2B^Cc|oXHWgXs1HnaP%7V&6DyRvIUd3531_#wM& zEghdkK1K)`lgI&kpxeF051zNGAA>rx0+n(>_LU{UoPCn{otV1!aaH-Czy%$CazI+q2&hWZ5Fq{M=*3Ph`_e-?h; zcxuKTZy7$%!oZ!FctWCPH;DQK&%;h`1^jf{kAq3jUKQYXgddGnTEa}Dmp336=XQuv ztcSbR--@*>fZhvRcX88sumc`&0WE`caDAqWPN$_mfbBFk=ZMb^t{W|$aOz>zkG_`S zhO&RU#K@GJ_mLy<0W+<7$age6w@`e5DZ0E^yKu9W)V1-aq;7}RgoLVm8ijvNR1`Pp z`6hGU<9Q3*bal&ag00G06izRj8q|OEp~t)Ir@s^qlsaWiem~bT6Ncu<>K}cmliK+QwITmn6aX%aB)j^3C^DL-M9tBw# zySLF!#i~EL_Gx@x&u6S?&wj>p7vb)LP1xZ8QH+HH2*Kz!*(%xq*q(&y9H|stjp-6` zZe|pnkac#;7Asa>-Y}LE(nIfK&FK5YA^gJx-G8`vso`+S2w@#^?OJJjM{`NV;*W<> zyT+G~G^++AyAc`?>Ze)%b4v;2_=q zl3ou^<9oyZOjOv2UTIi5^)$A$1qs}0lPk(}rBeLZ#Xd&xZ1tOhT?Q93%u^rZ0``c4 zku83s;l2}&EiO~d0)D!=;bf9+-M^o$B~eKGOG?V_nJ7$I7Z5Q06@2PN>Rk9yxDA4` z=wEcTJ;OERcdoY7#M<}n&@(S>C0BFYr3g%<&j3!Et#7Ow_$#q^>O05k=2sDXqcJvG z$#_#}hT)BmR7Jv;kI>MU*a z9n*-}%G{XZ@&c;WVa)G9OqhT|ZJ=54$%MZos;V(kn#=6%RCl$uemRa`CfvG3a1f-L zIq5fEo}1$79$X(ku9d0zqvrT^YUUgMp-Q&KW`yxXg5ry-q26x_1!N$&CQgF80vWKK?Jdkx|S)x$E!2K;x|t$o6eU4WG`O&dMQ6LSku?{zSAm?Ora z4eb9|vwByLG^D0=#`GQ-4NZpdbDi5bav56Qm4?LuLu|}D;pGY_vE+O@2t!N;p(}Zs zoyeP7Dc4S!6KybFDT0)lmUfsYGJkkld1~w+{LeiqV#B*q~CG7ksSOdk3ael0J)OhadO zd1BrQA7Z5$%+=SQ9RD~L^L~x@!91;3&tShWr*-4 z9b4HxuOiI1I7L7cb;(b|(?)M69yOywus!;h5he_d+oJR=g>yfd*pQ!Z6+0$xvxsGi z5~WNDz!$)EMr{yIot+v*R%mzVQY6EIbot!HcH_f9>GsvKt3%;#urxxIxug$SJt-rM zU}JEmIy5B^MEv2vi5?@bMFKe-rLXp=2QZI=@oJ~}(71>zgZtN07iW#huR$QCBdV6d z=ffFfvux14Txmt!N{$7p8`Dg{Z*N>*`zyEGr2G0HmH`7reC|<2F)O%8Yc3F{V#f;Rk$m^eAc-u0}@uBCYTb*((=V`n`2y6uk)T zB%7-nK%Ck7_UmYy??PDED7(Z?%Ic?TcJp;#z|xm9&L3$g;z^%QzvwmXxn})k1lfc- z%7Tlg*3oLUR}Jr4*!d0f;DAQi#$H{2_3V@aDG27+mZR+~N8rK^z}_*F(G;|i{m}kb za1ySLwC(YR-Z?UEig~h_vWjNAQc;EEhpTdkn)UW(pOCUI#=+R6BLp8MZ86m{&+*5Q zK08tHLCkClzNGgJ8@c2<;<{qy4_8c!ZoUXphZ=D|T4Uj5nr*>jG5q_{xvl%`@)`QN zeN8MC^4jMNTkBkgDIT~ULtXrBQV37bu4&WClG?-E=zl-!W-1sVdbq^Eyq-ndU(qFe z<+=D1OPlLP{DajNvssUcJ_U$Qb`U+V~**!ggqwngR-_ zIKfy`)iYD5CqzD@_XhNsC=iYT^;?X+J3nVp~P zUgB}CMfqGPYOHC08T^VD8qF4CQ8mYdbx3~6*@m9D@?}})!~w4exREfg1Sn46;U%Bc z>^a{g9!(BL^fFT*;Dk7}p&!}`{0$*az6Pf*jp-BHq;W8`ME@*>zl4HL|r|v*NS&BUC289sd#^D)`b@xQtvhn5_jS;Z95B?FQq z^MO)C{ntNiKV;A79p*fWH>*8TA)s36-_=Ac>&7w~2}7JKZCt0&4DTO>^(K-984Tg?tbTH@(kp!P%>W1^36=M?1f2KkO^anaS*BUF*GxNfi}#RKr`KX^E#{mr(2P-BbAYl#9}BSNLp zvKvMHa312SB`ehBb32D4oola%D|&Pp&8dWlKgNBfyq*x}f-)G{oi|wqTTQi1e(XJ` zi|O7(-8Y95^1OOe@AitbGO}lcT7N>tqnXWyC(~aZ?x`_+Rct?ze@97=Y9-2BvZpb+ zbWM~dWgQZeCB;9wEakDRdWuqOTf#IZi}~g1!b|`qT(R$^r(N(iz7B-?7)rv6=4_Kp z#WCeFH)F0l)BmrI(-|Zw{sp@y=8Xa6Sey0!QC-X1)LPyzyJm3BCC#Xa31j%zw%Pzp zJrKH>hTXU;gDu7{gE^l9@O25urztsccrhB|_vkNU=4gD^;}(X;n@U>#loMg_%__04u`eAP1F&2{)5il`F)o2RA@!SRpbPw=%%~1uBX1q z!n?dy&g(wwFh)8N|Lx7re;)qHzwl8a?y%~cUK9{`a!Ln;xN85-{Szltv%AGDMQ;Yl z1A@58r7U0gQqmGrH3aBz>Ph?F*W9xF*kGEenQSVDu+?IAv|v6-Bd5G>G_~|DWPIu+SuU*kwNv zEm{``2C2j;ojW+bYr{ewV;44VtS3({1mMH3aCKd`-1e6psOHamnJM}f={qT>zY^HE( zW|#K4rfYo;DVrsZYw-{CW@ukbCqUiUdVF5;z1O)%h@F+cc^%mu+KyXkWWLuwV^6(W zY?qSqxosa#_o4DvDUTMPvsyC7sgNYUg9hrHLUS-d8En}33hMyJF>65;$ig~9Nx@seDskj zq=t%Sal`M9tjz=T_Mf+2EQZ_xuJF16;uHLc;g0aj0rjVP1-5~jo_#($CM3Q^0T-Zk z2zJZ4adQhmJoDt9AkP(JxfiI`hRYz2X3wpW-^O*~LM1-|+T77c@976|sU~mAsEV^E zX1bzKA~yT8RWB;GC2?Bu)A|knD3r6XU^SL}Q71HOl#?knU2{-ETbU0ba!FJE&Pp!G$&h-NTPFLN? ze_7(ChdVhP1Ugvjx%xiTUyT&|``3;WpmwSRZMP7=q5Iem2>x^|Uh;6Z%%#xwji*y8 z`Vu|72x|^H`a`29eHwHGbKe^Y=J_SG_pIUe~9! zl@>n6_pyd`cEM>>TU8>y_h&{6xUqFWXTPAYar$&ldrHb?*_%s|I+-x+p0?t5iY8QJ zcdlW@5?&+Om156XKpHgfQP)~Kt(BDbhI}Wq7ni-td6bF~-ynn_iba;6nMpliADRs6 zRrkK3n>u~M+cTSY2<}_^U{AqX--(gJt%)ZLlkd;X*!IAfzgmHFDmn{%<*M%8RNK-3 z+W0|8^yf&gH4a)_Czm>U_v1CA#fh*EL}lKdJLfof;3rKNrk&qIR$k}I%G0ydbsoVQ zg9gbTtbNF`k;NQwX2mQ)bFNv69gfiMks4~q>%*QkUvS+=3*DsW&65lLq|PBQ# z)n248bn*1fSuNRKp2D#55!XL{fecua`lqwpgfD3}T8tB3Rz~p3ag~T6_Wy9DgKc~ zX4z5Ju;iZbUHeQMXpl8+hU`>{K_7DxkpN!bcI@Zp#D?EUulFm z`vC1Pd%VR<>-3H%b?5iS2}y7^H>eFK0Q%QS*O4YdC6=a-px5QR{`WJC+K41Onw6O5 z#gr*`ggZrByMQ%(0M36LwY{aVtzrJb{0Di-d4RK||F^whE+F|BB9MeEZ>E-Jm1*x+ z?9FcFe&&Dw+9&Z5G2>?tM!nnAAb$nN-I{>m%8Ky$}}bee;W-F6rn@CPz);Z|ra4b+t1$@}Fa!2dmG&~ISn zG+4=azW62+e{&*Ud!m~jFv?f|Js*LXylFAMz3m=yYngX9#zI%!mimibD z8sZ-y#Id(%#==|P&dMGbk`8?8_FjDB_JSf{aO12;XCEm?v##Ik=TB;8xOu{ zl5anQUEN@fRtEU#(b9ZpIBsl}U&_hUX>1W&3dL-XQc+LdWMvTEiamhq8sBawQ0k63xN3lz& z0WiWS6m-|}o0Uw#pu;ZLW2d8D9>71)+Z?I=Rf;O=pxY0}zgo~{qXXy{ud1Xw(A3}u z1`|<(&RPk$AP58x?e#^IswbYPVs@oMpn&fGs~@ESqPv@y{-kPB57|HH&{frKCM4W= zD(_|ghTcN_VA51KifrhgdU>5SyRz7PQi2IHbrOIi!QEQhTQSt_$7YdFaa^pD+GWy? z?(%Y_nDX@nlbajyYiXVh>=>&Es?i6{99vjKKf?6P&N!K*t{B05-m)t(A^dmxQpCG> z`3$-?85U*?oIvvS|NR4*JVLF#|Bc>3i*dHx^#;4PhinDfG9_(XuK-<6+69;l8u3)( zo;M?wgs1XA509WU({!zwjFFi#$cTPk0$U4JiZKj=ojNbbneEYA+XVAIkQxrC1Hg%z z+e@uqV`iT3;MjaP1>lZu-ke!yjlOnInFhI!+x+im&%Ms^wADYSd^b;%3ZQ++rw|2 zG?f{e_~Pc7R$@I+J8Q}kwe#Uq79%AegJGia_0s4}0CjhE)GWZ97N@a#SJS9-z7R%lRB_I7C;4`FVw{U18x(+T6G~_g*7#rDWOz1hp=zJ~*AZ)uFAYyTnfn!# z!w%NHtXb`r^1cF!ijYQ#$j1tG=}9e9w|L7j8}K~llY}*s*sDD}NzdZ-_}6`(8V%}g z|Cs5it^GsoN>Q@cxqb`fQ^D`c3eJsk_mkPLzMuL_SkM0 tXN`R=!((Po3LL`@*H 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 literal 284152 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

(t?uDJ;JMeF<~bjqNT8>TDM{oX}n&S)z&; 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�Qoe;bjU50vFH0 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�%*+84(@HXtEoBn99&M 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%0DBXQkGB@y)Vo;W<> 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 ji*t0tj;LetX$cTZu#g8wC&2G3p5K2yW;& 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 From e1b7b3f8af6672be084330f73bbcaed9f110a290 Mon Sep 17 00:00:00 2001 From: Kenneth Knowles Date: Tue, 26 Nov 2024 11:43:24 -0500 Subject: [PATCH 023/135] Increase Go test coverage run timeout to 25 minutes --- .github/workflows/go_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml index e85c4eba866b..5ae3609ed997 100644 --- a/.github/workflows/go_tests.yml +++ b/.github/workflows/go_tests.yml @@ -50,7 +50,7 @@ jobs: - name: Delete old coverage run: "cd sdks && rm -rf .coverage.txt || :" - name: Run coverage - run: cd sdks && go test -coverprofile=coverage.txt -covermode=atomic ./go/pkg/... ./go/container/... ./java/container/... ./python/container/... ./typescript/container/... + run: cd sdks && go test -timeout=25m -coverprofile=coverage.txt -covermode=atomic ./go/pkg/... ./go/container/... ./java/container/... ./python/container/... ./typescript/container/... - uses: codecov/codecov-action@v3 with: flags: go From 292da721a575732d257077e2220a2e49fb9045da Mon Sep 17 00:00:00 2001 From: Damon Date: Tue, 26 Nov 2024 10:04:30 -0800 Subject: [PATCH 024/135] Add beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow (#33181) * Add beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow * Fix yaml format --- ...ValidatesDistrolessContainer_Dataflow.json | 4 + ..._ValidatesDistrolessContainer_Dataflow.yml | 114 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 .github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json create mode 100644 .github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml diff --git a/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json b/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json new file mode 100644 index 000000000000..4897480d69ad --- /dev/null +++ b/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json @@ -0,0 +1,4 @@ +{ + "comment": "Modify this file in a trivial way to cause this test suite to run", + "modification": 1 +} \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml new file mode 100644 index 000000000000..a0de6d4a0428 --- /dev/null +++ b/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml @@ -0,0 +1,114 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +name: PostCommit Python ValidatesDistrolessContainer Dataflow + +on: + schedule: + - cron: '15 5/6 * * *' + pull_request_target: + paths: + - 'release/trigger_all_tests.json' + # Since distroless is based on original sdk container images, we want to also trigger distroless checks here. + - '.github/trigger_files/beam_PostCommit_Python_ValidatesContainer_Dataflow.json' + - '.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json' + workflow_dispatch: + issue_comment: + types: [created] + +#Setting explicit permissions for the action to avoid the default permissions which are `write-all` in case of pull_request_target event +permissions: + actions: write + pull-requests: write + checks: write + contents: read + deployments: read + id-token: none + issues: write + discussions: read + packages: read + pages: read + repository-projects: read + security-events: read + statuses: read + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.sha || github.head_ref || github.ref }}-${{ github.event.schedule || github.event.comment.id || github.event.sender.login }}' + cancel-in-progress: true + +env: + DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} + +jobs: + beam_PostCommit_Python_ValidatesContainer_Dataflow: + if: | + (github.event_name == 'schedule' && github.repository == 'apache/beam') || + github.event_name == 'workflow_dispatch' || + github.event_name == 'pull_request_target' || + startsWith(github.event.comment.body, 'Run Python Dataflow ValidatesDistrolessContainer') + runs-on: [self-hosted, ubuntu-20.04, main] + timeout-minutes: 100 + name: ${{ matrix.job_name }} (${{ matrix.job_phrase }} ${{ matrix.python_version }}) + strategy: + fail-fast: false + matrix: + job_name: ["beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow"] + job_phrase: ["Run Python Dataflow ValidatesDistrolessContainer"] + python_version: ['3.9','3.10','3.11','3.12'] + steps: + - uses: actions/checkout@v4 + - name: Setup repository + uses: ./.github/actions/setup-action + with: + comment_phrase: ${{ matrix.job_phrase }} ${{ matrix.python_version }} + github_token: ${{ secrets.GITHUB_TOKEN }} + github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }} ${{ matrix.python_version }}) + - name: Setup environment + uses: ./.github/actions/setup-environment-action + with: + java-version: | + 11 + 8 + python-version: ${{ matrix.python_version }} + - name: Set PY_VER_CLEAN + id: set_py_ver_clean + run: | + PY_VER=${{ matrix.python_version }} + PY_VER_CLEAN=${PY_VER//.} + echo "py_ver_clean=$PY_VER_CLEAN" >> $GITHUB_OUTPUT + - name: Run validatesDistrolessContainer script + env: + USER: github-actions + uses: ./.github/actions/gradle-command-self-hosted-action + with: + gradle-command: :sdks:python:test-suites:dataflow:py${{steps.set_py_ver_clean.outputs.py_ver_clean}}:validatesDistrolessContainer + arguments: | + -PpythonVersion=${{ matrix.python_version }} \ + - name: Archive Python Test Results + uses: actions/upload-artifact@v4 + if: failure() + with: + name: Python Test Results + path: '**/pytest*.xml' + - name: Publish Python Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + commit: '${{ env.prsha || env.GITHUB_SHA }}' + comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} + files: '**/pytest*.xml' From 54eb50d0edc08d4831eb99e034ca446ac4229308 Mon Sep 17 00:00:00 2001 From: Andrew Crites Date: Tue, 26 Nov 2024 18:11:32 +0000 Subject: [PATCH 025/135] Changes maxBufferingDuration payload to be taken from millis directly instead of seconds * 1000. This causes truncation. Also adds a test case that fails without this change. --- .../sdk/util/construction/GroupIntoBatchesTranslation.java | 2 +- .../util/construction/GroupIntoBatchesTranslationTest.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslation.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslation.java index 499c7fd21f51..7129854d44cc 100644 --- a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslation.java +++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslation.java @@ -83,7 +83,7 @@ private static GroupIntoBatchesPayload getPayloadFromParameters( return RunnerApi.GroupIntoBatchesPayload.newBuilder() .setBatchSize(params.getBatchSize()) .setBatchSizeBytes(params.getBatchSizeBytes()) - .setMaxBufferingDurationMillis(params.getMaxBufferingDuration().getStandardSeconds() * 1000) + .setMaxBufferingDurationMillis(params.getMaxBufferingDuration().getMillis()) .build(); } diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslationTest.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslationTest.java index cb2054e09144..65f571467ca4 100644 --- a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslationTest.java +++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/GroupIntoBatchesTranslationTest.java @@ -71,6 +71,7 @@ public static Iterable> transform() { ImmutableSet.of( // gib -> gib, // gib -> gib.withMaxBufferingDuration(Duration.ZERO), // + gib -> gib.withMaxBufferingDuration(Duration.millis(200)), // gib -> gib.withMaxBufferingDuration(Duration.standardSeconds(10))); return Sets.cartesianProduct( @@ -150,7 +151,7 @@ private void verifyPayload( assertThat(payload.getBatchSize(), equalTo(params.getBatchSize())); assertThat(payload.getBatchSizeBytes(), equalTo(params.getBatchSizeBytes())); assertThat( - payload.getMaxBufferingDurationMillis(), - equalTo(params.getMaxBufferingDuration().getStandardSeconds() * 1000)); + Duration.millis(payload.getMaxBufferingDurationMillis()), + equalTo(params.getMaxBufferingDuration())); } } From 720b8247b6ea6ff7531a8abc465dcfbbca29e075 Mon Sep 17 00:00:00 2001 From: martin trieu Date: Tue, 26 Nov 2024 14:08:20 -0600 Subject: [PATCH 026/135] use direct executor to deflake tests (#33187) * use direct executor to deflake tests * address PR comments --- .../FanOutStreamingEngineWorkerHarness.java | 22 +++- .../worker/windmill/WindmillEndpoints.java | 23 ++-- .../client/grpc/GrpcDispatcherClient.java | 4 +- .../client/grpc/stubs/ChannelCache.java | 24 ++-- ...anOutStreamingEngineWorkerHarnessTest.java | 123 ++++++++---------- .../client/grpc/stubs/ChannelCacheTest.java | 12 +- .../testing/FakeWindmillStubFactory.java | 2 +- 7 files changed, 100 insertions(+), 110 deletions(-) diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java index 4c73e8c7f61c..21aaa23d3f85 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarness.java @@ -68,6 +68,7 @@ import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableSet; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Streams; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.net.HostAndPort; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.util.concurrent.MoreExecutors; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,7 +126,8 @@ private FanOutStreamingEngineWorkerHarness( GetWorkBudgetDistributor getWorkBudgetDistributor, GrpcDispatcherClient dispatcherClient, Function workCommitterFactory, - ThrottlingGetDataMetricTracker getDataMetricTracker) { + ThrottlingGetDataMetricTracker getDataMetricTracker, + ExecutorService workerMetadataConsumer) { this.jobHeader = jobHeader; this.getDataMetricTracker = getDataMetricTracker; this.started = false; @@ -138,9 +140,7 @@ private FanOutStreamingEngineWorkerHarness( this.windmillStreamManager = Executors.newCachedThreadPool( new ThreadFactoryBuilder().setNameFormat(STREAM_MANAGER_THREAD_NAME).build()); - this.workerMetadataConsumer = - Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder().setNameFormat(WORKER_METADATA_CONSUMER_THREAD_NAME).build()); + this.workerMetadataConsumer = workerMetadataConsumer; this.getWorkBudgetDistributor = getWorkBudgetDistributor; this.totalGetWorkBudget = totalGetWorkBudget; this.activeMetadataVersion = Long.MIN_VALUE; @@ -171,7 +171,11 @@ public static FanOutStreamingEngineWorkerHarness create( getWorkBudgetDistributor, dispatcherClient, workCommitterFactory, - getDataMetricTracker); + getDataMetricTracker, + Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setNameFormat(WORKER_METADATA_CONSUMER_THREAD_NAME) + .build())); } @VisibleForTesting @@ -195,7 +199,13 @@ static FanOutStreamingEngineWorkerHarness forTesting( getWorkBudgetDistributor, dispatcherClient, workCommitterFactory, - getDataMetricTracker); + getDataMetricTracker, + // Run the workerMetadataConsumer on the direct calling thread to remove waiting and + // make unit tests more deterministic as we do not have to worry about network IO being + // blocked by the consumeWorkerMetadata() task. Test suites run in different + // environments and non-determinism has lead to past flakiness. See + // https://github.com/apache/beam/issues/28957. + MoreExecutors.newDirectExecutorService()); fanOutStreamingEngineWorkProvider.start(); return fanOutStreamingEngineWorkProvider; } diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/WindmillEndpoints.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/WindmillEndpoints.java index 13b3ea954198..dd7fdd45ab08 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/WindmillEndpoints.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/WindmillEndpoints.java @@ -88,17 +88,23 @@ private static Optional parseDirectEndpoint( .map(address -> AuthenticatedGcpServiceAddress.create(authenticatingService, address)) .map(WindmillServiceAddress::create); - return directEndpointIpV6Address.isPresent() - ? directEndpointIpV6Address - : tryParseEndpointIntoHostAndPort(endpointProto.getDirectEndpoint()) - .map(WindmillServiceAddress::create); + Optional windmillServiceAddress = + directEndpointIpV6Address.isPresent() + ? directEndpointIpV6Address + : tryParseEndpointIntoHostAndPort(endpointProto.getDirectEndpoint()) + .map(WindmillServiceAddress::create); + + if (!windmillServiceAddress.isPresent()) { + LOG.warn("Endpoint {} could not be parsed into a WindmillServiceAddress.", endpointProto); + } + + return windmillServiceAddress; } private static Optional tryParseEndpointIntoHostAndPort(String directEndpoint) { try { return Optional.of(HostAndPort.fromString(directEndpoint)); } catch (IllegalArgumentException e) { - LOG.warn("{} cannot be parsed into a gcpServiceAddress", directEndpoint); return Optional.empty(); } } @@ -113,19 +119,12 @@ private static Optional tryParseDirectEndpointIntoIpV6Address( try { directEndpointAddress = Inet6Address.getByName(endpointProto.getDirectEndpoint()); } catch (UnknownHostException e) { - LOG.warn( - "Error occurred trying to parse direct_endpoint={} into IPv6 address. Exception={}", - endpointProto.getDirectEndpoint(), - e.toString()); return Optional.empty(); } // Inet6Address.getByAddress returns either an IPv4 or an IPv6 address depending on the format // of the direct_endpoint string. if (!(directEndpointAddress instanceof Inet6Address)) { - LOG.warn( - "{} is not an IPv6 address. Direct endpoints are expected to be in IPv6 format.", - endpointProto.getDirectEndpoint()); return Optional.empty(); } diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/GrpcDispatcherClient.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/GrpcDispatcherClient.java index 6bae84483d16..234888831779 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/GrpcDispatcherClient.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/GrpcDispatcherClient.java @@ -150,6 +150,8 @@ public CloudWindmillMetadataServiceV1Alpha1Stub getWindmillMetadataServiceStubBl } } + LOG.info("Windmill Service endpoint initialized after {} seconds.", secondsWaited); + ImmutableList windmillMetadataServiceStubs = dispatcherStubs.get().windmillMetadataServiceStubs(); @@ -190,7 +192,7 @@ public void onJobConfig(StreamingGlobalConfig config) { public synchronized void consumeWindmillDispatcherEndpoints( ImmutableSet dispatcherEndpoints) { - consumeWindmillDispatcherEndpoints(dispatcherEndpoints, /*forceRecreateStubs=*/ false); + consumeWindmillDispatcherEndpoints(dispatcherEndpoints, /* forceRecreateStubs= */ false); } private synchronized void consumeWindmillDispatcherEndpoints( diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCache.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCache.java index db012c6bb412..c03459ee732e 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCache.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCache.java @@ -18,6 +18,7 @@ package org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs; import java.io.PrintWriter; +import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -31,6 +32,7 @@ import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.LoadingCache; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.RemovalListener; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.RemovalListeners; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.util.concurrent.MoreExecutors; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,14 +52,11 @@ public final class ChannelCache implements StatusDataProvider { private ChannelCache( Function channelFactory, - RemovalListener onChannelRemoved) { + RemovalListener onChannelRemoved, + Executor channelCloser) { this.channelCache = CacheBuilder.newBuilder() - .removalListener( - RemovalListeners.asynchronous( - onChannelRemoved, - Executors.newCachedThreadPool( - new ThreadFactoryBuilder().setNameFormat("GrpcChannelCloser").build()))) + .removalListener(RemovalListeners.asynchronous(onChannelRemoved, channelCloser)) .build( new CacheLoader() { @Override @@ -72,11 +71,13 @@ public static ChannelCache create( return new ChannelCache( channelFactory, // Shutdown the channels as they get removed from the cache, so they do not leak. - notification -> shutdownChannel(notification.getValue())); + notification -> shutdownChannel(notification.getValue()), + Executors.newCachedThreadPool( + new ThreadFactoryBuilder().setNameFormat("GrpcChannelCloser").build())); } @VisibleForTesting - static ChannelCache forTesting( + public static ChannelCache forTesting( Function channelFactory, Runnable onChannelShutdown) { return new ChannelCache( channelFactory, @@ -85,7 +86,11 @@ static ChannelCache forTesting( notification -> { shutdownChannel(notification.getValue()); onChannelShutdown.run(); - }); + }, + // Run the removal synchronously on the calling thread to prevent waiting on asynchronous + // tasks to run and make unit tests deterministic. In testing, we verify that things are + // removed from the cache. + MoreExecutors.directExecutor()); } private static void shutdownChannel(ManagedChannel channel) { @@ -108,6 +113,7 @@ public void remove(WindmillServiceAddress windmillServiceAddress) { public void clear() { channelCache.invalidateAll(); + channelCache.cleanUp(); } /** diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarnessTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarnessTest.java index bba6cad5529a..606d2b9dbdbc 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarnessTest.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/streaming/harness/FanOutStreamingEngineWorkerHarnessTest.java @@ -33,8 +33,6 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.beam.runners.dataflow.options.DataflowWorkerHarnessOptions; @@ -65,7 +63,6 @@ import org.apache.beam.vendor.grpc.v1p60p1.io.grpc.inprocess.InProcessSocketAddress; import org.apache.beam.vendor.grpc.v1p60p1.io.grpc.stub.StreamObserver; import org.apache.beam.vendor.grpc.v1p60p1.io.grpc.testing.GrpcCleanupRule; -import org.apache.beam.vendor.grpc.v1p60p1.io.grpc.util.MutableHandlerRegistry; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableCollection; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap; @@ -103,7 +100,6 @@ public class FanOutStreamingEngineWorkerHarnessTest { .build(); @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); - private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry(); private final GrpcWindmillStreamFactory streamFactory = spy(GrpcWindmillStreamFactory.of(JOB_HEADER).build()); private final ChannelCachingStubFactory stubFactory = @@ -146,22 +142,21 @@ private static WorkerMetadataResponse.Endpoint metadataResponseEndpoint(String w @Before public void setUp() throws IOException { - stubFactory.shutdown(); + getWorkerMetadataReady = new CountDownLatch(1); + fakeGetWorkerMetadataStub = new GetWorkerMetadataTestStub(getWorkerMetadataReady); fakeStreamingEngineServer = - grpcCleanup.register( - InProcessServerBuilder.forName(CHANNEL_NAME) - .fallbackHandlerRegistry(serviceRegistry) - .executor(Executors.newFixedThreadPool(1)) - .build()); + grpcCleanup + .register( + InProcessServerBuilder.forName(CHANNEL_NAME) + .directExecutor() + .addService(fakeGetWorkerMetadataStub) + .addService(new WindmillServiceFakeStub()) + .build()) + .start(); - fakeStreamingEngineServer.start(); dispatcherClient.consumeWindmillDispatcherEndpoints( ImmutableSet.of( HostAndPort.fromString(new InProcessSocketAddress(CHANNEL_NAME).toString()))); - getWorkerMetadataReady = new CountDownLatch(1); - fakeGetWorkerMetadataStub = new GetWorkerMetadataTestStub(getWorkerMetadataReady); - serviceRegistry.addService(fakeGetWorkerMetadataStub); - serviceRegistry.addService(new WindmillServiceFakeStub()); } @After @@ -174,27 +169,29 @@ public void cleanUp() { private FanOutStreamingEngineWorkerHarness newFanOutStreamingEngineWorkerHarness( GetWorkBudget getWorkBudget, GetWorkBudgetDistributor getWorkBudgetDistributor, - WorkItemScheduler workItemScheduler) { - return FanOutStreamingEngineWorkerHarness.forTesting( - JOB_HEADER, - getWorkBudget, - streamFactory, - workItemScheduler, - stubFactory, - getWorkBudgetDistributor, - dispatcherClient, - ignored -> mock(WorkCommitter.class), - new ThrottlingGetDataMetricTracker(mock(MemoryMonitor.class))); + WorkItemScheduler workItemScheduler) + throws InterruptedException { + FanOutStreamingEngineWorkerHarness harness = + FanOutStreamingEngineWorkerHarness.forTesting( + JOB_HEADER, + getWorkBudget, + streamFactory, + workItemScheduler, + stubFactory, + getWorkBudgetDistributor, + dispatcherClient, + ignored -> mock(WorkCommitter.class), + new ThrottlingGetDataMetricTracker(mock(MemoryMonitor.class))); + getWorkerMetadataReady.await(); + return harness; } @Test public void testStreamsStartCorrectly() throws InterruptedException { long items = 10L; long bytes = 10L; - int numBudgetDistributionsExpected = 1; - TestGetWorkBudgetDistributor getWorkBudgetDistributor = - spy(new TestGetWorkBudgetDistributor(numBudgetDistributionsExpected)); + TestGetWorkBudgetDistributor getWorkBudgetDistributor = spy(new TestGetWorkBudgetDistributor()); fanOutStreamingEngineWorkProvider = newFanOutStreamingEngineWorkerHarness( @@ -205,17 +202,13 @@ public void testStreamsStartCorrectly() throws InterruptedException { String workerToken = "workerToken1"; String workerToken2 = "workerToken2"; - WorkerMetadataResponse firstWorkerMetadata = + fakeGetWorkerMetadataStub.injectWorkerMetadata( WorkerMetadataResponse.newBuilder() .setMetadataVersion(1) .addWorkEndpoints(metadataResponseEndpoint(workerToken)) .addWorkEndpoints(metadataResponseEndpoint(workerToken2)) .putAllGlobalDataEndpoints(DEFAULT) - .build(); - - getWorkerMetadataReady.await(); - fakeGetWorkerMetadataStub.injectWorkerMetadata(firstWorkerMetadata); - assertTrue(getWorkBudgetDistributor.waitForBudgetDistribution()); + .build()); StreamingEngineBackends currentBackends = fanOutStreamingEngineWorkProvider.currentBackends(); @@ -249,8 +242,7 @@ public void testStreamsStartCorrectly() throws InterruptedException { @Test public void testOnNewWorkerMetadata_correctlyRemovesStaleWindmillServers() throws InterruptedException { - TestGetWorkBudgetDistributor getWorkBudgetDistributor = - spy(new TestGetWorkBudgetDistributor(1)); + TestGetWorkBudgetDistributor getWorkBudgetDistributor = spy(new TestGetWorkBudgetDistributor()); fanOutStreamingEngineWorkProvider = newFanOutStreamingEngineWorkerHarness( GetWorkBudget.builder().setItems(1).setBytes(1).build(), @@ -283,12 +275,8 @@ public void testOnNewWorkerMetadata_correctlyRemovesStaleWindmillServers() .build()) .build(); - getWorkerMetadataReady.await(); fakeGetWorkerMetadataStub.injectWorkerMetadata(firstWorkerMetadata); - assertTrue(getWorkBudgetDistributor.waitForBudgetDistribution()); - getWorkBudgetDistributor.expectNumDistributions(1); fakeGetWorkerMetadataStub.injectWorkerMetadata(secondWorkerMetadata); - assertTrue(getWorkBudgetDistributor.waitForBudgetDistribution()); StreamingEngineBackends currentBackends = fanOutStreamingEngineWorkProvider.currentBackends(); assertEquals(1, currentBackends.windmillStreams().size()); Set workerTokens = @@ -325,21 +313,15 @@ public void testOnNewWorkerMetadata_redistributesBudget() throws InterruptedExce .putAllGlobalDataEndpoints(DEFAULT) .build(); - TestGetWorkBudgetDistributor getWorkBudgetDistributor = - spy(new TestGetWorkBudgetDistributor(1)); + TestGetWorkBudgetDistributor getWorkBudgetDistributor = spy(new TestGetWorkBudgetDistributor()); fanOutStreamingEngineWorkProvider = newFanOutStreamingEngineWorkerHarness( GetWorkBudget.builder().setItems(1).setBytes(1).build(), getWorkBudgetDistributor, noOpProcessWorkItemFn()); - getWorkerMetadataReady.await(); - fakeGetWorkerMetadataStub.injectWorkerMetadata(firstWorkerMetadata); - assertTrue(getWorkBudgetDistributor.waitForBudgetDistribution()); - getWorkBudgetDistributor.expectNumDistributions(1); fakeGetWorkerMetadataStub.injectWorkerMetadata(secondWorkerMetadata); - assertTrue(getWorkBudgetDistributor.waitForBudgetDistribution()); verify(getWorkBudgetDistributor, times(2)).distributeBudget(any(), any()); } @@ -354,10 +336,14 @@ public StreamObserver getDataStream( public void onNext(Windmill.StreamingGetDataRequest getDataRequest) {} @Override - public void onError(Throwable throwable) {} + public void onError(Throwable throwable) { + responseObserver.onError(throwable); + } @Override - public void onCompleted() {} + public void onCompleted() { + responseObserver.onCompleted(); + } }; } @@ -369,10 +355,14 @@ public StreamObserver getWorkStream( public void onNext(Windmill.StreamingGetWorkRequest getWorkRequest) {} @Override - public void onError(Throwable throwable) {} + public void onError(Throwable throwable) { + responseObserver.onError(throwable); + } @Override - public void onCompleted() {} + public void onCompleted() { + responseObserver.onCompleted(); + } }; } @@ -384,10 +374,14 @@ public StreamObserver commitWorkStream( public void onNext(Windmill.StreamingCommitWorkRequest streamingCommitWorkRequest) {} @Override - public void onError(Throwable throwable) {} + public void onError(Throwable throwable) { + responseObserver.onError(throwable); + } @Override - public void onCompleted() {} + public void onCompleted() { + responseObserver.onCompleted(); + } }; } } @@ -422,7 +416,11 @@ public void onError(Throwable throwable) { } @Override - public void onCompleted() {} + public void onCompleted() { + if (responseObserver != null) { + responseObserver.onCompleted(); + } + } }; } @@ -434,25 +432,10 @@ private void injectWorkerMetadata(WorkerMetadataResponse response) { } private static class TestGetWorkBudgetDistributor implements GetWorkBudgetDistributor { - private CountDownLatch getWorkBudgetDistributorTriggered; - - private TestGetWorkBudgetDistributor(int numBudgetDistributionsExpected) { - this.getWorkBudgetDistributorTriggered = new CountDownLatch(numBudgetDistributionsExpected); - } - - private boolean waitForBudgetDistribution() throws InterruptedException { - return getWorkBudgetDistributorTriggered.await(5, TimeUnit.SECONDS); - } - - private void expectNumDistributions(int numBudgetDistributionsExpected) { - this.getWorkBudgetDistributorTriggered = new CountDownLatch(numBudgetDistributionsExpected); - } - @Override public void distributeBudget( ImmutableCollection streams, GetWorkBudget getWorkBudget) { streams.forEach(stream -> stream.setBudget(getWorkBudget.items(), getWorkBudget.bytes())); - getWorkBudgetDistributorTriggered.countDown(); } } } diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCacheTest.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCacheTest.java index 9f8a901cb629..1781261e3400 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCacheTest.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/stubs/ChannelCacheTest.java @@ -105,19 +105,10 @@ public ManagedChannel apply(WindmillServiceAddress windmillServiceAddress) { @Test public void testRemoveAndClose() throws InterruptedException { String channelName = "existingChannel"; - CountDownLatch verifyRemovalListenerAsync = new CountDownLatch(1); CountDownLatch notifyWhenChannelClosed = new CountDownLatch(1); cache = ChannelCache.forTesting( - ignored -> newChannel(channelName), - () -> { - try { - verifyRemovalListenerAsync.await(); - notifyWhenChannelClosed.countDown(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - }); + ignored -> newChannel(channelName), notifyWhenChannelClosed::countDown); WindmillServiceAddress someAddress = mock(WindmillServiceAddress.class); ManagedChannel cachedChannel = cache.get(someAddress); @@ -125,7 +116,6 @@ public void testRemoveAndClose() throws InterruptedException { // Assert that the removal happened before we check to see if the shutdowns happen to confirm // that removals are async. assertTrue(cache.isEmpty()); - verifyRemovalListenerAsync.countDown(); // Assert that the channel gets shutdown. notifyWhenChannelClosed.await(); diff --git a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/testing/FakeWindmillStubFactory.java b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/testing/FakeWindmillStubFactory.java index af3a3e8295bb..19e05efb50c6 100644 --- a/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/testing/FakeWindmillStubFactory.java +++ b/runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/windmill/testing/FakeWindmillStubFactory.java @@ -31,7 +31,7 @@ public final class FakeWindmillStubFactory implements ChannelCachingStubFactory private final ChannelCache channelCache; public FakeWindmillStubFactory(Supplier channelFactory) { - this.channelCache = ChannelCache.create(ignored -> channelFactory.get()); + this.channelCache = ChannelCache.forTesting(ignored -> channelFactory.get(), () -> {}); } @Override From 0d55231a19f890f3377fa3f523afbee0a0ea86d7 Mon Sep 17 00:00:00 2001 From: Ravi Magham Date: Tue, 26 Nov 2024 12:50:23 -0800 Subject: [PATCH 027/135] Better error messaging for chain-type pipeline (#33210) --- sdks/python/apache_beam/yaml/yaml_transform.py | 4 ++-- .../yaml/yaml_transform_unit_test.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/sdks/python/apache_beam/yaml/yaml_transform.py b/sdks/python/apache_beam/yaml/yaml_transform.py index ab86a2aaff56..0190fe20413f 100644 --- a/sdks/python/apache_beam/yaml/yaml_transform.py +++ b/sdks/python/apache_beam/yaml/yaml_transform.py @@ -576,8 +576,8 @@ def is_not_output_of_last_transform(new_transforms, value): pass else: raise ValueError( - f'Transform {identify_object(transform)} is part of a chain, ' - 'must have implicit inputs and outputs.') + f'Transform {identify_object(transform)} is part of a chain. ' + 'Cannot define explicit inputs on chain pipeline') if ix == 0: if is_explicitly_empty(transform.get('input', None)): pass diff --git a/sdks/python/apache_beam/yaml/yaml_transform_unit_test.py b/sdks/python/apache_beam/yaml/yaml_transform_unit_test.py index bc0493509d5a..084e03cdb197 100644 --- a/sdks/python/apache_beam/yaml/yaml_transform_unit_test.py +++ b/sdks/python/apache_beam/yaml/yaml_transform_unit_test.py @@ -325,6 +325,23 @@ def test_chain_as_composite_with_input(self): self.assertEqual( chain_as_composite(spec)['transforms'][0]['input'], {"input": "input"}) + def test_chain_as_composite_with_transform_input(self): + spec = ''' + type: chain + transforms: + - type: Create + config: + elements: [0,1,2] + - type: LogForTesting + input: Create + ''' + spec = yaml.load(spec, Loader=SafeLineLoader) + with self.assertRaisesRegex( + ValueError, + r"Transform .* is part of a chain. " + r"Cannot define explicit inputs on chain pipeline"): + chain_as_composite(spec) + def test_normalize_source_sink(self): spec = ''' source: From 9138697ef60ee2d0da315e15292d15d5a4df1183 Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:07:47 -0500 Subject: [PATCH 028/135] Cleanup Iceberg test files (#33218) * cleanup test files * handle IOException * handle Exception --- .../IO_Iceberg_Integration_Tests.json | 2 +- .../IO_Iceberg_Integration_Tests.yml | 2 +- .../beam/sdk/io/iceberg/IcebergIOIT.java | 41 +++++++++++++++---- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/trigger_files/IO_Iceberg_Integration_Tests.json b/.github/trigger_files/IO_Iceberg_Integration_Tests.json index bbdc3a3910ef..3f63c0c9975f 100644 --- a/.github/trigger_files/IO_Iceberg_Integration_Tests.json +++ b/.github/trigger_files/IO_Iceberg_Integration_Tests.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 3 + "modification": 2 } diff --git a/.github/workflows/IO_Iceberg_Integration_Tests.yml b/.github/workflows/IO_Iceberg_Integration_Tests.yml index 22b2b4f9287d..68a72790006f 100644 --- a/.github/workflows/IO_Iceberg_Integration_Tests.yml +++ b/.github/workflows/IO_Iceberg_Integration_Tests.yml @@ -75,4 +75,4 @@ jobs: - name: Run IcebergIO Integration Test uses: ./.github/actions/gradle-command-self-hosted-action with: - gradle-command: :sdks:java:io:iceberg:catalogTests \ No newline at end of file + gradle-command: :sdks:java:io:iceberg:catalogTests --info \ No newline at end of file diff --git a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java index 5df8604699a3..c79b0a550051 100644 --- a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java +++ b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java @@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertTrue; +import com.google.api.services.storage.model.Objects; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; @@ -36,6 +37,9 @@ import java.util.stream.LongStream; import java.util.stream.Stream; import org.apache.beam.sdk.extensions.gcp.options.GcpOptions; +import org.apache.beam.sdk.extensions.gcp.options.GcsOptions; +import org.apache.beam.sdk.extensions.gcp.util.GcsUtil; +import org.apache.beam.sdk.extensions.gcp.util.gcsfs.GcsPath; import org.apache.beam.sdk.managed.Managed; import org.apache.beam.sdk.schemas.logicaltypes.SqlTypes; import org.apache.beam.sdk.testing.PAssert; @@ -80,6 +84,7 @@ import org.joda.time.DateTimeZone; import org.joda.time.Duration; import org.joda.time.Instant; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -87,10 +92,14 @@ import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Integration tests for {@link IcebergIO} source and sink. */ @RunWith(JUnit4.class) public class IcebergIOIT implements Serializable { + private static final Logger LOG = LoggerFactory.getLogger(IcebergIOIT.class); + private static final org.apache.beam.sdk.schemas.Schema DOUBLY_NESTED_ROW_SCHEMA = org.apache.beam.sdk.schemas.Schema.builder() .addStringField("doubly_nested_str") @@ -176,27 +185,45 @@ public Record apply(Row input) { @Rule public TestName testName = new TestName(); - private String warehouseLocation; + private static String warehouseLocation; private String tableId; - private Catalog catalog; + private static Catalog catalog; @BeforeClass public static void beforeClass() { options = TestPipeline.testingPipelineOptions().as(GcpOptions.class); - + warehouseLocation = + String.format("%s/IcebergIOIT/%s", options.getTempLocation(), UUID.randomUUID()); catalogHadoopConf = new Configuration(); catalogHadoopConf.set("fs.gs.project.id", options.getProject()); catalogHadoopConf.set("fs.gs.auth.type", "APPLICATION_DEFAULT"); + catalog = new HadoopCatalog(catalogHadoopConf, warehouseLocation); } @Before public void setUp() { - warehouseLocation = - String.format("%s/IcebergIOIT/%s", options.getTempLocation(), UUID.randomUUID()); - tableId = testName.getMethodName() + ".test_table"; - catalog = new HadoopCatalog(catalogHadoopConf, warehouseLocation); + } + + @AfterClass + public static void afterClass() { + try { + GcsUtil gcsUtil = options.as(GcsOptions.class).getGcsUtil(); + GcsPath path = GcsPath.fromUri(warehouseLocation); + + Objects objects = + gcsUtil.listObjects( + path.getBucket(), "IcebergIOIT/" + path.getFileName().toString(), null); + List filesToDelete = + objects.getItems().stream() + .map(obj -> "gs://" + path.getBucket() + "/" + obj.getName()) + .collect(Collectors.toList()); + + gcsUtil.remove(filesToDelete); + } catch (Exception e) { + LOG.warn("Failed to clean up files.", e); + } } /** Populates the Iceberg table and Returns a {@link List} of expected elements. */ From 159c8300ab368d7719cb6fe50470aafc6cb4525d Mon Sep 17 00:00:00 2001 From: liferoad Date: Tue, 26 Nov 2024 17:15:36 -0500 Subject: [PATCH 029/135] Update beam_PreCommit_Flink_Container.yml (#33230) Installing Beam Python could take a while. --- .github/workflows/beam_PreCommit_Flink_Container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beam_PreCommit_Flink_Container.yml b/.github/workflows/beam_PreCommit_Flink_Container.yml index 77ddcdf18788..519b0273420a 100644 --- a/.github/workflows/beam_PreCommit_Flink_Container.yml +++ b/.github/workflows/beam_PreCommit_Flink_Container.yml @@ -131,7 +131,7 @@ jobs: # Run a Python Combine load test to verify the Flink container - name: Run Flink Container Test with Python Combine - timeout-minutes: 10 + timeout-minutes: 20 uses: ./.github/actions/gradle-command-self-hosted-action with: gradle-command: :sdks:python:apache_beam:testing:load_tests:run From 409a31a388e660599bd9a93a6c6361cac6d1d556 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Fri, 22 Nov 2024 10:13:14 -0800 Subject: [PATCH 030/135] Add bounded Trie metric type. --- sdks/python/apache_beam/metrics/cells.pxd | 11 + sdks/python/apache_beam/metrics/cells.py | 207 +++++++++++++++++ sdks/python/apache_beam/metrics/cells_test.py | 218 ++++++++++++++++++ 3 files changed, 436 insertions(+) diff --git a/sdks/python/apache_beam/metrics/cells.pxd b/sdks/python/apache_beam/metrics/cells.pxd index c583dabeb0c0..7590bd8b5966 100644 --- a/sdks/python/apache_beam/metrics/cells.pxd +++ b/sdks/python/apache_beam/metrics/cells.pxd @@ -60,3 +60,14 @@ cdef class DistributionData(object): cdef readonly libc.stdint.int64_t count cdef readonly libc.stdint.int64_t min cdef readonly libc.stdint.int64_t max + + +cdef class _BoundedTrieNode(object): + cdef readonly libc.stdint.int64_t _size + cdef readonly dict _children + cdef readonly bint _truncated + +cdef class BoundedTrieData(object): + cdef readonly libc.stdint.int64_t _bound + cdef readonly object _singleton + cdef readonly _BoundedTrieNode _root diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index 10ac7b3a1e69..f04468b00b5f 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -23,6 +23,7 @@ # pytype: skip-file +import copy import logging import threading import time @@ -312,6 +313,35 @@ def to_runner_api_monitoring_info_impl(self, name, transform_id): ptransform=transform_id) +class BoundedTrieCell(AbstractMetricCell): + """For internal use only; no backwards-compatibility guarantees. + + Tracks the current value for a StringSet metric. + + Each cell tracks the state of a metric independently per context per bundle. + Therefore, each metric has a different cell in each bundle, that is later + aggregated. + + This class is thread safe. + """ + def __init__(self): + super().__init__(BoundedTrieData) + + def add(self, value): + self.update(value) + + def _update_locked(self, value): + self.data.add(value) + + def to_runner_api_monitoring_info_impl(self, name, transform_id): + from apache_beam.metrics import monitoring_infos + return monitoring_infos.user_bounded_trie( + name.namespace, + name.name, + self.get_cumulative(), + ptransform=transform_id) + + class DistributionResult(object): """The result of a Distribution metric.""" def __init__(self, data): @@ -630,3 +660,180 @@ def singleton(value: str) -> "StringSetData": @staticmethod def identity_element() -> "StringSetData": return StringSetData() + + +class _BoundedTrieNode(object): + def __init__(self): + # invariant: size = len(self.flattened()) = min(1, sum(size of children)) + self._size = 1 + self._children: Optional[dict[str, '_BoundedTrieNode']] = {} + self._truncated = False + + def size(self): + return self._size + + def add(self, segments) -> int: + if self._truncated or not segments: + return 0 + head, *tail = segments + was_empty = not self._children + child = self._children.get(head, None) # type: ignore[union-attr] + if child is None: + child = self._children[head] = _BoundedTrieNode() # type: ignore[index] + delta = 0 if was_empty else 1 + else: + delta = 0 + if tail: + delta += child.add(tail) + self._size += delta + return delta + + def add_all(self, segments_iter): + return sum(self.add(segments) for segments in segments_iter) + + def trim(self) -> int: + if not self._children: + return 0 + max_child = max(self._children.values(), key=lambda child: child._size) + if max_child._size == 1: + delta = 1 - self._size + self._truncated = True + self._children = None + else: + delta = max_child.trim() + self._size += delta + return delta + + def merge(self, other: '_BoundedTrieNode') -> int: + if self._truncated: + delta = 0 + elif other._truncated: + delta = 1 - self._size + self._truncated = True + self._children = None + elif not other._children: + delta = 0 + elif not self._children: + self._children = other._children + delta = self._size - other._size + else: + delta = 0 + other_child: '_BoundedTrieNode' + self_child: Optional['_BoundedTrieNode'] + for prefix, other_child in other._children.items(): + self_child = self._children.get(prefix, None) + if self_child is None: + self._children[prefix] = other_child + delta += other_child._size + else: + delta += self_child.merge(other_child) + self._size += delta + return delta + + def flattened(self): + if self._truncated: + yield (True, ) + elif not self._children: + yield (False, ) + else: + for prefix, child in sorted(self._children.items()): + for flattened in child.flattened(): + yield (prefix, ) + flattened + + def __hash__(self): + return self._truncated or hash(sorted(self._children.items())) + + def __eq__(self, other): + if isinstance(other, _BoundedTrieNode): + return ( + self._truncated == other._truncated and + self._children == other._children) + else: + return False + + def __repr__(self): + return repr(set(''.join(str(s) for s in t) for t in self.flattened())) + + +class BoundedTrieData(object): + _DEFAULT_BOUND = 100 + + def __init__(self, *, root=None, singleton=None, bound=_DEFAULT_BOUND): + assert singleton is None or root is None + self._singleton = singleton + self._root = root + self._bound = bound + + def as_trie(self): + if self._root is not None: + return self._root + else: + root = _BoundedTrieNode() + if self._singleton is not None: + root.add(self._singleton) + return root + + def __eq__(self, other: object) -> bool: + if isinstance(other, BoundedTrieData): + return self.as_trie() == other.as_trie() + else: + return False + + def __hash__(self) -> int: + return hash(self.as_trie()) + + def __repr__(self) -> str: + return 'BoundedTrieData({})'.format(self.as_trie()) + + def get_cumulative(self) -> "BoundedTrieData": + return copy.deepcopy(self) + + def get_result(self) -> set[tuple]: + if self._root is None: + if self._singleton is None: + return set() + else: + return set([self._singleton + (False, )]) + else: + return set(self._root.flattened()) + + def add(self, segments): + if self._root is None and self._singleton is None: + self._singleton = segments + elif self._singleton is not None and self._singleton == segments: + # Optimize for the common case of re-adding the same value. + return + else: + if self._root is None: + self._root = self.as_trie() + self._root.add(segments) + if self._root._size > self._bound: + self._root.trim() + + def combine(self, other: "BoundedTrieData") -> "BoundedTrieData": + if self._root is None and self._singleton is None: + return other + elif other._root is None and other._singleton is None: + return self + else: + if self._root is None and other._root is not None: + self, other = other, self + combined = copy.deepcopy(self.as_trie()) + if other._root is not None: + combined.merge(other._root) + else: + combined.add(other._singleton) + self._bound = min(self._bound, other._bound) + while combined._size > self._bound: + combined.trim() + return BoundedTrieData(root=combined) + + @staticmethod + def singleton(value: str) -> "BoundedTrieData": + s = BoundedTrieData() + s.add(value) + return s + + @staticmethod + def identity_element() -> "BoundedTrieData": + return BoundedTrieData() diff --git a/sdks/python/apache_beam/metrics/cells_test.py b/sdks/python/apache_beam/metrics/cells_test.py index d1ee37b8ed82..68e3f12a73fd 100644 --- a/sdks/python/apache_beam/metrics/cells_test.py +++ b/sdks/python/apache_beam/metrics/cells_test.py @@ -17,9 +17,13 @@ # pytype: skip-file +import copy +import itertools +import random import threading import unittest +from apache_beam.metrics.cells import BoundedTrieData from apache_beam.metrics.cells import CounterCell from apache_beam.metrics.cells import DistributionCell from apache_beam.metrics.cells import DistributionData @@ -27,6 +31,7 @@ from apache_beam.metrics.cells import GaugeData from apache_beam.metrics.cells import StringSetCell from apache_beam.metrics.cells import StringSetData +from apache_beam.metrics.cells import _BoundedTrieNode from apache_beam.metrics.metricbase import MetricName @@ -203,5 +208,218 @@ def test_add_size_tracked_correctly(self): self.assertEqual(s.data.string_size, 3) +class TestBoundedTrieNode(unittest.TestCase): + @classmethod + def random_segments_fixed_depth(cls, n, depth, overlap, rand): + if depth == 0: + yield from ((), ) * n + else: + seen = [] + to_string = lambda ix: chr(ord('a') + ix) if ix < 26 else f'z{ix}' + for suffix in cls.random_segments_fixed_depth(n, depth - 1, overlap, + rand): + if not seen or rand.random() > overlap: + prefix = to_string(len(seen)) + seen.append(prefix) + else: + prefix = rand.choice(seen) + yield (prefix, ) + suffix + + @classmethod + def random_segments(cls, n, min_depth, max_depth, overlap, rand): + for depth, segments in zip( + itertools.cycle(range(min_depth, max_depth + 1)), + cls.random_segments_fixed_depth(n, max_depth, overlap, rand)): + yield segments[:depth] + + def assert_covers(self, node, expected, max_truncated=0): + self.assert_covers_flattened(node.flattened(), expected, max_truncated) + + def assert_covers_flattened(self, flattened, expected, max_truncated=0): + expected = set(expected) + # Split node into the exact and truncated segments. + partitioned = {True: set(), False: set()} + for segments in flattened: + partitioned[segments[-1]].add(segments[:-1]) + exact, truncated = partitioned[False], partitioned[True] + # Check we cover both parts. + self.assertLessEqual(len(truncated), max_truncated, truncated) + self.assertTrue(exact.issubset(expected), exact - expected) + seen_truncated = set() + for segments in expected - exact: + found = 0 + for ix in range(len(segments)): + if segments[:ix] in truncated: + seen_truncated.add(segments[:ix]) + found += 1 + if found != 1: + self.fail( + f"Expected exactly one prefix of {segments} " + f"to occur in {truncated}, found {found}") + self.assertEqual(seen_truncated, truncated, truncated - seen_truncated) + + def run_covers_test(self, flattened, expected, max_truncated): + def parse(s): + return tuple(s.strip('*')) + (s.endswith('*'), ) + + self.assert_covers_flattened([parse(s) for s in flattened], + [tuple(s) for s in expected], + max_truncated) + + def test_covers_exact(self): + self.run_covers_test(['ab', 'ac', 'cd'], ['ab', 'ac', 'cd'], 0) + with self.assertRaises(AssertionError): + self.run_covers_test(['ab', 'ac', 'cd'], ['ac', 'cd'], 0) + with self.assertRaises(AssertionError): + self.run_covers_test(['ab', 'ac'], ['ab', 'ac', 'cd'], 0) + with self.assertRaises(AssertionError): + self.run_covers_test(['a*', 'cd'], ['ab', 'ac', 'cd'], 0) + + def test_covers_trunacted(self): + self.run_covers_test(['a*', 'cd'], ['ab', 'ac', 'cd'], 1) + self.run_covers_test(['a*', 'cd'], ['ab', 'ac', 'abcde', 'cd'], 1) + with self.assertRaises(AssertionError): + self.run_covers_test(['ab', 'ac', 'cd'], ['ac', 'cd'], 1) + with self.assertRaises(AssertionError): + self.run_covers_test(['ab', 'ac'], ['ab', 'ac', 'cd'], 1) + with self.assertRaises(AssertionError): + self.run_covers_test(['a*', 'c*'], ['ab', 'ac', 'cd'], 1) + with self.assertRaises(AssertionError): + self.run_covers_test(['a*', 'c*'], ['ab', 'ac'], 1) + + def run_test(self, to_add): + everything = list(set(to_add)) + all_prefixees = set( + segments[:ix] for segments in everything for ix in range(len(segments))) + everything_deduped = set(everything) - all_prefixees + + # Check basic addition. + node = _BoundedTrieNode() + total_size = node.size() + self.assertEqual(total_size, 1) + for segments in everything: + total_size += node.add(segments) + self.assertEqual(node.size(), len(everything_deduped), node) + self.assertEqual(node.size(), total_size, node) + self.assert_covers(node, everything_deduped) + + # Check merging + node0 = _BoundedTrieNode() + node0.add_all(everything[0::2]) + node1 = _BoundedTrieNode() + node1.add_all(everything[1::2]) + pre_merge_size = node0.size() + merge_delta = node0.merge(node1) + self.assertEqual(node0.size(), pre_merge_size + merge_delta) + self.assertEqual(node0, node) + + # Check trimming. + if node.size() > 1: + trim_delta = node.trim() + self.assertLess(trim_delta, 0, node) + self.assertEqual(node.size(), total_size + trim_delta) + self.assert_covers(node, everything_deduped, max_truncated=1) + + if node.size() > 1: + trim2_delta = node.trim() + self.assertLess(trim2_delta, 0) + self.assertEqual(node.size(), total_size + trim_delta + trim2_delta) + self.assert_covers(node, everything_deduped, max_truncated=2) + + # Adding after trimming should be a no-op. + node_copy = copy.deepcopy(node) + for segments in everything: + self.assertEqual(node.add(segments), 0) + self.assertEqual(node, node_copy) + + # Merging after trimming should be a no-op. + self.assertEqual(node.merge(node0), 0) + self.assertEqual(node.merge(node1), 0) + self.assertEqual(node, node_copy) + + if node._truncated: + expected_delta = 0 + else: + expected_delta = 2 + + # Adding something new is not. + new_values = [('new1', ), ('new2', 'new2.1')] + self.assertEqual(node.add_all(new_values), expected_delta) + self.assert_covers( + node, list(everything_deduped) + new_values, max_truncated=2) + + # Nor is merging something new. + new_values_node = _BoundedTrieNode() + new_values_node.add_all(new_values) + self.assertEqual(node_copy.merge(new_values_node), expected_delta) + self.assert_covers( + node_copy, list(everything_deduped) + new_values, max_truncated=2) + + def run_fuzz(self, iterations=10, **params): + for _ in range(iterations): + seed = random.getrandbits(64) + segments = self.random_segments(**params, rand=random.Random(seed)) + try: + self.run_test(segments) + except: + print("SEED", seed) + raise + + def test_trivial(self): + self.run_test([('a', 'b'), ('a', 'c')]) + + def test_flat(self): + self.run_test([('a', 'a'), ('b', 'b'), ('c', 'c')]) + + def test_deep(self): + self.run_test([('a', ) * 10, ('b', ) * 12]) + + def test_small(self): + self.run_fuzz(n=5, min_depth=2, max_depth=3, overlap=0.5) + + def test_medium(self): + self.run_fuzz(n=20, min_depth=2, max_depth=4, overlap=0.5) + + def test_large_sparse(self): + self.run_fuzz(n=120, min_depth=2, max_depth=4, overlap=0.2) + + def test_large_dense(self): + self.run_fuzz(n=120, min_depth=2, max_depth=4, overlap=0.8) + + def test_bounded_trie_data_combine(self): + empty = BoundedTrieData() + # The merging here isn't complicated we're just ensuring that + # BoundedTrieData invokes _BoundedTrieNode correctly. + singletonA = BoundedTrieData(singleton=('a', 'a')) + singletonB = BoundedTrieData(singleton=('b', 'b')) + lots_root = _BoundedTrieNode() + lots_root.add_all([('c', 'c'), ('d', 'd')]) + lots = BoundedTrieData(root=lots_root) + self.assertEqual(empty.get_result(), set()) + self.assertEqual( + empty.combine(singletonA).get_result(), set([('a', 'a', False)])) + self.assertEqual( + singletonA.combine(empty).get_result(), set([('a', 'a', False)])) + self.assertEqual( + singletonA.combine(singletonB).get_result(), + set([('a', 'a', False), ('b', 'b', False)])) + self.assertEqual( + singletonA.combine(lots).get_result(), + set([('a', 'a', False), ('c', 'c', False), ('d', 'd', False)])) + self.assertEqual( + lots.combine(singletonA).get_result(), + set([('a', 'a', False), ('c', 'c', False), ('d', 'd', False)])) + + def test_bounded_trie_data_combine_trim(self): + left = _BoundedTrieNode() + left.add_all([('a', 'x'), ('b', 'd')]) + right = _BoundedTrieNode() + right.add_all([('a', 'y'), ('c', 'd')]) + self.assertEqual( + BoundedTrieData(root=left).combine( + BoundedTrieData(root=right, bound=3)).get_result(), + set([('a', True), ('b', 'd', False), ('c', 'd', False)])) + + if __name__ == '__main__': unittest.main() From f83c6836ba0625dcdd86882c277edc0811fb0367 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 25 Nov 2024 11:55:15 -0800 Subject: [PATCH 031/135] Bounded Trie proto representation. --- .../beam/model/pipeline/v1/metrics.proto | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/model/pipeline/src/main/proto/org/apache/beam/model/pipeline/v1/metrics.proto b/model/pipeline/src/main/proto/org/apache/beam/model/pipeline/v1/metrics.proto index 4ec189e4637f..33bb5ae729f8 100644 --- a/model/pipeline/src/main/proto/org/apache/beam/model/pipeline/v1/metrics.proto +++ b/model/pipeline/src/main/proto/org/apache/beam/model/pipeline/v1/metrics.proto @@ -198,6 +198,17 @@ message MonitoringInfoSpecs { }] }]; + // Represents a set of strings seen across bundles. + USER_BOUNDED_TRIE = 22 [(monitoring_info_spec) = { + urn: "beam:metric:user:bounded_trie:v1", + type: "beam:metrics:bounded_trie:v1", + required_labels: ["PTRANSFORM", "NAMESPACE", "NAME"], + annotations: [{ + key: "description", + value: "URN utilized to report user metric." + }] + }]; + // General monitored state information which contains structured information // which does not fit into a typical metric format. See MonitoringTableData // for more details. @@ -576,6 +587,12 @@ message MonitoringInfoTypeUrns { SET_STRING_TYPE = 11 [(org.apache.beam.model.pipeline.v1.beam_urn) = "beam:metrics:set_string:v1"]; + // Represents a bounded trie of strings. + // + // Encoding: BoundedTrie proto + BOUNDED_TRIE_TYPE = 12 [(org.apache.beam.model.pipeline.v1.beam_urn) = + "beam:metrics:bounded_trie:v1"]; + // General monitored state information which contains structured information // which does not fit into a typical metric format. See MonitoringTableData // for more details. @@ -588,6 +605,30 @@ message MonitoringInfoTypeUrns { } } + +// A single node in a BoundedTrie. +message BoundedTrieNode { + // Whether this node has been truncated. + // A truncated leaf represents possibly many children with the same prefix. + bool truncated = 1; + + // Children of this node. Must be empty if truncated is true. + map children = 2; +} + +// The message type used for encoding metrics of type bounded trie. +message BoundedTrie { + // The maximum number of elements to store before truncation. + int32 bound = 1; + + // A compact representation of all the elements in this trie. + BoundedTrieNode root = 2; + + // A more efficient representation for metrics consisting of a single value. + repeated string singleton = 3; +} + + // General monitored state information which contains structured information // which does not fit into a typical metric format. // From d8358cb95b07d290a4e2b50ef1e9ddbb128fad98 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 25 Nov 2024 17:53:13 -0800 Subject: [PATCH 032/135] Bounded Trie translation. --- sdks/python/apache_beam/metrics/cells.py | 38 +++++++++++++++++++ .../apache_beam/metrics/monitoring_infos.py | 24 +++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index f04468b00b5f..e044693a6e9a 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -32,6 +32,8 @@ from typing import Optional from typing import Set +from apache_beam.portability.api import metrics_pb2 + try: import cython except ImportError: @@ -669,6 +671,29 @@ def __init__(self): self._children: Optional[dict[str, '_BoundedTrieNode']] = {} self._truncated = False + def to_proto(self) -> metrics_pb2.BoundedTrieNode: + return metrics_pb2.BoundedTrieNode( + truncated=self._truncated, + children={ + name: child.to_proto() + for name, child in self._children.items() + } if self._children else None) + + @staticmethod + def from_proto(proto: metrics_pb2.BoundedTrieNode) -> '_BoundedTrieNode': + node = _BoundedTrieNode() + if proto.truncated: + node._truncated = True + node._children = None + else: + node._children = { + name: _BoundedTrieNode.from_proto(child) + for name, + child in proto.children.items() + } + node._size = min(1, sum(child._size for child in node._children.values())) + return node + def size(self): return self._size @@ -764,6 +789,19 @@ def __init__(self, *, root=None, singleton=None, bound=_DEFAULT_BOUND): self._root = root self._bound = bound + def to_proto(self) -> metrics_pb2.BoundedTrie: + return metrics_pb2.BoundedTrie( + bound=self._bound, + singleton=self._singlton if self._singleton else None, + root=self._root.to_proto() if self._root else None) + + @staticmethod + def from_proto(proto: metrics_pb2.BoundedTrie) -> 'BoundedTrieData': + return BoundedTrieData( + bound=proto.bound, + singleton=tuple(proto.singleton) if proto.singleton else None, + root=_BoundedTrieNode.from_proto(proto.root) if proto.root else None) + def as_trie(self): if self._root is not None: return self._root diff --git a/sdks/python/apache_beam/metrics/monitoring_infos.py b/sdks/python/apache_beam/metrics/monitoring_infos.py index 5227a4c9872b..397fcc578d53 100644 --- a/sdks/python/apache_beam/metrics/monitoring_infos.py +++ b/sdks/python/apache_beam/metrics/monitoring_infos.py @@ -50,11 +50,14 @@ common_urns.monitoring_info_specs.USER_DISTRIBUTION_INT64.spec.urn) USER_GAUGE_URN = common_urns.monitoring_info_specs.USER_LATEST_INT64.spec.urn USER_STRING_SET_URN = common_urns.monitoring_info_specs.USER_SET_STRING.spec.urn +USER_BOUNDED_TRIE_URN = ( + common_urns.monitoring_info_specs.USER_BOUNDED_TRIE.spec.urn) USER_METRIC_URNS = set([ USER_COUNTER_URN, USER_DISTRIBUTION_URN, USER_GAUGE_URN, - USER_STRING_SET_URN + USER_STRING_SET_URN, + USER_BOUNDED_TRIE_URN, ]) WORK_REMAINING_URN = common_urns.monitoring_info_specs.WORK_REMAINING.spec.urn WORK_COMPLETED_URN = common_urns.monitoring_info_specs.WORK_COMPLETED.spec.urn @@ -72,11 +75,13 @@ LATEST_INT64_TYPE = common_urns.monitoring_info_types.LATEST_INT64_TYPE.urn PROGRESS_TYPE = common_urns.monitoring_info_types.PROGRESS_TYPE.urn STRING_SET_TYPE = common_urns.monitoring_info_types.SET_STRING_TYPE.urn +BOUNDED_TRIE_TYPE = common_urns.monitoring_info_types.BOUNDED_TRIE_TYPE.urn COUNTER_TYPES = set([SUM_INT64_TYPE]) DISTRIBUTION_TYPES = set([DISTRIBUTION_INT64_TYPE]) GAUGE_TYPES = set([LATEST_INT64_TYPE]) STRING_SET_TYPES = set([STRING_SET_TYPE]) +BOUNDED_TRIE_TYPES = set([BOUNDED_TRIE_TYPE]) # TODO(migryz) extract values from beam_fn_api.proto::MonitoringInfoLabels PCOLLECTION_LABEL = ( @@ -320,6 +325,23 @@ def user_set_string(namespace, name, metric, ptransform=None): USER_STRING_SET_URN, STRING_SET_TYPE, metric, labels) +def user_bounded_trie(namespace, name, metric, ptransform=None): + """Return the string set monitoring info for the URN, metric and labels. + + Args: + namespace: User-defined namespace of BoundedTrie. + name: Name of BoundedTrie. + metric: The BoundedTrieData representing the metrics. + ptransform: The ptransform id used as a label. + """ + labels = create_labels(ptransform=ptransform, namespace=namespace, name=name) + return create_monitoring_info( + USER_BOUNDED_TRIE_URN, + BOUNDED_TRIE_TYPE, + metric.to_proto().SerializeToString(), + labels) + + def create_monitoring_info( urn, type_urn, payload, labels=None) -> metrics_pb2.MonitoringInfo: """Return the gauge monitoring info for the URN, type, metric and labels. From 5a6c90287198cc42fedf6d9623c8239d21f6ac74 Mon Sep 17 00:00:00 2001 From: liferoad Date: Tue, 26 Nov 2024 19:05:41 -0500 Subject: [PATCH 033/135] Make the model names unique and fix the tox package deps for tensorflow tests (#33216) * make the model names unique * fixed lint * pin keras==3.6.0 * pin keras==2.12.0 * pin tf_keras==2.18.0 * pin tensorflow==2.18.0 * use 1 worker for pytest * Fixed the tensorflow version --- .../trigger_files/beam_PostCommit_Python_Dependency.json | 4 ++++ .../ml/inference/tensorflow_inference_test.py | 9 +++++---- sdks/python/tox.ini | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Python_Dependency.json b/.github/trigger_files/beam_PostCommit_Python_Dependency.json index e69de29bb2d1..a7fc54b3e4bb 100644 --- a/.github/trigger_files/beam_PostCommit_Python_Dependency.json +++ b/.github/trigger_files/beam_PostCommit_Python_Dependency.json @@ -0,0 +1,4 @@ +{ + "comment": "Modify this file in a trivial way to cause this test suite to run", + "modification": 1 + } \ No newline at end of file diff --git a/sdks/python/apache_beam/ml/inference/tensorflow_inference_test.py b/sdks/python/apache_beam/ml/inference/tensorflow_inference_test.py index 52123516de1a..dc35aa016013 100644 --- a/sdks/python/apache_beam/ml/inference/tensorflow_inference_test.py +++ b/sdks/python/apache_beam/ml/inference/tensorflow_inference_test.py @@ -21,6 +21,7 @@ import shutil import tempfile import unittest +import uuid from typing import Any from typing import Dict from typing import Iterable @@ -127,7 +128,7 @@ def test_predict_tensor(self): def test_predict_tensor_with_batch_size(self): model = _create_mult2_model() - model_path = os.path.join(self.tmpdir, 'mult2.keras') + model_path = os.path.join(self.tmpdir, f'mult2_{uuid.uuid4()}.keras') tf.keras.models.save_model(model, model_path) with TestPipeline() as pipeline: @@ -173,7 +174,7 @@ def fake_batching_inference_fn( def test_predict_tensor_with_large_model(self): model = _create_mult2_model() - model_path = os.path.join(self.tmpdir, 'mult2.keras') + model_path = os.path.join(self.tmpdir, f'mult2_{uuid.uuid4()}.keras') tf.keras.models.save_model(model, model_path) with TestPipeline() as pipeline: @@ -220,7 +221,7 @@ def fake_batching_inference_fn( def test_predict_numpy_with_batch_size(self): model = _create_mult2_model() - model_path = os.path.join(self.tmpdir, 'mult2_numpy.keras') + model_path = os.path.join(self.tmpdir, f'mult2_{uuid.uuid4()}.keras') tf.keras.models.save_model(model, model_path) with TestPipeline() as pipeline: @@ -263,7 +264,7 @@ def fake_batching_inference_fn( def test_predict_numpy_with_large_model(self): model = _create_mult2_model() - model_path = os.path.join(self.tmpdir, 'mult2_numpy.keras') + model_path = os.path.join(self.tmpdir, f'mult2_{uuid.uuid4()}.keras') tf.keras.models.save_model(model, model_path) with TestPipeline() as pipeline: diff --git a/sdks/python/tox.ini b/sdks/python/tox.ini index 85f71e559c15..7a2424325890 100644 --- a/sdks/python/tox.ini +++ b/sdks/python/tox.ini @@ -410,8 +410,9 @@ deps = tensorflow>=2.12rc1,<2.13 # Help pip resolve conflict with typing-extensions for old version of TF https://github.com/apache/beam/issues/30852 pydantic<2.7 - protobuf==4.25.5 -extras = test,gcp,ml_test +extras = test,gcp +commands_pre = + pip install -U 'protobuf==4.25.5' commands = # Log tensorflow version for debugging /bin/sh -c "pip freeze | grep -E tensorflow" From 96962011e7e323c568a6594fd1d93a3b8b6a4861 Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Wed, 27 Nov 2024 09:49:03 -0500 Subject: [PATCH 034/135] Unpin Dataflow legacy worker container for Nexmark test (#33224) --- .../trigger_files/beam_PostCommit_Java_Nexmark_Dataflow.json | 1 + sdks/java/testing/nexmark/build.gradle | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) create mode 100644 .github/trigger_files/beam_PostCommit_Java_Nexmark_Dataflow.json diff --git a/.github/trigger_files/beam_PostCommit_Java_Nexmark_Dataflow.json b/.github/trigger_files/beam_PostCommit_Java_Nexmark_Dataflow.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/.github/trigger_files/beam_PostCommit_Java_Nexmark_Dataflow.json @@ -0,0 +1 @@ +{} diff --git a/sdks/java/testing/nexmark/build.gradle b/sdks/java/testing/nexmark/build.gradle index a09ed9238991..0a09c357ed57 100644 --- a/sdks/java/testing/nexmark/build.gradle +++ b/sdks/java/testing/nexmark/build.gradle @@ -119,11 +119,7 @@ def getNexmarkArgs = { } } else { def dataflowWorkerJar = project.findProperty('dataflowWorkerJar') ?: project(":runners:google-cloud-dataflow-java:worker").shadowJar.archivePath - // Provide job with a customizable worker jar. - // With legacy worker jar, containerImage is set to empty (i.e. to use the internal build). - // More context and discussions can be found in PR#6694. nexmarkArgsList.add("--dataflowWorkerJar=${dataflowWorkerJar}".toString()) - nexmarkArgsList.add('--workerHarnessContainerImage=') def nexmarkProfile = project.findProperty(nexmarkProfilingProperty) ?: "" if (nexmarkProfile.equals("true")) { From 9560fe1d18eabc849b62c89698097d84a7452008 Mon Sep 17 00:00:00 2001 From: Bartosz Zablocki Date: Wed, 27 Nov 2024 14:50:04 +0000 Subject: [PATCH 035/135] SolaceIO: refactor to allow inheritance of BasicAuthSempClient (#32400) * Refactored to allow inheritance and overriding of BasicAuthSempClient * Fix docs and use Map#computeIfAbsent with a lambda. * Fix integration test * Remove 'serializable' * Revert 'Remove 'serializable'' --- .../io/solace/broker/BasicAuthSempClient.java | 31 +-- .../broker/SempBasicAuthClientExecutor.java | 35 ++- .../sdk/io/solace/write/AddShardKeyDoFn.java | 5 +- .../it/BasicAuthMultipleSempClient.java | 62 ++++++ .../BasicAuthMultipleSempClientFactory.java | 92 ++++++++ .../io/solace/it/SolaceIOMultipleSempIT.java | 207 ++++++++++++++++++ 6 files changed, 403 insertions(+), 29 deletions(-) create mode 100644 sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClient.java create mode 100644 sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClientFactory.java create mode 100644 sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/SolaceIOMultipleSempIT.java diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthSempClient.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthSempClient.java index 4884bb61e628..0a9ee4618b1e 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthSempClient.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthSempClient.java @@ -17,14 +17,10 @@ */ package org.apache.beam.sdk.io.solace.broker; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.http.HttpRequestFactory; import com.solacesystems.jcsmp.JCSMPFactory; import java.io.IOException; import org.apache.beam.sdk.annotations.Internal; -import org.apache.beam.sdk.io.solace.data.Semp.Queue; import org.apache.beam.sdk.util.SerializableSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,8 +36,6 @@ @Internal public class BasicAuthSempClient implements SempClient { private static final Logger LOG = LoggerFactory.getLogger(BasicAuthSempClient.class); - private final ObjectMapper objectMapper = - new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); private final SempBasicAuthClientExecutor sempBasicAuthClientExecutor; @@ -58,13 +52,12 @@ public BasicAuthSempClient( @Override public boolean isQueueNonExclusive(String queueName) throws IOException { - LOG.info("SolaceIO.Read: SempOperations: query SEMP if queue {} is nonExclusive", queueName); - BrokerResponse response = sempBasicAuthClientExecutor.getQueueResponse(queueName); - if (response.content == null) { - throw new IOException("SolaceIO: response from SEMP is empty!"); - } - Queue q = mapJsonToClass(response.content, Queue.class); - return q.data().accessType().equals("non-exclusive"); + boolean queueNonExclusive = sempBasicAuthClientExecutor.isQueueNonExclusive(queueName); + LOG.info( + "SolaceIO.Read: SempOperations: queried SEMP if queue {} is non-exclusive: {}", + queueName, + queueNonExclusive); + return queueNonExclusive; } @Override @@ -77,12 +70,7 @@ public com.solacesystems.jcsmp.Queue createQueueForTopic(String queueName, Strin @Override public long getBacklogBytes(String queueName) throws IOException { - BrokerResponse response = sempBasicAuthClientExecutor.getQueueResponse(queueName); - if (response.content == null) { - throw new IOException("SolaceIO: response from SEMP is empty!"); - } - Queue q = mapJsonToClass(response.content, Queue.class); - return q.data().msgSpoolUsage(); + return sempBasicAuthClientExecutor.getBacklogBytes(queueName); } private void createQueue(String queueName) throws IOException { @@ -94,9 +82,4 @@ private void createSubscription(String queueName, String topicName) throws IOExc LOG.info("SolaceIO.Read: Creating new subscription {} for topic {}.", queueName, topicName); sempBasicAuthClientExecutor.createSubscriptionResponse(queueName, topicName); } - - private T mapJsonToClass(String content, Class mapSuccessToClass) - throws JsonProcessingException { - return objectMapper.readValue(content, mapSuccessToClass); - } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SempBasicAuthClientExecutor.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SempBasicAuthClientExecutor.java index 99a81f716435..965fc8741374 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SempBasicAuthClientExecutor.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SempBasicAuthClientExecutor.java @@ -19,6 +19,9 @@ import static org.apache.beam.sdk.util.Preconditions.checkStateNotNull; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpContent; import com.google.api.client.http.HttpHeaders; @@ -40,6 +43,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import org.apache.beam.sdk.io.solace.data.Semp.Queue; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap; import org.checkerframework.checker.nullness.qual.Nullable; @@ -52,7 +56,7 @@ * response is 401 Unauthorized, the client will execute an additional request with Basic Auth * header to refresh the token. */ -class SempBasicAuthClientExecutor implements Serializable { +public class SempBasicAuthClientExecutor implements Serializable { // Every request will be repeated 2 times in case of abnormal connection failures. private static final int REQUEST_NUM_RETRIES = 2; private static final Map COOKIE_MANAGER_MAP = @@ -65,8 +69,10 @@ class SempBasicAuthClientExecutor implements Serializable { private final String password; private final CookieManagerKey cookieManagerKey; private final transient HttpRequestFactory requestFactory; + private final ObjectMapper objectMapper = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - SempBasicAuthClientExecutor( + public SempBasicAuthClientExecutor( String host, String username, String password, @@ -78,7 +84,16 @@ class SempBasicAuthClientExecutor implements Serializable { this.password = password; this.requestFactory = httpRequestFactory; this.cookieManagerKey = new CookieManagerKey(this.baseUrl, this.username); - COOKIE_MANAGER_MAP.putIfAbsent(this.cookieManagerKey, new CookieManager()); + COOKIE_MANAGER_MAP.computeIfAbsent(this.cookieManagerKey, key -> new CookieManager()); + } + + public boolean isQueueNonExclusive(String queueName) throws IOException { + BrokerResponse response = getQueueResponse(queueName); + if (response.content == null) { + throw new IOException("SolaceIO: response from SEMP is empty!"); + } + Queue q = mapJsonToClass(response.content, Queue.class); + return q.data().accessType().equals("non-exclusive"); } private static String getQueueEndpoint(String messageVpn, String queueName) @@ -199,6 +214,20 @@ private static String urlEncode(String queueName) throws UnsupportedEncodingExce return URLEncoder.encode(queueName, StandardCharsets.UTF_8.name()); } + private T mapJsonToClass(String content, Class mapSuccessToClass) + throws JsonProcessingException { + return objectMapper.readValue(content, mapSuccessToClass); + } + + public long getBacklogBytes(String queueName) throws IOException { + BrokerResponse response = getQueueResponse(queueName); + if (response.content == null) { + throw new IOException("SolaceIO: response from SEMP is empty!"); + } + Queue q = mapJsonToClass(response.content, Queue.class); + return q.data().msgSpoolUsage(); + } + private static class CookieManagerKey implements Serializable { private final String baseUrl; private final String username; diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/write/AddShardKeyDoFn.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/write/AddShardKeyDoFn.java index 12d8a8507d8a..c55d37942c72 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/write/AddShardKeyDoFn.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/write/AddShardKeyDoFn.java @@ -23,8 +23,9 @@ import org.apache.beam.sdk.values.KV; /** - * This class a pseudo-key with a given cardinality. The downstream steps will use state {@literal - * &} timers to distribute the data and control for the number of parallel workers used for writing. + * This class adds pseudo-key with a given cardinality. The downstream steps will use state + * {@literal &} timers to distribute the data and control for the number of parallel workers used + * for writing. */ @Internal public class AddShardKeyDoFn extends DoFn> { diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClient.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClient.java new file mode 100644 index 000000000000..637cecdcfd15 --- /dev/null +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClient.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.beam.sdk.io.solace.it; + +import com.google.api.client.http.HttpRequestFactory; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.beam.sdk.io.solace.broker.BasicAuthSempClient; +import org.apache.beam.sdk.io.solace.broker.SempBasicAuthClientExecutor; +import org.apache.beam.sdk.util.SerializableSupplier; + +/** + * Example class showing how the {@link BasicAuthSempClient} can be extended or have functionalities + * overridden. In this case, the modified method is {@link + * BasicAuthSempClient#getBacklogBytes(String)}, which queries multiple SEMP endpoints to collect + * accurate backlog metrics. For usage, see {@link SolaceIOMultipleSempIT}. + */ +public class BasicAuthMultipleSempClient extends BasicAuthSempClient { + private final List sempBacklogBasicAuthClientExecutors; + + public BasicAuthMultipleSempClient( + String mainHost, + List backlogHosts, + String username, + String password, + String vpnName, + SerializableSupplier httpRequestFactorySupplier) { + super(mainHost, username, password, vpnName, httpRequestFactorySupplier); + sempBacklogBasicAuthClientExecutors = + backlogHosts.stream() + .map( + host -> + new SempBasicAuthClientExecutor( + host, username, password, vpnName, httpRequestFactorySupplier.get())) + .collect(Collectors.toList()); + } + + @Override + public long getBacklogBytes(String queueName) throws IOException { + long backlog = 0; + for (SempBasicAuthClientExecutor client : sempBacklogBasicAuthClientExecutors) { + backlog += client.getBacklogBytes(queueName); + } + return backlog; + } +} diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClientFactory.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClientFactory.java new file mode 100644 index 000000000000..0a548c10555c --- /dev/null +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/BasicAuthMultipleSempClientFactory.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.beam.sdk.io.solace.it; + +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.auto.value.AutoValue; +import java.util.List; +import org.apache.beam.sdk.io.solace.broker.SempClient; +import org.apache.beam.sdk.io.solace.broker.SempClientFactory; +import org.apache.beam.sdk.util.SerializableSupplier; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Example class showing how to implement a custom {@link SempClientFactory} with custom client. For + * usage, see {@link SolaceIOMultipleSempIT}. + */ +@AutoValue +public abstract class BasicAuthMultipleSempClientFactory implements SempClientFactory { + + public abstract String mainHost(); + + public abstract List backlogHosts(); + + public abstract String username(); + + public abstract String password(); + + public abstract String vpnName(); + + public abstract @Nullable SerializableSupplier httpRequestFactorySupplier(); + + public static Builder builder() { + return new AutoValue_BasicAuthMultipleSempClientFactory.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + /** Set Solace host, format: [Protocol://]Host[:Port]. */ + public abstract Builder mainHost(String host); + + public abstract Builder backlogHosts(List hosts); + + /** Set Solace username. */ + public abstract Builder username(String username); + /** Set Solace password. */ + public abstract Builder password(String password); + + /** Set Solace vpn name. */ + public abstract Builder vpnName(String vpnName); + + abstract Builder httpRequestFactorySupplier( + SerializableSupplier httpRequestFactorySupplier); + + public abstract BasicAuthMultipleSempClientFactory build(); + } + + @Override + public SempClient create() { + return new BasicAuthMultipleSempClient( + mainHost(), + backlogHosts(), + username(), + password(), + vpnName(), + getHttpRequestFactorySupplier()); + } + + @SuppressWarnings("return") + private @NonNull SerializableSupplier getHttpRequestFactorySupplier() { + SerializableSupplier httpRequestSupplier = httpRequestFactorySupplier(); + return httpRequestSupplier != null + ? httpRequestSupplier + : () -> new NetHttpTransport().createRequestFactory(); + } +} diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/SolaceIOMultipleSempIT.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/SolaceIOMultipleSempIT.java new file mode 100644 index 000000000000..77d00b4e41ec --- /dev/null +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/it/SolaceIOMultipleSempIT.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.beam.sdk.io.solace.it; + +import static org.apache.beam.sdk.io.solace.it.SolaceContainerManager.TOPIC_NAME; +import static org.apache.beam.sdk.values.TypeDescriptors.strings; +import static org.junit.Assert.assertEquals; + +import com.solacesystems.jcsmp.DeliveryMode; +import java.io.IOException; +import java.util.Arrays; +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.PipelineResult; +import org.apache.beam.sdk.coders.KvCoder; +import org.apache.beam.sdk.extensions.avro.coders.AvroCoder; +import org.apache.beam.sdk.io.solace.SolaceIO; +import org.apache.beam.sdk.io.solace.SolaceIO.WriterType; +import org.apache.beam.sdk.io.solace.broker.BasicAuthJcsmpSessionServiceFactory; +import org.apache.beam.sdk.io.solace.broker.SempClientFactory; +import org.apache.beam.sdk.io.solace.data.Solace; +import org.apache.beam.sdk.io.solace.data.Solace.Queue; +import org.apache.beam.sdk.io.solace.data.SolaceDataUtils; +import org.apache.beam.sdk.io.solace.write.SolaceOutput; +import org.apache.beam.sdk.metrics.Counter; +import org.apache.beam.sdk.metrics.Metrics; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.options.StreamingOptions; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.testing.TestPipelineOptions; +import org.apache.beam.sdk.testing.TestStream; +import org.apache.beam.sdk.testutils.metrics.MetricsReader; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.MapElements; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.values.KV; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.TypeDescriptor; +import org.joda.time.Duration; +import org.joda.time.Instant; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +public class SolaceIOMultipleSempIT { + private static final String NAMESPACE = SolaceIOMultipleSempIT.class.getName(); + private static final String READ_COUNT = "read_count"; + private static final String QUEUE_NAME = "test_queue"; + private static final long PUBLISH_MESSAGE_COUNT = 20; + private static final TestPipelineOptions pipelineOptions; + private static SolaceContainerManager solaceContainerManager; + + static { + pipelineOptions = PipelineOptionsFactory.create().as(TestPipelineOptions.class); + pipelineOptions.as(StreamingOptions.class).setStreaming(true); + // For the read connector tests, we need to make sure that p.run() does not block + pipelineOptions.setBlockOnRun(false); + pipelineOptions.as(TestPipelineOptions.class).setBlockOnRun(false); + } + + @Rule public final TestPipeline pipeline = TestPipeline.fromOptions(pipelineOptions); + + @BeforeClass + public static void setup() throws IOException { + solaceContainerManager = new SolaceContainerManager(); + solaceContainerManager.start(); + solaceContainerManager.createQueueWithSubscriptionTopic(QUEUE_NAME); + } + + @AfterClass + public static void afterClass() { + if (solaceContainerManager != null) { + solaceContainerManager.stop(); + } + } + + /** + * This test verifies the functionality of reading data from a Solace queue using the + * SolaceIO.read() transform. This test does not actually test functionalities of {@link + * BasicAuthMultipleSempClientFactory}, but it demonstrates how to integrate a custom + * implementation of {@link SempClientFactory}, in this case, {@link + * BasicAuthMultipleSempClientFactory}, to handle authentication and configuration interactions + * with the Solace message broker. + */ + @Test + public void test01writeAndReadWithMultipleSempClientFactory() { + Pipeline writerPipeline = + createWriterPipeline(WriterType.BATCHED, solaceContainerManager.jcsmpPortMapped); + writerPipeline + .apply( + "Read from Solace", + SolaceIO.read() + .from(Queue.fromName(QUEUE_NAME)) + .withMaxNumConnections(1) + .withDeduplicateRecords(true) + .withSempClientFactory( + BasicAuthMultipleSempClientFactory.builder() + .backlogHosts( + Arrays.asList( + "http://localhost:" + solaceContainerManager.sempPortMapped, + "http://localhost:" + solaceContainerManager.sempPortMapped)) + .mainHost("http://localhost:" + solaceContainerManager.sempPortMapped) + .username("admin") + .password("admin") + .vpnName(SolaceContainerManager.VPN_NAME) + .build()) + .withSessionServiceFactory( + BasicAuthJcsmpSessionServiceFactory.builder() + .host("localhost:" + solaceContainerManager.jcsmpPortMapped) + .username(SolaceContainerManager.USERNAME) + .password(SolaceContainerManager.PASSWORD) + .vpnName(SolaceContainerManager.VPN_NAME) + .build())) + .apply("Count", ParDo.of(new CountingFn<>(NAMESPACE, READ_COUNT))); + + PipelineResult pipelineResult = writerPipeline.run(); + // We need enough time for Beam to pull all messages from the queue, but we need a timeout too, + // as the Read connector will keep attempting to read forever. + pipelineResult.waitUntilFinish(Duration.standardSeconds(15)); + + MetricsReader metricsReader = new MetricsReader(pipelineResult, NAMESPACE); + long actualRecordsCount = metricsReader.getCounterMetric(READ_COUNT); + assertEquals(PUBLISH_MESSAGE_COUNT, actualRecordsCount); + } + + private Pipeline createWriterPipeline( + SolaceIO.WriterType writerType, int solaceContainerJcsmpPort) { + TestStream.Builder> kvBuilder = + TestStream.create(KvCoder.of(AvroCoder.of(String.class), AvroCoder.of(String.class))) + .advanceWatermarkTo(Instant.EPOCH); + + for (int i = 0; i < PUBLISH_MESSAGE_COUNT; i++) { + String key = "Solace-Message-ID:m" + solaceContainerJcsmpPort + i; + String payload = String.format("{\"field_str\":\"value\",\"field_int\":123%d}", i); + kvBuilder = + kvBuilder + .addElements(KV.of(key, payload)) + .advanceProcessingTime(Duration.standardSeconds(60)); + } + + TestStream> testStream = kvBuilder.advanceWatermarkToInfinity(); + + PCollection> kvs = + pipeline.apply(String.format("Test stream %s", writerType), testStream); + + PCollection records = + kvs.apply( + String.format("To Record %s", writerType), + MapElements.into(TypeDescriptor.of(Solace.Record.class)) + .via(kv -> SolaceDataUtils.getSolaceRecord(kv.getValue(), kv.getKey()))); + + SolaceOutput result = + records.apply( + String.format("Write to Solace %s", writerType), + SolaceIO.write() + .to(Solace.Topic.fromName(TOPIC_NAME)) + .withSubmissionMode(SolaceIO.SubmissionMode.TESTING) + .withWriterType(writerType) + .withDeliveryMode(DeliveryMode.PERSISTENT) + .withNumberOfClientsPerWorker(1) + .withNumShards(1) + .withSessionServiceFactory( + BasicAuthJcsmpSessionServiceFactory.builder() + .host("localhost:" + solaceContainerJcsmpPort) + .username(SolaceContainerManager.USERNAME) + .password(SolaceContainerManager.PASSWORD) + .vpnName(SolaceContainerManager.VPN_NAME) + .build())); + result + .getSuccessfulPublish() + .apply( + String.format("Get ids %s", writerType), + MapElements.into(strings()).via(Solace.PublishResult::getMessageId)); + + return pipeline; + } + + private static class CountingFn extends DoFn { + + private final Counter elementCounter; + + CountingFn(String namespace, String name) { + elementCounter = Metrics.counter(namespace, name); + } + + @ProcessElement + public void processElement(@Element T record, OutputReceiver c) { + elementCounter.inc(1L); + c.output(record); + } + } +} From d723216eeb38698156226a43740f59d4fb751e33 Mon Sep 17 00:00:00 2001 From: liferoad Date: Wed, 27 Nov 2024 11:00:52 -0500 Subject: [PATCH 036/135] fixed ML tests (#33234) * fixed ML tests * try some new setups * created py312-ml tox section --- .../apache_beam/ml/transforms/base_test.py | 21 +++++++++---------- .../ml/transforms/handlers_test.py | 8 +++---- sdks/python/setup.py | 20 +++++++++++++----- sdks/python/tox.ini | 14 ++++++++++++- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/sdks/python/apache_beam/ml/transforms/base_test.py b/sdks/python/apache_beam/ml/transforms/base_test.py index 1a21f6caf7e1..3db5a63b9542 100644 --- a/sdks/python/apache_beam/ml/transforms/base_test.py +++ b/sdks/python/apache_beam/ml/transforms/base_test.py @@ -21,7 +21,6 @@ import shutil import tempfile import time -import typing import unittest from collections.abc import Sequence from typing import Any @@ -140,8 +139,8 @@ def test_ml_transform_on_list_dict(self): 'x': int, 'y': float }, expected_dtype={ - 'x': typing.Sequence[np.float32], - 'y': typing.Sequence[np.float32], + 'x': Sequence[np.float32], + 'y': Sequence[np.float32], }, ), param( @@ -153,8 +152,8 @@ def test_ml_transform_on_list_dict(self): 'x': np.int32, 'y': np.float32 }, expected_dtype={ - 'x': typing.Sequence[np.float32], - 'y': typing.Sequence[np.float32], + 'x': Sequence[np.float32], + 'y': Sequence[np.float32], }, ), param( @@ -165,8 +164,8 @@ def test_ml_transform_on_list_dict(self): 'x': list[int], 'y': list[float] }, expected_dtype={ - 'x': typing.Sequence[np.float32], - 'y': typing.Sequence[np.float32], + 'x': Sequence[np.float32], + 'y': Sequence[np.float32], }, ), param( @@ -174,12 +173,12 @@ def test_ml_transform_on_list_dict(self): 'x': [1, 2, 3], 'y': [2.0, 3.0, 4.0] }], input_types={ - 'x': typing.Sequence[int], - 'y': typing.Sequence[float], + 'x': Sequence[int], + 'y': Sequence[float], }, expected_dtype={ - 'x': typing.Sequence[np.float32], - 'y': typing.Sequence[np.float32], + 'x': Sequence[np.float32], + 'y': Sequence[np.float32], }, ), ]) diff --git a/sdks/python/apache_beam/ml/transforms/handlers_test.py b/sdks/python/apache_beam/ml/transforms/handlers_test.py index 4b53026c36a4..bb5f9b5f0f70 100644 --- a/sdks/python/apache_beam/ml/transforms/handlers_test.py +++ b/sdks/python/apache_beam/ml/transforms/handlers_test.py @@ -20,9 +20,9 @@ import shutil import sys import tempfile -import typing import unittest import uuid +from collections.abc import Sequence from typing import NamedTuple from typing import Union @@ -276,9 +276,9 @@ def test_tft_process_handler_transformed_data_schema(self): schema_utils.schema_from_feature_spec(raw_data_feature_spec)) expected_transformed_data_schema = { - 'x': typing.Sequence[np.float32], - 'y': typing.Sequence[np.float32], - 'z': typing.Sequence[bytes] + 'x': Sequence[np.float32], + 'y': Sequence[np.float32], + 'z': Sequence[bytes] } actual_transformed_data_schema = ( diff --git a/sdks/python/setup.py b/sdks/python/setup.py index 3b45cbf82fc1..53c7a532e706 100644 --- a/sdks/python/setup.py +++ b/sdks/python/setup.py @@ -490,12 +490,9 @@ def get_portability_package_data(): 'sentence-transformers', 'skl2onnx', 'pillow', - # Support TF 2.16.0: https://github.com/apache/beam/issues/31294 - # Once TF version is unpinned, also don't restrict Python version. - 'tensorflow<2.16.0;python_version<"3.12"', + 'tensorflow', 'tensorflow-hub', - # https://github.com/tensorflow/transform/issues/313 - 'tensorflow-transform;python_version<"3.11"', + 'tensorflow-transform', 'tf2onnx', 'torch', 'transformers', @@ -504,6 +501,19 @@ def get_portability_package_data(): # https://github.com/apache/beam/issues/31285 # 'xgboost<2.0', # https://github.com/apache/beam/issues/31252 ], + 'p312_ml_test': [ + 'datatable', + 'embeddings', + 'onnxruntime', + 'sentence-transformers', + 'skl2onnx', + 'pillow', + 'tensorflow', + 'tensorflow-hub', + 'tf2onnx', + 'torch', + 'transformers', + ], 'aws': ['boto3>=1.9,<2'], 'azure': [ 'azure-storage-blob>=12.3.2,<13', diff --git a/sdks/python/tox.ini b/sdks/python/tox.ini index 7a2424325890..68ac15ced70d 100644 --- a/sdks/python/tox.ini +++ b/sdks/python/tox.ini @@ -101,11 +101,23 @@ commands = python apache_beam/examples/complete/autocomplete_test.py bash {toxinidir}/scripts/run_pytest.sh {envname} "{posargs}" -[testenv:py{39,310,311,312}-ml] +[testenv:py{39,310,311}-ml] # Don't set TMPDIR to avoid "AF_UNIX path too long" errors in certain tests. setenv = extras = test,gcp,dataframe,ml_test commands = + # Log tensorflow version for debugging + /bin/sh -c "pip freeze | grep -E tensorflow" + bash {toxinidir}/scripts/run_pytest.sh {envname} "{posargs}" + +[testenv:py312-ml] +# many packages do not support py3.12 +# Don't set TMPDIR to avoid "AF_UNIX path too long" errors in certain tests. +setenv = +extras = test,gcp,dataframe,p312_ml_test +commands = + # Log tensorflow version for debugging + /bin/sh -c "pip freeze | grep -E tensorflow" bash {toxinidir}/scripts/run_pytest.sh {envname} "{posargs}" [testenv:py{39,310,311,312}-dask] From d01424997156915821561bd3ee80c7b9277da0eb Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 27 Nov 2024 11:10:03 -0800 Subject: [PATCH 037/135] Remove (long) obsolete declaration of unsupported features that are now supported. (#33239) --- .../content/en/documentation/sdks/python-streaming.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/website/www/site/content/en/documentation/sdks/python-streaming.md b/website/www/site/content/en/documentation/sdks/python-streaming.md index 2d0bdfa9500b..7122cc8b9ae5 100644 --- a/website/www/site/content/en/documentation/sdks/python-streaming.md +++ b/website/www/site/content/en/documentation/sdks/python-streaming.md @@ -155,11 +155,3 @@ about executing streaming pipelines: - [DirectRunner streaming execution](/documentation/runners/direct/#streaming-execution) - [DataflowRunner streaming execution](/documentation/runners/dataflow/#streaming-execution) - [Portable Flink runner](/documentation/runners/flink/) - -## Unsupported features - -Python streaming execution does not currently support the following features: - -- Custom source API -- User-defined custom merging `WindowFn` (with fnapi) -- For portable runners, see [portability support table](https://s.apache.org/apache-beam-portability-support-table). From 4d2d2ce8da04287948131dab1b20a84fad90e893 Mon Sep 17 00:00:00 2001 From: Damon Date: Wed, 27 Nov 2024 11:57:37 -0800 Subject: [PATCH 038/135] Add Java Distroless Github workflow check (#33233) * Create trigger json' file. * Rename trigger file * Create Java validates Distroless container workflow --- ...ValidatesDistrolessContainer_Dataflow.json | 4 + ..._ValidatesDistrolessContainer_Dataflow.yml | 108 ++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 .github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json create mode 100644 .github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml diff --git a/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json b/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json new file mode 100644 index 000000000000..e3d6056a5de9 --- /dev/null +++ b/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json @@ -0,0 +1,4 @@ +{ + "comment": "Modify this file in a trivial way to cause this test suite to run", + "modification": 1 +} diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml new file mode 100644 index 000000000000..98b604be00e8 --- /dev/null +++ b/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml @@ -0,0 +1,108 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +name: PostCommit Java ValidatesDistrolessContainer Dataflow + +on: + schedule: + - cron: '30 6/8 * * *' + pull_request_target: + paths: + - 'release/trigger_all_tests.json' + - '.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2.json' + - '.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json' + + workflow_dispatch: + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.sha || github.head_ref || github.ref }}-${{ github.event.schedule || github.event.comment.id || github.event.sender.login }}' + cancel-in-progress: true + +#Setting explicit permissions for the action to avoid the default permissions which are `write-all` in case of pull_request_target event +permissions: + actions: write + pull-requests: write + checks: write + contents: read + deployments: read + id-token: none + issues: write + discussions: read + packages: read + pages: read + repository-projects: read + security-events: read + statuses: read + +env: + DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} + +jobs: + beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow: + name: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + runs-on: [self-hosted, ubuntu-20.04, main] + timeout-minutes: 390 + strategy: + matrix: + job_name: [beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow] + job_phrase: [Run Java Dataflow ValidatesDistrolessContainer] + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'pull_request_target' || + (github.event_name == 'schedule' && github.repository == 'apache/beam') || + github.event.comment.body == 'Run Java Dataflow ValidatesDistrolessContainer' + steps: + - uses: actions/checkout@v4 + - name: Setup repository + uses: ./.github/actions/setup-action + with: + comment_phrase: ${{ matrix.job_phrase }} + github_token: ${{ secrets.GITHUB_TOKEN }} + github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + - name: Setup environment + uses: ./.github/actions/setup-environment-action + with: + java-version: | + 17 + 21 + - name: Setup docker + run: | + gcloud auth configure-docker us-docker.pkg.dev --quiet + gcloud auth configure-docker us.gcr.io --quiet + gcloud auth configure-docker gcr.io --quiet + gcloud auth configure-docker us-central1-docker.pkg.dev --quiet + - name: run validatesDistrolessContainer script + uses: ./.github/actions/gradle-command-self-hosted-action + with: + gradle-command: :runners:google-cloud-dataflow-java:examplesJavaRunnerV2IntegrationTestDistroless + max-workers: 12 + - name: Archive JUnit Test Results + uses: actions/upload-artifact@v4 + if: ${{ !success() }} + with: + name: JUnit Test Results + path: "**/build/reports/tests/" + - name: Publish JUnit Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + commit: '${{ env.prsha || env.GITHUB_SHA }}' + comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} + files: '**/build/test-results/**/*.xml' From e8dd3c6ed695e3b63780feab03e37d490a7a6a5b Mon Sep 17 00:00:00 2001 From: Damon Date: Wed, 27 Nov 2024 11:58:33 -0800 Subject: [PATCH 039/135] Fix python distroless workflow (#33232) * Fix python distroless workflow (#33228) * Add artifact registry credential setup * Edit trigger file to trigger workflow * Add setup docker stage * Add gcloud auth configure-docker stage * Add registries to configure-docker step --- ...Commit_Python_ValidatesDistrolessContainer_Dataflow.json | 4 ++-- ...tCommit_Python_ValidatesDistrolessContainer_Dataflow.yml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json b/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json index 4897480d69ad..3f63c0c9975f 100644 --- a/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json +++ b/.github/trigger_files/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 1 -} \ No newline at end of file + "modification": 2 +} diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml index a0de6d4a0428..6f8a7bdd0631 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml @@ -85,6 +85,12 @@ jobs: 11 8 python-version: ${{ matrix.python_version }} + - name: Setup docker + run: | + gcloud auth configure-docker us-docker.pkg.dev --quiet + gcloud auth configure-docker us.gcr.io --quiet + gcloud auth configure-docker gcr.io --quiet + gcloud auth configure-docker us-central1-docker.pkg.dev --quiet - name: Set PY_VER_CLEAN id: set_py_ver_clean run: | From 95322c69a18254a5e08f5c9259861450752b04cd Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Wed, 27 Nov 2024 16:00:05 -0500 Subject: [PATCH 040/135] bump hadoop version (#33011) * bump hadoop version * add to readme.md --- CHANGES.md | 1 + .../main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 654512c3a4e2..a5d0cca0b559 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -63,6 +63,7 @@ ## I/Os * Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). +* Upgraded the default version of Hadoop dependencies to 3.4.1. Hadoop 2.10.2 is still supported (Java) ([#33011](https://github.com/apache/beam/issues/33011)). ## New Features / Improvements diff --git a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy index 84c7c3ecfd4a..2abd43a5d4cc 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -614,7 +614,7 @@ class BeamModulePlugin implements Plugin { // [bomupgrader] determined by: io.grpc:grpc-netty, consistent with: google_cloud_platform_libraries_bom def grpc_version = "1.67.1" def guava_version = "33.1.0-jre" - def hadoop_version = "2.10.2" + def hadoop_version = "3.4.1" def hamcrest_version = "2.1" def influxdb_version = "2.19" def httpclient_version = "4.5.13" From f28e65f5e57f74834d74a66c2e510b405cd6694f Mon Sep 17 00:00:00 2001 From: liferoad Date: Mon, 2 Dec 2024 09:11:38 -0500 Subject: [PATCH 041/135] Update beam_LoadTests_Python_Combine_Flink_Streaming.yml (#33241) * Update beam_LoadTests_Python_Combine_Flink_Streaming.yml Increase the number of workers. * updated parallelism * try high mem machines * updated the script * try n1 * fixed zone * change heap size * try prop * more propts * try more * try props * more opts * more props * props * n1-highmem-8 * n1-highmem-16 * n1-highmem-32 * props * move props * fix heap * task mem * more mem * updated props * increase --max_cache_memory_usage_mb=256 * try new props * more mem * updated mem * mem * reduce size * options * reduce top count * timeout * mem * fanout * small * added small load tests now * restore old ones * fixed args * minor comments --- ...adTests_Python_Combine_Flink_Streaming.yml | 19 +++++++----- ...n_Combine_Flink_Streaming_2GB_Fanout_4.txt | 3 +- ...n_Combine_Flink_Streaming_2GB_Fanout_8.txt | 3 +- ...Combine_Flink_Streaming_small_Fanout_1.txt | 31 +++++++++++++++++++ ...Combine_Flink_Streaming_small_Fanout_2.txt | 31 +++++++++++++++++++ .test-infra/dataproc/flink_cluster.sh | 27 +++++++++++----- 6 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_1.txt create mode 100644 .github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_2.txt diff --git a/.github/workflows/beam_LoadTests_Python_Combine_Flink_Streaming.yml b/.github/workflows/beam_LoadTests_Python_Combine_Flink_Streaming.yml index baf950589c8e..243e9d32c066 100644 --- a/.github/workflows/beam_LoadTests_Python_Combine_Flink_Streaming.yml +++ b/.github/workflows/beam_LoadTests_Python_Combine_Flink_Streaming.yml @@ -65,7 +65,7 @@ jobs: (github.event_name == 'schedule' && github.repository == 'apache/beam') || github.event.comment.body == 'Run Load Tests Python Combine Flink Streaming' runs-on: [self-hosted, ubuntu-20.04, main] - timeout-minutes: 720 + timeout-minutes: 80 name: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) strategy: matrix: @@ -89,17 +89,22 @@ jobs: test-type: load test-language: python argument-file-paths: | - ${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_4.txt - ${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_8.txt + ${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_1.txt + ${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_2.txt + # large loads do not work now + # ${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_4.txt + # ${{ github.workspace }}/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_8.txt - name: Start Flink with parallelism 16 env: FLINK_NUM_WORKERS: 16 + HIGH_MEM_MACHINE: n1-highmem-16 + HIGH_MEM_FLINK_PROPS: flink:taskmanager.memory.process.size=16g,flink:taskmanager.memory.flink.size=12g,flink:taskmanager.memory.jvm-overhead.max=4g,flink:jobmanager.memory.process.size=6g,flink:jobmanager.memory.jvm-overhead.max= 2g,flink:jobmanager.memory.flink.size=4g run: | cd ${{ github.workspace }}/.test-infra/dataproc; ./flink_cluster.sh create - name: get current time run: echo "NOW_UTC=$(date '+%m%d%H%M%S' --utc)" >> $GITHUB_ENV - # The env variables are created and populated in the test-arguments-action as "_test_arguments_" - - name: run Load test 2GB Fanout 4 + # The env variables are created and populated in the test-arguments-action as "_test_arguments_" + - name: run Load test small Fanout 1 uses: ./.github/actions/gradle-command-self-hosted-action with: gradle-command: :sdks:python:apache_beam:testing:load_tests:run @@ -108,7 +113,7 @@ jobs: -PloadTest.mainClass=apache_beam.testing.load_tests.combine_test \ -Prunner=PortableRunner \ '-PloadTest.args=${{ env.beam_LoadTests_Python_Combine_Flink_Streaming_test_arguments_1 }} --job_name=load-tests-python-flink-streaming-combine-4-${{env.NOW_UTC}}' \ - - name: run Load test 2GB Fanout 8 + - name: run Load test small Fanout 2 uses: ./.github/actions/gradle-command-self-hosted-action with: gradle-command: :sdks:python:apache_beam:testing:load_tests:run @@ -123,4 +128,4 @@ jobs: ${{ github.workspace }}/.test-infra/dataproc/flink_cluster.sh delete # // TODO(https://github.com/apache/beam/issues/20402). Skipping some cases because they are too slow: - # load-tests-python-flink-streaming-combine-1' \ No newline at end of file + # load-tests-python-flink-streaming-combine-1' diff --git a/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_4.txt b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_4.txt index 650236a9c500..6280e01dccdb 100644 --- a/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_4.txt +++ b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_4.txt @@ -27,4 +27,5 @@ --top_count=20 --streaming --use_stateful_load_generator ---runner=PortableRunner \ No newline at end of file +--runner=PortableRunner +--max_cache_memory_usage_mb=256 \ No newline at end of file diff --git a/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_8.txt b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_8.txt index 4208571fef62..e1b77d15b95b 100644 --- a/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_8.txt +++ b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_2GB_Fanout_8.txt @@ -27,4 +27,5 @@ --top_count=20 --streaming --use_stateful_load_generator ---runner=PortableRunner \ No newline at end of file +--runner=PortableRunner +--max_cache_memory_usage_mb=256 \ No newline at end of file diff --git a/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_1.txt b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_1.txt new file mode 100644 index 000000000000..f16e9e4b06ef --- /dev/null +++ b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_1.txt @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--publish_to_big_query=true +--metrics_dataset=load_test +--metrics_table=python_flink_streaming_combine_4 +--influx_measurement=python_streaming_combine_4 +--input_options=''{\\"num_records\\":200000,\\"key_size\\":10,\\"value_size\\":90,\\"algorithm\\":\\"lcg\\"}'' +--parallelism=16 +--job_endpoint=localhost:8099 +--environment_type=DOCKER +--environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_python3.9_sdk:latest +--fanout=1 +--top_count=20 +--streaming +--use_stateful_load_generator +--runner=PortableRunner +--max_cache_memory_usage_mb=256 \ No newline at end of file diff --git a/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_2.txt b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_2.txt new file mode 100644 index 000000000000..5f66e519c31a --- /dev/null +++ b/.github/workflows/load-tests-pipeline-options/python_Combine_Flink_Streaming_small_Fanout_2.txt @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--publish_to_big_query=true +--metrics_dataset=load_test +--metrics_table=python_flink_streaming_combine_5 +--influx_measurement=python_streaming_combine_5 +--input_options=''{\\"num_records\\":200000,\\"key_size\\":10,\\"value_size\\":90,\\"algorithm\\":\\"lcg\\"}'' +--parallelism=16 +--job_endpoint=localhost:8099 +--environment_type=DOCKER +--environment_config=gcr.io/apache-beam-testing/beam-sdk/beam_python3.9_sdk:latest +--fanout=2 +--top_count=20 +--streaming +--use_stateful_load_generator +--runner=PortableRunner +--max_cache_memory_usage_mb=256 \ No newline at end of file diff --git a/.test-infra/dataproc/flink_cluster.sh b/.test-infra/dataproc/flink_cluster.sh index 24d51ac13bde..4a97850f5ac1 100755 --- a/.test-infra/dataproc/flink_cluster.sh +++ b/.test-infra/dataproc/flink_cluster.sh @@ -129,13 +129,26 @@ function create_cluster() { local image_version=$DATAPROC_VERSION echo "Starting dataproc cluster. Dataproc version: $image_version" - # Docker init action restarts yarn so we need to start yarn session after this restart happens. - # This is why flink init action is invoked last. - # TODO(11/22/2024) remove --worker-machine-type and --master-machine-type once N2 CPUs quota relaxed - # Dataproc 2.1 uses n2-standard-2 by default but there is N2 CPUs=24 quota limit for this project - gcloud dataproc clusters create $CLUSTER_NAME --enable-component-gateway --region=$GCLOUD_REGION --num-workers=$FLINK_NUM_WORKERS --public-ip-address \ - --master-machine-type=n1-standard-2 --worker-machine-type=n1-standard-2 --metadata "${metadata}", \ - --image-version=$image_version --zone=$GCLOUD_ZONE --optional-components=FLINK,DOCKER --quiet + local worker_machine_type="n1-standard-2" # Default worker type + local master_machine_type="n1-standard-2" # Default master type + + if [[ -n "${HIGH_MEM_MACHINE:=}" ]]; then + worker_machine_type="${HIGH_MEM_MACHINE}" + master_machine_type="${HIGH_MEM_MACHINE}" + + gcloud dataproc clusters create $CLUSTER_NAME --enable-component-gateway --region=$GCLOUD_REGION --num-workers=$FLINK_NUM_WORKERS --public-ip-address \ + --master-machine-type=${master_machine_type} --worker-machine-type=${worker_machine_type} --metadata "${metadata}", \ + --image-version=$image_version --zone=$GCLOUD_ZONE --optional-components=FLINK,DOCKER \ + --properties="${HIGH_MEM_FLINK_PROPS}" + else + # Docker init action restarts yarn so we need to start yarn session after this restart happens. + # This is why flink init action is invoked last. + # TODO(11/22/2024) remove --worker-machine-type and --master-machine-type once N2 CPUs quota relaxed + # Dataproc 2.1 uses n2-standard-2 by default but there is N2 CPUs=24 quota limit for this project + gcloud dataproc clusters create $CLUSTER_NAME --enable-component-gateway --region=$GCLOUD_REGION --num-workers=$FLINK_NUM_WORKERS --public-ip-address \ + --master-machine-type=${master_machine_type} --worker-machine-type=${worker_machine_type} --metadata "${metadata}", \ + --image-version=$image_version --zone=$GCLOUD_ZONE --optional-components=FLINK,DOCKER --quiet + fi } # Runs init actions for Docker, Portability framework (Beam) and Flink cluster From 58e1625794a6c1756925d329c27ccfc0d46723ec Mon Sep 17 00:00:00 2001 From: Mattie Fu Date: Mon, 2 Dec 2024 09:51:34 -0500 Subject: [PATCH 042/135] update 2.60.0 changelog to include a fix in Bigtable (#33053) * update 2.60.0 changelog to include a fix in Bigtable * update main changelog --------- Co-authored-by: Danny McCormick --- CHANGES.md | 1 + website/www/site/content/en/blog/beam-2.60.0.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a5d0cca0b559..8dd1e89aec91 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -173,6 +173,7 @@ when running on 3.8. ([#31192](https://github.com/apache/beam/issues/31192)) * (Java) Fixed custom delimiter issues in TextIO ([#32249](https://github.com/apache/beam/issues/32249), [#32251](https://github.com/apache/beam/issues/32251)). * (Java, Python, Go) Fixed PeriodicSequence backlog bytes reporting, which was preventing Dataflow Runner autoscaling from functioning properly ([#32506](https://github.com/apache/beam/issues/32506)). * (Java) Fix improper decoding of rows with schemas containing nullable fields when encoded with a schema with equal encoding positions but modified field order. ([#32388](https://github.com/apache/beam/issues/32388)). +* (Java) Skip close on bundles in BigtableIO.Read ([#32661](https://github.com/apache/beam/pull/32661), [#32759](https://github.com/apache/beam/pull/32759)). ## Known Issues diff --git a/website/www/site/content/en/blog/beam-2.60.0.md b/website/www/site/content/en/blog/beam-2.60.0.md index ae5e0284ccdd..e5767cff5114 100644 --- a/website/www/site/content/en/blog/beam-2.60.0.md +++ b/website/www/site/content/en/blog/beam-2.60.0.md @@ -67,6 +67,7 @@ when running on 3.8. ([#31192](https://github.com/apache/beam/issues/31192)) * (Java) Fixed custom delimiter issues in TextIO ([#32249](https://github.com/apache/beam/issues/32249), [#32251](https://github.com/apache/beam/issues/32251)). * (Java, Python, Go) Fixed PeriodicSequence backlog bytes reporting, which was preventing Dataflow Runner autoscaling from functioning properly ([#32506](https://github.com/apache/beam/issues/32506)). * (Java) Fix improper decoding of rows with schemas containing nullable fields when encoded with a schema with equal encoding positions but modified field order. ([#32388](https://github.com/apache/beam/issues/32388)). +* (Java) Skip close on bundles in BigtableIO.Read ([#32661](https://github.com/apache/beam/pull/32661), [#32759](https://github.com/apache/beam/pull/32759)). ## Known Issues From 727a865c2eb55010ac8d61a4045705bed7834fb8 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 2 Dec 2024 09:51:51 -0500 Subject: [PATCH 043/135] Add safe to ignore category to dashboard/alerting (#33168) --- .../github/github_runs_prefetcher/code/config.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.test-infra/metrics/sync/github/github_runs_prefetcher/code/config.yaml b/.test-infra/metrics/sync/github/github_runs_prefetcher/code/config.yaml index 722cc4e8f29e..eccaaa5f3b17 100644 --- a/.test-infra/metrics/sync/github/github_runs_prefetcher/code/config.yaml +++ b/.test-infra/metrics/sync/github/github_runs_prefetcher/code/config.yaml @@ -124,7 +124,6 @@ categories: - "PostCommit Java PVR Samza" - "PreCommit Java Tika IO Direct" - "PostCommit Java SingleStoreIO IT" - - "PostCommit Java Sickbay" - "PostCommit Java ValidatesRunner Direct" - "PreCommit Java SingleStore IO Direct" - "PreCommit Java InfluxDb IO Direct" @@ -227,7 +226,6 @@ categories: - "PreCommit Python Transforms" - "Build python source distribution and wheels" - "Python tests" - - "PostCommit Sickbay Python" - "PreCommit Portable Python" - "PreCommit Python Coverage" - "PreCommit Python Docker" @@ -317,7 +315,6 @@ categories: - "PostCommit PortableJar Spark" - "PreCommit Integration and Load Test Framework" - "pr-bot-update-reviewers" - - "Cut Release Branch" - "Generate issue report" - "Dask Runner Tests" - "PreCommit Typescript" @@ -328,6 +325,12 @@ categories: - "Assign Milestone on issue close" - "Local environment tests" - "PreCommit SQL" - - "LabelPrs" + - "LabelPrs" + - name: safe_to_ignore + groupThreshold: 0 + tests: - "build_release_candidate" + - "Cut Release Branch" + - "PostCommit Java Sickbay" + - "PostCommit Sickbay Python" From e279e55f47683bcee1c59c0b7aea2b21741fadb3 Mon Sep 17 00:00:00 2001 From: Bartosz Zablocki Date: Mon, 2 Dec 2024 15:54:16 +0000 Subject: [PATCH 044/135] SolaceIO.Read: handle occasional cases when finalizeCheckpoint is not executed (#32962) * SolaceIO.Read: handle occasional cases when finalizeCheckpoint is not called * Implement cache for storing the session service and implement an eviction strategy and close services more eagerly. * Wrap message acknowledgment in a try-catch block, remove active AtomicBool from the reader. * Store messages in a Queue that is referenced from the CheckpointMark --- .../broker/BasicAuthJcsmpSessionService.java | 18 +-- .../sdk/io/solace/broker/MessageReceiver.java | 7 - .../sdk/io/solace/broker/SessionService.java | 7 - .../solace/broker/SolaceMessageReceiver.java | 17 +-- .../io/solace/read/SolaceCheckpointMark.java | 46 +++--- .../io/solace/read/UnboundedSolaceReader.java | 135 +++++++++++++----- .../io/solace/MockEmptySessionService.java | 5 - .../sdk/io/solace/MockSessionService.java | 10 -- .../beam/sdk/io/solace/SolaceIOReadTest.java | 20 +-- 9 files changed, 138 insertions(+), 127 deletions(-) diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java index b2196dbf1067..d4c9a3ec6210 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java @@ -102,10 +102,7 @@ public void close() { if (messageReceiver != null) { messageReceiver.close(); } - if (messageProducer != null) { - messageProducer.close(); - } - if (!isClosed()) { + if (jcsmpSession != null) { checkStateNotNull(jcsmpSession).closeSession(); } return 0; @@ -119,8 +116,9 @@ public MessageReceiver getReceiver() { this.messageReceiver = retryCallableManager.retryCallable( this::createFlowReceiver, ImmutableSet.of(JCSMPException.class)); + this.messageReceiver.start(); } - return this.messageReceiver; + return checkStateNotNull(this.messageReceiver); } @Override @@ -138,15 +136,10 @@ public java.util.Queue getPublishedResultsQueue() { return publishedResultsQueue; } - @Override - public boolean isClosed() { - return jcsmpSession == null || jcsmpSession.isClosed(); - } - private MessageProducer createXMLMessageProducer(SubmissionMode submissionMode) throws JCSMPException, IOException { - if (isClosed()) { + if (jcsmpSession == null) { connectWriteSession(submissionMode); } @@ -165,9 +158,6 @@ private MessageProducer createXMLMessageProducer(SubmissionMode submissionMode) } private MessageReceiver createFlowReceiver() throws JCSMPException, IOException { - if (isClosed()) { - connectSession(); - } Queue queue = JCSMPFactory.onlyInstance() diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java index 95f989bd1be9..017a63260678 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java @@ -35,13 +35,6 @@ public interface MessageReceiver { */ void start(); - /** - * Returns {@literal true} if the message receiver is closed, {@literal false} otherwise. - * - *

A message receiver is closed when it is no longer able to receive messages. - */ - boolean isClosed(); - /** * Receives a message from the broker. * diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java index 84a876a9d0bc..6dcd0b652616 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java @@ -120,13 +120,6 @@ public abstract class SessionService implements Serializable { /** Gracefully closes the connection to the service. */ public abstract void close(); - /** - * Checks whether the connection to the service is currently closed. This method is called when an - * `UnboundedSolaceReader` is starting to read messages - a session will be created if this - * returns true. - */ - public abstract boolean isClosed(); - /** * Returns a MessageReceiver object for receiving messages from Solace. If it is the first time * this method is used, the receiver is created from the session instance, otherwise it returns diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java index d548d2049a5b..d74f3cae89fe 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java @@ -24,12 +24,8 @@ import java.io.IOException; import org.apache.beam.sdk.io.solace.RetryCallableManager; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class SolaceMessageReceiver implements MessageReceiver { - private static final Logger LOG = LoggerFactory.getLogger(SolaceMessageReceiver.class); - public static final int DEFAULT_ADVANCE_TIMEOUT_IN_MILLIS = 100; private final FlowReceiver flowReceiver; private final RetryCallableManager retryCallableManager = RetryCallableManager.create(); @@ -52,19 +48,14 @@ private void startFlowReceiver() { ImmutableSet.of(JCSMPException.class)); } - @Override - public boolean isClosed() { - return flowReceiver == null || flowReceiver.isClosed(); - } - @Override public BytesXMLMessage receive() throws IOException { try { return flowReceiver.receive(DEFAULT_ADVANCE_TIMEOUT_IN_MILLIS); } catch (StaleSessionException e) { - LOG.warn("SolaceIO: Caught StaleSessionException, restarting the FlowReceiver."); startFlowReceiver(); - throw new IOException(e); + throw new IOException( + "SolaceIO: Caught StaleSessionException, restarting the FlowReceiver.", e); } catch (JCSMPException e) { throw new IOException(e); } @@ -72,8 +63,6 @@ public BytesXMLMessage receive() throws IOException { @Override public void close() { - if (!isClosed()) { - this.flowReceiver.close(); - } + flowReceiver.close(); } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java index 77f6eed8f62c..a913fd6133ea 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java @@ -18,17 +18,16 @@ package org.apache.beam.sdk.io.solace.read; import com.solacesystems.jcsmp.BytesXMLMessage; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Queue; import org.apache.beam.sdk.annotations.Internal; import org.apache.beam.sdk.coders.DefaultCoder; import org.apache.beam.sdk.extensions.avro.coders.AvroCoder; import org.apache.beam.sdk.io.UnboundedSource; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Checkpoint for an unbounded Solace source. Consists of the Solace messages waiting to be @@ -38,10 +37,8 @@ @Internal @VisibleForTesting public class SolaceCheckpointMark implements UnboundedSource.CheckpointMark { - private transient AtomicBoolean activeReader; - // BytesXMLMessage is not serializable so if a job restarts from the checkpoint, we cannot retry - // these messages here. We relay on Solace's retry mechanism. - private transient ArrayDeque ackQueue; + private static final Logger LOG = LoggerFactory.getLogger(SolaceCheckpointMark.class); + private transient Queue safeToAck; @SuppressWarnings("initialization") // Avro will set the fields by breaking abstraction private SolaceCheckpointMark() {} @@ -49,25 +46,24 @@ private SolaceCheckpointMark() {} /** * Creates a new {@link SolaceCheckpointMark}. * - * @param activeReader {@link AtomicBoolean} indicating if the related reader is active. The - * reader creating the messages has to be active to acknowledge the messages. - * @param ackQueue {@link List} of {@link BytesXMLMessage} to be acknowledged. + * @param safeToAck - a queue of {@link BytesXMLMessage} to be acknowledged. */ - SolaceCheckpointMark(AtomicBoolean activeReader, List ackQueue) { - this.activeReader = activeReader; - this.ackQueue = new ArrayDeque<>(ackQueue); + SolaceCheckpointMark(Queue safeToAck) { + this.safeToAck = safeToAck; } @Override public void finalizeCheckpoint() { - if (activeReader == null || !activeReader.get() || ackQueue == null) { - return; - } - - while (!ackQueue.isEmpty()) { - BytesXMLMessage msg = ackQueue.poll(); - if (msg != null) { + BytesXMLMessage msg; + while ((msg = safeToAck.poll()) != null) { + try { msg.ackMessage(); + } catch (IllegalStateException e) { + LOG.error( + "SolaceIO.Read: cannot acknowledge the message with applicationMessageId={}, ackMessageId={}. It will not be retried.", + msg.getApplicationMessageId(), + msg.getAckMessageId(), + e); } } } @@ -84,15 +80,11 @@ public boolean equals(@Nullable Object o) { return false; } SolaceCheckpointMark that = (SolaceCheckpointMark) o; - // Needed to convert to ArrayList because ArrayDeque.equals checks only for reference, not - // content. - ArrayList ackList = new ArrayList<>(ackQueue); - ArrayList thatAckList = new ArrayList<>(that.ackQueue); - return Objects.equals(activeReader, that.activeReader) && Objects.equals(ackList, thatAckList); + return Objects.equals(safeToAck, that.safeToAck); } @Override public int hashCode() { - return Objects.hash(activeReader, ackQueue); + return Objects.hash(safeToAck); } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java index a421970370da..dc84e0a07017 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java @@ -22,17 +22,26 @@ import com.solacesystems.jcsmp.BytesXMLMessage; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.apache.beam.sdk.io.UnboundedSource; import org.apache.beam.sdk.io.UnboundedSource.UnboundedReader; import org.apache.beam.sdk.io.solace.broker.SempClient; import org.apache.beam.sdk.io.solace.broker.SessionService; +import org.apache.beam.sdk.io.solace.broker.SessionServiceFactory; import org.apache.beam.sdk.transforms.windowing.BoundedWindow; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.Cache; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.CacheBuilder; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.RemovalNotification; import org.checkerframework.checker.nullness.qual.Nullable; import org.joda.time.Instant; import org.slf4j.Logger; @@ -46,48 +55,92 @@ class UnboundedSolaceReader extends UnboundedReader { private final UnboundedSolaceSource currentSource; private final WatermarkPolicy watermarkPolicy; private final SempClient sempClient; + private final UUID readerUuid; + private final SessionServiceFactory sessionServiceFactory; private @Nullable BytesXMLMessage solaceOriginalRecord; private @Nullable T solaceMappedRecord; - private @Nullable SessionService sessionService; - AtomicBoolean active = new AtomicBoolean(true); /** - * Queue to place advanced messages before {@link #getCheckpointMark()} be called non-concurrent - * queue, should only be accessed by the reader thread A given {@link UnboundedReader} object will - * only be accessed by a single thread at once. + * Queue to place advanced messages before {@link #getCheckpointMark()} is called. CAUTION: + * Accessed by both reader and checkpointing threads. */ - private final java.util.Queue elementsToCheckpoint = new ArrayDeque<>(); + private final Queue safeToAckMessages = new ConcurrentLinkedQueue<>(); + + /** + * Queue for messages that were ingested in the {@link #advance()} method, but not sent yet to a + * {@link SolaceCheckpointMark}. + */ + private final Queue receivedMessages = new ArrayDeque<>(); + + private static final Cache sessionServiceCache; + private static final ScheduledExecutorService cleanUpThread = Executors.newScheduledThreadPool(1); + + static { + Duration cacheExpirationTimeout = Duration.ofMinutes(1); + sessionServiceCache = + CacheBuilder.newBuilder() + .expireAfterAccess(cacheExpirationTimeout) + .removalListener( + (RemovalNotification notification) -> { + LOG.info( + "SolaceIO.Read: Closing session for the reader with uuid {} as it has been idle for over {}.", + notification.getKey(), + cacheExpirationTimeout); + SessionService sessionService = notification.getValue(); + if (sessionService != null) { + sessionService.close(); + } + }) + .build(); + + startCleanUpThread(); + } + + @SuppressWarnings("FutureReturnValueIgnored") + private static void startCleanUpThread() { + cleanUpThread.scheduleAtFixedRate(sessionServiceCache::cleanUp, 1, 1, TimeUnit.MINUTES); + } public UnboundedSolaceReader(UnboundedSolaceSource currentSource) { this.currentSource = currentSource; this.watermarkPolicy = WatermarkPolicy.create( currentSource.getTimestampFn(), currentSource.getWatermarkIdleDurationThreshold()); - this.sessionService = currentSource.getSessionServiceFactory().create(); + this.sessionServiceFactory = currentSource.getSessionServiceFactory(); this.sempClient = currentSource.getSempClientFactory().create(); + this.readerUuid = UUID.randomUUID(); + } + + private SessionService getSessionService() { + try { + return sessionServiceCache.get( + readerUuid, + () -> { + LOG.info("SolaceIO.Read: creating a new session for reader with uuid {}.", readerUuid); + SessionService sessionService = sessionServiceFactory.create(); + sessionService.connect(); + sessionService.getReceiver().start(); + return sessionService; + }); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } } @Override public boolean start() { - populateSession(); - checkNotNull(sessionService).getReceiver().start(); + // Create and initialize SessionService with Receiver + getSessionService(); return advance(); } - public void populateSession() { - if (sessionService == null) { - sessionService = getCurrentSource().getSessionServiceFactory().create(); - } - if (sessionService.isClosed()) { - checkNotNull(sessionService).connect(); - } - } - @Override public boolean advance() { + finalizeReadyMessages(); + BytesXMLMessage receivedXmlMessage; try { - receivedXmlMessage = checkNotNull(sessionService).getReceiver().receive(); + receivedXmlMessage = getSessionService().getReceiver().receive(); } catch (IOException e) { LOG.warn("SolaceIO.Read: Exception when pulling messages from the broker.", e); return false; @@ -96,23 +149,40 @@ public boolean advance() { if (receivedXmlMessage == null) { return false; } - elementsToCheckpoint.add(receivedXmlMessage); solaceOriginalRecord = receivedXmlMessage; solaceMappedRecord = getCurrentSource().getParseFn().apply(receivedXmlMessage); - watermarkPolicy.update(solaceMappedRecord); + receivedMessages.add(receivedXmlMessage); + return true; } @Override public void close() { - active.set(false); - checkNotNull(sessionService).close(); + finalizeReadyMessages(); + sessionServiceCache.invalidate(readerUuid); + } + + public void finalizeReadyMessages() { + BytesXMLMessage msg; + while ((msg = safeToAckMessages.poll()) != null) { + try { + msg.ackMessage(); + } catch (IllegalStateException e) { + LOG.error( + "SolaceIO.Read: failed to acknowledge the message with applicationMessageId={}, ackMessageId={}. Returning the message to queue to retry.", + msg.getApplicationMessageId(), + msg.getAckMessageId(), + e); + safeToAckMessages.add(msg); // In case the error was transient, might succeed later + break; // Commit is only best effort + } + } } @Override public Instant getWatermark() { // should be only used by a test receiver - if (checkNotNull(sessionService).getReceiver().isEOF()) { + if (getSessionService().getReceiver().isEOF()) { return BoundedWindow.TIMESTAMP_MAX_VALUE; } return watermarkPolicy.getWatermark(); @@ -120,14 +190,9 @@ public Instant getWatermark() { @Override public UnboundedSource.CheckpointMark getCheckpointMark() { - List ackQueue = new ArrayList<>(); - while (!elementsToCheckpoint.isEmpty()) { - BytesXMLMessage msg = elementsToCheckpoint.poll(); - if (msg != null) { - ackQueue.add(msg); - } - } - return new SolaceCheckpointMark(active, ackQueue); + safeToAckMessages.addAll(receivedMessages); + receivedMessages.clear(); + return new SolaceCheckpointMark(safeToAckMessages); } @Override diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java index 38b4953a5984..7631d32f63cc 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java @@ -40,11 +40,6 @@ public void close() { throw new UnsupportedOperationException(exceptionMessage); } - @Override - public boolean isClosed() { - throw new UnsupportedOperationException(exceptionMessage); - } - @Override public MessageReceiver getReceiver() { throw new UnsupportedOperationException(exceptionMessage); diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java index bd52dee7ea86..6d28bcefc84c 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java @@ -77,11 +77,6 @@ public abstract Builder mockProducerFn( @Override public void close() {} - @Override - public boolean isClosed() { - return false; - } - @Override public MessageReceiver getReceiver() { if (messageReceiver == null) { @@ -131,11 +126,6 @@ public MockReceiver( @Override public void start() {} - @Override - public boolean isClosed() { - return false; - } - @Override public BytesXMLMessage receive() throws IOException { return getRecordFn.apply(counter.getAndIncrement()); diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java index c718c55e1b48..a1f80932eddf 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java @@ -447,25 +447,29 @@ public void testCheckpointMarkAndFinalizeSeparately() throws Exception { // start the reader and move to the first record assertTrue(reader.start()); - // consume 3 messages (NB: start already consumed the first message) + // consume 3 messages (NB: #start() already consumed the first message) for (int i = 0; i < 3; i++) { assertTrue(String.format("Failed at %d-th message", i), reader.advance()); } - // create checkpoint but don't finalize yet + // #advance() was called, but the messages were not ready to be acknowledged. + assertEquals(0, countAckMessages.get()); + + // mark all consumed messages as ready to be acknowledged CheckpointMark checkpointMark = reader.getCheckpointMark(); - // consume 2 more messages - reader.advance(); + // consume 1 more message. This will call #ackMsg() on messages that were ready to be acked. reader.advance(); + assertEquals(4, countAckMessages.get()); - // check if messages are still not acknowledged - assertEquals(0, countAckMessages.get()); + // consume 1 more message. No change in the acknowledged messages. + reader.advance(); + assertEquals(4, countAckMessages.get()); // acknowledge from the first checkpoint checkpointMark.finalizeCheckpoint(); - - // only messages from the first checkpoint are acknowledged + // No change in the acknowledged messages, because they were acknowledged in the #advance() + // method. assertEquals(4, countAckMessages.get()); } From 0ec76af32f4b3ebe3644d06e2634ba44ea4d3be5 Mon Sep 17 00:00:00 2001 From: Damon Date: Mon, 2 Dec 2024 07:55:39 -0800 Subject: [PATCH 045/135] Fix java validatesdistrolesscontainer dataflow workflow (#33245) * Set testJavaVersion in each run * Modify trigger file * Add dockerTag --- ...ommit_Java_ValidatesDistrolessContainer_Dataflow.json | 2 +- ...Commit_Java_ValidatesDistrolessContainer_Dataflow.yml | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json b/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json index e3d6056a5de9..b26833333238 100644 --- a/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json +++ b/.github/trigger_files/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 1 + "modification": 2 } diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml index 98b604be00e8..4fb236c7c991 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml @@ -88,10 +88,17 @@ jobs: gcloud auth configure-docker us.gcr.io --quiet gcloud auth configure-docker gcr.io --quiet gcloud auth configure-docker us-central1-docker.pkg.dev --quiet - - name: run validatesDistrolessContainer script + - name: run validatesDistrolessContainer script (Java 17) uses: ./.github/actions/gradle-command-self-hosted-action with: gradle-command: :runners:google-cloud-dataflow-java:examplesJavaRunnerV2IntegrationTestDistroless + arguments: '-PtestJavaVersion=java17 -PdockerTag=$(date +%s)' + max-workers: 12 + - name: run validatesDistrolessContainer script (Java 21) + uses: ./.github/actions/gradle-command-self-hosted-action + with: + gradle-command: :runners:google-cloud-dataflow-java:examplesJavaRunnerV2IntegrationTestDistroless + arguments: '-PtestJavaVersion=java21 -PdockerTag=$(date +%s)' max-workers: 12 - name: Archive JUnit Test Results uses: actions/upload-artifact@v4 From 2481d4bece762507a286203cb4dbcd065273a95b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:32:07 -0500 Subject: [PATCH 046/135] Bump golang.org/x/net from 0.30.0 to 0.31.0 in /sdks (#33111) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.30.0 to 0.31.0. - [Commits](https://github.com/golang/net/compare/v0.30.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 8 ++++---- sdks/go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 42f099d747bb..ed2c7402fd42 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -53,11 +53,11 @@ require ( github.com/xitongsys/parquet-go v1.6.2 github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c go.mongodb.org/mongo-driver v1.17.1 - golang.org/x/net v0.30.0 + golang.org/x/net v0.31.0 golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.9.0 golang.org/x/sys v0.27.0 - golang.org/x/text v0.19.0 + golang.org/x/text v0.20.0 google.golang.org/api v0.203.0 google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 google.golang.org/grpc v1.67.1 @@ -190,7 +190,7 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/sdks/go.sum b/sdks/go.sum index 5e42448c7d50..1e7713fa2b6a 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1264,8 +1264,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1386,8 +1386,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1435,8 +1435,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1534,8 +1534,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1552,8 +1552,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 2141dd7c1288898b92ecaf40129635a675295ea9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:32:49 -0500 Subject: [PATCH 047/135] Bump cloud.google.com/go/spanner from 1.70.0 to 1.73.0 in /sdks (#33126) Bumps [cloud.google.com/go/spanner](https://github.com/googleapis/google-cloud-go) from 1.70.0 to 1.73.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.70.0...spanner/v1.73.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/spanner dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 2 +- sdks/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index ed2c7402fd42..fc4c60006fe1 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -28,7 +28,7 @@ require ( cloud.google.com/go/datastore v1.20.0 cloud.google.com/go/profiler v0.4.1 cloud.google.com/go/pubsub v1.45.1 - cloud.google.com/go/spanner v1.70.0 + cloud.google.com/go/spanner v1.73.0 cloud.google.com/go/storage v1.45.0 github.com/aws/aws-sdk-go-v2 v1.32.4 github.com/aws/aws-sdk-go-v2/config v1.28.4 diff --git a/sdks/go.sum b/sdks/go.sum index 1e7713fa2b6a..bbc0d0d22204 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -542,8 +542,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+ cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/spanner v1.70.0 h1:nj6p/GJTgMDiSQ1gQ034ItsKuJgHiMOjtOlONOg8PSo= -cloud.google.com/go/spanner v1.70.0/go.mod h1:X5T0XftydYp0K1adeJQDJtdWpbrOeJ7wHecM4tK6FiE= +cloud.google.com/go/spanner v1.73.0 h1:0bab8QDn6MNj9lNK6XyGAVFhMlhMU2waePPa6GZNoi8= +cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= From 7e25649b88b1ac08e48db28f8af09978b649da17 Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Mon, 2 Dec 2024 11:49:50 -0500 Subject: [PATCH 048/135] Revert "bump hadoop version (#33011)" (#33257) This reverts commit 95322c69a18254a5e08f5c9259861450752b04cd. --- CHANGES.md | 1 - .../main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8dd1e89aec91..c120906fdd0d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -63,7 +63,6 @@ ## I/Os * Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). -* Upgraded the default version of Hadoop dependencies to 3.4.1. Hadoop 2.10.2 is still supported (Java) ([#33011](https://github.com/apache/beam/issues/33011)). ## New Features / Improvements diff --git a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy index 2abd43a5d4cc..84c7c3ecfd4a 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -614,7 +614,7 @@ class BeamModulePlugin implements Plugin { // [bomupgrader] determined by: io.grpc:grpc-netty, consistent with: google_cloud_platform_libraries_bom def grpc_version = "1.67.1" def guava_version = "33.1.0-jre" - def hadoop_version = "3.4.1" + def hadoop_version = "2.10.2" def hamcrest_version = "2.1" def influxdb_version = "2.19" def httpclient_version = "4.5.13" From 4356253a5e8124bf39152a9ead9ad26ef7267750 Mon Sep 17 00:00:00 2001 From: Bartosz Zablocki Date: Mon, 2 Dec 2024 18:18:42 +0000 Subject: [PATCH 049/135] Revert "SolaceIO.Read: handle occasional cases when finalizeCheckpoint is not executed (#32962)" (#33259) This reverts commit e279e55f47683bcee1c59c0b7aea2b21741fadb3. --- .../broker/BasicAuthJcsmpSessionService.java | 18 ++- .../sdk/io/solace/broker/MessageReceiver.java | 7 + .../sdk/io/solace/broker/SessionService.java | 7 + .../solace/broker/SolaceMessageReceiver.java | 17 ++- .../io/solace/read/SolaceCheckpointMark.java | 46 +++--- .../io/solace/read/UnboundedSolaceReader.java | 135 +++++------------- .../io/solace/MockEmptySessionService.java | 5 + .../sdk/io/solace/MockSessionService.java | 10 ++ .../beam/sdk/io/solace/SolaceIOReadTest.java | 20 ++- 9 files changed, 127 insertions(+), 138 deletions(-) diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java index d4c9a3ec6210..b2196dbf1067 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java @@ -102,7 +102,10 @@ public void close() { if (messageReceiver != null) { messageReceiver.close(); } - if (jcsmpSession != null) { + if (messageProducer != null) { + messageProducer.close(); + } + if (!isClosed()) { checkStateNotNull(jcsmpSession).closeSession(); } return 0; @@ -116,9 +119,8 @@ public MessageReceiver getReceiver() { this.messageReceiver = retryCallableManager.retryCallable( this::createFlowReceiver, ImmutableSet.of(JCSMPException.class)); - this.messageReceiver.start(); } - return checkStateNotNull(this.messageReceiver); + return this.messageReceiver; } @Override @@ -136,10 +138,15 @@ public java.util.Queue getPublishedResultsQueue() { return publishedResultsQueue; } + @Override + public boolean isClosed() { + return jcsmpSession == null || jcsmpSession.isClosed(); + } + private MessageProducer createXMLMessageProducer(SubmissionMode submissionMode) throws JCSMPException, IOException { - if (jcsmpSession == null) { + if (isClosed()) { connectWriteSession(submissionMode); } @@ -158,6 +165,9 @@ private MessageProducer createXMLMessageProducer(SubmissionMode submissionMode) } private MessageReceiver createFlowReceiver() throws JCSMPException, IOException { + if (isClosed()) { + connectSession(); + } Queue queue = JCSMPFactory.onlyInstance() diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java index 017a63260678..95f989bd1be9 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java @@ -35,6 +35,13 @@ public interface MessageReceiver { */ void start(); + /** + * Returns {@literal true} if the message receiver is closed, {@literal false} otherwise. + * + *

These utilities are based on the Avro - * 1.8.1 specification. - */ +/** A set of utilities for working with Avro files. */ class BigQueryAvroUtils { + private static final String VERSION_AVRO = + Optional.ofNullable(Schema.class.getPackage()) + .map(Package::getImplementationVersion) + .orElse(""); + // org.apache.avro.LogicalType static class DateTimeLogicalType extends LogicalType { public DateTimeLogicalType() { @@ -74,6 +76,8 @@ public DateTimeLogicalType() { * export * @see BQ * avro storage + * @see BQ avro + * load */ static Schema getPrimitiveType(TableFieldSchema schema, Boolean useAvroLogicalTypes) { String bqType = schema.getType(); @@ -116,6 +120,9 @@ static Schema getPrimitiveType(TableFieldSchema schema, Boolean useAvroLogicalTy } case "DATETIME": if (useAvroLogicalTypes) { + // BQ export uses a custom logical type + // TODO for load/storage use + // LogicalTypes.date().addToSchema(SchemaBuilder.builder().intType()) return DATETIME_LOGICAL_TYPE.addToSchema(SchemaBuilder.builder().stringType()); } else { return SchemaBuilder.builder().stringBuilder().prop("sqlType", bqType).endString(); @@ -158,6 +165,12 @@ static Schema getPrimitiveType(TableFieldSchema schema, Boolean useAvroLogicalTy @VisibleForTesting static String formatTimestamp(Long timestampMicro) { + String dateTime = formatDatetime(timestampMicro); + return dateTime + " UTC"; + } + + @VisibleForTesting + static String formatDatetime(Long timestampMicro) { // timestampMicro is in "microseconds since epoch" format, // e.g., 1452062291123456L means "2016-01-06 06:38:11.123456 UTC". // Separate into seconds and microseconds. @@ -168,11 +181,13 @@ static String formatTimestamp(Long timestampMicro) { timestampSec -= 1; } String dayAndTime = DATE_AND_SECONDS_FORMATTER.print(timestampSec * 1000); - if (micros == 0) { - return String.format("%s UTC", dayAndTime); + return dayAndTime; + } else if (micros % 1000 == 0) { + return String.format("%s.%03d", dayAndTime, micros / 1000); + } else { + return String.format("%s.%06d", dayAndTime, micros); } - return String.format("%s.%06d UTC", dayAndTime, micros); } /** @@ -274,8 +289,7 @@ static TableRow convertGenericRecordToTableRow(GenericRecord record) { case UNION: return convertNullableField(name, schema, v); case MAP: - throw new UnsupportedOperationException( - String.format("Unexpected Avro field schema type %s for field named %s", type, name)); + return convertMapField(name, schema, v); default: return convertRequiredField(name, schema, v); } @@ -296,6 +310,26 @@ private static List convertRepeatedField(String name, Schema elementType return values; } + private static List convertMapField(String name, Schema map, Object v) { + // Avro maps are represented as key/value RECORD. + if (v == null) { + // Handle the case of an empty map. + return new ArrayList<>(); + } + + Schema type = map.getValueType(); + Map elements = (Map) v; + ArrayList values = new ArrayList<>(); + for (Map.Entry element : elements.entrySet()) { + TableRow row = + new TableRow() + .set("key", element.getKey()) + .set("value", convertRequiredField(name, type, element.getValue())); + values.add(row); + } + return values; + } + private static Object convertRequiredField(String name, Schema schema, Object v) { // REQUIRED fields are represented as the corresponding Avro types. For example, a BigQuery // INTEGER type maps to an Avro LONG type. @@ -305,45 +339,83 @@ private static Object convertRequiredField(String name, Schema schema, Object v) LogicalType logicalType = schema.getLogicalType(); switch (type) { case BOOLEAN: - // SQL types BOOL, BOOLEAN + // SQL type BOOL (BOOLEAN) return v; case INT: if (logicalType instanceof LogicalTypes.Date) { - // SQL types DATE + // SQL type DATE + // ideally LocalDate but TableRowJsonCoder encodes as String return formatDate((Integer) v); + } else if (logicalType instanceof LogicalTypes.TimeMillis) { + // Write only: SQL type TIME + // ideally LocalTime but TableRowJsonCoder encodes as String + return formatTime(((Integer) v) * 1000L); } else { - throw new UnsupportedOperationException( - String.format("Unexpected Avro field schema type %s for field named %s", type, name)); + // Write only: SQL type INT64 (INT, SMALLINT, INTEGER, BIGINT, TINYINT, BYTEINT) + // ideally Integer but keep consistency with BQ JSON export that uses String + return ((Integer) v).toString(); } case LONG: if (logicalType instanceof LogicalTypes.TimeMicros) { - // SQL types TIME + // SQL type TIME + // ideally LocalTime but TableRowJsonCoder encodes as String return formatTime((Long) v); + } else if (logicalType instanceof LogicalTypes.TimestampMillis) { + // Write only: SQL type TIMESTAMP + // ideally Instant but TableRowJsonCoder encodes as String + return formatTimestamp((Long) v * 1000L); } else if (logicalType instanceof LogicalTypes.TimestampMicros) { - // SQL types TIMESTAMP + // SQL type TIMESTAMP + // ideally Instant but TableRowJsonCoder encodes as String return formatTimestamp((Long) v); + } else if (!(VERSION_AVRO.startsWith("1.8") || VERSION_AVRO.startsWith("1.9")) + && logicalType instanceof LogicalTypes.LocalTimestampMillis) { + // Write only: SQL type DATETIME + // ideally LocalDateTime but TableRowJsonCoder encodes as String + return formatDatetime(((Long) v) * 1000); + } else if (!(VERSION_AVRO.startsWith("1.8") || VERSION_AVRO.startsWith("1.9")) + && logicalType instanceof LogicalTypes.LocalTimestampMicros) { + // Write only: SQL type DATETIME + // ideally LocalDateTime but TableRowJsonCoder encodes as String + return formatDatetime((Long) v); } else { - // SQL types INT64 (INT, SMALLINT, INTEGER, BIGINT, TINYINT, BYTEINT) + // SQL type INT64 (INT, SMALLINT, INTEGER, BIGINT, TINYINT, BYTEINT) + // ideally Long if in [2^53+1, 2^53-1] but keep consistency with BQ JSON export that uses + // String return ((Long) v).toString(); } + case FLOAT: + // Write only: SQL type FLOAT64 + // ideally Float but TableRowJsonCoder decodes as Double + return Double.valueOf(v.toString()); case DOUBLE: - // SQL types FLOAT64 + // SQL type FLOAT64 return v; case BYTES: if (logicalType instanceof LogicalTypes.Decimal) { // SQL tpe NUMERIC, BIGNUMERIC + // ideally BigDecimal but TableRowJsonCoder encodes as String return new Conversions.DecimalConversion() .fromBytes((ByteBuffer) v, schema, logicalType) .toString(); } else { - // SQL types BYTES + // SQL type BYTES + // ideally byte[] but TableRowJsonCoder encodes as String return BaseEncoding.base64().encode(((ByteBuffer) v).array()); } case STRING: // SQL types STRING, DATETIME, GEOGRAPHY, JSON // when not using logical type DATE, TIME too return v.toString(); + case ENUM: + // SQL types STRING + return v.toString(); + case FIXED: + // SQL type BYTES + // ideally byte[] but TableRowJsonCoder encodes as String + return BaseEncoding.base64().encode(((ByteBuffer) v).array()); case RECORD: + // SQL types RECORD return convertGenericRecordToTableRow((GenericRecord) v); default: throw new UnsupportedOperationException( diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtilsTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtilsTest.java index 662f2658eb6b..2333278a11f5 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtilsTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtilsTest.java @@ -28,23 +28,23 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; import org.apache.avro.Conversions; import org.apache.avro.LogicalType; import org.apache.avro.LogicalTypes; import org.apache.avro.Schema; -import org.apache.avro.Schema.Field; -import org.apache.avro.Schema.Type; +import org.apache.avro.SchemaBuilder; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericRecord; -import org.apache.avro.reflect.AvroSchema; -import org.apache.avro.reflect.Nullable; -import org.apache.avro.reflect.ReflectData; +import org.apache.avro.generic.GenericRecordBuilder; import org.apache.avro.util.Utf8; -import org.apache.beam.sdk.extensions.avro.coders.AvroCoder; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Lists; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.io.BaseEncoding; import org.junit.Test; @@ -54,363 +54,678 @@ /** Tests for {@link BigQueryAvroUtils}. */ @RunWith(JUnit4.class) public class BigQueryAvroUtilsTest { - private List subFields = - Lists.newArrayList( - new TableFieldSchema().setName("species").setType("STRING").setMode("NULLABLE")); - /* - * Note that the quality and quantity fields do not have their mode set, so they should default - * to NULLABLE. This is an important test of BigQuery semantics. - * - * All the other fields we set in this function are required on the Schema response. - * - * See https://cloud.google.com/bigquery/docs/reference/v2/tables#schema - */ - private List fields = - Lists.newArrayList( - new TableFieldSchema().setName("number").setType("INTEGER").setMode("REQUIRED"), - new TableFieldSchema().setName("species").setType("STRING").setMode("NULLABLE"), - new TableFieldSchema().setName("quality").setType("FLOAT") /* default to NULLABLE */, - new TableFieldSchema().setName("quantity").setType("INTEGER") /* default to NULLABLE */, - new TableFieldSchema().setName("birthday").setType("TIMESTAMP").setMode("NULLABLE"), - new TableFieldSchema().setName("birthdayMoney").setType("NUMERIC").setMode("NULLABLE"), - new TableFieldSchema() - .setName("lotteryWinnings") - .setType("BIGNUMERIC") - .setMode("NULLABLE"), - new TableFieldSchema().setName("flighted").setType("BOOLEAN").setMode("NULLABLE"), - new TableFieldSchema().setName("sound").setType("BYTES").setMode("NULLABLE"), - new TableFieldSchema().setName("anniversaryDate").setType("DATE").setMode("NULLABLE"), - new TableFieldSchema() - .setName("anniversaryDatetime") - .setType("DATETIME") - .setMode("NULLABLE"), - new TableFieldSchema().setName("anniversaryTime").setType("TIME").setMode("NULLABLE"), - new TableFieldSchema() - .setName("scion") - .setType("RECORD") - .setMode("NULLABLE") - .setFields(subFields), - new TableFieldSchema() - .setName("associates") - .setType("RECORD") - .setMode("REPEATED") - .setFields(subFields), - new TableFieldSchema().setName("geoPositions").setType("GEOGRAPHY").setMode("NULLABLE")); - - private ByteBuffer convertToBytes(BigDecimal bigDecimal, int precision, int scale) { - LogicalType bigDecimalLogicalType = LogicalTypes.decimal(precision, scale); - return new Conversions.DecimalConversion().toBytes(bigDecimal, null, bigDecimalLogicalType); + + private TableSchema tableSchema(Function fn) { + TableFieldSchema column = new TableFieldSchema().setName("value"); + TableSchema tableSchema = new TableSchema(); + tableSchema.setFields(Lists.newArrayList(fn.apply(column))); + return tableSchema; + } + + private Schema avroSchema( + Function, SchemaBuilder.FieldAssembler> fn) { + return fn.apply( + SchemaBuilder.record("root") + .namespace("org.apache.beam.sdk.io.gcp.bigquery") + .doc("Translated Avro Schema for root") + .fields() + .name("value")) + .endRecord(); } + @SuppressWarnings("JavaInstantGetSecondsGetNano") @Test - public void testConvertGenericRecordToTableRow() throws Exception { - BigDecimal numeric = new BigDecimal("123456789.123456789"); - ByteBuffer numericBytes = convertToBytes(numeric, 38, 9); - BigDecimal bigNumeric = - new BigDecimal( - "578960446186580977117854925043439539266.34992332820282019728792003956564819967"); - ByteBuffer bigNumericBytes = convertToBytes(bigNumeric, 77, 38); - Schema avroSchema = ReflectData.get().getSchema(Bird.class); - - { - // Test nullable fields. - GenericRecord record = new GenericData.Record(avroSchema); - record.put("number", 5L); - TableRow convertedRow = BigQueryAvroUtils.convertGenericRecordToTableRow(record); - TableRow row = new TableRow().set("number", "5").set("associates", new ArrayList()); - assertEquals(row, convertedRow); - TableRow clonedRow = convertedRow.clone(); - assertEquals(convertedRow, clonedRow); - } - { - // Test type conversion for: - // INTEGER, FLOAT, NUMERIC, TIMESTAMP, BOOLEAN, BYTES, DATE, DATETIME, TIME. - GenericRecord record = new GenericData.Record(avroSchema); - byte[] soundBytes = "chirp,chirp".getBytes(StandardCharsets.UTF_8); - ByteBuffer soundByteBuffer = ByteBuffer.wrap(soundBytes); - soundByteBuffer.rewind(); - record.put("number", 5L); - record.put("quality", 5.0); - record.put("birthday", 5L); - record.put("birthdayMoney", numericBytes); - record.put("lotteryWinnings", bigNumericBytes); - record.put("flighted", Boolean.TRUE); - record.put("sound", soundByteBuffer); - record.put("anniversaryDate", new Utf8("2000-01-01")); - record.put("anniversaryDatetime", new String("2000-01-01 00:00:00.000005")); - record.put("anniversaryTime", new Utf8("00:00:00.000005")); - record.put("geoPositions", new String("LINESTRING(1 2, 3 4, 5 6, 7 8)")); - TableRow convertedRow = BigQueryAvroUtils.convertGenericRecordToTableRow(record); - TableRow row = - new TableRow() - .set("number", "5") - .set("birthday", "1970-01-01 00:00:00.000005 UTC") - .set("birthdayMoney", numeric.toString()) - .set("lotteryWinnings", bigNumeric.toString()) - .set("quality", 5.0) - .set("associates", new ArrayList()) - .set("flighted", Boolean.TRUE) - .set("sound", BaseEncoding.base64().encode(soundBytes)) - .set("anniversaryDate", "2000-01-01") - .set("anniversaryDatetime", "2000-01-01 00:00:00.000005") - .set("anniversaryTime", "00:00:00.000005") - .set("geoPositions", "LINESTRING(1 2, 3 4, 5 6, 7 8)"); - TableRow clonedRow = convertedRow.clone(); - assertEquals(convertedRow, clonedRow); - assertEquals(row, convertedRow); - } - { - // Test repeated fields. - Schema subBirdSchema = AvroCoder.of(Bird.SubBird.class).getSchema(); - GenericRecord nestedRecord = new GenericData.Record(subBirdSchema); - nestedRecord.put("species", "other"); - GenericRecord record = new GenericData.Record(avroSchema); - record.put("number", 5L); - record.put("associates", Lists.newArrayList(nestedRecord)); - record.put("birthdayMoney", numericBytes); - record.put("lotteryWinnings", bigNumericBytes); - TableRow convertedRow = BigQueryAvroUtils.convertGenericRecordToTableRow(record); - TableRow row = + public void testConvertGenericRecordToTableRow() { + { + // bool + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().booleanType().noDefault())) + .set("value", false) + .build(); + TableRow expected = new TableRow().set("value", false); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // int + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().intType().noDefault())) + .set("value", 5) + .build(); + TableRow expected = new TableRow().set("value", "5"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // long + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().longType().noDefault())) + .set("value", 5L) + .build(); + TableRow expected = new TableRow().set("value", "5"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // float + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().floatType().noDefault())) + .set("value", 5.5f) + .build(); + TableRow expected = new TableRow().set("value", 5.5); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // double + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().doubleType().noDefault())) + .set("value", 5.55) + .build(); + TableRow expected = new TableRow().set("value", 5.55); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // bytes + byte[] bytes = "chirp,chirp".getBytes(StandardCharsets.UTF_8); + ByteBuffer bb = ByteBuffer.wrap(bytes); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().bytesType().noDefault())) + .set("value", bb) + .build(); + TableRow expected = new TableRow().set("value", BaseEncoding.base64().encode(bytes)); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // string + Schema schema = avroSchema(f -> f.type().stringType().noDefault()); + GenericRecord record = new GenericRecordBuilder(schema).set("value", "test").build(); + GenericRecord utf8Record = + new GenericRecordBuilder(schema).set("value", new Utf8("test")).build(); + TableRow expected = new TableRow().set("value", "test"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + TableRow utf8Row = BigQueryAvroUtils.convertGenericRecordToTableRow(utf8Record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + assertEquals(expected, utf8Row); + assertEquals(expected, utf8Row.clone()); + } + + { + // decimal + LogicalType lt = LogicalTypes.decimal(38, 9); + Schema decimalType = lt.addToSchema(SchemaBuilder.builder().bytesType()); + BigDecimal bd = new BigDecimal("123456789.123456789"); + ByteBuffer bytes = new Conversions.DecimalConversion().toBytes(bd, null, lt); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(decimalType).noDefault())) + .set("value", bytes) + .build(); + TableRow expected = new TableRow().set("value", bd.toString()); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // date + LogicalType lt = LogicalTypes.date(); + Schema dateType = lt.addToSchema(SchemaBuilder.builder().intType()); + LocalDate date = LocalDate.of(2000, 1, 1); + int days = (int) date.toEpochDay(); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(dateType).noDefault())) + .set("value", days) + .build(); + TableRow expected = new TableRow().set("value", "2000-01-01"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // time-millis + LogicalType lt = LogicalTypes.timeMillis(); + Schema timeType = lt.addToSchema(SchemaBuilder.builder().intType()); + LocalTime time = LocalTime.of(1, 2, 3, 123456789); + int millis = (int) (time.toNanoOfDay() / 1000000); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(timeType).noDefault())) + .set("value", millis) + .build(); + TableRow expected = new TableRow().set("value", "01:02:03.123"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // time-micros + LogicalType lt = LogicalTypes.timeMicros(); + Schema timeType = lt.addToSchema(SchemaBuilder.builder().longType()); + LocalTime time = LocalTime.of(1, 2, 3, 123456789); + long micros = time.toNanoOfDay() / 1000; + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(timeType).noDefault())) + .set("value", micros) + .build(); + TableRow expected = new TableRow().set("value", "01:02:03.123456"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // local-timestamp-millis + LogicalType lt = LogicalTypes.localTimestampMillis(); + Schema timestampType = lt.addToSchema(SchemaBuilder.builder().longType()); + LocalDate date = LocalDate.of(2000, 1, 1); + LocalTime time = LocalTime.of(1, 2, 3, 123456789); + LocalDateTime ts = LocalDateTime.of(date, time); + long millis = ts.toInstant(ZoneOffset.UTC).toEpochMilli(); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(timestampType).noDefault())) + .set("value", millis) + .build(); + TableRow expected = new TableRow().set("value", "2000-01-01 01:02:03.123"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // local-timestamp-micros + LogicalType lt = LogicalTypes.localTimestampMicros(); + Schema timestampType = lt.addToSchema(SchemaBuilder.builder().longType()); + LocalDate date = LocalDate.of(2000, 1, 1); + LocalTime time = LocalTime.of(1, 2, 3, 123456789); + LocalDateTime ts = LocalDateTime.of(date, time); + long seconds = ts.toInstant(ZoneOffset.UTC).getEpochSecond(); + int nanos = ts.toInstant(ZoneOffset.UTC).getNano(); + long micros = seconds * 1000000 + (nanos / 1000); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(timestampType).noDefault())) + .set("value", micros) + .build(); + TableRow expected = new TableRow().set("value", "2000-01-01 01:02:03.123456"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // timestamp-micros + LogicalType lt = LogicalTypes.timestampMillis(); + Schema timestampType = lt.addToSchema(SchemaBuilder.builder().longType()); + LocalDate date = LocalDate.of(2000, 1, 1); + LocalTime time = LocalTime.of(1, 2, 3, 123456789); + LocalDateTime ts = LocalDateTime.of(date, time); + long millis = ts.toInstant(ZoneOffset.UTC).toEpochMilli(); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(timestampType).noDefault())) + .set("value", millis) + .build(); + TableRow expected = new TableRow().set("value", "2000-01-01 01:02:03.123 UTC"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // timestamp-millis + LogicalType lt = LogicalTypes.timestampMicros(); + Schema timestampType = lt.addToSchema(SchemaBuilder.builder().longType()); + LocalDate date = LocalDate.of(2000, 1, 1); + LocalTime time = LocalTime.of(1, 2, 3, 123456789); + LocalDateTime ts = LocalDateTime.of(date, time); + long seconds = ts.toInstant(ZoneOffset.UTC).getEpochSecond(); + int nanos = ts.toInstant(ZoneOffset.UTC).getNano(); + long micros = seconds * 1000000 + (nanos / 1000); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(timestampType).noDefault())) + .set("value", micros) + .build(); + TableRow expected = new TableRow().set("value", "2000-01-01 01:02:03.123456 UTC"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // enum + Schema enumSchema = SchemaBuilder.enumeration("color").symbols("red", "green", "blue"); + GenericData.EnumSymbol symbol = new GenericData.EnumSymbol(enumSchema, "RED"); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(enumSchema).noDefault())) + .set("value", symbol) + .build(); + TableRow expected = new TableRow().set("value", "RED"); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // fixed + UUID uuid = UUID.randomUUID(); + ByteBuffer bb = ByteBuffer.allocate(16); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + bb.rewind(); + byte[] bytes = bb.array(); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().fixed("uuid").size(16).noDefault())) + .set("value", bb) + .build(); + TableRow expected = new TableRow().set("value", BaseEncoding.base64().encode(bytes)); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // null + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().optional().booleanType())).build(); + TableRow expected = new TableRow(); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // array + GenericRecord record = + new GenericRecordBuilder( + avroSchema(f -> f.type().array().items().booleanType().noDefault())) + .set("value", Lists.newArrayList(true, false)) + .build(); + TableRow expected = new TableRow().set("value", Lists.newArrayList(true, false)); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // map + Map map = new HashMap<>(); + map.put("left", 1); + map.put("right", -1); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type().map().values().intType().noDefault())) + .set("value", map) + .build(); + TableRow expected = new TableRow() - .set("associates", Lists.newArrayList(new TableRow().set("species", "other"))) - .set("number", "5") - .set("birthdayMoney", numeric.toString()) - .set("lotteryWinnings", bigNumeric.toString()); - assertEquals(row, convertedRow); - TableRow clonedRow = convertedRow.clone(); - assertEquals(convertedRow, clonedRow); + .set( + "value", + Lists.newArrayList( + new TableRow().set("key", "left").set("value", "1"), + new TableRow().set("key", "right").set("value", "-1"))); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); + } + + { + // record + Schema subSchema = + SchemaBuilder.builder() + .record("record") + .fields() + .name("int") + .type() + .intType() + .noDefault() + .name("float") + .type() + .floatType() + .noDefault() + .endRecord(); + GenericRecord subRecord = + new GenericRecordBuilder(subSchema).set("int", 5).set("float", 5.5f).build(); + GenericRecord record = + new GenericRecordBuilder(avroSchema(f -> f.type(subSchema).noDefault())) + .set("value", subRecord) + .build(); + TableRow expected = + new TableRow().set("value", new TableRow().set("int", "5").set("float", 5.5)); + TableRow row = BigQueryAvroUtils.convertGenericRecordToTableRow(record); + + assertEquals(expected, row); + assertEquals(expected, row.clone()); } } @Test public void testConvertBigQuerySchemaToAvroSchema() { - TableSchema tableSchema = new TableSchema(); - tableSchema.setFields(fields); - Schema avroSchema = BigQueryAvroUtils.toGenericAvroSchema(tableSchema); + { + // REQUIRED + TableSchema tableSchema = tableSchema(f -> f.setType("BOOLEAN").setMode("REQUIRED")); + Schema expected = avroSchema(f -> f.type().booleanType().noDefault()); - assertThat(avroSchema.getField("number").schema(), equalTo(Schema.create(Type.LONG))); - assertThat( - avroSchema.getField("species").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), Schema.create(Type.STRING)))); - assertThat( - avroSchema.getField("quality").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), Schema.create(Type.DOUBLE)))); - assertThat( - avroSchema.getField("quantity").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), Schema.create(Type.LONG)))); - assertThat( - avroSchema.getField("birthday").schema(), - equalTo( - Schema.createUnion( - Schema.create(Type.NULL), - LogicalTypes.timestampMicros().addToSchema(Schema.create(Type.LONG))))); - assertThat( - avroSchema.getField("birthdayMoney").schema(), - equalTo( - Schema.createUnion( - Schema.create(Type.NULL), - LogicalTypes.decimal(38, 9).addToSchema(Schema.create(Type.BYTES))))); - assertThat( - avroSchema.getField("lotteryWinnings").schema(), - equalTo( - Schema.createUnion( - Schema.create(Type.NULL), - LogicalTypes.decimal(77, 38).addToSchema(Schema.create(Type.BYTES))))); - assertThat( - avroSchema.getField("flighted").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), Schema.create(Type.BOOLEAN)))); - assertThat( - avroSchema.getField("sound").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), Schema.create(Type.BYTES)))); - Schema dateSchema = Schema.create(Type.INT); - LogicalTypes.date().addToSchema(dateSchema); - assertThat( - avroSchema.getField("anniversaryDate").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), dateSchema))); - Schema dateTimeSchema = Schema.create(Type.STRING); - BigQueryAvroUtils.DATETIME_LOGICAL_TYPE.addToSchema(dateTimeSchema); - assertThat( - avroSchema.getField("anniversaryDatetime").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), dateTimeSchema))); - Schema timeSchema = Schema.create(Type.LONG); - LogicalTypes.timeMicros().addToSchema(timeSchema); - assertThat( - avroSchema.getField("anniversaryTime").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), timeSchema))); - Schema geoSchema = Schema.create(Type.STRING); - geoSchema.addProp("sqlType", "GEOGRAPHY"); - assertThat( - avroSchema.getField("geoPositions").schema(), - equalTo(Schema.createUnion(Schema.create(Type.NULL), geoSchema))); - assertThat( - avroSchema.getField("scion").schema(), - equalTo( - Schema.createUnion( - Schema.create(Type.NULL), - Schema.createRecord( - "scion", - "Translated Avro Schema for scion", - "org.apache.beam.sdk.io.gcp.bigquery", - false, - ImmutableList.of( - new Field( - "species", - Schema.createUnion( - Schema.create(Type.NULL), Schema.create(Type.STRING)), - null, - (Object) null)))))); - assertThat( - avroSchema.getField("associates").schema(), - equalTo( - Schema.createArray( - Schema.createRecord( - "associates", - "Translated Avro Schema for associates", - "org.apache.beam.sdk.io.gcp.bigquery", - false, - ImmutableList.of( - new Field( - "species", - Schema.createUnion( - Schema.create(Type.NULL), Schema.create(Type.STRING)), - null, - (Object) null)))))); - } + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + } - @Test - public void testConvertBigQuerySchemaToAvroSchemaWithoutLogicalTypes() { - TableSchema tableSchema = new TableSchema(); - tableSchema.setFields(fields); - Schema avroSchema = BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false); + { + // NULLABLE + TableSchema tableSchema = tableSchema(f -> f.setType("BOOLEAN").setMode("NULLABLE")); + Schema expected = + avroSchema(f -> f.type().unionOf().nullType().and().booleanType().endUnion().noDefault()); - assertThat(avroSchema.getField("number").schema(), equalTo(Schema.create(Schema.Type.LONG))); - assertThat( - avroSchema.getField("species").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.STRING)))); - assertThat( - avroSchema.getField("quality").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.DOUBLE)))); - assertThat( - avroSchema.getField("quantity").schema(), - equalTo( - Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.LONG)))); - assertThat( - avroSchema.getField("birthday").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), - LogicalTypes.timestampMicros().addToSchema(Schema.create(Schema.Type.LONG))))); - assertThat( - avroSchema.getField("birthdayMoney").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), - LogicalTypes.decimal(38, 9).addToSchema(Schema.create(Schema.Type.BYTES))))); - assertThat( - avroSchema.getField("lotteryWinnings").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), - LogicalTypes.decimal(77, 38).addToSchema(Schema.create(Schema.Type.BYTES))))); - assertThat( - avroSchema.getField("flighted").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.BOOLEAN)))); - assertThat( - avroSchema.getField("sound").schema(), - equalTo( - Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.BYTES)))); - Schema dateSchema = Schema.create(Schema.Type.STRING); - dateSchema.addProp("sqlType", "DATE"); - assertThat( - avroSchema.getField("anniversaryDate").schema(), - equalTo(Schema.createUnion(Schema.create(Schema.Type.NULL), dateSchema))); - Schema dateTimeSchema = Schema.create(Schema.Type.STRING); - dateTimeSchema.addProp("sqlType", "DATETIME"); - assertThat( - avroSchema.getField("anniversaryDatetime").schema(), - equalTo(Schema.createUnion(Schema.create(Schema.Type.NULL), dateTimeSchema))); - Schema timeSchema = Schema.create(Schema.Type.STRING); - timeSchema.addProp("sqlType", "TIME"); - assertThat( - avroSchema.getField("anniversaryTime").schema(), - equalTo(Schema.createUnion(Schema.create(Schema.Type.NULL), timeSchema))); - Schema geoSchema = Schema.create(Type.STRING); - geoSchema.addProp("sqlType", "GEOGRAPHY"); - assertThat( - avroSchema.getField("geoPositions").schema(), - equalTo(Schema.createUnion(Schema.create(Schema.Type.NULL), geoSchema))); - assertThat( - avroSchema.getField("scion").schema(), - equalTo( - Schema.createUnion( - Schema.create(Schema.Type.NULL), - Schema.createRecord( - "scion", - "Translated Avro Schema for scion", - "org.apache.beam.sdk.io.gcp.bigquery", - false, - ImmutableList.of( - new Schema.Field( - "species", - Schema.createUnion( - Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.STRING)), - null, - (Object) null)))))); - assertThat( - avroSchema.getField("associates").schema(), - equalTo( - Schema.createArray( - Schema.createRecord( - "associates", - "Translated Avro Schema for associates", - "org.apache.beam.sdk.io.gcp.bigquery", - false, - ImmutableList.of( - new Schema.Field( - "species", - Schema.createUnion( - Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.STRING)), - null, - (Object) null)))))); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + } + + { + // default mode -> NULLABLE + TableSchema tableSchema = tableSchema(f -> f.setType("BOOLEAN")); + Schema expected = + avroSchema(f -> f.type().unionOf().nullType().and().booleanType().endUnion().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + } + + { + // REPEATED + TableSchema tableSchema = tableSchema(f -> f.setType("BOOLEAN").setMode("REPEATED")); + Schema expected = avroSchema(f -> f.type().array().items().booleanType().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + } + + { + // INTEGER + TableSchema tableSchema = tableSchema(f -> f.setType("INTEGER").setMode("REQUIRED")); + Schema expected = avroSchema(f -> f.type().longType().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + } + + { + // FLOAT + TableSchema tableSchema = tableSchema(f -> f.setType("FLOAT").setMode("REQUIRED")); + Schema expected = avroSchema(f -> f.type().doubleType().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // BYTES + TableSchema tableSchema = tableSchema(f -> f.setType("BYTES").setMode("REQUIRED")); + Schema expected = avroSchema(f -> f.type().bytesType().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // STRING + TableSchema tableSchema = tableSchema(f -> f.setType("STRING").setMode("REQUIRED")); + Schema expected = avroSchema(f -> f.type().stringType().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // NUMERIC + TableSchema tableSchema = tableSchema(f -> f.setType("NUMERIC").setMode("REQUIRED")); + Schema decimalType = + LogicalTypes.decimal(38, 9).addToSchema(SchemaBuilder.builder().bytesType()); + Schema expected = avroSchema(f -> f.type(decimalType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // NUMERIC with precision + TableSchema tableSchema = + tableSchema(f -> f.setType("NUMERIC").setPrecision(29L).setMode("REQUIRED")); + Schema decimalType = + LogicalTypes.decimal(29, 0).addToSchema(SchemaBuilder.builder().bytesType()); + Schema expected = avroSchema(f -> f.type(decimalType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // NUMERIC with precision and scale + TableSchema tableSchema = + tableSchema(f -> f.setType("NUMERIC").setPrecision(10L).setScale(9L).setMode("REQUIRED")); + Schema decimalType = + LogicalTypes.decimal(10, 9).addToSchema(SchemaBuilder.builder().bytesType()); + Schema expected = avroSchema(f -> f.type(decimalType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // BIGNUMERIC + TableSchema tableSchema = tableSchema(f -> f.setType("BIGNUMERIC").setMode("REQUIRED")); + Schema decimalType = + LogicalTypes.decimal(77, 38).addToSchema(SchemaBuilder.builder().bytesType()); + Schema expected = avroSchema(f -> f.type(decimalType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // BIGNUMERIC with precision + TableSchema tableSchema = + tableSchema(f -> f.setType("BIGNUMERIC").setPrecision(38L).setMode("REQUIRED")); + Schema decimalType = + LogicalTypes.decimal(38, 0).addToSchema(SchemaBuilder.builder().bytesType()); + Schema expected = avroSchema(f -> f.type(decimalType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // BIGNUMERIC with precision and scale + TableSchema tableSchema = + tableSchema( + f -> f.setType("BIGNUMERIC").setPrecision(39L).setScale(38L).setMode("REQUIRED")); + Schema decimalType = + LogicalTypes.decimal(39, 38).addToSchema(SchemaBuilder.builder().bytesType()); + Schema expected = avroSchema(f -> f.type(decimalType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // DATE + TableSchema tableSchema = tableSchema(f -> f.setType("DATE").setMode("REQUIRED")); + Schema dateType = LogicalTypes.date().addToSchema(SchemaBuilder.builder().intType()); + Schema expected = avroSchema(f -> f.type(dateType).noDefault()); + Schema expectedExport = + avroSchema(f -> f.type().stringBuilder().prop("sqlType", "DATE").endString().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expectedExport, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // TIME + TableSchema tableSchema = tableSchema(f -> f.setType("TIME").setMode("REQUIRED")); + Schema timeType = LogicalTypes.timeMicros().addToSchema(SchemaBuilder.builder().longType()); + Schema expected = avroSchema(f -> f.type(timeType).noDefault()); + Schema expectedExport = + avroSchema(f -> f.type().stringBuilder().prop("sqlType", "TIME").endString().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expectedExport, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // DATETIME + TableSchema tableSchema = tableSchema(f -> f.setType("DATETIME").setMode("REQUIRED")); + Schema timeType = + BigQueryAvroUtils.DATETIME_LOGICAL_TYPE.addToSchema(SchemaBuilder.builder().stringType()); + Schema expected = avroSchema(f -> f.type(timeType).noDefault()); + Schema expectedExport = + avroSchema( + f -> f.type().stringBuilder().prop("sqlType", "DATETIME").endString().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expectedExport, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // TIMESTAMP + TableSchema tableSchema = tableSchema(f -> f.setType("TIMESTAMP").setMode("REQUIRED")); + Schema timestampType = + LogicalTypes.timestampMicros().addToSchema(SchemaBuilder.builder().longType()); + Schema expected = avroSchema(f -> f.type(timestampType).noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // GEOGRAPHY + TableSchema tableSchema = tableSchema(f -> f.setType("GEOGRAPHY").setMode("REQUIRED")); + Schema expected = + avroSchema( + f -> f.type().stringBuilder().prop("sqlType", "GEOGRAPHY").endString().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // JSON + TableSchema tableSchema = tableSchema(f -> f.setType("JSON").setMode("REQUIRED")); + Schema expected = + avroSchema(f -> f.type().stringBuilder().prop("sqlType", "JSON").endString().noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(tableSchema, false)); + } + + { + // STRUCT/RECORD + TableFieldSchema subInteger = + new TableFieldSchema().setName("int").setType("INTEGER").setMode("NULLABLE"); + TableFieldSchema subFloat = + new TableFieldSchema().setName("float").setType("FLOAT").setMode("REQUIRED"); + TableSchema structTableSchema = + tableSchema( + f -> + f.setType("STRUCT") + .setMode("REQUIRED") + .setFields(Lists.newArrayList(subInteger, subFloat))); + TableSchema recordTableSchema = + tableSchema( + f -> + f.setType("RECORD") + .setMode("REQUIRED") + .setFields(Lists.newArrayList(subInteger, subFloat))); + + Schema expected = + avroSchema( + f -> + f.type() + .record("value") + .fields() + .name("int") + .type() + .unionOf() + .nullType() + .and() + .longType() + .endUnion() + .noDefault() + .name("float") + .type() + .doubleType() + .noDefault() + .endRecord() + .noDefault()); + + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(structTableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(structTableSchema, false)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(recordTableSchema)); + assertEquals(expected, BigQueryAvroUtils.toGenericAvroSchema(recordTableSchema, false)); + } } @Test public void testFormatTimestamp() { - assertThat( - BigQueryAvroUtils.formatTimestamp(1452062291123456L), - equalTo("2016-01-06 06:38:11.123456 UTC")); + long micros = 1452062291123456L; + String expected = "2016-01-06 06:38:11.123456"; + assertThat(BigQueryAvroUtils.formatDatetime(micros), equalTo(expected)); + assertThat(BigQueryAvroUtils.formatTimestamp(micros), equalTo(expected + " UTC")); } @Test - public void testFormatTimestampLeadingZeroesOnMicros() { - assertThat( - BigQueryAvroUtils.formatTimestamp(1452062291000456L), - equalTo("2016-01-06 06:38:11.000456 UTC")); + public void testFormatTimestampMillis() { + long millis = 1452062291123L; + long micros = millis * 1000L; + String expected = "2016-01-06 06:38:11.123"; + assertThat(BigQueryAvroUtils.formatDatetime(micros), equalTo(expected)); + assertThat(BigQueryAvroUtils.formatTimestamp(micros), equalTo(expected + " UTC")); } @Test - public void testFormatTimestampTrailingZeroesOnMicros() { - assertThat( - BigQueryAvroUtils.formatTimestamp(1452062291123000L), - equalTo("2016-01-06 06:38:11.123000 UTC")); + public void testFormatTimestampSeconds() { + long seconds = 1452062291L; + long micros = seconds * 1000L * 1000L; + String expected = "2016-01-06 06:38:11"; + assertThat(BigQueryAvroUtils.formatDatetime(micros), equalTo(expected)); + assertThat(BigQueryAvroUtils.formatTimestamp(micros), equalTo(expected + " UTC")); } @Test public void testFormatTimestampNegative() { - assertThat(BigQueryAvroUtils.formatTimestamp(-1L), equalTo("1969-12-31 23:59:59.999999 UTC")); - assertThat( - BigQueryAvroUtils.formatTimestamp(-100_000L), equalTo("1969-12-31 23:59:59.900000 UTC")); - assertThat(BigQueryAvroUtils.formatTimestamp(-1_000_000L), equalTo("1969-12-31 23:59:59 UTC")); + assertThat(BigQueryAvroUtils.formatDatetime(-1L), equalTo("1969-12-31 23:59:59.999999")); + assertThat(BigQueryAvroUtils.formatDatetime(-100_000L), equalTo("1969-12-31 23:59:59.900")); + assertThat(BigQueryAvroUtils.formatDatetime(-1_000_000L), equalTo("1969-12-31 23:59:59")); // No leap seconds before 1972. 477 leap years from 1 through 1969. assertThat( - BigQueryAvroUtils.formatTimestamp(-(1969L * 365 + 477) * 86400 * 1_000_000), - equalTo("0001-01-01 00:00:00 UTC")); + BigQueryAvroUtils.formatDatetime(-(1969L * 365 + 477) * 86400 * 1_000_000), + equalTo("0001-01-01 00:00:00")); } @Test @@ -501,48 +816,4 @@ public void testSchemaCollisionsInAvroConversion() { String output = BigQueryAvroUtils.toGenericAvroSchema(schema, false).toString(); assertThat(output.length(), greaterThan(0)); } - - /** Pojo class used as the record type in tests. */ - @SuppressWarnings("unused") // Used by Avro reflection. - static class Bird { - long number; - @Nullable String species; - @Nullable Double quality; - @Nullable Long quantity; - - @AvroSchema(value = "[\"null\", {\"type\": \"long\", \"logicalType\": \"timestamp-micros\"}]") - Instant birthday; - - @AvroSchema( - value = - "[\"null\", {\"type\": \"bytes\", \"logicalType\": \"decimal\", \"precision\": 38, \"scale\": 9}]") - BigDecimal birthdayMoney; - - @AvroSchema( - value = - "[\"null\", {\"type\": \"bytes\", \"logicalType\": \"decimal\", \"precision\": 77, \"scale\": 38}]") - BigDecimal lotteryWinnings; - - @AvroSchema(value = "[\"null\", {\"type\": \"string\", \"sqlType\": \"GEOGRAPHY\"}]") - String geoPositions; - - @Nullable Boolean flighted; - @Nullable ByteBuffer sound; - @Nullable Utf8 anniversaryDate; - @Nullable String anniversaryDatetime; - @Nullable Utf8 anniversaryTime; - @Nullable SubBird scion; - SubBird[] associates; - - static class SubBird { - @Nullable String species; - - public SubBird() {} - } - - public Bird() { - associates = new SubBird[1]; - associates[0] = new SubBird(); - } - } } From 76bcec0086cf0137d2ea6c6259eadc15456e96bc Mon Sep 17 00:00:00 2001 From: Damon Date: Tue, 10 Dec 2024 09:50:51 -0800 Subject: [PATCH 092/135] Remove the use of google-github-actions/auth@v1 (#33338) * Remove use of google-github-actions/auth step. * Create beam_PostCommit_Java_IO_Performance_Tests.json --- .../beam_PostCommit_Java_IO_Performance_Tests.json | 4 ++++ .../workflows/beam_PostCommit_Java_IO_Performance_Tests.yml | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 .github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json diff --git a/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json b/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json new file mode 100644 index 000000000000..b26833333238 --- /dev/null +++ b/.github/trigger_files/beam_PostCommit_Java_IO_Performance_Tests.json @@ -0,0 +1,4 @@ +{ + "comment": "Modify this file in a trivial way to cause this test suite to run", + "modification": 2 +} diff --git a/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml b/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml index a6a2749c8d82..c6d9dc2236d3 100644 --- a/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml +++ b/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml @@ -88,11 +88,6 @@ jobs: uses: ./.github/actions/setup-environment-action with: java-version: default - - name: Authenticate on GCP - uses: google-github-actions/auth@v1 - with: - credentials_json: ${{ secrets.GCP_SA_KEY }} - project_id: ${{ secrets.GCP_PROJECT_ID }} - name: run scheduled javaPostcommitIOPerformanceTests script if: github.event_name == 'schedule' #This ensures only scheduled runs publish metrics publicly by changing which exportTable is configured uses: ./.github/actions/gradle-command-self-hosted-action From b4fbf8957e9a39ec018ace9aec284a7af3144f2d Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:14:43 -0500 Subject: [PATCH 093/135] Add hadoop-auth (#33342) * add hadoop auth * trigger xlang tests * place dep in expansion service --- .../trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json | 2 +- .../main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy | 1 + sdks/java/io/expansion-service/build.gradle | 1 + sdks/java/io/iceberg/build.gradle | 3 +-- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json index c537844dc84a..b26833333238 100644 --- a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json +++ b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 3 + "modification": 2 } diff --git a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy index 2abd43a5d4cc..a59c1d7630b0 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -795,6 +795,7 @@ class BeamModulePlugin implements Plugin { grpc_xds : "io.grpc:grpc-xds", // google_cloud_platform_libraries_bom sets version guava : "com.google.guava:guava:$guava_version", guava_testlib : "com.google.guava:guava-testlib:$guava_version", + hadoop_auth : "org.apache.hadoop:hadoop-auth:$hadoop_version", hadoop_client : "org.apache.hadoop:hadoop-client:$hadoop_version", hadoop_common : "org.apache.hadoop:hadoop-common:$hadoop_version", hadoop_mapreduce_client_core : "org.apache.hadoop:hadoop-mapreduce-client-core:$hadoop_version", diff --git a/sdks/java/io/expansion-service/build.gradle b/sdks/java/io/expansion-service/build.gradle index cc8eccf98997..421719b8f986 100644 --- a/sdks/java/io/expansion-service/build.gradle +++ b/sdks/java/io/expansion-service/build.gradle @@ -54,6 +54,7 @@ dependencies { permitUnusedDeclared project(":sdks:java:io:kafka:upgrade") // BEAM-11761 // **** IcebergIO runtime dependencies **** + runtimeOnly library.java.hadoop_auth runtimeOnly library.java.hadoop_client // Needed when using GCS as the warehouse location. runtimeOnly library.java.bigdataoss_gcs_connector diff --git a/sdks/java/io/iceberg/build.gradle b/sdks/java/io/iceberg/build.gradle index fa1e2426ce69..0cfa8da4eb7d 100644 --- a/sdks/java/io/iceberg/build.gradle +++ b/sdks/java/io/iceberg/build.gradle @@ -53,15 +53,14 @@ dependencies { implementation "org.apache.iceberg:iceberg-api:$iceberg_version" implementation "org.apache.iceberg:iceberg-parquet:$iceberg_version" implementation "org.apache.iceberg:iceberg-orc:$iceberg_version" - runtimeOnly "org.apache.iceberg:iceberg-gcp:$iceberg_version" implementation library.java.hadoop_common + runtimeOnly "org.apache.iceberg:iceberg-gcp:$iceberg_version" testImplementation project(":sdks:java:managed") testImplementation library.java.hadoop_client testImplementation library.java.bigdataoss_gcsio testImplementation library.java.bigdataoss_gcs_connector testImplementation library.java.bigdataoss_util_hadoop - testImplementation "org.apache.iceberg:iceberg-gcp:$iceberg_version" testImplementation "org.apache.iceberg:iceberg-data:$iceberg_version" testImplementation project(path: ":sdks:java:core", configuration: "shadowTest") testImplementation project(":sdks:java:extensions:google-cloud-platform-core") From 7a94b6192332c49e9a39fe6bb316f4c3bec62929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:43:29 -0800 Subject: [PATCH 094/135] Bump golang.org/x/net from 0.31.0 to 0.32.0 in /sdks (#33326) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.31.0 to 0.32.0. - [Commits](https://github.com/golang/net/compare/v0.31.0...v0.32.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 6 +++--- sdks/go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index a73cd530325f..0a09ee59c805 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -53,10 +53,10 @@ require ( github.com/xitongsys/parquet-go v1.6.2 github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c go.mongodb.org/mongo-driver v1.17.1 - golang.org/x/net v0.31.0 + golang.org/x/net v0.32.0 golang.org/x/oauth2 v0.24.0 golang.org/x/sync v0.10.0 - golang.org/x/sys v0.27.0 + golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 google.golang.org/api v0.210.0 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 @@ -190,7 +190,7 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.30.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/sdks/go.sum b/sdks/go.sum index a0f784b9789e..bf2a9478a787 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1266,8 +1266,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1388,8 +1388,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1526,8 +1526,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1536,8 +1536,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 04b059ff2e40dfd1ef295e35610f8db4a5ec4613 Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Tue, 10 Dec 2024 22:32:26 -0500 Subject: [PATCH 095/135] Remove mandatory beam-sdks-io-kafka dependency for dataflow worker jar (#33302) --- .../worker/build.gradle | 1 - ...icsToPerStepNamespaceMetricsConverter.java | 6 ++-- .../worker/StreamingDataflowWorker.java | 5 --- .../dataflow/worker/streaming/StageInfo.java | 5 ++- .../beam/sdk/io/kafka/KafkaIOInitializer.java | 34 +++++++++++++++++++ 5 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaIOInitializer.java diff --git a/runners/google-cloud-dataflow-java/worker/build.gradle b/runners/google-cloud-dataflow-java/worker/build.gradle index 92beccd067e2..b7e6e981effe 100644 --- a/runners/google-cloud-dataflow-java/worker/build.gradle +++ b/runners/google-cloud-dataflow-java/worker/build.gradle @@ -54,7 +54,6 @@ def sdk_provided_project_dependencies = [ ":runners:google-cloud-dataflow-java", ":sdks:java:extensions:avro", ":sdks:java:extensions:google-cloud-platform-core", - ":sdks:java:io:kafka", // For metric propagation into worker ":sdks:java:io:google-cloud-platform", ] diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/MetricsToPerStepNamespaceMetricsConverter.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/MetricsToPerStepNamespaceMetricsConverter.java index 77f867793ae2..91baefa0be4c 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/MetricsToPerStepNamespaceMetricsConverter.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/MetricsToPerStepNamespaceMetricsConverter.java @@ -32,7 +32,6 @@ import java.util.Map.Entry; import java.util.Optional; import org.apache.beam.sdk.io.gcp.bigquery.BigQuerySinkMetrics; -import org.apache.beam.sdk.io.kafka.KafkaSinkMetrics; import org.apache.beam.sdk.metrics.LabeledMetricNameUtils; import org.apache.beam.sdk.metrics.MetricName; import org.apache.beam.sdk.util.HistogramData; @@ -43,6 +42,9 @@ * converter. */ public class MetricsToPerStepNamespaceMetricsConverter { + // Avoids to introduce mandatory kafka-io dependency to Dataflow worker + // keep in sync with org.apache.beam.sdk.io.kafka.KafkaSinkMetrics.METRICS_NAMESPACE + public static String KAFKA_SINK_METRICS_NAMESPACE = "KafkaSink"; private static Optional getParsedMetricName( MetricName metricName, @@ -70,7 +72,7 @@ private static Optional convertCounterToMetricValue( if (value == 0 || (!metricName.getNamespace().equals(BigQuerySinkMetrics.METRICS_NAMESPACE) - && !metricName.getNamespace().equals(KafkaSinkMetrics.METRICS_NAMESPACE))) { + && !metricName.getNamespace().equals(KAFKA_SINK_METRICS_NAMESPACE))) { return Optional.empty(); } diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java index 088a28e9b2db..0112ab4af80a 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingDataflowWorker.java @@ -110,7 +110,6 @@ import org.apache.beam.sdk.fn.JvmInitializers; import org.apache.beam.sdk.io.FileSystems; import org.apache.beam.sdk.io.gcp.bigquery.BigQuerySinkMetrics; -import org.apache.beam.sdk.io.kafka.KafkaSinkMetrics; import org.apache.beam.sdk.metrics.MetricsEnvironment; import org.apache.beam.sdk.util.construction.CoderTranslation; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; @@ -835,10 +834,6 @@ public static void main(String[] args) throws Exception { enableBigQueryMetrics(); } - if (DataflowRunner.hasExperiment(options, "enable_kafka_metrics")) { - KafkaSinkMetrics.setSupportKafkaMetrics(true); - } - JvmInitializers.runBeforeProcessing(options); worker.startStatusPages(); worker.start(); diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/StageInfo.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/StageInfo.java index 525464ef2e1f..d9fe95f3421b 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/StageInfo.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/streaming/StageInfo.java @@ -17,6 +17,7 @@ */ package org.apache.beam.runners.dataflow.worker.streaming; +import static org.apache.beam.runners.dataflow.worker.MetricsToPerStepNamespaceMetricsConverter.KAFKA_SINK_METRICS_NAMESPACE; import static org.apache.beam.sdk.metrics.Metrics.THROTTLE_TIME_COUNTER_NAME; import com.google.api.services.dataflow.model.CounterStructuredName; @@ -35,7 +36,6 @@ import org.apache.beam.runners.dataflow.worker.counters.DataflowCounterUpdateExtractor; import org.apache.beam.runners.dataflow.worker.counters.NameContext; import org.apache.beam.sdk.io.gcp.bigquery.BigQuerySinkMetrics; -import org.apache.beam.sdk.io.kafka.KafkaSinkMetrics; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables; /** Contains a few of the stage specific fields. E.g. metrics container registry, counters etc. */ @@ -120,8 +120,7 @@ private void translateKnownPerWorkerCounters(List metri for (PerStepNamespaceMetrics perStepnamespaceMetrics : metrics) { if (!BigQuerySinkMetrics.METRICS_NAMESPACE.equals( perStepnamespaceMetrics.getMetricsNamespace()) - && !KafkaSinkMetrics.METRICS_NAMESPACE.equals( - perStepnamespaceMetrics.getMetricsNamespace())) { + && !KAFKA_SINK_METRICS_NAMESPACE.equals(perStepnamespaceMetrics.getMetricsNamespace())) { continue; } for (MetricValue metric : perStepnamespaceMetrics.getMetricValues()) { diff --git a/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaIOInitializer.java b/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaIOInitializer.java new file mode 100644 index 000000000000..3dfb31715ced --- /dev/null +++ b/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaIOInitializer.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.beam.sdk.io.kafka; + +import com.google.auto.service.AutoService; +import org.apache.beam.sdk.harness.JvmInitializer; +import org.apache.beam.sdk.options.ExperimentalOptions; +import org.apache.beam.sdk.options.PipelineOptions; + +/** Initialize KafkaIO feature flags on worker. */ +@AutoService(JvmInitializer.class) +public class KafkaIOInitializer implements JvmInitializer { + @Override + public void beforeProcessing(PipelineOptions options) { + if (ExperimentalOptions.hasExperiment(options, "enable_kafka_metrics")) { + KafkaSinkMetrics.setSupportKafkaMetrics(true); + } + } +} From b26e2fad761bc85832192dfa0abf335ae5c31111 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:51:05 -0800 Subject: [PATCH 096/135] Bump github.com/nats-io/nats-server/v2 from 2.10.22 to 2.10.23 in /sdks (#33352) Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.22 to 2.10.23. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.10.22...v2.10.23) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 4 ++-- sdks/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 0a09ee59c805..5406c2b70cbc 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -44,7 +44,7 @@ require ( github.com/johannesboyne/gofakes3 v0.0.0-20221110173912-32fb85c5aed6 github.com/lib/pq v1.10.9 github.com/linkedin/goavro/v2 v2.13.0 - github.com/nats-io/nats-server/v2 v2.10.22 + github.com/nats-io/nats-server/v2 v2.10.23 github.com/nats-io/nats.go v1.37.0 github.com/proullon/ramsql v0.1.4 github.com/spf13/cobra v1.8.1 @@ -98,7 +98,7 @@ require ( github.com/moby/sys/user v0.1.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/nats-io/jwt/v2 v2.5.8 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/nkeys v0.4.8 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect diff --git a/sdks/go.sum b/sdks/go.sum index bf2a9478a787..bb96c54af087 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1085,12 +1085,12 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.22 h1:Yt63BGu2c3DdMoBZNcR6pjGQwk/asrKU7VX846ibxDA= -github.com/nats-io/nats-server/v2 v2.10.22/go.mod h1:X/m1ye9NYansUXYFrbcDwUi/blHkrgHh2rgCJaakonk= +github.com/nats-io/nats-server/v2 v2.10.23 h1:jvfb9cEi5h8UG6HkZgJGdn9f1UPaX3Dohk0PohEekJI= +github.com/nats-io/nats-server/v2 v2.10.23/go.mod h1:hMFnpDT2XUXsvHglABlFl/uroQCCOcW6X/0esW6GpBk= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nkeys v0.4.8 h1:+wee30071y3vCZAYRsnrmIPaOe47A/SkK/UBDPdIV70= +github.com/nats-io/nkeys v0.4.8/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.52/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= From 00ec6cc2d1a24af27defc3f19c77c51cda637104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Stankiewicz?= Date: Wed, 11 Dec 2024 17:51:20 +0100 Subject: [PATCH 097/135] fix Error processing result file: CData section too big found in publish-int-test-result-action #29966 (#33353) --- .github/workflows/IO_Iceberg_Unit_Tests.yml | 1 + .../workflows/beam_LoadTests_Java_CoGBK_Dataflow_Streaming.yml | 3 ++- .../beam_PerformanceTests_BigQueryIO_Batch_Java_Avro.yml | 3 ++- .../beam_PerformanceTests_BigQueryIO_Batch_Java_Json.yml | 3 ++- .../beam_PerformanceTests_BigQueryIO_Streaming_Java.yml | 3 ++- .../beam_PerformanceTests_SQLBigQueryIO_Batch_Java.yml | 3 ++- .../beam_PerformanceTests_WordCountIT_PythonVersions.yml | 3 ++- .github/workflows/beam_PostCommit_Java.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Avro_Versions.yml | 3 ++- .../workflows/beam_PostCommit_Java_BigQueryEarlyRollout.yml | 1 + .github/workflows/beam_PostCommit_Java_DataflowV1.yml | 3 ++- .github/workflows/beam_PostCommit_Java_DataflowV2.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Examples_Dataflow.yml | 3 ++- .../workflows/beam_PostCommit_Java_Examples_Dataflow_ARM.yml | 1 + .../workflows/beam_PostCommit_Java_Examples_Dataflow_Java.yml | 3 ++- .../workflows/beam_PostCommit_Java_Examples_Dataflow_V2.yml | 3 ++- .../beam_PostCommit_Java_Examples_Dataflow_V2_Java.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Examples_Direct.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Examples_Flink.yml | 1 + .github/workflows/beam_PostCommit_Java_Examples_Spark.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Hadoop_Versions.yml | 3 ++- .../workflows/beam_PostCommit_Java_IO_Performance_Tests.yml | 1 + .../workflows/beam_PostCommit_Java_Jpms_Dataflow_Java11.yml | 3 ++- .../workflows/beam_PostCommit_Java_Jpms_Dataflow_Java17.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Jpms_Direct_Java11.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Jpms_Direct_Java17.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Jpms_Direct_Java21.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Jpms_Flink_Java11.yml | 3 ++- .github/workflows/beam_PostCommit_Java_Jpms_Spark_Java11.yml | 3 ++- .github/workflows/beam_PostCommit_Java_PVR_Flink_Streaming.yml | 1 + .github/workflows/beam_PostCommit_Java_PVR_Samza.yml | 3 ++- .../workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml | 3 ++- ...m_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml | 1 + .../beam_PostCommit_Java_ValidatesRunner_Dataflow.yml | 3 ++- ...m_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions.yml | 3 ++- ...beam_PostCommit_Java_ValidatesRunner_Dataflow_Streaming.yml | 3 ++- .../beam_PostCommit_Java_ValidatesRunner_Dataflow_V2.yml | 3 ++- ...m_PostCommit_Java_ValidatesRunner_Dataflow_V2_Streaming.yml | 3 ++- .../workflows/beam_PostCommit_Java_ValidatesRunner_Direct.yml | 3 ++- ...eam_PostCommit_Java_ValidatesRunner_Direct_JavaVersions.yml | 3 ++- .../workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml | 1 + .../beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml | 1 + .../workflows/beam_PostCommit_Java_ValidatesRunner_Samza.yml | 3 ++- .../workflows/beam_PostCommit_Java_ValidatesRunner_Spark.yml | 3 ++- ...ostCommit_Java_ValidatesRunner_SparkStructuredStreaming.yml | 3 ++- .../beam_PostCommit_Java_ValidatesRunner_Spark_Java8.yml | 3 ++- .../beam_PostCommit_Java_ValidatesRunner_Twister2.yml | 3 ++- .github/workflows/beam_PostCommit_Java_ValidatesRunner_ULR.yml | 3 ++- .github/workflows/beam_PostCommit_PortableJar_Flink.yml | 3 ++- .github/workflows/beam_PostCommit_PortableJar_Spark.yml | 3 ++- .github/workflows/beam_PostCommit_Python.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Arm.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Dependency.yml | 1 + .github/workflows/beam_PostCommit_Python_Examples_Dataflow.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Examples_Direct.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Examples_Flink.yml | 1 + .github/workflows/beam_PostCommit_Python_Examples_Spark.yml | 3 ++- .github/workflows/beam_PostCommit_Python_MongoDBIO_IT.yml | 3 ++- .../beam_PostCommit_Python_ValidatesContainer_Dataflow.yml | 1 + ...m_PostCommit_Python_ValidatesContainer_Dataflow_With_RC.yml | 3 ++- ...PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml | 1 + .../beam_PostCommit_Python_ValidatesRunner_Dataflow.yml | 3 ++- .../workflows/beam_PostCommit_Python_ValidatesRunner_Flink.yml | 3 ++- .../workflows/beam_PostCommit_Python_ValidatesRunner_Samza.yml | 3 ++- .../workflows/beam_PostCommit_Python_ValidatesRunner_Spark.yml | 3 ++- .../workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml | 3 ++- .github/workflows/beam_PostCommit_Python_Xlang_IO_Direct.yml | 3 ++- .github/workflows/beam_PostCommit_SQL.yml | 1 + .github/workflows/beam_PostCommit_TransformService_Direct.yml | 3 ++- .github/workflows/beam_PostCommit_XVR_Direct.yml | 3 ++- .github/workflows/beam_PostCommit_XVR_Flink.yml | 1 + .github/workflows/beam_PostCommit_XVR_GoUsingJava_Dataflow.yml | 1 + .../workflows/beam_PostCommit_XVR_JavaUsingPython_Dataflow.yml | 3 ++- .../beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.yml | 3 ++- .../workflows/beam_PostCommit_XVR_PythonUsingJava_Dataflow.yml | 3 ++- .github/workflows/beam_PostCommit_XVR_Samza.yml | 3 ++- .github/workflows/beam_PostCommit_XVR_Spark3.yml | 3 ++- .github/workflows/beam_PreCommit_ItFramework.yml | 3 ++- .github/workflows/beam_PreCommit_Java.yml | 1 + .../beam_PreCommit_Java_Amazon-Web-Services2_IO_Direct.yml | 1 + .../beam_PreCommit_Java_Amazon-Web-Services_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Azure_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Cassandra_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Cdap_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Clickhouse_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Csv_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Debezium_IO_Direct.yml | 1 + .../workflows/beam_PreCommit_Java_ElasticSearch_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Examples_Dataflow.yml | 3 ++- .../workflows/beam_PreCommit_Java_Examples_Dataflow_Java21.yml | 1 + .../beam_PreCommit_Java_File-schema-transform_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Flink_Versions.yml | 3 ++- .github/workflows/beam_PreCommit_Java_GCP_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Google-ads_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_HBase_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_HCatalog_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Hadoop_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_IOs_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_InfluxDb_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_JDBC_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Jms_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Kafka_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Kinesis_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Kudu_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_MongoDb_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Mqtt_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Neo4j_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_PVR_Flink_Docker.yml | 1 + .github/workflows/beam_PreCommit_Java_Parquet_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Pulsar_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_RabbitMq_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Redis_IO_Direct.yml | 1 + .../beam_PreCommit_Java_RequestResponse_IO_Direct.yml | 1 + .../workflows/beam_PreCommit_Java_SingleStore_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Snowflake_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Solace_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Solr_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Spark3_Versions.yml | 3 ++- .github/workflows/beam_PreCommit_Java_Splunk_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Thrift_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Java_Tika_IO_Direct.yml | 1 + .github/workflows/beam_PreCommit_Python.yml | 3 ++- .github/workflows/beam_PreCommit_Python_Coverage.yml | 3 ++- .github/workflows/beam_PreCommit_Python_Dataframes.yml | 3 ++- .github/workflows/beam_PreCommit_Python_Examples.yml | 3 ++- .github/workflows/beam_PreCommit_Python_Integration.yml | 3 ++- .github/workflows/beam_PreCommit_Python_ML.yml | 3 ++- .github/workflows/beam_PreCommit_Python_PVR_Flink.yml | 3 ++- .github/workflows/beam_PreCommit_Python_Runners.yml | 3 ++- .github/workflows/beam_PreCommit_Python_Transforms.yml | 3 ++- .github/workflows/beam_PreCommit_SQL.yml | 1 + .github/workflows/beam_PreCommit_SQL_Java17.yml | 1 + .github/workflows/beam_PreCommit_SQL_Java8.yml | 1 + .github/workflows/beam_PreCommit_Yaml_Xlang_Direct.yml | 1 + 136 files changed, 212 insertions(+), 76 deletions(-) diff --git a/.github/workflows/IO_Iceberg_Unit_Tests.yml b/.github/workflows/IO_Iceberg_Unit_Tests.yml index 0d72b0da8597..d063f6ac71db 100644 --- a/.github/workflows/IO_Iceberg_Unit_Tests.yml +++ b/.github/workflows/IO_Iceberg_Unit_Tests.yml @@ -111,6 +111,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_LoadTests_Java_CoGBK_Dataflow_Streaming.yml b/.github/workflows/beam_LoadTests_Java_CoGBK_Dataflow_Streaming.yml index 2b631d2f7664..659c85b002df 100644 --- a/.github/workflows/beam_LoadTests_Java_CoGBK_Dataflow_Streaming.yml +++ b/.github/workflows/beam_LoadTests_Java_CoGBK_Dataflow_Streaming.yml @@ -124,4 +124,5 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Avro.yml b/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Avro.yml index af0569f4784a..74932079fe4c 100644 --- a/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Avro.yml +++ b/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Avro.yml @@ -102,4 +102,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Json.yml b/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Json.yml index 9e3962e2576e..05e5369a6384 100644 --- a/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Json.yml +++ b/.github/workflows/beam_PerformanceTests_BigQueryIO_Batch_Java_Json.yml @@ -102,4 +102,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PerformanceTests_BigQueryIO_Streaming_Java.yml b/.github/workflows/beam_PerformanceTests_BigQueryIO_Streaming_Java.yml index 7514bd5cacb3..32db2cff6cbc 100644 --- a/.github/workflows/beam_PerformanceTests_BigQueryIO_Streaming_Java.yml +++ b/.github/workflows/beam_PerformanceTests_BigQueryIO_Streaming_Java.yml @@ -102,4 +102,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PerformanceTests_SQLBigQueryIO_Batch_Java.yml b/.github/workflows/beam_PerformanceTests_SQLBigQueryIO_Batch_Java.yml index 6ac07a1bd76c..d04a6e63c800 100644 --- a/.github/workflows/beam_PerformanceTests_SQLBigQueryIO_Batch_Java.yml +++ b/.github/workflows/beam_PerformanceTests_SQLBigQueryIO_Batch_Java.yml @@ -101,4 +101,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PerformanceTests_WordCountIT_PythonVersions.yml b/.github/workflows/beam_PerformanceTests_WordCountIT_PythonVersions.yml index e9ef9cd1716a..756ecb5a58c2 100644 --- a/.github/workflows/beam_PerformanceTests_WordCountIT_PythonVersions.yml +++ b/.github/workflows/beam_PerformanceTests_WordCountIT_PythonVersions.yml @@ -115,4 +115,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java.yml b/.github/workflows/beam_PostCommit_Java.yml index 3428551cb8f9..4fafa3b2a993 100644 --- a/.github/workflows/beam_PostCommit_Java.yml +++ b/.github/workflows/beam_PostCommit_Java.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Avro_Versions.yml b/.github/workflows/beam_PostCommit_Java_Avro_Versions.yml index e3a9db23ed67..8ffcc4a28a71 100644 --- a/.github/workflows/beam_PostCommit_Java_Avro_Versions.yml +++ b/.github/workflows/beam_PostCommit_Java_Avro_Versions.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_BigQueryEarlyRollout.yml b/.github/workflows/beam_PostCommit_Java_BigQueryEarlyRollout.yml index 1a6f7c14db50..8707b515e10b 100644 --- a/.github/workflows/beam_PostCommit_Java_BigQueryEarlyRollout.yml +++ b/.github/workflows/beam_PostCommit_Java_BigQueryEarlyRollout.yml @@ -110,3 +110,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_DataflowV1.yml b/.github/workflows/beam_PostCommit_Java_DataflowV1.yml index e7c2aa6fe7e2..752b15936b5f 100644 --- a/.github/workflows/beam_PostCommit_Java_DataflowV1.yml +++ b/.github/workflows/beam_PostCommit_Java_DataflowV1.yml @@ -94,4 +94,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_DataflowV2.yml b/.github/workflows/beam_PostCommit_Java_DataflowV2.yml index 3c0a46d6bb40..cb107572b621 100644 --- a/.github/workflows/beam_PostCommit_Java_DataflowV2.yml +++ b/.github/workflows/beam_PostCommit_Java_DataflowV2.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow.yml b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow.yml index 469d7e31f173..81725c4005af 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow.yml @@ -89,4 +89,5 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_ARM.yml b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_ARM.yml index 9fd84daef63b..eacdfe5a5c23 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_ARM.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_ARM.yml @@ -119,3 +119,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_Java.yml b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_Java.yml index 13ab05f8f173..efb926681cbf 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_Java.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_Java.yml @@ -97,4 +97,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2.yml b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2.yml index 9be8a34f3732..1882cdf1d76b 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2.yml @@ -91,4 +91,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2_Java.yml b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2_Java.yml index cd2486ae8e10..05b28ac93658 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2_Java.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Dataflow_V2_Java.yml @@ -104,4 +104,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Direct.yml b/.github/workflows/beam_PostCommit_Java_Examples_Direct.yml index ca06e72877c7..a746acb4333f 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Direct.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Direct.yml @@ -92,4 +92,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Flink.yml b/.github/workflows/beam_PostCommit_Java_Examples_Flink.yml index e42d6a88b8df..f72910bd15bc 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Flink.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Flink.yml @@ -94,3 +94,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_Examples_Spark.yml b/.github/workflows/beam_PostCommit_Java_Examples_Spark.yml index 8008daf4584f..c3620e46fac9 100644 --- a/.github/workflows/beam_PostCommit_Java_Examples_Spark.yml +++ b/.github/workflows/beam_PostCommit_Java_Examples_Spark.yml @@ -92,4 +92,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Hadoop_Versions.yml b/.github/workflows/beam_PostCommit_Java_Hadoop_Versions.yml index 67a48b105955..1202ecc0e27f 100644 --- a/.github/workflows/beam_PostCommit_Java_Hadoop_Versions.yml +++ b/.github/workflows/beam_PostCommit_Java_Hadoop_Versions.yml @@ -100,4 +100,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml b/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml index c6d9dc2236d3..6023a895a458 100644 --- a/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml +++ b/.github/workflows/beam_PostCommit_Java_IO_Performance_Tests.yml @@ -117,3 +117,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java11.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java11.yml index 37f784770477..323f85b9851a 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java11.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java11.yml @@ -91,4 +91,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java17.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java17.yml index 377602ad08dd..1ccb26f5aa1f 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java17.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Dataflow_Java17.yml @@ -96,4 +96,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java11.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java11.yml index 80406cf4eb0c..02ac93135957 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java11.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java11.yml @@ -91,4 +91,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java17.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java17.yml index 3cbc317317c2..2cbf60a48d2e 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java17.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java17.yml @@ -96,4 +96,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java21.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java21.yml index 97fd1fb4913e..6a7058ef566d 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java21.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Direct_Java21.yml @@ -97,4 +97,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Flink_Java11.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Flink_Java11.yml index 1a7405836f69..1559061634d3 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Flink_Java11.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Flink_Java11.yml @@ -91,4 +91,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_Jpms_Spark_Java11.yml b/.github/workflows/beam_PostCommit_Java_Jpms_Spark_Java11.yml index eec4867a997b..1b4f8c5bcce5 100644 --- a/.github/workflows/beam_PostCommit_Java_Jpms_Spark_Java11.yml +++ b/.github/workflows/beam_PostCommit_Java_Jpms_Spark_Java11.yml @@ -91,4 +91,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_PVR_Flink_Streaming.yml b/.github/workflows/beam_PostCommit_Java_PVR_Flink_Streaming.yml index 987be7789b29..8c5fcb1acff4 100644 --- a/.github/workflows/beam_PostCommit_Java_PVR_Flink_Streaming.yml +++ b/.github/workflows/beam_PostCommit_Java_PVR_Flink_Streaming.yml @@ -91,3 +91,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_PVR_Samza.yml b/.github/workflows/beam_PostCommit_Java_PVR_Samza.yml index 7cc48ebd4b0e..c1a22b9c871d 100644 --- a/.github/workflows/beam_PostCommit_Java_PVR_Samza.yml +++ b/.github/workflows/beam_PostCommit_Java_PVR_Samza.yml @@ -100,4 +100,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml b/.github/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml index ad10bfc684d8..76ab560f15ec 100644 --- a/.github/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml +++ b/.github/workflows/beam_PostCommit_Java_PVR_Spark3_Streaming.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml index 4fb236c7c991..73fd6f0b78fa 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesDistrolessContainer_Dataflow.yml @@ -113,3 +113,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow.yml index d66381393725..c85c0b8468dc 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions.yml index da2ba2f88465..5963a33007e0 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions.yml @@ -111,4 +111,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_Streaming.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_Streaming.yml index edb055321c87..2e8227fb84a6 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_Streaming.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_Streaming.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2.yml index 8957ce7de053..2abc081e6ae5 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2_Streaming.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2_Streaming.yml index 2a98746a0b84..fde10e0898e9 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2_Streaming.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_V2_Streaming.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct.yml index 3f48bb921805..f439be9ec58e 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct_JavaVersions.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct_JavaVersions.yml index 75ebbda93f80..eb70a654c93d 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct_JavaVersions.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Direct_JavaVersions.yml @@ -106,4 +106,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml index f79ca8747828..1442f5ffafc0 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml @@ -93,3 +93,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml index c51c39987236..0f12ce6f90ef 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml @@ -110,3 +110,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Samza.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Samza.yml index 794308d3a85e..edcb45303fd4 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Samza.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Samza.yml @@ -96,4 +96,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark.yml index d1f264aaac01..d05963263931 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.yml index 15863d4c8c9b..da04582a7caa 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark_Java8.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark_Java8.yml index c05284186617..8d531c120dd6 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark_Java8.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Spark_Java8.yml @@ -108,4 +108,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Twister2.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Twister2.yml index 522cb300c687..8310e5ed8bb2 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Twister2.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Twister2.yml @@ -90,4 +90,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_ULR.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_ULR.yml index 36fc06aea421..3b130b6d290f 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_ULR.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_ULR.yml @@ -89,4 +89,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_PortableJar_Flink.yml b/.github/workflows/beam_PostCommit_PortableJar_Flink.yml index 37bfe68d9b20..318b5104c39c 100644 --- a/.github/workflows/beam_PostCommit_PortableJar_Flink.yml +++ b/.github/workflows/beam_PostCommit_PortableJar_Flink.yml @@ -94,4 +94,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_PortableJar_Spark.yml b/.github/workflows/beam_PostCommit_PortableJar_Spark.yml index ce7be60133d7..0712dfb255b7 100644 --- a/.github/workflows/beam_PostCommit_PortableJar_Spark.yml +++ b/.github/workflows/beam_PostCommit_PortableJar_Spark.yml @@ -94,4 +94,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python.yml b/.github/workflows/beam_PostCommit_Python.yml index 4770515c75fb..93b85a318487 100644 --- a/.github/workflows/beam_PostCommit_Python.yml +++ b/.github/workflows/beam_PostCommit_Python.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Arm.yml b/.github/workflows/beam_PostCommit_Python_Arm.yml index 48fb00b1bb9d..352b95e6747a 100644 --- a/.github/workflows/beam_PostCommit_Python_Arm.yml +++ b/.github/workflows/beam_PostCommit_Python_Arm.yml @@ -124,4 +124,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Dependency.yml b/.github/workflows/beam_PostCommit_Python_Dependency.yml index 6e7c4ddbd3eb..80e1bbc290c9 100644 --- a/.github/workflows/beam_PostCommit_Python_Dependency.yml +++ b/.github/workflows/beam_PostCommit_Python_Dependency.yml @@ -96,3 +96,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/pytest*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Python_Examples_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_Examples_Dataflow.yml index 4ce3b1893215..bf8330a2ae58 100644 --- a/.github/workflows/beam_PostCommit_Python_Examples_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_Examples_Dataflow.yml @@ -94,4 +94,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Examples_Direct.yml b/.github/workflows/beam_PostCommit_Python_Examples_Direct.yml index a6bb49f4e444..e271b7da9a7b 100644 --- a/.github/workflows/beam_PostCommit_Python_Examples_Direct.yml +++ b/.github/workflows/beam_PostCommit_Python_Examples_Direct.yml @@ -101,4 +101,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Examples_Flink.yml b/.github/workflows/beam_PostCommit_Python_Examples_Flink.yml index f23674a2c70a..28fd13c181b3 100644 --- a/.github/workflows/beam_PostCommit_Python_Examples_Flink.yml +++ b/.github/workflows/beam_PostCommit_Python_Examples_Flink.yml @@ -102,3 +102,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/pytest*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Python_Examples_Spark.yml b/.github/workflows/beam_PostCommit_Python_Examples_Spark.yml index d866d412507b..5df6bcf8c01c 100644 --- a/.github/workflows/beam_PostCommit_Python_Examples_Spark.yml +++ b/.github/workflows/beam_PostCommit_Python_Examples_Spark.yml @@ -101,4 +101,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_MongoDBIO_IT.yml b/.github/workflows/beam_PostCommit_Python_MongoDBIO_IT.yml index 578775a9d3ed..0d334b679dc5 100644 --- a/.github/workflows/beam_PostCommit_Python_MongoDBIO_IT.yml +++ b/.github/workflows/beam_PostCommit_Python_MongoDBIO_IT.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow.yml index bcd936324124..6e16f43476b2 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow.yml @@ -108,3 +108,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/pytest*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow_With_RC.yml b/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow_With_RC.yml index f2eba045722c..3ab7257f8a9d 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow_With_RC.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesContainer_Dataflow_With_RC.yml @@ -106,4 +106,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml index 6f8a7bdd0631..c294dd3c9068 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesDistrolessContainer_Dataflow.yml @@ -118,3 +118,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/pytest*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Dataflow.yml index 1876950c7a93..f8daa1a96634 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Dataflow.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Flink.yml b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Flink.yml index f837c7476e12..9277bd68fc01 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Flink.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Flink.yml @@ -103,4 +103,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Samza.yml b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Samza.yml index 91c249adf338..e058724cd2ac 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Samza.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Samza.yml @@ -102,4 +102,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Spark.yml b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Spark.yml index 7e87aaff22cc..a47f758ed410 100644 --- a/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Spark.yml +++ b/.github/workflows/beam_PostCommit_Python_ValidatesRunner_Spark.yml @@ -101,4 +101,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml index b3f37c6b39f0..bd266cf6fdab 100644 --- a/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Dataflow.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml b/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml index 137d7bc13d2f..6d26d1c46012 100644 --- a/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml +++ b/.github/workflows/beam_PostCommit_Python_Xlang_Gcp_Direct.yml @@ -92,4 +92,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml b/.github/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml index 8fc0db189078..08e99fa0fe0f 100644 --- a/.github/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_Python_Xlang_IO_Dataflow.yml @@ -95,4 +95,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_Python_Xlang_IO_Direct.yml b/.github/workflows/beam_PostCommit_Python_Xlang_IO_Direct.yml index 5092a1981154..a7643c795af4 100644 --- a/.github/workflows/beam_PostCommit_Python_Xlang_IO_Direct.yml +++ b/.github/workflows/beam_PostCommit_Python_Xlang_IO_Direct.yml @@ -93,4 +93,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_SQL.yml b/.github/workflows/beam_PostCommit_SQL.yml index c7d0b6dc98b9..aebea2b0564b 100644 --- a/.github/workflows/beam_PostCommit_SQL.yml +++ b/.github/workflows/beam_PostCommit_SQL.yml @@ -91,3 +91,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_TransformService_Direct.yml b/.github/workflows/beam_PostCommit_TransformService_Direct.yml index cb339eb9fb40..d0d72f3df13c 100644 --- a/.github/workflows/beam_PostCommit_TransformService_Direct.yml +++ b/.github/workflows/beam_PostCommit_TransformService_Direct.yml @@ -98,4 +98,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_XVR_Direct.yml b/.github/workflows/beam_PostCommit_XVR_Direct.yml index 023ae4f8cd31..af8b7fb1bf54 100644 --- a/.github/workflows/beam_PostCommit_XVR_Direct.yml +++ b/.github/workflows/beam_PostCommit_XVR_Direct.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_XVR_Flink.yml b/.github/workflows/beam_PostCommit_XVR_Flink.yml index 1f1d7d863b7e..fe4404247448 100644 --- a/.github/workflows/beam_PostCommit_XVR_Flink.yml +++ b/.github/workflows/beam_PostCommit_XVR_Flink.yml @@ -111,3 +111,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_XVR_GoUsingJava_Dataflow.yml b/.github/workflows/beam_PostCommit_XVR_GoUsingJava_Dataflow.yml index 228f10b90cd0..0620023ce7d2 100644 --- a/.github/workflows/beam_PostCommit_XVR_GoUsingJava_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_XVR_GoUsingJava_Dataflow.yml @@ -102,3 +102,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PostCommit_XVR_JavaUsingPython_Dataflow.yml b/.github/workflows/beam_PostCommit_XVR_JavaUsingPython_Dataflow.yml index 66770c9a1683..11a8a5c5f4f7 100644 --- a/.github/workflows/beam_PostCommit_XVR_JavaUsingPython_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_XVR_JavaUsingPython_Dataflow.yml @@ -95,4 +95,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.yml b/.github/workflows/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.yml index bfb602f89daf..c393a4113589 100644 --- a/.github/workflows/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_XVR_PythonUsingJavaSQL_Dataflow.yml @@ -92,4 +92,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_XVR_PythonUsingJava_Dataflow.yml b/.github/workflows/beam_PostCommit_XVR_PythonUsingJava_Dataflow.yml index f1269a0ddd09..082aeb3f2ab2 100644 --- a/.github/workflows/beam_PostCommit_XVR_PythonUsingJava_Dataflow.yml +++ b/.github/workflows/beam_PostCommit_XVR_PythonUsingJava_Dataflow.yml @@ -95,4 +95,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_XVR_Samza.yml b/.github/workflows/beam_PostCommit_XVR_Samza.yml index 2d26c9131839..7e2dca61d41d 100644 --- a/.github/workflows/beam_PostCommit_XVR_Samza.yml +++ b/.github/workflows/beam_PostCommit_XVR_Samza.yml @@ -111,4 +111,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PostCommit_XVR_Spark3.yml b/.github/workflows/beam_PostCommit_XVR_Spark3.yml index c1880e01292b..17fb58d9dd73 100644 --- a/.github/workflows/beam_PostCommit_XVR_Spark3.yml +++ b/.github/workflows/beam_PostCommit_XVR_Spark3.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_ItFramework.yml b/.github/workflows/beam_PreCommit_ItFramework.yml index e078d4645757..e803fc023c67 100644 --- a/.github/workflows/beam_PreCommit_ItFramework.yml +++ b/.github/workflows/beam_PreCommit_ItFramework.yml @@ -101,4 +101,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Java.yml b/.github/workflows/beam_PreCommit_Java.yml index 20dafca72a57..bc25fb94f8f0 100644 --- a/.github/workflows/beam_PreCommit_Java.yml +++ b/.github/workflows/beam_PreCommit_Java.yml @@ -198,6 +198,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services2_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services2_IO_Direct.yml index ecbc85ca1b1d..cf0d0b660782 100644 --- a/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services2_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services2_IO_Direct.yml @@ -130,6 +130,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services_IO_Direct.yml index 55935251e6d9..9053bb730371 100644 --- a/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Amazon-Web-Services_IO_Direct.yml @@ -130,6 +130,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Azure_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Azure_IO_Direct.yml index 4fbacecde4a4..8c0bb07e1acb 100644 --- a/.github/workflows/beam_PreCommit_Java_Azure_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Azure_IO_Direct.yml @@ -123,6 +123,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Cassandra_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Cassandra_IO_Direct.yml index e37bc5c56e2e..317b2e1f2ec1 100644 --- a/.github/workflows/beam_PreCommit_Java_Cassandra_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Cassandra_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Cdap_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Cdap_IO_Direct.yml index 68ebe3c28fb3..3e0208b758cc 100644 --- a/.github/workflows/beam_PreCommit_Java_Cdap_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Cdap_IO_Direct.yml @@ -109,6 +109,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Clickhouse_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Clickhouse_IO_Direct.yml index 5c0b169b0ba1..2be7607b5bc7 100644 --- a/.github/workflows/beam_PreCommit_Java_Clickhouse_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Clickhouse_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Csv_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Csv_IO_Direct.yml index ce91551c1121..6901e56c0bbb 100644 --- a/.github/workflows/beam_PreCommit_Java_Csv_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Csv_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Debezium_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Debezium_IO_Direct.yml index b6a0e6b999bd..6f32c3844b1a 100644 --- a/.github/workflows/beam_PreCommit_Java_Debezium_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Debezium_IO_Direct.yml @@ -114,6 +114,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_ElasticSearch_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_ElasticSearch_IO_Direct.yml index 78ab882d4774..11a95cf476c7 100644 --- a/.github/workflows/beam_PreCommit_Java_ElasticSearch_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_ElasticSearch_IO_Direct.yml @@ -117,6 +117,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Examples_Dataflow.yml b/.github/workflows/beam_PreCommit_Java_Examples_Dataflow.yml index 4bfb20a28e7c..8e22318bdcb9 100644 --- a/.github/workflows/beam_PreCommit_Java_Examples_Dataflow.yml +++ b/.github/workflows/beam_PreCommit_Java_Examples_Dataflow.yml @@ -117,4 +117,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Java_Examples_Dataflow_Java21.yml b/.github/workflows/beam_PreCommit_Java_Examples_Dataflow_Java21.yml index 72fc945018f6..763de153b137 100644 --- a/.github/workflows/beam_PreCommit_Java_Examples_Dataflow_Java21.yml +++ b/.github/workflows/beam_PreCommit_Java_Examples_Dataflow_Java21.yml @@ -133,6 +133,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/beam_PreCommit_Java_File-schema-transform_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_File-schema-transform_IO_Direct.yml index e96dc7c883bf..e121fe1e53a2 100644 --- a/.github/workflows/beam_PreCommit_Java_File-schema-transform_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_File-schema-transform_IO_Direct.yml @@ -106,6 +106,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Flink_Versions.yml b/.github/workflows/beam_PreCommit_Java_Flink_Versions.yml index 19b0d56a8051..09bf906e5a38 100644 --- a/.github/workflows/beam_PreCommit_Java_Flink_Versions.yml +++ b/.github/workflows/beam_PreCommit_Java_Flink_Versions.yml @@ -104,4 +104,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Java_GCP_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_GCP_IO_Direct.yml index 2256d0a91cb8..ee5bea3d3ab3 100644 --- a/.github/workflows/beam_PreCommit_Java_GCP_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_GCP_IO_Direct.yml @@ -127,6 +127,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Google-ads_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Google-ads_IO_Direct.yml index c481251bef03..0e6bd11e7f1e 100644 --- a/.github/workflows/beam_PreCommit_Java_Google-ads_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Google-ads_IO_Direct.yml @@ -103,6 +103,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_HBase_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_HBase_IO_Direct.yml index 3b99e30bfbac..c334edd7f32d 100644 --- a/.github/workflows/beam_PreCommit_Java_HBase_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_HBase_IO_Direct.yml @@ -107,6 +107,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_HCatalog_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_HCatalog_IO_Direct.yml index 6d45ba82aa49..ed079c1e9dd1 100644 --- a/.github/workflows/beam_PreCommit_Java_HCatalog_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_HCatalog_IO_Direct.yml @@ -122,6 +122,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Hadoop_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Hadoop_IO_Direct.yml index c2beaa3c1099..442085586a3c 100644 --- a/.github/workflows/beam_PreCommit_Java_Hadoop_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Hadoop_IO_Direct.yml @@ -145,6 +145,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_IOs_Direct.yml b/.github/workflows/beam_PreCommit_Java_IOs_Direct.yml index 4e19a56dde0c..cd73d402c7ea 100644 --- a/.github/workflows/beam_PreCommit_Java_IOs_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_IOs_Direct.yml @@ -122,6 +122,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_InfluxDb_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_InfluxDb_IO_Direct.yml index 903a7cd73526..977781de506f 100644 --- a/.github/workflows/beam_PreCommit_Java_InfluxDb_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_InfluxDb_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_JDBC_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_JDBC_IO_Direct.yml index 071cdb3bda3e..4759d48d979f 100644 --- a/.github/workflows/beam_PreCommit_Java_JDBC_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_JDBC_IO_Direct.yml @@ -112,6 +112,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Jms_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Jms_IO_Direct.yml index 650036345274..935315463358 100644 --- a/.github/workflows/beam_PreCommit_Java_Jms_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Jms_IO_Direct.yml @@ -112,6 +112,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Kafka_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Kafka_IO_Direct.yml index 0ede01376ce7..f177ec85fada 100644 --- a/.github/workflows/beam_PreCommit_Java_Kafka_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Kafka_IO_Direct.yml @@ -114,6 +114,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Kinesis_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Kinesis_IO_Direct.yml index 494a738abf45..785748e793e9 100644 --- a/.github/workflows/beam_PreCommit_Java_Kinesis_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Kinesis_IO_Direct.yml @@ -137,6 +137,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Kudu_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Kudu_IO_Direct.yml index e38c9a761dee..853e52db14db 100644 --- a/.github/workflows/beam_PreCommit_Java_Kudu_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Kudu_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_MongoDb_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_MongoDb_IO_Direct.yml index 11be57c05759..b3292ac5f29b 100644 --- a/.github/workflows/beam_PreCommit_Java_MongoDb_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_MongoDb_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Mqtt_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Mqtt_IO_Direct.yml index ac8800f55cdf..ed0189d8006b 100644 --- a/.github/workflows/beam_PreCommit_Java_Mqtt_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Mqtt_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Neo4j_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Neo4j_IO_Direct.yml index 553300f1889c..62429a611f2a 100644 --- a/.github/workflows/beam_PreCommit_Java_Neo4j_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Neo4j_IO_Direct.yml @@ -114,6 +114,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_PVR_Flink_Docker.yml b/.github/workflows/beam_PreCommit_Java_PVR_Flink_Docker.yml index 5feb0270c68c..48f165f4e59f 100644 --- a/.github/workflows/beam_PreCommit_Java_PVR_Flink_Docker.yml +++ b/.github/workflows/beam_PreCommit_Java_PVR_Flink_Docker.yml @@ -115,3 +115,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true diff --git a/.github/workflows/beam_PreCommit_Java_Parquet_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Parquet_IO_Direct.yml index 0bec073fc37b..d217f0e88c39 100644 --- a/.github/workflows/beam_PreCommit_Java_Parquet_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Parquet_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Pulsar_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Pulsar_IO_Direct.yml index e25b4ff6fa94..3a9d62fb64c6 100644 --- a/.github/workflows/beam_PreCommit_Java_Pulsar_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Pulsar_IO_Direct.yml @@ -123,6 +123,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_RabbitMq_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_RabbitMq_IO_Direct.yml index eb343f193395..c72b04bc108d 100644 --- a/.github/workflows/beam_PreCommit_Java_RabbitMq_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_RabbitMq_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Redis_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Redis_IO_Direct.yml index 13b9c26b4b81..cd4ddc387ffc 100644 --- a/.github/workflows/beam_PreCommit_Java_Redis_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Redis_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_RequestResponse_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_RequestResponse_IO_Direct.yml index f1e8c3699aa6..1037ab972447 100644 --- a/.github/workflows/beam_PreCommit_Java_RequestResponse_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_RequestResponse_IO_Direct.yml @@ -103,6 +103,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_SingleStore_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_SingleStore_IO_Direct.yml index 4d289882353e..478dad9989b9 100644 --- a/.github/workflows/beam_PreCommit_Java_SingleStore_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_SingleStore_IO_Direct.yml @@ -107,6 +107,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Snowflake_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Snowflake_IO_Direct.yml index 03577eff2860..403c26ac0ab0 100644 --- a/.github/workflows/beam_PreCommit_Java_Snowflake_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Snowflake_IO_Direct.yml @@ -116,6 +116,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Solace_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Solace_IO_Direct.yml index 5aeaaec11dec..ca05b44875cb 100644 --- a/.github/workflows/beam_PreCommit_Java_Solace_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Solace_IO_Direct.yml @@ -112,6 +112,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Solr_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Solr_IO_Direct.yml index e6138a0c10d9..80cd5e492992 100644 --- a/.github/workflows/beam_PreCommit_Java_Solr_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Solr_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Spark3_Versions.yml b/.github/workflows/beam_PreCommit_Java_Spark3_Versions.yml index 18f5a6c0c86e..c6b2d7e57128 100644 --- a/.github/workflows/beam_PreCommit_Java_Spark3_Versions.yml +++ b/.github/workflows/beam_PreCommit_Java_Spark3_Versions.yml @@ -112,4 +112,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/build/test-results/**/*.xml' \ No newline at end of file + files: '**/build/test-results/**/*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Java_Splunk_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Splunk_IO_Direct.yml index 73a1a0b5cdb2..53f3c4327739 100644 --- a/.github/workflows/beam_PreCommit_Java_Splunk_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Splunk_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Thrift_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Thrift_IO_Direct.yml index 4cddfa728cc1..b5336537c556 100644 --- a/.github/workflows/beam_PreCommit_Java_Thrift_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Thrift_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Java_Tika_IO_Direct.yml b/.github/workflows/beam_PreCommit_Java_Tika_IO_Direct.yml index e08b5048b359..195e9aa1f168 100644 --- a/.github/workflows/beam_PreCommit_Java_Tika_IO_Direct.yml +++ b/.github/workflows/beam_PreCommit_Java_Tika_IO_Direct.yml @@ -105,6 +105,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Python.yml b/.github/workflows/beam_PreCommit_Python.yml index fb1c6c80873a..68c69ae953a4 100644 --- a/.github/workflows/beam_PreCommit_Python.yml +++ b/.github/workflows/beam_PreCommit_Python.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_Coverage.yml b/.github/workflows/beam_PreCommit_Python_Coverage.yml index 0e295250817d..3c7c3b05d8bc 100644 --- a/.github/workflows/beam_PreCommit_Python_Coverage.yml +++ b/.github/workflows/beam_PreCommit_Python_Coverage.yml @@ -104,4 +104,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_Dataframes.yml b/.github/workflows/beam_PreCommit_Python_Dataframes.yml index f045842e061d..ecbb1a30e5f7 100644 --- a/.github/workflows/beam_PreCommit_Python_Dataframes.yml +++ b/.github/workflows/beam_PreCommit_Python_Dataframes.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_Examples.yml b/.github/workflows/beam_PreCommit_Python_Examples.yml index 09d46217d6d6..44329f63014d 100644 --- a/.github/workflows/beam_PreCommit_Python_Examples.yml +++ b/.github/workflows/beam_PreCommit_Python_Examples.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_Integration.yml b/.github/workflows/beam_PreCommit_Python_Integration.yml index 20aade431f6d..3a709c70f077 100644 --- a/.github/workflows/beam_PreCommit_Python_Integration.yml +++ b/.github/workflows/beam_PreCommit_Python_Integration.yml @@ -116,4 +116,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_ML.yml b/.github/workflows/beam_PreCommit_Python_ML.yml index 714eceef5f6b..3b3a2150ac28 100644 --- a/.github/workflows/beam_PreCommit_Python_ML.yml +++ b/.github/workflows/beam_PreCommit_Python_ML.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_PVR_Flink.yml b/.github/workflows/beam_PreCommit_Python_PVR_Flink.yml index dbc1264fcc04..5dd12d49ccd9 100644 --- a/.github/workflows/beam_PreCommit_Python_PVR_Flink.yml +++ b/.github/workflows/beam_PreCommit_Python_PVR_Flink.yml @@ -125,4 +125,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_Runners.yml b/.github/workflows/beam_PreCommit_Python_Runners.yml index 5db6e94be781..689d9b2c3c3f 100644 --- a/.github/workflows/beam_PreCommit_Python_Runners.yml +++ b/.github/workflows/beam_PreCommit_Python_Runners.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_Python_Transforms.yml b/.github/workflows/beam_PreCommit_Python_Transforms.yml index 820ca3e26df6..431b82c02fb7 100644 --- a/.github/workflows/beam_PreCommit_Python_Transforms.yml +++ b/.github/workflows/beam_PreCommit_Python_Transforms.yml @@ -109,4 +109,5 @@ jobs: with: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} - files: '**/pytest*.xml' \ No newline at end of file + files: '**/pytest*.xml' + large_files: true \ No newline at end of file diff --git a/.github/workflows/beam_PreCommit_SQL.yml b/.github/workflows/beam_PreCommit_SQL.yml index b4002fcc2a79..5bc8bb581955 100644 --- a/.github/workflows/beam_PreCommit_SQL.yml +++ b/.github/workflows/beam_PreCommit_SQL.yml @@ -103,6 +103,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_SQL_Java17.yml b/.github/workflows/beam_PreCommit_SQL_Java17.yml index 0e5dcc87d16f..1cfd7502389d 100644 --- a/.github/workflows/beam_PreCommit_SQL_Java17.yml +++ b/.github/workflows/beam_PreCommit_SQL_Java17.yml @@ -110,6 +110,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_SQL_Java8.yml b/.github/workflows/beam_PreCommit_SQL_Java8.yml index 23938821b2e8..6b59739dd72d 100644 --- a/.github/workflows/beam_PreCommit_SQL_Java8.yml +++ b/.github/workflows/beam_PreCommit_SQL_Java8.yml @@ -114,6 +114,7 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' + large_files: true - name: Archive SpotBugs Results uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/beam_PreCommit_Yaml_Xlang_Direct.yml b/.github/workflows/beam_PreCommit_Yaml_Xlang_Direct.yml index b17913946a7e..b9e310a7a133 100644 --- a/.github/workflows/beam_PreCommit_Yaml_Xlang_Direct.yml +++ b/.github/workflows/beam_PreCommit_Yaml_Xlang_Direct.yml @@ -105,3 +105,4 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/pytest*.xml' + large_files: true From e2ff659c98c245df7fa8b833b297b8c75895f804 Mon Sep 17 00:00:00 2001 From: Bartosz Zablocki Date: Wed, 11 Dec 2024 17:11:05 +0000 Subject: [PATCH 098/135] Reapply "SolaceIO.Read: handle occasional cases when finalizeCheckpoint is not executed (#32962)" (#33259) (#33269) This reverts commit 4356253a5e8124bf39152a9ead9ad26ef7267750. --- .../broker/BasicAuthJcsmpSessionService.java | 18 +-- .../sdk/io/solace/broker/MessageReceiver.java | 7 - .../sdk/io/solace/broker/SessionService.java | 7 - .../solace/broker/SolaceMessageReceiver.java | 17 +-- .../io/solace/read/SolaceCheckpointMark.java | 46 +++--- .../io/solace/read/UnboundedSolaceReader.java | 135 +++++++++++++----- .../io/solace/MockEmptySessionService.java | 5 - .../sdk/io/solace/MockSessionService.java | 10 -- .../beam/sdk/io/solace/SolaceIOReadTest.java | 20 +-- 9 files changed, 138 insertions(+), 127 deletions(-) diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java index b2196dbf1067..d4c9a3ec6210 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BasicAuthJcsmpSessionService.java @@ -102,10 +102,7 @@ public void close() { if (messageReceiver != null) { messageReceiver.close(); } - if (messageProducer != null) { - messageProducer.close(); - } - if (!isClosed()) { + if (jcsmpSession != null) { checkStateNotNull(jcsmpSession).closeSession(); } return 0; @@ -119,8 +116,9 @@ public MessageReceiver getReceiver() { this.messageReceiver = retryCallableManager.retryCallable( this::createFlowReceiver, ImmutableSet.of(JCSMPException.class)); + this.messageReceiver.start(); } - return this.messageReceiver; + return checkStateNotNull(this.messageReceiver); } @Override @@ -138,15 +136,10 @@ public java.util.Queue getPublishedResultsQueue() { return publishedResultsQueue; } - @Override - public boolean isClosed() { - return jcsmpSession == null || jcsmpSession.isClosed(); - } - private MessageProducer createXMLMessageProducer(SubmissionMode submissionMode) throws JCSMPException, IOException { - if (isClosed()) { + if (jcsmpSession == null) { connectWriteSession(submissionMode); } @@ -165,9 +158,6 @@ private MessageProducer createXMLMessageProducer(SubmissionMode submissionMode) } private MessageReceiver createFlowReceiver() throws JCSMPException, IOException { - if (isClosed()) { - connectSession(); - } Queue queue = JCSMPFactory.onlyInstance() diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java index 95f989bd1be9..017a63260678 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/MessageReceiver.java @@ -35,13 +35,6 @@ public interface MessageReceiver { */ void start(); - /** - * Returns {@literal true} if the message receiver is closed, {@literal false} otherwise. - * - *

A message receiver is closed when it is no longer able to receive messages. - */ - boolean isClosed(); - /** * Receives a message from the broker. * diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java index 84a876a9d0bc..6dcd0b652616 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java @@ -120,13 +120,6 @@ public abstract class SessionService implements Serializable { /** Gracefully closes the connection to the service. */ public abstract void close(); - /** - * Checks whether the connection to the service is currently closed. This method is called when an - * `UnboundedSolaceReader` is starting to read messages - a session will be created if this - * returns true. - */ - public abstract boolean isClosed(); - /** * Returns a MessageReceiver object for receiving messages from Solace. If it is the first time * this method is used, the receiver is created from the session instance, otherwise it returns diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java index d548d2049a5b..d74f3cae89fe 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java @@ -24,12 +24,8 @@ import java.io.IOException; import org.apache.beam.sdk.io.solace.RetryCallableManager; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class SolaceMessageReceiver implements MessageReceiver { - private static final Logger LOG = LoggerFactory.getLogger(SolaceMessageReceiver.class); - public static final int DEFAULT_ADVANCE_TIMEOUT_IN_MILLIS = 100; private final FlowReceiver flowReceiver; private final RetryCallableManager retryCallableManager = RetryCallableManager.create(); @@ -52,19 +48,14 @@ private void startFlowReceiver() { ImmutableSet.of(JCSMPException.class)); } - @Override - public boolean isClosed() { - return flowReceiver == null || flowReceiver.isClosed(); - } - @Override public BytesXMLMessage receive() throws IOException { try { return flowReceiver.receive(DEFAULT_ADVANCE_TIMEOUT_IN_MILLIS); } catch (StaleSessionException e) { - LOG.warn("SolaceIO: Caught StaleSessionException, restarting the FlowReceiver."); startFlowReceiver(); - throw new IOException(e); + throw new IOException( + "SolaceIO: Caught StaleSessionException, restarting the FlowReceiver.", e); } catch (JCSMPException e) { throw new IOException(e); } @@ -72,8 +63,6 @@ public BytesXMLMessage receive() throws IOException { @Override public void close() { - if (!isClosed()) { - this.flowReceiver.close(); - } + flowReceiver.close(); } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java index 77f6eed8f62c..a913fd6133ea 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java @@ -18,17 +18,16 @@ package org.apache.beam.sdk.io.solace.read; import com.solacesystems.jcsmp.BytesXMLMessage; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Queue; import org.apache.beam.sdk.annotations.Internal; import org.apache.beam.sdk.coders.DefaultCoder; import org.apache.beam.sdk.extensions.avro.coders.AvroCoder; import org.apache.beam.sdk.io.UnboundedSource; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Checkpoint for an unbounded Solace source. Consists of the Solace messages waiting to be @@ -38,10 +37,8 @@ @Internal @VisibleForTesting public class SolaceCheckpointMark implements UnboundedSource.CheckpointMark { - private transient AtomicBoolean activeReader; - // BytesXMLMessage is not serializable so if a job restarts from the checkpoint, we cannot retry - // these messages here. We relay on Solace's retry mechanism. - private transient ArrayDeque ackQueue; + private static final Logger LOG = LoggerFactory.getLogger(SolaceCheckpointMark.class); + private transient Queue safeToAck; @SuppressWarnings("initialization") // Avro will set the fields by breaking abstraction private SolaceCheckpointMark() {} @@ -49,25 +46,24 @@ private SolaceCheckpointMark() {} /** * Creates a new {@link SolaceCheckpointMark}. * - * @param activeReader {@link AtomicBoolean} indicating if the related reader is active. The - * reader creating the messages has to be active to acknowledge the messages. - * @param ackQueue {@link List} of {@link BytesXMLMessage} to be acknowledged. + * @param safeToAck - a queue of {@link BytesXMLMessage} to be acknowledged. */ - SolaceCheckpointMark(AtomicBoolean activeReader, List ackQueue) { - this.activeReader = activeReader; - this.ackQueue = new ArrayDeque<>(ackQueue); + SolaceCheckpointMark(Queue safeToAck) { + this.safeToAck = safeToAck; } @Override public void finalizeCheckpoint() { - if (activeReader == null || !activeReader.get() || ackQueue == null) { - return; - } - - while (!ackQueue.isEmpty()) { - BytesXMLMessage msg = ackQueue.poll(); - if (msg != null) { + BytesXMLMessage msg; + while ((msg = safeToAck.poll()) != null) { + try { msg.ackMessage(); + } catch (IllegalStateException e) { + LOG.error( + "SolaceIO.Read: cannot acknowledge the message with applicationMessageId={}, ackMessageId={}. It will not be retried.", + msg.getApplicationMessageId(), + msg.getAckMessageId(), + e); } } } @@ -84,15 +80,11 @@ public boolean equals(@Nullable Object o) { return false; } SolaceCheckpointMark that = (SolaceCheckpointMark) o; - // Needed to convert to ArrayList because ArrayDeque.equals checks only for reference, not - // content. - ArrayList ackList = new ArrayList<>(ackQueue); - ArrayList thatAckList = new ArrayList<>(that.ackQueue); - return Objects.equals(activeReader, that.activeReader) && Objects.equals(ackList, thatAckList); + return Objects.equals(safeToAck, that.safeToAck); } @Override public int hashCode() { - return Objects.hash(activeReader, ackQueue); + return Objects.hash(safeToAck); } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java index a421970370da..dc84e0a07017 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java @@ -22,17 +22,26 @@ import com.solacesystems.jcsmp.BytesXMLMessage; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.apache.beam.sdk.io.UnboundedSource; import org.apache.beam.sdk.io.UnboundedSource.UnboundedReader; import org.apache.beam.sdk.io.solace.broker.SempClient; import org.apache.beam.sdk.io.solace.broker.SessionService; +import org.apache.beam.sdk.io.solace.broker.SessionServiceFactory; import org.apache.beam.sdk.transforms.windowing.BoundedWindow; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.Cache; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.CacheBuilder; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.RemovalNotification; import org.checkerframework.checker.nullness.qual.Nullable; import org.joda.time.Instant; import org.slf4j.Logger; @@ -46,48 +55,92 @@ class UnboundedSolaceReader extends UnboundedReader { private final UnboundedSolaceSource currentSource; private final WatermarkPolicy watermarkPolicy; private final SempClient sempClient; + private final UUID readerUuid; + private final SessionServiceFactory sessionServiceFactory; private @Nullable BytesXMLMessage solaceOriginalRecord; private @Nullable T solaceMappedRecord; - private @Nullable SessionService sessionService; - AtomicBoolean active = new AtomicBoolean(true); /** - * Queue to place advanced messages before {@link #getCheckpointMark()} be called non-concurrent - * queue, should only be accessed by the reader thread A given {@link UnboundedReader} object will - * only be accessed by a single thread at once. + * Queue to place advanced messages before {@link #getCheckpointMark()} is called. CAUTION: + * Accessed by both reader and checkpointing threads. */ - private final java.util.Queue elementsToCheckpoint = new ArrayDeque<>(); + private final Queue safeToAckMessages = new ConcurrentLinkedQueue<>(); + + /** + * Queue for messages that were ingested in the {@link #advance()} method, but not sent yet to a + * {@link SolaceCheckpointMark}. + */ + private final Queue receivedMessages = new ArrayDeque<>(); + + private static final Cache sessionServiceCache; + private static final ScheduledExecutorService cleanUpThread = Executors.newScheduledThreadPool(1); + + static { + Duration cacheExpirationTimeout = Duration.ofMinutes(1); + sessionServiceCache = + CacheBuilder.newBuilder() + .expireAfterAccess(cacheExpirationTimeout) + .removalListener( + (RemovalNotification notification) -> { + LOG.info( + "SolaceIO.Read: Closing session for the reader with uuid {} as it has been idle for over {}.", + notification.getKey(), + cacheExpirationTimeout); + SessionService sessionService = notification.getValue(); + if (sessionService != null) { + sessionService.close(); + } + }) + .build(); + + startCleanUpThread(); + } + + @SuppressWarnings("FutureReturnValueIgnored") + private static void startCleanUpThread() { + cleanUpThread.scheduleAtFixedRate(sessionServiceCache::cleanUp, 1, 1, TimeUnit.MINUTES); + } public UnboundedSolaceReader(UnboundedSolaceSource currentSource) { this.currentSource = currentSource; this.watermarkPolicy = WatermarkPolicy.create( currentSource.getTimestampFn(), currentSource.getWatermarkIdleDurationThreshold()); - this.sessionService = currentSource.getSessionServiceFactory().create(); + this.sessionServiceFactory = currentSource.getSessionServiceFactory(); this.sempClient = currentSource.getSempClientFactory().create(); + this.readerUuid = UUID.randomUUID(); + } + + private SessionService getSessionService() { + try { + return sessionServiceCache.get( + readerUuid, + () -> { + LOG.info("SolaceIO.Read: creating a new session for reader with uuid {}.", readerUuid); + SessionService sessionService = sessionServiceFactory.create(); + sessionService.connect(); + sessionService.getReceiver().start(); + return sessionService; + }); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } } @Override public boolean start() { - populateSession(); - checkNotNull(sessionService).getReceiver().start(); + // Create and initialize SessionService with Receiver + getSessionService(); return advance(); } - public void populateSession() { - if (sessionService == null) { - sessionService = getCurrentSource().getSessionServiceFactory().create(); - } - if (sessionService.isClosed()) { - checkNotNull(sessionService).connect(); - } - } - @Override public boolean advance() { + finalizeReadyMessages(); + BytesXMLMessage receivedXmlMessage; try { - receivedXmlMessage = checkNotNull(sessionService).getReceiver().receive(); + receivedXmlMessage = getSessionService().getReceiver().receive(); } catch (IOException e) { LOG.warn("SolaceIO.Read: Exception when pulling messages from the broker.", e); return false; @@ -96,23 +149,40 @@ public boolean advance() { if (receivedXmlMessage == null) { return false; } - elementsToCheckpoint.add(receivedXmlMessage); solaceOriginalRecord = receivedXmlMessage; solaceMappedRecord = getCurrentSource().getParseFn().apply(receivedXmlMessage); - watermarkPolicy.update(solaceMappedRecord); + receivedMessages.add(receivedXmlMessage); + return true; } @Override public void close() { - active.set(false); - checkNotNull(sessionService).close(); + finalizeReadyMessages(); + sessionServiceCache.invalidate(readerUuid); + } + + public void finalizeReadyMessages() { + BytesXMLMessage msg; + while ((msg = safeToAckMessages.poll()) != null) { + try { + msg.ackMessage(); + } catch (IllegalStateException e) { + LOG.error( + "SolaceIO.Read: failed to acknowledge the message with applicationMessageId={}, ackMessageId={}. Returning the message to queue to retry.", + msg.getApplicationMessageId(), + msg.getAckMessageId(), + e); + safeToAckMessages.add(msg); // In case the error was transient, might succeed later + break; // Commit is only best effort + } + } } @Override public Instant getWatermark() { // should be only used by a test receiver - if (checkNotNull(sessionService).getReceiver().isEOF()) { + if (getSessionService().getReceiver().isEOF()) { return BoundedWindow.TIMESTAMP_MAX_VALUE; } return watermarkPolicy.getWatermark(); @@ -120,14 +190,9 @@ public Instant getWatermark() { @Override public UnboundedSource.CheckpointMark getCheckpointMark() { - List ackQueue = new ArrayList<>(); - while (!elementsToCheckpoint.isEmpty()) { - BytesXMLMessage msg = elementsToCheckpoint.poll(); - if (msg != null) { - ackQueue.add(msg); - } - } - return new SolaceCheckpointMark(active, ackQueue); + safeToAckMessages.addAll(receivedMessages); + receivedMessages.clear(); + return new SolaceCheckpointMark(safeToAckMessages); } @Override diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java index 38b4953a5984..7631d32f63cc 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java @@ -40,11 +40,6 @@ public void close() { throw new UnsupportedOperationException(exceptionMessage); } - @Override - public boolean isClosed() { - throw new UnsupportedOperationException(exceptionMessage); - } - @Override public MessageReceiver getReceiver() { throw new UnsupportedOperationException(exceptionMessage); diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java index bd52dee7ea86..6d28bcefc84c 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java @@ -77,11 +77,6 @@ public abstract Builder mockProducerFn( @Override public void close() {} - @Override - public boolean isClosed() { - return false; - } - @Override public MessageReceiver getReceiver() { if (messageReceiver == null) { @@ -131,11 +126,6 @@ public MockReceiver( @Override public void start() {} - @Override - public boolean isClosed() { - return false; - } - @Override public BytesXMLMessage receive() throws IOException { return getRecordFn.apply(counter.getAndIncrement()); diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java index c718c55e1b48..a1f80932eddf 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java @@ -447,25 +447,29 @@ public void testCheckpointMarkAndFinalizeSeparately() throws Exception { // start the reader and move to the first record assertTrue(reader.start()); - // consume 3 messages (NB: start already consumed the first message) + // consume 3 messages (NB: #start() already consumed the first message) for (int i = 0; i < 3; i++) { assertTrue(String.format("Failed at %d-th message", i), reader.advance()); } - // create checkpoint but don't finalize yet + // #advance() was called, but the messages were not ready to be acknowledged. + assertEquals(0, countAckMessages.get()); + + // mark all consumed messages as ready to be acknowledged CheckpointMark checkpointMark = reader.getCheckpointMark(); - // consume 2 more messages - reader.advance(); + // consume 1 more message. This will call #ackMsg() on messages that were ready to be acked. reader.advance(); + assertEquals(4, countAckMessages.get()); - // check if messages are still not acknowledged - assertEquals(0, countAckMessages.get()); + // consume 1 more message. No change in the acknowledged messages. + reader.advance(); + assertEquals(4, countAckMessages.get()); // acknowledge from the first checkpoint checkpointMark.finalizeCheckpoint(); - - // only messages from the first checkpoint are acknowledged + // No change in the acknowledged messages, because they were acknowledged in the #advance() + // method. assertEquals(4, countAckMessages.get()); } From a27282363aa2b97dda96750f6d1cbf55ebd752ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Stankiewicz?= Date: Wed, 11 Dec 2024 20:35:42 +0100 Subject: [PATCH 099/135] Update confluent version to fix CVE-2024-26308 CVE-2024-25710 (#32674) * bump confluent version Kafka Schema Registry Client has been reported with following vuln CVE-2024-26308 CVE-2024-25710 due to vulnerable dependencies. * try slighly older version due to unmet dependencies to ThrottlingQuotaExceededException * try slighly older version due to unmet dependencies to ThrottlingQuotaExceededException * comment on version --- sdks/java/io/kafka/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdks/java/io/kafka/build.gradle b/sdks/java/io/kafka/build.gradle index c2f056b0b7cb..04563c478d6d 100644 --- a/sdks/java/io/kafka/build.gradle +++ b/sdks/java/io/kafka/build.gradle @@ -31,7 +31,8 @@ enableJavaPerformanceTesting() description = "Apache Beam :: SDKs :: Java :: IO :: Kafka" ext { summary = "Library to read Kafka topics." - confluentVersion = "7.6.0" + // newer versions e.g. 7.6.* require dropping support for older kafka versions. + confluentVersion = "7.5.5" } def kafkaVersions = [ From 3dcd10424f66e20ec746eb9f7ff3122cf716c53c Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 11 Dec 2024 11:52:45 -0800 Subject: [PATCH 100/135] Update reference. --- sdks/python/apache_beam/metrics/cells.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index e044693a6e9a..8b4b7e1271c2 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -318,7 +318,7 @@ def to_runner_api_monitoring_info_impl(self, name, transform_id): class BoundedTrieCell(AbstractMetricCell): """For internal use only; no backwards-compatibility guarantees. - Tracks the current value for a StringSet metric. + Tracks the current value for a BoundedTrie metric. Each cell tracks the state of a metric independently per context per bundle. Therefore, each metric has a different cell in each bundle, that is later From bb0c910b195bc44bcef0e268a8b08cc25ab44ef8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:27:43 -0500 Subject: [PATCH 101/135] Bump cloud.google.com/go/profiler from 0.4.1 to 0.4.2 in /sdks (#33351) Bumps [cloud.google.com/go/profiler](https://github.com/googleapis/google-cloud-go) from 0.4.1 to 0.4.2. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/ai/v0.4.1...apps/v0.4.2) --- updated-dependencies: - dependency-name: cloud.google.com/go/profiler dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 4 ++-- sdks/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 5406c2b70cbc..30433676f6f4 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -26,7 +26,7 @@ require ( cloud.google.com/go/bigquery v1.64.0 cloud.google.com/go/bigtable v1.33.0 cloud.google.com/go/datastore v1.20.0 - cloud.google.com/go/profiler v0.4.1 + cloud.google.com/go/profiler v0.4.2 cloud.google.com/go/pubsub v1.45.3 cloud.google.com/go/spanner v1.73.0 cloud.google.com/go/storage v1.47.0 @@ -159,7 +159,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect - github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect diff --git a/sdks/go.sum b/sdks/go.sum index bb96c54af087..6394ce515c4d 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -441,8 +441,8 @@ cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//h cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/profiler v0.4.1 h1:Q7+lOvikTGMJ/IAWocpYYGit4SIIoILmVZfEEWTORSY= -cloud.google.com/go/profiler v0.4.1/go.mod h1:LBrtEX6nbvhv1w/e5CPZmX9ajGG9BGLtGbv56Tg4SHs= +cloud.google.com/go/profiler v0.4.2 h1:KojCmZ+bEPIQrd7bo2UFvZ2xUPLHl55KzHl7iaR4V2I= +cloud.google.com/go/profiler v0.4.2/go.mod h1:7GcWzs9deJHHdJ5J9V1DzKQ9JoIoTGhezwlLbwkOoCs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -948,8 +948,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g= -github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= From 224900c42a6723ec4d08b1acae6d3e16f41ddb0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:28:14 -0500 Subject: [PATCH 102/135] Bump cloud.google.com/go/storage from 1.47.0 to 1.48.0 in /sdks (#33327) Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.47.0 to 1.48.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.47.0...spanner/v1.48.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 4 ++-- sdks/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 30433676f6f4..9eda64804cb6 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -29,7 +29,7 @@ require ( cloud.google.com/go/profiler v0.4.2 cloud.google.com/go/pubsub v1.45.3 cloud.google.com/go/spanner v1.73.0 - cloud.google.com/go/storage v1.47.0 + cloud.google.com/go/storage v1.48.0 github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.28.6 github.com/aws/aws-sdk-go-v2/credentials v1.17.47 @@ -60,7 +60,7 @@ require ( golang.org/x/text v0.21.0 google.golang.org/api v0.210.0 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 - google.golang.org/grpc v1.67.1 + google.golang.org/grpc v1.67.2 google.golang.org/protobuf v1.35.2 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/sdks/go.sum b/sdks/go.sum index 6394ce515c4d..eae0e0007706 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -561,8 +561,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= -cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= +cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= +cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1895,8 +1895,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.67.2 h1:Lq11HW1nr5m4OYV+ZVy2BjOK78/zqnTx24vyDBP1JcQ= +google.golang.org/grpc v1.67.2/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= From 272feb8ff69a1d6174d04693347ca9bd2ac1980b Mon Sep 17 00:00:00 2001 From: Shunping Huang Date: Thu, 12 Dec 2024 10:30:38 -0500 Subject: [PATCH 103/135] Fix custom coder not being used in Reshuffle (global window) (#33339) * Fix typehint in ReshufflePerKey on global window setting. * Only update the type hint on global window setting. Need more work in non-global windows. * Apply yapf * Fix some failed tests. * Revert change to setup.py --- sdks/python/apache_beam/transforms/util.py | 13 ++++++- .../apache_beam/transforms/util_test.py | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/sdks/python/apache_beam/transforms/util.py b/sdks/python/apache_beam/transforms/util.py index a03652de2496..43d4a6c20e94 100644 --- a/sdks/python/apache_beam/transforms/util.py +++ b/sdks/python/apache_beam/transforms/util.py @@ -33,6 +33,7 @@ from typing import Callable from typing import Iterable from typing import List +from typing import Optional from typing import Tuple from typing import TypeVar from typing import Union @@ -78,6 +79,7 @@ from apache_beam.utils import windowed_value from apache_beam.utils.annotations import deprecated from apache_beam.utils.sharded_key import ShardedKey +from apache_beam.utils.timestamp import Timestamp if TYPE_CHECKING: from apache_beam.runners.pipeline_context import PipelineContext @@ -953,6 +955,10 @@ def restore_timestamps(element): window.GlobalWindows.windowed_value((key, value), timestamp) for (value, timestamp) in values ] + + ungrouped = pcoll | Map(reify_timestamps).with_input_types( + Tuple[K, V]).with_output_types( + Tuple[K, Tuple[V, Optional[Timestamp]]]) else: # typing: All conditional function variants must have identical signatures @@ -966,7 +972,9 @@ def restore_timestamps(element): key, windowed_values = element return [wv.with_value((key, wv.value)) for wv in windowed_values] - ungrouped = pcoll | Map(reify_timestamps).with_output_types(Any) + # TODO(https://github.com/apache/beam/issues/33356): Support reshuffling + # unpicklable objects with a non-global window setting. + ungrouped = pcoll | Map(reify_timestamps).with_output_types(Any) # TODO(https://github.com/apache/beam/issues/19785) Using global window as # one of the standard window. This is to mitigate the Dataflow Java Runner @@ -1018,7 +1026,8 @@ def expand(self, pcoll): pcoll | 'AddRandomKeys' >> Map(lambda t: (random.randrange(0, self.num_buckets), t) ).with_input_types(T).with_output_types(Tuple[int, T]) - | ReshufflePerKey() + | ReshufflePerKey().with_input_types(Tuple[int, T]).with_output_types( + Tuple[int, T]) | 'RemoveRandomKeys' >> Map(lambda t: t[1]).with_input_types( Tuple[int, T]).with_output_types(T)) diff --git a/sdks/python/apache_beam/transforms/util_test.py b/sdks/python/apache_beam/transforms/util_test.py index d86509c7dde3..7f166f78ef0a 100644 --- a/sdks/python/apache_beam/transforms/util_test.py +++ b/sdks/python/apache_beam/transforms/util_test.py @@ -1010,6 +1010,45 @@ def format_with_timestamp(element, timestamp=beam.DoFn.TimestampParam): equal_to(expected_data), label="formatted_after_reshuffle") + def test_reshuffle_unpicklable_in_global_window(self): + global _Unpicklable + + class _Unpicklable(object): + def __init__(self, value): + self.value = value + + def __getstate__(self): + raise NotImplementedError() + + def __setstate__(self, state): + raise NotImplementedError() + + class _UnpicklableCoder(beam.coders.Coder): + def encode(self, value): + return str(value.value).encode() + + def decode(self, encoded): + return _Unpicklable(int(encoded.decode())) + + def to_type_hint(self): + return _Unpicklable + + def is_deterministic(self): + return True + + beam.coders.registry.register_coder(_Unpicklable, _UnpicklableCoder) + + with TestPipeline() as pipeline: + data = [_Unpicklable(i) for i in range(5)] + expected_data = [0, 10, 20, 30, 40] + result = ( + pipeline + | beam.Create(data) + | beam.WindowInto(GlobalWindows()) + | beam.Reshuffle() + | beam.Map(lambda u: u.value * 10)) + assert_that(result, equal_to(expected_data)) + class WithKeysTest(unittest.TestCase): def setUp(self): From 7b883373d4a9dc69e10949f0c1b1c7479df62d48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:36:54 -0800 Subject: [PATCH 104/135] Bump golang.org/x/crypto from 0.30.0 to 0.31.0 in /sdks (#33366) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 2 +- sdks/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 9eda64804cb6..cd993672663b 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -190,7 +190,7 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.30.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/sdks/go.sum b/sdks/go.sum index eae0e0007706..a95dcac4913c 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1266,8 +1266,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= From fd17dcea9cae8a444fcd1fc9708dffdd2e33cda0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:01:10 -0500 Subject: [PATCH 105/135] Update numpy requirement from <2.2.0,>=1.14.3 to >=1.14.3,<2.3.0 in /sdks/python (#33325) * Update numpy requirement in /sdks/python Updates the requirements on [numpy](https://github.com/numpy/numpy) to permit the latest version. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.14.3...v2.2.0) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production ... Signed-off-by: dependabot[bot] * increment in setup.py --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jack McCluskey --- sdks/python/pyproject.toml | 2 +- sdks/python/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 4eb827297019..8000c24f28aa 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -26,7 +26,7 @@ requires = [ # Avoid https://github.com/pypa/virtualenv/issues/2006 "distlib==0.3.7", # Numpy headers - "numpy>=1.14.3,<2.2.0", # Update setup.py as well. + "numpy>=1.14.3,<2.3.0", # Update setup.py as well. # having cython here will create wheels that are platform dependent. "cython>=3.0,<4", ## deps for generating external transform wrappers: diff --git a/sdks/python/setup.py b/sdks/python/setup.py index 53c7a532e706..da9e0b2e7477 100644 --- a/sdks/python/setup.py +++ b/sdks/python/setup.py @@ -361,7 +361,7 @@ def get_portability_package_data(): 'jsonpickle>=3.0.0,<4.0.0', # numpy can have breaking changes in minor versions. # Use a strict upper bound. - 'numpy>=1.14.3,<2.2.0', # Update pyproject.toml as well. + 'numpy>=1.14.3,<2.3.0', # Update pyproject.toml as well. 'objsize>=0.6.1,<0.8.0', 'packaging>=22.0', 'pymongo>=3.8.0,<5.0.0', From 2efd124429b4810dcc1d7f622f48c0fed7fc3c48 Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Thu, 12 Dec 2024 14:09:40 -0500 Subject: [PATCH 106/135] Switch to use unshaded hive-exec for io expansion service (#33349) * Switch to use unshaded hive-exec for io expansion service * This enables the shadow jar pick up dependencies of newer versions * cleanup leftovers --- .../IO_Iceberg_Integration_Tests.json | 2 +- ...eam_PostCommit_Python_Xlang_IO_Direct.json | 2 +- sdks/java/io/expansion-service/build.gradle | 2 +- sdks/java/io/iceberg/hive/build.gradle | 33 ++++++++-- sdks/java/io/iceberg/hive/exec/build.gradle | 65 ------------------- settings.gradle.kts | 2 - 6 files changed, 31 insertions(+), 75 deletions(-) delete mode 100644 sdks/java/io/iceberg/hive/exec/build.gradle diff --git a/.github/trigger_files/IO_Iceberg_Integration_Tests.json b/.github/trigger_files/IO_Iceberg_Integration_Tests.json index 3f63c0c9975f..bbdc3a3910ef 100644 --- a/.github/trigger_files/IO_Iceberg_Integration_Tests.json +++ b/.github/trigger_files/IO_Iceberg_Integration_Tests.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 2 + "modification": 3 } diff --git a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json index b26833333238..e3d6056a5de9 100644 --- a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json +++ b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 2 + "modification": 1 } diff --git a/sdks/java/io/expansion-service/build.gradle b/sdks/java/io/expansion-service/build.gradle index 421719b8f986..a27a66b1f3dc 100644 --- a/sdks/java/io/expansion-service/build.gradle +++ b/sdks/java/io/expansion-service/build.gradle @@ -60,7 +60,7 @@ dependencies { runtimeOnly library.java.bigdataoss_gcs_connector // Needed for HiveCatalog runtimeOnly ("org.apache.iceberg:iceberg-hive-metastore:1.4.2") - runtimeOnly project(path: ":sdks:java:io:iceberg:hive:exec", configuration: "shadow") + runtimeOnly project(path: ":sdks:java:io:iceberg:hive") runtimeOnly library.java.kafka_clients runtimeOnly library.java.slf4j_jdk14 diff --git a/sdks/java/io/iceberg/hive/build.gradle b/sdks/java/io/iceberg/hive/build.gradle index bfa6c75251c4..2d0d2bcc5cde 100644 --- a/sdks/java/io/iceberg/hive/build.gradle +++ b/sdks/java/io/iceberg/hive/build.gradle @@ -21,19 +21,39 @@ plugins { id 'org.apache.beam.module' } applyJavaNature( automaticModuleName: 'org.apache.beam.sdk.io.iceberg.hive', exportJavadoc: false, - shadowClosure: {}, + publish: false, // it's an intermediate jar for io-expansion-service ) description = "Apache Beam :: SDKs :: Java :: IO :: Iceberg :: Hive" ext.summary = "Runtime dependencies needed for Hive catalog integration." def hive_version = "3.1.3" +def hbase_version = "2.6.1-hadoop3" +def hadoop_version = "3.4.1" def iceberg_version = "1.4.2" +def avatica_version = "1.25.0" dependencies { // dependencies needed to run with iceberg's hive catalog - runtimeOnly ("org.apache.iceberg:iceberg-hive-metastore:$iceberg_version") - runtimeOnly project(path: ":sdks:java:io:iceberg:hive:exec", configuration: "shadow") + // these dependencies are going to be included in io-expansion-service + implementation ("org.apache.iceberg:iceberg-hive-metastore:$iceberg_version") + permitUnusedDeclared ("org.apache.iceberg:iceberg-hive-metastore:$iceberg_version") + // analyzeClassesDependencies fails with "Cannot accept visitor on URL", likely the plugin does not recognize "core" classifier + // use "core" classifier to depend on un-shaded jar + runtimeOnly ("org.apache.hive:hive-exec:$hive_version:core") { + // old hadoop-yarn-server-resourcemanager contains critical log4j vulneribility + exclude group: "org.apache.hadoop", module: "hadoop-yarn-server-resourcemanager" + // old hadoop-yarn-server-resourcemanager contains critical log4j and hadoop vulneribility + exclude group: "org.apache.hbase", module: "hbase-client" + // old calcite leaks old protobuf-java + exclude group: "org.apache.calcite.avatica", module: "avatica" + } + runtimeOnly ("org.apache.hadoop:hadoop-yarn-server-resourcemanager:$hadoop_version") + runtimeOnly ("org.apache.hbase:hbase-client:$hbase_version") + runtimeOnly ("org.apache.calcite.avatica:avatica-core:$avatica_version") + implementation ("org.apache.hive:hive-metastore:$hive_version") + runtimeOnly ("org.apache.iceberg:iceberg-parquet:$iceberg_version") + permitUnusedDeclared ("org.apache.hive:hive-metastore:$hive_version") // ----- below dependencies are for testing and will not appear in the shaded jar ----- // Beam IcebergIO dependencies @@ -52,8 +72,6 @@ dependencies { testImplementation library.java.junit // needed to set up test Hive Metastore and run tests - testImplementation ("org.apache.iceberg:iceberg-hive-metastore:$iceberg_version") - testImplementation project(path: ":sdks:java:io:iceberg:hive:exec", configuration: "shadow") testRuntimeOnly ("org.apache.hive.hcatalog:hive-hcatalog-core:$hive_version") { exclude group: "org.apache.hive", module: "hive-exec" exclude group: "org.apache.parquet", module: "parquet-hadoop-bundle" @@ -62,6 +80,11 @@ dependencies { testImplementation "org.apache.parquet:parquet-column:1.12.0" } +configurations.all { + // the fatjar "parquet-hadoop-bundle" conflicts with "parquet-hadoop" used by org.apache.iceberg:iceberg-parquet + exclude group: "org.apache.parquet", module: "parquet-hadoop-bundle" +} + task integrationTest(type: Test) { group = "Verification" def gcpTempLocation = project.findProperty('gcpTempLocation') ?: 'gs://temp-storage-for-end-to-end-tests/iceberg-hive-it' diff --git a/sdks/java/io/iceberg/hive/exec/build.gradle b/sdks/java/io/iceberg/hive/exec/build.gradle deleted file mode 100644 index f266ab2ef4db..000000000000 --- a/sdks/java/io/iceberg/hive/exec/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ -plugins { - id 'org.apache.beam.module' - id 'java' - id 'com.github.johnrengelman.shadow' -} - -dependencies { - implementation("org.apache.hive:hive-exec:3.1.3") - permitUnusedDeclared("org.apache.hive:hive-exec:3.1.3") -} - -configurations { - shadow -} - -artifacts { - shadow(archives(shadowJar) { - builtBy shadowJar - }) -} - -shadowJar { - zip64 true - - def problematicPackages = [ - 'com.google.protobuf', - 'com.google.common', - 'shaded.parquet', - 'org.apache.parquet', - 'org.joda' - ] - - problematicPackages.forEach { - relocate it, getJavaRelocatedPath("iceberg.hive.${it}") - } - - version "3.1.3" - mergeServiceFiles() - - exclude 'LICENSE' - exclude( - 'org/xml/**', - 'javax/**', - 'com/sun/**' - ) -} -description = "Apache Beam :: SDKs :: Java :: IO :: Iceberg :: Hive :: Exec" -ext.summary = "A copy of the hive-exec dependency with some popular libraries relocated." diff --git a/settings.gradle.kts b/settings.gradle.kts index d90bb3fb5b82..a8bee45a05ac 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -357,5 +357,3 @@ include("sdks:java:extensions:combiners") findProject(":sdks:java:extensions:combiners")?.name = "combiners" include("sdks:java:io:iceberg:hive") findProject(":sdks:java:io:iceberg:hive")?.name = "hive" -include("sdks:java:io:iceberg:hive:exec") -findProject(":sdks:java:io:iceberg:hive:exec")?.name = "exec" From e9c0d35ff3390a3ff4cca3562508597dc3a69a65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:21:13 -0500 Subject: [PATCH 107/135] Bump cloud.google.com/go/bigquery from 1.64.0 to 1.65.0 in /sdks (#33365) Bumps [cloud.google.com/go/bigquery](https://github.com/googleapis/google-cloud-go) from 1.64.0 to 1.65.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.64.0...spanner/v1.65.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/bigquery dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 2 +- sdks/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index cd993672663b..e6a182035f5e 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -23,7 +23,7 @@ module github.com/apache/beam/sdks/v2 go 1.21.0 require ( - cloud.google.com/go/bigquery v1.64.0 + cloud.google.com/go/bigquery v1.65.0 cloud.google.com/go/bigtable v1.33.0 cloud.google.com/go/datastore v1.20.0 cloud.google.com/go/profiler v0.4.2 diff --git a/sdks/go.sum b/sdks/go.sum index a95dcac4913c..34e34ef2f8cd 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -133,8 +133,8 @@ cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/Zur cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/bigquery v1.64.0 h1:vSSZisNyhr2ioJE1OuYBQrnrpB7pIhRQm4jfjc7E/js= -cloud.google.com/go/bigquery v1.64.0/go.mod h1:gy8Ooz6HF7QmA+TRtX8tZmXBKH5mCFBwUApGAb3zI7Y= +cloud.google.com/go/bigquery v1.65.0 h1:ZZ1EOJMHTYf6R9lhxIXZJic1qBD4/x9loBIS+82moUs= +cloud.google.com/go/bigquery v1.65.0/go.mod h1:9WXejQ9s5YkTW4ryDYzKXBooL78u5+akWGXgJqQkY6A= cloud.google.com/go/bigtable v1.33.0 h1:2BDaWLRAwXO14DJL/u8crbV2oUbMZkIa2eGq8Yao1bk= cloud.google.com/go/bigtable v1.33.0/go.mod h1:HtpnH4g25VT1pejHRtInlFPnN5sjTxbQlsYBjh9t5l0= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= From de5e8ebcee290a00036c868a1f444018bd445506 Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Thu, 12 Dec 2024 13:04:26 -0800 Subject: [PATCH 108/135] [#32211] Support OnWindowExpiration in Prism. (#33337) --- CHANGES.md | 2 + runners/prism/java/build.gradle | 18 +- .../prism/internal/engine/elementmanager.go | 224 +++++++++++++++--- .../internal/engine/elementmanager_test.go | 160 +++++++++++++ .../beam/runners/prism/internal/execute.go | 4 + .../runners/prism/internal/jobservices/job.go | 1 + .../prism/internal/jobservices/management.go | 2 - .../beam/runners/prism/internal/preprocess.go | 3 + .../pkg/beam/runners/prism/internal/stage.go | 24 +- .../runners/prism/internal/worker/bundle.go | 4 +- 10 files changed, 388 insertions(+), 54 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dbadd588ae3f..ea29b6aadc7d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -69,6 +69,8 @@ ## New Features / Improvements * X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). +* Support OnWindowExpiration in Prism ([#32211](https://github.com/apache/beam/issues/32211)). + * This enables initial Java GroupIntoBatches support. ## Breaking Changes diff --git a/runners/prism/java/build.gradle b/runners/prism/java/build.gradle index 82eb62b9e207..ce71151099bd 100644 --- a/runners/prism/java/build.gradle +++ b/runners/prism/java/build.gradle @@ -106,6 +106,12 @@ def sickbayTests = [ 'org.apache.beam.sdk.testing.TestStreamTest.testMultipleStreams', 'org.apache.beam.sdk.testing.TestStreamTest.testProcessingTimeTrigger', + // GroupIntoBatchesTest tests that fail: + // Teststream has bad KV encodings due to using an outer context. + 'org.apache.beam.sdk.transforms.GroupIntoBatchesTest.testInStreamingMode', + // ShardedKey not yet implemented. + 'org.apache.beam.sdk.transforms.GroupIntoBatchesTest.testWithShardedKeyInGlobalWindow', + // Coding error somehow: short write: reached end of stream after reading 5 bytes; 98 bytes expected 'org.apache.beam.sdk.testing.TestStreamTest.testMultiStage', @@ -228,14 +234,16 @@ def createPrismValidatesRunnerTask = { name, environmentType -> excludeCategories 'org.apache.beam.sdk.testing.UsesSdkHarnessEnvironment' // Not yet implemented in Prism - // https://github.com/apache/beam/issues/32211 - excludeCategories 'org.apache.beam.sdk.testing.UsesOnWindowExpiration' // https://github.com/apache/beam/issues/32929 excludeCategories 'org.apache.beam.sdk.testing.UsesOrderedListState' - // Not supported in Portable Java SDK yet. - // https://github.com/apache/beam/issues?q=is%3Aissue+is%3Aopen+MultimapState - excludeCategories 'org.apache.beam.sdk.testing.UsesMultimapState' + // Not supported in Portable Java SDK yet. + // https://github.com/apache/beam/issues?q=is%3Aissue+is%3Aopen+MultimapState + excludeCategories 'org.apache.beam.sdk.testing.UsesMultimapState' + + // Processing time with TestStream is unreliable without being able to control + // SDK side time portably. Ignore these tests. + excludeCategories 'org.apache.beam.sdk.testing.UsesTestStreamWithProcessingTime' } filter { for (String test : sickbayTests) { diff --git a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go index 1739efdb742a..00e18c669afa 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go +++ b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go @@ -184,7 +184,8 @@ type Config struct { // // Watermarks are advanced based on consumed input, except if the stage produces residuals. type ElementManager struct { - config Config + config Config + nextBundID func() string // Generates unique bundleIDs. Set in the Bundles method. impulses set[string] // List of impulse stages. stages map[string]*stageState // The state for each stage. @@ -197,6 +198,7 @@ type ElementManager struct { refreshCond sync.Cond // refreshCond protects the following fields with it's lock, and unblocks bundle scheduling. inprogressBundles set[string] // Active bundleIDs changedStages set[string] // Stages that have changed and need their watermark refreshed. + injectedBundles []RunBundle // Represents ready to execute bundles prepared outside of the main loop, such as for onWindowExpiration, or for Triggers. livePending atomic.Int64 // An accessible live pending count. DEBUG USE ONLY pendingElements sync.WaitGroup // pendingElements counts all unprocessed elements in a job. Jobs with no pending elements terminate successfully. @@ -271,6 +273,16 @@ func (em *ElementManager) StageStateful(ID string) { em.stages[ID].stateful = true } +// StageOnWindowExpiration marks the given stage as stateful, which means elements are +// processed by key. +func (em *ElementManager) StageOnWindowExpiration(stageID string, timer StaticTimerID) { + ss := em.stages[stageID] + ss.onWindowExpiration = timer + ss.keysToExpireByWindow = map[typex.Window]set[string]{} + ss.inProgressExpiredWindows = map[typex.Window]int{} + ss.expiryWindowsByBundles = map[string]typex.Window{} +} + // StageProcessingTimeTimers indicates which timers are processingTime domain timers. func (em *ElementManager) StageProcessingTimeTimers(ID string, ptTimers map[string]bool) { em.stages[ID].processingTimeTimersFamilies = ptTimers @@ -338,6 +350,8 @@ func (rb RunBundle) LogValue() slog.Value { // The returned channel is closed when the context is canceled, or there are no pending elements // remaining. func (em *ElementManager) Bundles(ctx context.Context, upstreamCancelFn context.CancelCauseFunc, nextBundID func() string) <-chan RunBundle { + // Make it easier for injected bundles to get unique IDs. + em.nextBundID = nextBundID runStageCh := make(chan RunBundle) ctx, cancelFn := context.WithCancelCause(ctx) go func() { @@ -370,8 +384,9 @@ func (em *ElementManager) Bundles(ctx context.Context, upstreamCancelFn context. changedByProcessingTime := em.processTimeEvents.AdvanceTo(emNow) em.changedStages.merge(changedByProcessingTime) - // If there are no changed stages or ready processing time events available, we wait until there are. - for len(em.changedStages)+len(changedByProcessingTime) == 0 { + // If there are no changed stages, ready processing time events, + // or injected bundles available, we wait until there are. + for len(em.changedStages)+len(changedByProcessingTime)+len(em.injectedBundles) == 0 { // Check to see if we must exit select { case <-ctx.Done(): @@ -386,6 +401,19 @@ func (em *ElementManager) Bundles(ctx context.Context, upstreamCancelFn context. changedByProcessingTime = em.processTimeEvents.AdvanceTo(emNow) em.changedStages.merge(changedByProcessingTime) } + // Run any injected bundles first. + for len(em.injectedBundles) > 0 { + rb := em.injectedBundles[0] + em.injectedBundles = em.injectedBundles[1:] + em.refreshCond.L.Unlock() + + select { + case <-ctx.Done(): + return + case runStageCh <- rb: + } + em.refreshCond.L.Lock() + } // We know there is some work we can do that may advance the watermarks, // refresh them, and see which stages have advanced. @@ -628,6 +656,12 @@ type Block struct { Transform, Family string } +// StaticTimerID represents the static user identifiers for a timer, +// in particular, the ID of the Transform, and the family for the timer. +type StaticTimerID struct { + TransformID, TimerFamily string +} + // StateForBundle retreives relevant state for the given bundle, WRT the data in the bundle. // // TODO(lostluck): Consider unifiying with InputForBundle, to reduce lock contention. @@ -847,6 +881,19 @@ func (em *ElementManager) PersistBundle(rb RunBundle, col2Coders map[string]PCol } delete(stage.inprogressHoldsByBundle, rb.BundleID) + // Clean up OnWindowExpiration bundle accounting, so window state + // may be garbage collected. + if stage.expiryWindowsByBundles != nil { + win, ok := stage.expiryWindowsByBundles[rb.BundleID] + if ok { + stage.inProgressExpiredWindows[win] -= 1 + if stage.inProgressExpiredWindows[win] == 0 { + delete(stage.inProgressExpiredWindows, win) + } + delete(stage.expiryWindowsByBundles, rb.BundleID) + } + } + // If there are estimated output watermarks, set the estimated // output watermark for the stage. if len(residuals.MinOutputWatermarks) > 0 { @@ -1068,6 +1115,12 @@ type stageState struct { strat winStrat // Windowing Strategy for aggregation fireings. processingTimeTimersFamilies map[string]bool // Indicates which timer families use the processing time domain. + // onWindowExpiration management + onWindowExpiration StaticTimerID // The static ID of the OnWindowExpiration callback. + keysToExpireByWindow map[typex.Window]set[string] // Tracks all keys ever used with a window, so they may be expired. + inProgressExpiredWindows map[typex.Window]int // Tracks the number of bundles currently expiring these windows, so we don't prematurely garbage collect them. + expiryWindowsByBundles map[string]typex.Window // Tracks which bundle is handling which window, so the above map can be cleared. + mu sync.Mutex upstreamWatermarks sync.Map // watermark set from inputPCollection's parent. input mtime.Time // input watermark for the parallel input. @@ -1158,6 +1211,14 @@ func (ss *stageState) AddPending(newPending []element) int { timers: map[timerKey]timerTimes{}, } ss.pendingByKeys[string(e.keyBytes)] = dnt + if ss.keysToExpireByWindow != nil { + w, ok := ss.keysToExpireByWindow[e.window] + if !ok { + w = make(set[string]) + ss.keysToExpireByWindow[e.window] = w + } + w.insert(string(e.keyBytes)) + } } heap.Push(&dnt.elements, e) @@ -1555,48 +1616,143 @@ func (ss *stageState) updateWatermarks(em *ElementManager) set[string] { if minWatermarkHold < newOut { newOut = minWatermarkHold } - refreshes := set[string]{} + // If the newOut is smaller, then don't change downstream watermarks. + if newOut <= ss.output { + return nil + } + // If bigger, advance the output watermark - if newOut > ss.output { - ss.output = newOut - for _, outputCol := range ss.outputIDs { - consumers := em.consumers[outputCol] - - for _, sID := range consumers { - em.stages[sID].updateUpstreamWatermark(outputCol, ss.output) - refreshes.insert(sID) - } - // Inform side input consumers, but don't update the upstream watermark. - for _, sID := range em.sideConsumers[outputCol] { - refreshes.insert(sID.Global) - } - } - // Garbage collect state, timers and side inputs, for all windows - // that are before the new output watermark. - // They'll never be read in again. - for _, wins := range ss.sideInputs { - for win := range wins { - // TODO(#https://github.com/apache/beam/issues/31438): - // Adjust with AllowedLateness - // Clear out anything we've already used. - if win.MaxTimestamp() < newOut { - delete(wins, win) + preventDownstreamUpdate := ss.createOnWindowExpirationBundles(newOut, em) + + // Garbage collect state, timers and side inputs, for all windows + // that are before the new output watermark, if they aren't in progress + // of being expired. + // They'll never be read in again. + for _, wins := range ss.sideInputs { + for win := range wins { + // TODO(#https://github.com/apache/beam/issues/31438): + // Adjust with AllowedLateness + // Clear out anything we've already used. + if win.MaxTimestamp() < newOut { + // If the expiry is in progress, skip this window. + if ss.inProgressExpiredWindows[win] > 0 { + continue } + delete(wins, win) } } - for _, wins := range ss.state { - for win := range wins { - // TODO(#https://github.com/apache/beam/issues/31438): - // Adjust with AllowedLateness - if win.MaxTimestamp() < newOut { - delete(wins, win) + } + for _, wins := range ss.state { + for win := range wins { + // TODO(#https://github.com/apache/beam/issues/31438): + // Adjust with AllowedLateness + if win.MaxTimestamp() < newOut { + // If the expiry is in progress, skip collecting this window. + if ss.inProgressExpiredWindows[win] > 0 { + continue } + delete(wins, win) } } } + // If there are windows to expire, we don't update the output watermark yet. + if preventDownstreamUpdate { + return nil + } + + // Update this stage's output watermark, and then propagate that to downstream stages + refreshes := set[string]{} + ss.output = newOut + for _, outputCol := range ss.outputIDs { + consumers := em.consumers[outputCol] + + for _, sID := range consumers { + em.stages[sID].updateUpstreamWatermark(outputCol, ss.output) + refreshes.insert(sID) + } + // Inform side input consumers, but don't update the upstream watermark. + for _, sID := range em.sideConsumers[outputCol] { + refreshes.insert(sID.Global) + } + } return refreshes } +// createOnWindowExpirationBundles injects bundles when windows +// expire for all keys that were used in that window. Returns true if any +// bundles are created, which means that the window must not yet be garbage +// collected. +// +// Must be called within the stageState.mu's and the ElementManager.refreshCond +// critical sections. +func (ss *stageState) createOnWindowExpirationBundles(newOut mtime.Time, em *ElementManager) bool { + var preventDownstreamUpdate bool + for win, keys := range ss.keysToExpireByWindow { + // Check if the window has expired. + // TODO(#https://github.com/apache/beam/issues/31438): + // Adjust with AllowedLateness + if win.MaxTimestamp() >= newOut { + continue + } + // We can't advance the output watermark if there's garbage to collect. + preventDownstreamUpdate = true + // Hold off on garbage collecting data for these windows while these + // are in progress. + ss.inProgressExpiredWindows[win] += 1 + + // Produce bundle(s) for these keys and window, and inject them. + wm := win.MaxTimestamp() + rb := RunBundle{StageID: ss.ID, BundleID: "owe-" + em.nextBundID(), Watermark: wm} + + // Now we need to actually build the bundle. + var toProcess []element + busyKeys := set[string]{} + usedKeys := set[string]{} + for k := range keys { + if ss.inprogressKeys.present(k) { + busyKeys.insert(k) + continue + } + usedKeys.insert(k) + toProcess = append(toProcess, element{ + window: win, + timestamp: wm, + pane: typex.NoFiringPane(), + holdTimestamp: wm, + transform: ss.onWindowExpiration.TransformID, + family: ss.onWindowExpiration.TimerFamily, + sequence: 1, + keyBytes: []byte(k), + elmBytes: nil, + }) + } + em.addPending(len(toProcess)) + ss.watermarkHolds.Add(wm, 1) + ss.makeInProgressBundle( + func() string { return rb.BundleID }, + toProcess, + wm, + usedKeys, + map[mtime.Time]int{wm: 1}, + ) + ss.expiryWindowsByBundles[rb.BundleID] = win + + slog.Debug("OnWindowExpiration-Bundle Created", slog.Any("bundle", rb), slog.Any("usedKeys", usedKeys), slog.Any("window", win), slog.Any("toProcess", toProcess), slog.Any("busyKeys", busyKeys)) + // We're already in the refreshCond critical section. + // Insert that this is in progress here to avoid a race condition. + em.inprogressBundles.insert(rb.BundleID) + em.injectedBundles = append(em.injectedBundles, rb) + + // Remove the key accounting, or continue tracking which keys still need clearing. + if len(busyKeys) == 0 { + delete(ss.keysToExpireByWindow, win) + } else { + ss.keysToExpireByWindow[win] = busyKeys + } + } + return preventDownstreamUpdate +} + // bundleReady returns the maximum allowed watermark for this stage, and whether // it's permitted to execute by side inputs. func (ss *stageState) bundleReady(em *ElementManager, emNow mtime.Time) (mtime.Time, bool, bool) { diff --git a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager_test.go b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager_test.go index d5904b13fb88..0d7da5ea163f 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager_test.go +++ b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "io" + "sync/atomic" "testing" "github.com/apache/beam/sdks/v2/go/pkg/beam/core/graph/coder" @@ -524,3 +525,162 @@ func TestElementManager(t *testing.T) { } }) } + +func TestElementManager_OnWindowExpiration(t *testing.T) { + t.Run("createOnWindowExpirationBundles", func(t *testing.T) { + // Unlike the other tests above, we synthesize the input configuration, + em := NewElementManager(Config{}) + var instID uint64 + em.nextBundID = func() string { + return fmt.Sprintf("inst%03d", atomic.AddUint64(&instID, 1)) + } + em.AddStage("impulse", nil, []string{"input"}, nil) + em.AddStage("dofn", []string{"input"}, nil, nil) + onWE := StaticTimerID{ + TransformID: "dofn1", + TimerFamily: "onWinExp", + } + em.StageOnWindowExpiration("dofn", onWE) + em.Impulse("impulse") + + stage := em.stages["dofn"] + stage.pendingByKeys = map[string]*dataAndTimers{} + stage.inprogressKeys = set[string]{} + + validateInProgressExpiredWindows := func(t *testing.T, win typex.Window, want int) { + t.Helper() + if got := stage.inProgressExpiredWindows[win]; got != want { + t.Errorf("stage.inProgressExpiredWindows[%v] = %v, want %v", win, got, want) + } + } + validateSideBundles := func(t *testing.T, keys set[string]) { + t.Helper() + if len(em.injectedBundles) == 0 { + t.Errorf("no injectedBundles exist when checking keys: %v", keys) + } + // Check that all keys are marked as in progress + for k := range keys { + if !stage.inprogressKeys.present(k) { + t.Errorf("key %q not marked as in progress", k) + } + } + + bundleID := "" + sideBundles: + for _, rb := range em.injectedBundles { + // find that a side channel bundle exists with these keys. + bkeys := stage.inprogressKeysByBundle[rb.BundleID] + if len(bkeys) != len(keys) { + continue sideBundles + } + for k := range keys { + if !bkeys.present(k) { + continue sideBundles + } + } + bundleID = rb.BundleID + break + } + if bundleID == "" { + t.Errorf("no bundle found with all the given keys: %v: bundles: %v keysByBundle: %v", keys, em.injectedBundles, stage.inprogressKeysByBundle) + } + } + + newOut := mtime.EndOfGlobalWindowTime + // No windows exist, so no side channel bundles should be set. + if got, want := stage.createOnWindowExpirationBundles(newOut, em), false; got != want { + t.Errorf("createOnWindowExpirationBundles(%v) = %v, want %v", newOut, got, want) + } + // Validate that no side channel bundles were created. + if got, want := len(stage.inProgressExpiredWindows), 0; got != want { + t.Errorf("len(stage.inProgressExpiredWindows) = %v, want %v", got, want) + } + if got, want := len(em.injectedBundles), 0; got != want { + t.Errorf("len(em.injectedBundles) = %v, want %v", got, want) + } + + // Configure a few conditions to validate in the call. + // Each window is in it's own bundle, all are in the same bundle. + // Bundle 1 + expiredWindow1 := window.IntervalWindow{Start: 0, End: newOut - 1} + + akey := "\u0004key1" + keys1 := singleSet(akey) + stage.keysToExpireByWindow[expiredWindow1] = keys1 + // Bundle 2 + expiredWindow2 := window.IntervalWindow{Start: 1, End: newOut - 1} + keys2 := singleSet("\u0004key2") + keys2.insert("\u0004key3") + keys2.insert("\u0004key4") + stage.keysToExpireByWindow[expiredWindow2] = keys2 + + // We should never see this key and window combination, as the window is + // not yet expired. + liveWindow := window.IntervalWindow{Start: 2, End: newOut + 1} + stage.keysToExpireByWindow[liveWindow] = singleSet("\u0010keyNotSeen") + + if got, want := stage.createOnWindowExpirationBundles(newOut, em), true; got != want { + t.Errorf("createOnWindowExpirationBundles(%v) = %v, want %v", newOut, got, want) + } + + // We should only see 2 injectedBundles at this point. + if got, want := len(em.injectedBundles), 2; got != want { + t.Errorf("len(em.injectedBundles) = %v, want %v", got, want) + } + + validateInProgressExpiredWindows(t, expiredWindow1, 1) + validateInProgressExpiredWindows(t, expiredWindow2, 1) + validateSideBundles(t, keys1) + validateSideBundles(t, keys2) + + // Bundle 3 + expiredWindow3 := window.IntervalWindow{Start: 3, End: newOut - 1} + keys3 := singleSet(akey) // We shouldn't see this key, since it's in progress. + keys3.insert("\u0004key5") // We should see this key since it isn't. + stage.keysToExpireByWindow[expiredWindow3] = keys3 + + if got, want := stage.createOnWindowExpirationBundles(newOut, em), true; got != want { + t.Errorf("createOnWindowExpirationBundles(%v) = %v, want %v", newOut, got, want) + } + + // We should see 3 injectedBundles at this point. + if got, want := len(em.injectedBundles), 3; got != want { + t.Errorf("len(em.injectedBundles) = %v, want %v", got, want) + } + + validateInProgressExpiredWindows(t, expiredWindow1, 1) + validateInProgressExpiredWindows(t, expiredWindow2, 1) + validateInProgressExpiredWindows(t, expiredWindow3, 1) + validateSideBundles(t, keys1) + validateSideBundles(t, keys2) + validateSideBundles(t, singleSet("\u0004key5")) + + // remove key1 from "inprogress keys", and the associated bundle. + stage.inprogressKeys.remove(akey) + delete(stage.inProgressExpiredWindows, expiredWindow1) + for bundID, bkeys := range stage.inprogressKeysByBundle { + if bkeys.present(akey) { + t.Logf("bundID: %v, bkeys: %v, keyByBundle: %v", bundID, bkeys, stage.inprogressKeysByBundle) + delete(stage.inprogressKeysByBundle, bundID) + win := stage.expiryWindowsByBundles[bundID] + delete(stage.expiryWindowsByBundles, bundID) + if win != expiredWindow1 { + t.Fatalf("Unexpected window: got %v, want %v", win, expiredWindow1) + } + break + } + } + + // Now we should get another bundle for expiredWindow3, and have none for expiredWindow1 + if got, want := stage.createOnWindowExpirationBundles(newOut, em), true; got != want { + t.Errorf("createOnWindowExpirationBundles(%v) = %v, want %v", newOut, got, want) + } + + validateInProgressExpiredWindows(t, expiredWindow1, 0) + validateInProgressExpiredWindows(t, expiredWindow2, 1) + validateInProgressExpiredWindows(t, expiredWindow3, 2) + validateSideBundles(t, keys1) // Should still have this key present, but with a different bundle. + validateSideBundles(t, keys2) + validateSideBundles(t, singleSet("\u0004key5")) // still exist.. + }) +} diff --git a/sdks/go/pkg/beam/runners/prism/internal/execute.go b/sdks/go/pkg/beam/runners/prism/internal/execute.go index 614edee47721..fde62f00c7c1 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/execute.go +++ b/sdks/go/pkg/beam/runners/prism/internal/execute.go @@ -318,6 +318,10 @@ func executePipeline(ctx context.Context, wks map[string]*worker.W, j *jobservic if stage.stateful { em.StageStateful(stage.ID) } + if stage.onWindowExpiration.TimerFamily != "" { + slog.Debug("OnWindowExpiration", slog.String("stage", stage.ID), slog.Any("values", stage.onWindowExpiration)) + em.StageOnWindowExpiration(stage.ID, stage.onWindowExpiration) + } if len(stage.processingTimeTimers) > 0 { em.StageProcessingTimeTimers(stage.ID, stage.processingTimeTimers) } diff --git a/sdks/go/pkg/beam/runners/prism/internal/jobservices/job.go b/sdks/go/pkg/beam/runners/prism/internal/jobservices/job.go index deef259a99d1..6158cd6d612c 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/jobservices/job.go +++ b/sdks/go/pkg/beam/runners/prism/internal/jobservices/job.go @@ -45,6 +45,7 @@ var supportedRequirements = map[string]struct{}{ urns.RequirementSplittableDoFn: {}, urns.RequirementStatefulProcessing: {}, urns.RequirementBundleFinalization: {}, + urns.RequirementOnWindowExpiration: {}, } // TODO, move back to main package, and key off of executor handlers? diff --git a/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go b/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go index a2840760bf7a..894a6e1427a2 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go +++ b/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go @@ -182,8 +182,6 @@ func (s *Server) Prepare(ctx context.Context, req *jobpb.PrepareJobRequest) (_ * check("TimerFamilySpecs.TimeDomain.Urn", spec.GetTimeDomain(), pipepb.TimeDomain_EVENT_TIME, pipepb.TimeDomain_PROCESSING_TIME) } - check("OnWindowExpirationTimerFamily", pardo.GetOnWindowExpirationTimerFamilySpec(), "") // Unsupported for now. - // Check for a stateful SDF and direct user to https://github.com/apache/beam/issues/32139 if pardo.GetRestrictionCoderId() != "" && isStateful { check("Splittable+Stateful DoFn", "See https://github.com/apache/beam/issues/32139 for information.", "") diff --git a/sdks/go/pkg/beam/runners/prism/internal/preprocess.go b/sdks/go/pkg/beam/runners/prism/internal/preprocess.go index dceaa9ab8fcb..0d3ec7c365c1 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/preprocess.go +++ b/sdks/go/pkg/beam/runners/prism/internal/preprocess.go @@ -449,6 +449,9 @@ func finalizeStage(stg *stage, comps *pipepb.Components, pipelineFacts *fusionFa if len(pardo.GetTimerFamilySpecs())+len(pardo.GetStateSpecs())+len(pardo.GetOnWindowExpirationTimerFamilySpec()) > 0 { stg.stateful = true } + if pardo.GetOnWindowExpirationTimerFamilySpec() != "" { + stg.onWindowExpiration = engine.StaticTimerID{TransformID: link.Transform, TimerFamily: pardo.GetOnWindowExpirationTimerFamilySpec()} + } sis = pardo.GetSideInputs() } if _, ok := sis[link.Local]; ok { diff --git a/sdks/go/pkg/beam/runners/prism/internal/stage.go b/sdks/go/pkg/beam/runners/prism/internal/stage.go index 9f00c22789b6..9dd6cbdafec8 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/stage.go +++ b/sdks/go/pkg/beam/runners/prism/internal/stage.go @@ -57,18 +57,20 @@ type link struct { // account, but all serialization boundaries remain since the pcollections // would continue to get serialized. type stage struct { - ID string - transforms []string - primaryInput string // PCollection used as the parallel input. - outputs []link // PCollections that must escape this stage. - sideInputs []engine.LinkID // Non-parallel input PCollections and their consumers - internalCols []string // PCollections that escape. Used for precise coder sending. - envID string - finalize bool - stateful bool + ID string + transforms []string + primaryInput string // PCollection used as the parallel input. + outputs []link // PCollections that must escape this stage. + sideInputs []engine.LinkID // Non-parallel input PCollections and their consumers + internalCols []string // PCollections that escape. Used for precise coder sending. + envID string + finalize bool + stateful bool + onWindowExpiration engine.StaticTimerID + // hasTimers indicates the transform+timerfamily pairs that need to be waited on for // the stage to be considered complete. - hasTimers []struct{ Transform, TimerFamily string } + hasTimers []engine.StaticTimerID processingTimeTimers map[string]bool exe transformExecuter @@ -452,7 +454,7 @@ func buildDescriptor(stg *stage, comps *pipepb.Components, wk *worker.W, em *eng } } for timerID, v := range pardo.GetTimerFamilySpecs() { - stg.hasTimers = append(stg.hasTimers, struct{ Transform, TimerFamily string }{Transform: tid, TimerFamily: timerID}) + stg.hasTimers = append(stg.hasTimers, engine.StaticTimerID{TransformID: tid, TimerFamily: timerID}) if v.TimeDomain == pipepb.TimeDomain_PROCESSING_TIME { if stg.processingTimeTimers == nil { stg.processingTimeTimers = map[string]bool{} diff --git a/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go b/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go index 83ad1bda9841..14cd84aef821 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go +++ b/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go @@ -42,7 +42,7 @@ type B struct { InputTransformID string Input []*engine.Block // Data and Timers for this bundle. EstimatedInputElements int - HasTimers []struct{ Transform, TimerFamily string } // Timer streams to terminate. + HasTimers []engine.StaticTimerID // Timer streams to terminate. // IterableSideInputData is a map from transformID + inputID, to window, to data. IterableSideInputData map[SideInputKey]map[typex.Window][][]byte @@ -190,7 +190,7 @@ func (b *B) ProcessOn(ctx context.Context, wk *W) <-chan struct{} { for _, tid := range b.HasTimers { timers = append(timers, &fnpb.Elements_Timers{ InstructionId: b.InstID, - TransformId: tid.Transform, + TransformId: tid.TransformID, TimerFamilyId: tid.TimerFamily, IsLast: true, }) From 02f9cb94fcd31527933164ecc3709c5d9f04cae3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:16:26 -0800 Subject: [PATCH 109/135] Bump google.golang.org/api from 0.210.0 to 0.211.0 in /sdks (#33374) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.210.0 to 0.211.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.210.0...v0.211.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 8 ++++---- sdks/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index e6a182035f5e..79b32b051df3 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -58,7 +58,7 @@ require ( golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 - google.golang.org/api v0.210.0 + google.golang.org/api v0.211.0 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 google.golang.org/grpc v1.67.2 google.golang.org/protobuf v1.35.2 @@ -75,7 +75,7 @@ require ( require ( cel.dev/expr v0.16.1 // indirect - cloud.google.com/go/auth v0.11.0 // indirect + cloud.google.com/go/auth v0.12.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/monitoring v1.21.2 // indirect dario.cat/mergo v1.0.0 // indirect @@ -194,6 +194,6 @@ require ( golang.org/x/mod v0.20.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect ) diff --git a/sdks/go.sum b/sdks/go.sum index 34e34ef2f8cd..0e7792f499f4 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -101,8 +101,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= -cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4= +cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= @@ -1707,8 +1707,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= -google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= +google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= +google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1850,10 +1850,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= -google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= -google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From 994d2f0cad0a4e7d2511c518af6d4f5a9696cd21 Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" <6454655+adoroszlai@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:13:51 +0100 Subject: [PATCH 110/135] Fix Apache snapshot repo usage (#33370) * limit Apache snapshot repo content type * move up Confluent repo --- .../apache/beam/gradle/Repositories.groovy | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/groovy/org/apache/beam/gradle/Repositories.groovy b/buildSrc/src/main/groovy/org/apache/beam/gradle/Repositories.groovy index 52cbbd15c35b..58ec64a0add3 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/Repositories.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/Repositories.groovy @@ -39,20 +39,25 @@ class Repositories { mavenCentral() mavenLocal() + // For Confluent Kafka dependencies + maven { + url "https://packages.confluent.io/maven/" + content { includeGroup "io.confluent" } + } + // Release staging repository maven { url "https://oss.sonatype.org/content/repositories/staging/" } // Apache nightly snapshots - maven { url "https://repository.apache.org/snapshots" } + maven { + url "https://repository.apache.org/snapshots" + mavenContent { + snapshotsOnly() + } + } // Apache release snapshots maven { url "https://repository.apache.org/content/repositories/releases" } - - // For Confluent Kafka dependencies - maven { - url "https://packages.confluent.io/maven/" - content { includeGroup "io.confluent" } - } } // Apply a plugin which provides the 'updateOfflineRepository' task that creates an offline From a6061feb05363a6175e28f80f684b6d306a42150 Mon Sep 17 00:00:00 2001 From: twosom <72733442+twosom@users.noreply.github.com> Date: Sat, 14 Dec 2024 00:44:48 +0900 Subject: [PATCH 111/135] Batch optimized SparkRunner groupByKey (#33322) * feat : optimized SparkRunner batch groupByKey * update CHANGES.md * touch trigger files * remove unused test --- .../beam_PostCommit_Java_PVR_Spark_Batch.json | 2 +- ...PostCommit_Java_ValidatesRunner_Spark.json | 3 +- ...mit_Java_ValidatesRunner_Spark_Java11.json | 3 +- CHANGES.md | 1 + .../translation/GroupCombineFunctions.java | 46 ++++++--- .../GroupNonMergingWindowsFunctions.java | 98 ++++++++++++++----- .../GroupNonMergingWindowsFunctionsTest.java | 57 ----------- 7 files changed, 116 insertions(+), 94 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Java_PVR_Spark_Batch.json b/.github/trigger_files/beam_PostCommit_Java_PVR_Spark_Batch.json index f1ba03a243ee..455144f02a35 100644 --- a/.github/trigger_files/beam_PostCommit_Java_PVR_Spark_Batch.json +++ b/.github/trigger_files/beam_PostCommit_Java_PVR_Spark_Batch.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 5 + "modification": 6 } diff --git a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark.json b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark.json index 9b023f630c36..03d86a8d023e 100644 --- a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark.json +++ b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark.json @@ -2,5 +2,6 @@ "comment": "Modify this file in a trivial way to cause this test suite to run", "https://github.com/apache/beam/pull/31156": "noting that PR #31156 should run this test", "https://github.com/apache/beam/pull/31798": "noting that PR #31798 should run this test", - "https://github.com/apache/beam/pull/32546": "noting that PR #32546 should run this test" + "https://github.com/apache/beam/pull/32546": "noting that PR #32546 should run this test", + "https://github.com/apache/beam/pull/33322": "noting that PR #33322 should run this test" } diff --git a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark_Java11.json b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark_Java11.json index 9b023f630c36..03d86a8d023e 100644 --- a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark_Java11.json +++ b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark_Java11.json @@ -2,5 +2,6 @@ "comment": "Modify this file in a trivial way to cause this test suite to run", "https://github.com/apache/beam/pull/31156": "noting that PR #31156 should run this test", "https://github.com/apache/beam/pull/31798": "noting that PR #31798 should run this test", - "https://github.com/apache/beam/pull/32546": "noting that PR #32546 should run this test" + "https://github.com/apache/beam/pull/32546": "noting that PR #32546 should run this test", + "https://github.com/apache/beam/pull/33322": "noting that PR #33322 should run this test" } diff --git a/CHANGES.md b/CHANGES.md index ea29b6aadc7d..5bbb10f76efb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -68,6 +68,7 @@ ## New Features / Improvements +* Improved batch performance of SparkRunner's GroupByKey ([#20943](https://github.com/apache/beam/pull/20943)). * X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). * Support OnWindowExpiration in Prism ([#32211](https://github.com/apache/beam/issues/32211)). * This enables initial Java GroupIntoBatches support. diff --git a/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupCombineFunctions.java b/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupCombineFunctions.java index 62c5e2579427..1d8901ed5ffc 100644 --- a/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupCombineFunctions.java +++ b/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupCombineFunctions.java @@ -17,6 +17,9 @@ */ package org.apache.beam.runners.spark.translation; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; import org.apache.beam.runners.spark.coders.CoderHelpers; import org.apache.beam.runners.spark.util.ByteArray; import org.apache.beam.sdk.coders.Coder; @@ -27,6 +30,7 @@ import org.apache.beam.sdk.values.KV; import org.apache.beam.sdk.values.WindowingStrategy; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterators; import org.apache.spark.Partitioner; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; @@ -49,18 +53,36 @@ public static JavaRDD>>> groupByKeyOnly( @Nullable Partitioner partitioner) { // we use coders to convert objects in the PCollection to byte arrays, so they // can be transferred over the network for the shuffle. - JavaPairRDD pairRDD = - rdd.map(new ReifyTimestampsAndWindowsFunction<>()) - .mapToPair(TranslationUtils.toPairFunction()) - .mapToPair(CoderHelpers.toByteFunction(keyCoder, wvCoder)); - - // If no partitioner is passed, the default group by key operation is called - JavaPairRDD> groupedRDD = - (partitioner != null) ? pairRDD.groupByKey(partitioner) : pairRDD.groupByKey(); - - return groupedRDD - .mapToPair(CoderHelpers.fromByteFunctionIterable(keyCoder, wvCoder)) - .map(new TranslationUtils.FromPairFunction<>()); + final JavaPairRDD pairRDD = + rdd.mapPartitionsToPair( + (Iterator>> iter) -> + Iterators.transform( + iter, + (WindowedValue> wv) -> { + final K key = wv.getValue().getKey(); + final WindowedValue windowedValue = wv.withValue(wv.getValue().getValue()); + final ByteArray keyBytes = + new ByteArray(CoderHelpers.toByteArray(key, keyCoder)); + final byte[] windowedValueBytes = + CoderHelpers.toByteArray(windowedValue, wvCoder); + return Tuple2.apply(keyBytes, windowedValueBytes); + })); + + final JavaPairRDD> combined = + GroupNonMergingWindowsFunctions.combineByKey(pairRDD, partitioner).cache(); + + return combined.mapPartitions( + (Iterator>> iter) -> + Iterators.transform( + iter, + (Tuple2> tuple) -> { + final K key = CoderHelpers.fromByteArray(tuple._1().getValue(), keyCoder); + final List> windowedValues = + tuple._2().stream() + .map(bytes -> CoderHelpers.fromByteArray(bytes, wvCoder)) + .collect(Collectors.toList()); + return KV.of(key, windowedValues); + })); } /** diff --git a/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctions.java b/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctions.java index 2461d5cc8d66..14630fbb0a1f 100644 --- a/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctions.java +++ b/runners/spark/src/main/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctions.java @@ -17,7 +17,9 @@ */ package org.apache.beam.runners.spark.translation; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Objects; import org.apache.beam.runners.spark.coders.CoderHelpers; import org.apache.beam.runners.spark.util.ByteArray; @@ -41,6 +43,9 @@ import org.apache.spark.Partitioner; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.Function; +import org.apache.spark.api.java.function.Function2; +import org.checkerframework.checker.nullness.qual.Nullable; import org.joda.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -259,9 +264,12 @@ private WindowedValue> decodeItem(Tuple2 item) { } /** - * Group all values with a given key for that composite key with Spark's groupByKey, dropping the - * Window (which must be GlobalWindow) and returning the grouped result in the appropriate global - * window. + * Groups values with a given key using Spark's combineByKey operation in the Global Window + * context. The window information (which must be GlobalWindow) is dropped during processing, and + * the grouped results are returned in the appropriate global window with the maximum timestamp. + * + *

This implementation uses {@link JavaPairRDD#combineByKey} for better performance compared to + * {@link JavaPairRDD#groupByKey}, as it allows for local aggregation before shuffle operations. */ static JavaRDD>>> groupByKeyInGlobalWindow( @@ -269,24 +277,70 @@ JavaRDD>>> groupByKeyInGlobalWindow( Coder keyCoder, Coder valueCoder, Partitioner partitioner) { - JavaPairRDD rawKeyValues = - rdd.mapToPair( - wv -> - new Tuple2<>( - new ByteArray(CoderHelpers.toByteArray(wv.getValue().getKey(), keyCoder)), - CoderHelpers.toByteArray(wv.getValue().getValue(), valueCoder))); - - JavaPairRDD> grouped = - (partitioner == null) ? rawKeyValues.groupByKey() : rawKeyValues.groupByKey(partitioner); - return grouped.map( - kvs -> - WindowedValue.timestampedValueInGlobalWindow( - KV.of( - CoderHelpers.fromByteArray(kvs._1.getValue(), keyCoder), - Iterables.transform( - kvs._2, - encodedValue -> CoderHelpers.fromByteArray(encodedValue, valueCoder))), - GlobalWindow.INSTANCE.maxTimestamp(), - PaneInfo.ON_TIME_AND_ONLY_FIRING)); + final JavaPairRDD rawKeyValues = + rdd.mapPartitionsToPair( + (Iterator>> iter) -> + Iterators.transform( + iter, + (WindowedValue> wv) -> { + final ByteArray keyBytes = + new ByteArray(CoderHelpers.toByteArray(wv.getValue().getKey(), keyCoder)); + final byte[] valueBytes = + CoderHelpers.toByteArray(wv.getValue().getValue(), valueCoder); + return Tuple2.apply(keyBytes, valueBytes); + })); + + JavaPairRDD> combined = combineByKey(rawKeyValues, partitioner).cache(); + + return combined.mapPartitions( + (Iterator>> iter) -> + Iterators.transform( + iter, + kvs -> + WindowedValue.timestampedValueInGlobalWindow( + KV.of( + CoderHelpers.fromByteArray(kvs._1.getValue(), keyCoder), + Iterables.transform( + kvs._2(), + encodedValue -> + CoderHelpers.fromByteArray(encodedValue, valueCoder))), + GlobalWindow.INSTANCE.maxTimestamp(), + PaneInfo.ON_TIME_AND_ONLY_FIRING))); + } + + /** + * Combines values by key using Spark's {@link JavaPairRDD#combineByKey} operation. + * + * @param rawKeyValues Input RDD of key-value pairs + * @param partitioner Optional custom partitioner for data distribution + * @return RDD with values combined into Lists per key + */ + static JavaPairRDD> combineByKey( + JavaPairRDD rawKeyValues, @Nullable Partitioner partitioner) { + + final Function> createCombiner = + value -> { + List list = new ArrayList<>(); + list.add(value); + return list; + }; + + final Function2, byte[], List> mergeValues = + (list, value) -> { + list.add(value); + return list; + }; + + final Function2, List, List> mergeCombiners = + (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + + if (partitioner == null) { + return rawKeyValues.combineByKey(createCombiner, mergeValues, mergeCombiners); + } + + return rawKeyValues.combineByKey(createCombiner, mergeValues, mergeCombiners, partitioner); } } diff --git a/runners/spark/src/test/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctionsTest.java b/runners/spark/src/test/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctionsTest.java index ed7bc078564e..fd299924af91 100644 --- a/runners/spark/src/test/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctionsTest.java +++ b/runners/spark/src/test/java/org/apache/beam/runners/spark/translation/GroupNonMergingWindowsFunctionsTest.java @@ -18,12 +18,6 @@ package org.apache.beam.runners.spark.translation; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Iterator; @@ -45,9 +39,6 @@ import org.apache.beam.sdk.values.KV; import org.apache.beam.sdk.values.WindowingStrategy; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.primitives.Bytes; -import org.apache.spark.Partitioner; -import org.apache.spark.api.java.JavaPairRDD; -import org.apache.spark.api.java.JavaRDD; import org.joda.time.Duration; import org.joda.time.Instant; import org.junit.Assert; @@ -121,54 +112,6 @@ public void testGbkIteratorValuesCannotBeReiterated() throws Coder.NonDeterminis } } - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - public void testGroupByKeyInGlobalWindowWithPartitioner() { - // mocking - Partitioner mockPartitioner = mock(Partitioner.class); - JavaRDD mockRdd = mock(JavaRDD.class); - Coder mockKeyCoder = mock(Coder.class); - Coder mockValueCoder = mock(Coder.class); - JavaPairRDD mockRawKeyValues = mock(JavaPairRDD.class); - JavaPairRDD mockGrouped = mock(JavaPairRDD.class); - - when(mockRdd.mapToPair(any())).thenReturn(mockRawKeyValues); - when(mockRawKeyValues.groupByKey(any(Partitioner.class))) - .thenAnswer( - invocation -> { - Partitioner partitioner = invocation.getArgument(0); - assertEquals(partitioner, mockPartitioner); - return mockGrouped; - }); - when(mockGrouped.map(any())).thenReturn(mock(JavaRDD.class)); - - GroupNonMergingWindowsFunctions.groupByKeyInGlobalWindow( - mockRdd, mockKeyCoder, mockValueCoder, mockPartitioner); - - verify(mockRawKeyValues, never()).groupByKey(); - verify(mockRawKeyValues, times(1)).groupByKey(any(Partitioner.class)); - } - - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - public void testGroupByKeyInGlobalWindowWithoutPartitioner() { - // mocking - JavaRDD mockRdd = mock(JavaRDD.class); - Coder mockKeyCoder = mock(Coder.class); - Coder mockValueCoder = mock(Coder.class); - JavaPairRDD mockRawKeyValues = mock(JavaPairRDD.class); - JavaPairRDD mockGrouped = mock(JavaPairRDD.class); - - when(mockRdd.mapToPair(any())).thenReturn(mockRawKeyValues); - when(mockRawKeyValues.groupByKey()).thenReturn(mockGrouped); - - GroupNonMergingWindowsFunctions.groupByKeyInGlobalWindow( - mockRdd, mockKeyCoder, mockValueCoder, null); - - verify(mockRawKeyValues, times(1)).groupByKey(); - verify(mockRawKeyValues, never()).groupByKey(any(Partitioner.class)); - } - private GroupByKeyIterator createGbkIterator() throws Coder.NonDeterministicException { return createGbkIterator( From 011ec94b9e36bb013f165f9dc4799474aeaa5ac2 Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Fri, 13 Dec 2024 08:46:43 -0800 Subject: [PATCH 112/135] remove flaky tests that terminate before read. (#33371) Co-authored-by: lostluck <13907733+lostluck@users.noreply.github.com> --- .../beam/core/runtime/harness/datamgr_test.go | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/sdks/go/pkg/beam/core/runtime/harness/datamgr_test.go b/sdks/go/pkg/beam/core/runtime/harness/datamgr_test.go index 92c4d0a8f8cd..9f6f8a986a3f 100644 --- a/sdks/go/pkg/beam/core/runtime/harness/datamgr_test.go +++ b/sdks/go/pkg/beam/core/runtime/harness/datamgr_test.go @@ -261,22 +261,6 @@ func TestElementChan(t *testing.T) { return elms }, wantSum: 6, wantCount: 3, - }, { - name: "FillBufferThenAbortThenRead", - sequenceFn: func(ctx context.Context, t *testing.T, client *fakeChanClient, c *DataChannel) <-chan exec.Elements { - for i := 0; i < bufElements+2; i++ { - client.Send(&fnpb.Elements{Data: []*fnpb.Elements_Data{dataElm(1, false)}}) - } - elms := openChan(ctx, t, c, timerID) - c.removeInstruction(instID) - - // These will be ignored - client.Send(&fnpb.Elements{Data: []*fnpb.Elements_Data{dataElm(1, false)}}) - client.Send(&fnpb.Elements{Data: []*fnpb.Elements_Data{dataElm(2, false)}}) - client.Send(&fnpb.Elements{Data: []*fnpb.Elements_Data{dataElm(3, true)}}) - return elms - }, - wantSum: bufElements, wantCount: bufElements, }, { name: "DataThenReaderThenLast", sequenceFn: func(ctx context.Context, t *testing.T, client *fakeChanClient, c *DataChannel) <-chan exec.Elements { @@ -389,18 +373,6 @@ func TestElementChan(t *testing.T) { return elms }, wantSum: 0, wantCount: 0, - }, { - name: "SomeTimersAndADataThenReaderThenCleanup", - sequenceFn: func(ctx context.Context, t *testing.T, client *fakeChanClient, c *DataChannel) <-chan exec.Elements { - client.Send(&fnpb.Elements{ - Timers: []*fnpb.Elements_Timers{timerElm(1, false), timerElm(2, true)}, - Data: []*fnpb.Elements_Data{dataElm(3, true)}, - }) - elms := openChan(ctx, t, c, timerID) - c.removeInstruction(instID) - return elms - }, - wantSum: 6, wantCount: 3, }, } for _, test := range tests { From f92dde16fa66fe104cd6057612bfadb4732c186b Mon Sep 17 00:00:00 2001 From: Damon Date: Fri, 13 Dec 2024 10:41:07 -0800 Subject: [PATCH 113/135] Clean up Java Tests GitHub workflow (#33354) * Remove use of static credentials * Stage for adding back dataflow * Remove unnecessary dataflow test --- .github/workflows/java_tests.yml | 72 +------------------------------- 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/.github/workflows/java_tests.yml b/.github/workflows/java_tests.yml index 1d6441b24681..bdc78b88cb97 100644 --- a/.github/workflows/java_tests.yml +++ b/.github/workflows/java_tests.yml @@ -20,11 +20,7 @@ name: Java Tests on: workflow_dispatch: - inputs: - runDataflow: - description: 'Type "true" if you want to run Dataflow tests (GCP variables must be configured, check CI.md)' - default: 'false' - required: false + schedule: - cron: '10 2 * * *' push: @@ -33,8 +29,7 @@ on: pull_request: branches: ['master', 'release-*'] tags: ['v*'] - paths: ['sdks/java/**', 'model/**', 'runners/**', 'examples/java/**', - 'examples/kotlin/**', 'release/**', 'buildSrc/**'] + paths: ['sdks/java/**', 'model/**', 'runners/**', 'examples/java/**', 'examples/kotlin/**', 'release/**', 'buildSrc/**'] # This allows a subsequently queued workflow run to interrupt previous runs concurrency: group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.event.pull_request.head.label || github.sha || github.head_ref || github.ref }}-${{ github.event.schedule || github.event.comment.id || github.event.sender.login}}' @@ -44,26 +39,6 @@ env: GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} jobs: - check_gcp_variables: - timeout-minutes: 5 - name: "Check GCP variables set" - runs-on: [self-hosted, ubuntu-20.04, main] - outputs: - gcp-variables-set: ${{ steps.check_gcp_variables.outputs.gcp-variables-set }} - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: "Check are GCP variables set" - run: "./scripts/ci/ci_check_are_gcp_variables_set.sh" - id: check_gcp_variables - env: - GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} - GCP_SA_EMAIL: ${{ secrets.GCP_SA_EMAIL }} - GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} - GCP_TESTING_BUCKET: ${{ secrets.GCP_TESTING_BUCKET }} - GCP_REGION: "not-needed-here" - GCP_PYTHON_WHEELS_BUCKET: "not-needed-here" - java_unit_tests: name: 'Java Unit Tests' runs-on: ${{ matrix.os }} @@ -152,46 +127,3 @@ jobs: with: name: java_wordcount_direct_runner-${{matrix.os}} path: examples/java/build/reports/tests/integrationTest - - java_wordcount_dataflow: - name: 'Java Wordcount Dataflow' - needs: - - check_gcp_variables - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [[self-hosted, ubuntu-20.04, main], windows-latest] - # TODO(https://github.com/apache/beam/issues/31848) run on Dataflow after fixes credential on macOS/win GHA runner - if: | - needs.check_gcp_variables.outputs.gcp-variables-set == 'true' && - (github.event_name == 'workflow_dispatch' && github.event.inputs.runDataflow == 'true') - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - persist-credentials: false - submodules: recursive - - name: Setup environment - uses: ./.github/actions/setup-environment-action - with: - java-version: 11 - go-version: default - - name: Authenticate on GCP - uses: google-github-actions/auth@v1 - with: - credentials_json: ${{ secrets.GCP_SA_KEY }} - project_id: ${{ secrets.GCP_PROJECT_ID }} - - name: Run WordCount - uses: ./.github/actions/gradle-command-self-hosted-action - with: - gradle-command: integrationTest - arguments: -p examples/ --tests org.apache.beam.examples.WordCountIT - -DintegrationTestPipelineOptions=[\"--runner=DataflowRunner\",\"--project=${{ secrets.GCP_PROJECT_ID }}\",\"--tempRoot=gs://${{ secrets.GCP_TESTING_BUCKET }}/tmp/\"] - -DintegrationTestRunner=dataflow - - name: Upload test logs - uses: actions/upload-artifact@v4 - if: always() - with: - name: java_wordcount_dataflow-${{matrix.os}} - path: examples/java/build/reports/tests/integrationTest \ No newline at end of file From 88398f33ce663748d87a7df61fe5d948e6e1b4f9 Mon Sep 17 00:00:00 2001 From: Jeffrey Kinard Date: Fri, 13 Dec 2024 14:41:02 -0500 Subject: [PATCH 114/135] fix BoundedTrieData get_result return type hint Signed-off-by: Jeffrey Kinard --- sdks/python/apache_beam/metrics/cells.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index 9a62cae14691..b765c830f69c 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -826,7 +826,7 @@ def __repr__(self) -> str: def get_cumulative(self) -> "BoundedTrieData": return copy.deepcopy(self) - def get_result(self) -> set[tuple]: + def get_result(self) -> Set[tuple]: if self._root is None: if self._singleton is None: return set() From 9d1eea496a49c391d55cb9855282e4949da7f437 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 27 Nov 2024 10:00:57 -0800 Subject: [PATCH 115/135] End to end BoundedTrie metrics through worker and runner APIs. --- sdks/python/apache_beam/metrics/cells.py | 29 +++++++++++++++++-- sdks/python/apache_beam/metrics/execution.py | 17 ++++++++++- sdks/python/apache_beam/metrics/metric.py | 24 +++++++++++++++ sdks/python/apache_beam/metrics/metricbase.py | 9 ++++++ .../apache_beam/metrics/monitoring_infos.py | 18 +++++++++++- .../runners/direct/direct_metrics.py | 16 +++++++++- .../runners/direct/direct_runner_test.py | 10 +++++++ .../portability/fn_api_runner/fn_runner.py | 13 +++++++-- .../fn_api_runner/fn_runner_test.py | 8 +++++ .../runners/portability/portable_metrics.py | 5 +++- .../runners/portability/portable_runner.py | 5 ++-- 11 files changed, 142 insertions(+), 12 deletions(-) diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index 9a62cae14691..a6549272a67e 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -691,12 +691,18 @@ def from_proto(proto: metrics_pb2.BoundedTrieNode) -> '_BoundedTrieNode': for name, child in proto.children.items() } - node._size = min(1, sum(child._size for child in node._children.values())) + node._size = max(1, sum(child._size for child in node._children.values())) return node def size(self): return self._size + def contains(self, segments): + if self._truncated or not segments: + return True + head, *tail = segments + return head in self._children and self._children[head].contains(tail) + def add(self, segments) -> int: if self._truncated or not segments: return 0 @@ -784,15 +790,31 @@ class BoundedTrieData(object): _DEFAULT_BOUND = 100 def __init__(self, *, root=None, singleton=None, bound=_DEFAULT_BOUND): - assert singleton is None or root is None self._singleton = singleton self._root = root self._bound = bound + assert singleton is None or root is None + + def size(self): + if self._singleton is not None: + return 1 + elif self._root is not None: + return self._root.size() + else: + return 0 + + def contains(self, value): + if self._singleton is not None: + return tuple(value) == self._singleton + elif self._root is not None: + return self._root.contains(value) + else: + return False def to_proto(self) -> metrics_pb2.BoundedTrie: return metrics_pb2.BoundedTrie( bound=self._bound, - singleton=self._singlton if self._singleton else None, + singleton=self._singleton if self._singleton else None, root=self._root.to_proto() if self._root else None) @staticmethod @@ -844,6 +866,7 @@ def add(self, segments): else: if self._root is None: self._root = self.as_trie() + self._singleton = None self._root.add(segments) if self._root._size > self._bound: self._root.trim() diff --git a/sdks/python/apache_beam/metrics/execution.py b/sdks/python/apache_beam/metrics/execution.py index fa70d3a4d9c0..c28c8340a505 100644 --- a/sdks/python/apache_beam/metrics/execution.py +++ b/sdks/python/apache_beam/metrics/execution.py @@ -43,6 +43,7 @@ from typing import cast from apache_beam.metrics import monitoring_infos +from apache_beam.metrics.cells import BoundedTrieCell from apache_beam.metrics.cells import CounterCell from apache_beam.metrics.cells import DistributionCell from apache_beam.metrics.cells import GaugeCell @@ -52,6 +53,7 @@ from apache_beam.runners.worker.statesampler import get_current_tracker if TYPE_CHECKING: + from apache_beam.metrics.cells import BoundedTrieData from apache_beam.metrics.cells import GaugeData from apache_beam.metrics.cells import DistributionData from apache_beam.metrics.cells import MetricCell @@ -265,6 +267,9 @@ def get_string_set(self, metric_name): StringSetCell, self.get_metric_cell(_TypedMetricName(StringSetCell, metric_name))) + def get_bounded_trie(self, metric_name): + return self.get_metric_cell(_TypedMetricName(BoundedTrieCell, metric_name)) + def get_metric_cell(self, typed_metric_name): # type: (_TypedMetricName) -> MetricCell cell = self.metrics.get(typed_metric_name, None) @@ -304,7 +309,14 @@ def get_cumulative(self): v in self.metrics.items() if k.cell_type == StringSetCell } - return MetricUpdates(counters, distributions, gauges, string_sets) + bounded_tries = { + MetricKey(self.step_name, k.metric_name): v.get_cumulative() + for k, + v in self.metrics.items() if k.cell_type == BoundedTrieCell + } + + return MetricUpdates( + counters, distributions, gauges, string_sets, bounded_tries) def to_runner_api(self): return [ @@ -358,6 +370,7 @@ def __init__( distributions=None, # type: Optional[Dict[MetricKey, DistributionData]] gauges=None, # type: Optional[Dict[MetricKey, GaugeData]] string_sets=None, # type: Optional[Dict[MetricKey, StringSetData]] + bounded_tries=None, # type: Optional[Dict[MetricKey, BoundedTrieData]] ): # type: (...) -> None @@ -368,8 +381,10 @@ def __init__( distributions: Dictionary of MetricKey:MetricUpdate objects. gauges: Dictionary of MetricKey:MetricUpdate objects. string_sets: Dictionary of MetricKey:MetricUpdate objects. + bounded_tries: Dictionary of MetricKey:MetricUpdate objects. """ self.counters = counters or {} self.distributions = distributions or {} self.gauges = gauges or {} self.string_sets = string_sets or {} + self.bounded_tries = bounded_tries or {} diff --git a/sdks/python/apache_beam/metrics/metric.py b/sdks/python/apache_beam/metrics/metric.py index 3e665dd805ea..33af25e20ca4 100644 --- a/sdks/python/apache_beam/metrics/metric.py +++ b/sdks/python/apache_beam/metrics/metric.py @@ -42,6 +42,7 @@ from apache_beam.metrics import cells from apache_beam.metrics.execution import MetricResult from apache_beam.metrics.execution import MetricUpdater +from apache_beam.metrics.metricbase import BoundedTrie from apache_beam.metrics.metricbase import Counter from apache_beam.metrics.metricbase import Distribution from apache_beam.metrics.metricbase import Gauge @@ -135,6 +136,22 @@ def string_set( namespace = Metrics.get_namespace(namespace) return Metrics.DelegatingStringSet(MetricName(namespace, name)) + @staticmethod + def bounded_trie( + namespace: Union[Type, str], + name: str) -> 'Metrics.DelegatingBoundedTrie': + """Obtains or creates a Bounded Trie metric. + + Args: + namespace: A class or string that gives the namespace to a metric + name: A string that gives a unique name to a metric + + Returns: + A BoundedTrie object. + """ + namespace = Metrics.get_namespace(namespace) + return Metrics.DelegatingBoundedTrie(MetricName(namespace, name)) + class DelegatingCounter(Counter): """Metrics Counter that Delegates functionality to MetricsEnvironment.""" def __init__( @@ -164,12 +181,19 @@ def __init__(self, metric_name: MetricName) -> None: super().__init__(metric_name) self.add = MetricUpdater(cells.StringSetCell, metric_name) # type: ignore[method-assign] + class DelegatingBoundedTrie(BoundedTrie): + """Metrics StringSet that Delegates functionality to MetricsEnvironment.""" + def __init__(self, metric_name: MetricName) -> None: + super().__init__(metric_name) + self.add = MetricUpdater(cells.BoundedTrieCell, metric_name) # type: ignore[method-assign] + class MetricResults(object): COUNTERS = "counters" DISTRIBUTIONS = "distributions" GAUGES = "gauges" STRINGSETS = "string_sets" + BOUNDED_TRIES = "bounded_tries" @staticmethod def _matches_name(filter: 'MetricsFilter', metric_key: 'MetricKey') -> bool: diff --git a/sdks/python/apache_beam/metrics/metricbase.py b/sdks/python/apache_beam/metrics/metricbase.py index 7819dbb093a5..9b35bb24f895 100644 --- a/sdks/python/apache_beam/metrics/metricbase.py +++ b/sdks/python/apache_beam/metrics/metricbase.py @@ -43,6 +43,7 @@ 'Distribution', 'Gauge', 'StringSet', + 'BoundedTrie', 'Histogram', 'MetricName' ] @@ -152,6 +153,14 @@ def add(self, value): raise NotImplementedError +class BoundedTrie(Metric): + """BoundedTrie Metric interface. + + Reports set of unique string values during pipeline execution..""" + def add(self, value): + raise NotImplementedError + + class Histogram(Metric): """Histogram Metric interface. diff --git a/sdks/python/apache_beam/metrics/monitoring_infos.py b/sdks/python/apache_beam/metrics/monitoring_infos.py index 397fcc578d53..e343793b3ad4 100644 --- a/sdks/python/apache_beam/metrics/monitoring_infos.py +++ b/sdks/python/apache_beam/metrics/monitoring_infos.py @@ -27,6 +27,7 @@ from apache_beam.coders import coder_impl from apache_beam.coders import coders +from apache_beam.metrics.cells import BoundedTrieData from apache_beam.metrics.cells import DistributionData from apache_beam.metrics.cells import DistributionResult from apache_beam.metrics.cells import GaugeData @@ -168,6 +169,14 @@ def extract_string_set_value(monitoring_info_proto): return set(coder.decode(monitoring_info_proto.payload)) +def extract_bounded_trie_value(monitoring_info_proto): + if not is_bounded_trie(monitoring_info_proto): + raise ValueError('Unsupported type %s' % monitoring_info_proto.type) + + return BoundedTrieData.from_proto( + metrics_pb2.BoundedTrie.FromString(monitoring_info_proto.payload)) + + def create_labels(ptransform=None, namespace=None, name=None, pcollection=None): """Create the label dictionary based on the provided values. @@ -382,6 +391,11 @@ def is_string_set(monitoring_info_proto): return monitoring_info_proto.type in STRING_SET_TYPES +def is_bounded_trie(monitoring_info_proto): + """Returns true if the monitoring info is a StringSet metric.""" + return monitoring_info_proto.type in BOUNDED_TRIE_TYPES + + def is_user_monitoring_info(monitoring_info_proto): """Returns true if the monitoring info is a user metric.""" return monitoring_info_proto.urn in USER_METRIC_URNS @@ -389,7 +403,7 @@ def is_user_monitoring_info(monitoring_info_proto): def extract_metric_result_map_value( monitoring_info_proto -) -> Union[None, int, DistributionResult, GaugeResult, set]: +) -> Union[None, int, DistributionResult, GaugeResult, set, BoundedTrieData]: """Returns the relevant GaugeResult, DistributionResult or int value for counter metric, set for StringSet metric. @@ -407,6 +421,8 @@ def extract_metric_result_map_value( return GaugeResult(GaugeData(value, timestamp)) if is_string_set(monitoring_info_proto): return extract_string_set_value(monitoring_info_proto) + if is_bounded_trie(monitoring_info_proto): + return extract_bounded_trie_value(monitoring_info_proto) return None diff --git a/sdks/python/apache_beam/runners/direct/direct_metrics.py b/sdks/python/apache_beam/runners/direct/direct_metrics.py index d20849d769af..5beb19d4610a 100644 --- a/sdks/python/apache_beam/runners/direct/direct_metrics.py +++ b/sdks/python/apache_beam/runners/direct/direct_metrics.py @@ -27,6 +27,7 @@ from typing import Any from typing import SupportsInt +from apache_beam.metrics.cells import BoundedTrieData from apache_beam.metrics.cells import DistributionData from apache_beam.metrics.cells import GaugeData from apache_beam.metrics.cells import StringSetData @@ -102,6 +103,8 @@ def __init__(self): lambda: DirectMetric(GenericAggregator(GaugeData))) self._string_sets = defaultdict( lambda: DirectMetric(GenericAggregator(StringSetData))) + self._bounded_tries = defaultdict( + lambda: DirectMetric(GenericAggregator(BoundedTrieData))) def _apply_operation(self, bundle, updates, op): for k, v in updates.counters.items(): @@ -116,6 +119,9 @@ def _apply_operation(self, bundle, updates, op): for k, v in updates.string_sets.items(): op(self._string_sets[k], bundle, v) + for k, v in updates.bounded_tries.items(): + op(self._bounded_tries[k], bundle, v) + def commit_logical(self, bundle, updates): op = lambda obj, bundle, update: obj.commit_logical(bundle, update) self._apply_operation(bundle, updates, op) @@ -157,12 +163,20 @@ def query(self, filter=None): v.extract_latest_attempted()) for k, v in self._string_sets.items() if self.matches(filter, k) ] + bounded_tries = [ + MetricResult( + MetricKey(k.step, k.metric), + v.extract_committed(), + v.extract_latest_attempted()) for k, + v in self._bounded_tries.items() if self.matches(filter, k) + ] return { self.COUNTERS: counters, self.DISTRIBUTIONS: distributions, self.GAUGES: gauges, - self.STRINGSETS: string_sets + self.STRINGSETS: string_sets, + self.BOUNDED_TRIES: bounded_tries, } diff --git a/sdks/python/apache_beam/runners/direct/direct_runner_test.py b/sdks/python/apache_beam/runners/direct/direct_runner_test.py index d8f1ea097b88..1af5f1bc7bea 100644 --- a/sdks/python/apache_beam/runners/direct/direct_runner_test.py +++ b/sdks/python/apache_beam/runners/direct/direct_runner_test.py @@ -78,6 +78,8 @@ def process(self, element): distro.update(element) str_set = Metrics.string_set(self.__class__, 'element_str_set') str_set.add(str(element % 4)) + Metrics.bounded_trie(self.__class__, 'element_bounded_trie').add( + ("a", "b", str(element % 4))) return [element] p = Pipeline(DirectRunner()) @@ -124,6 +126,14 @@ def process(self, element): hc.assert_that(len(str_set_result.committed), hc.equal_to(4)) hc.assert_that(len(str_set_result.attempted), hc.equal_to(4)) + bounded_trie_results = metrics['bounded_tries'][0] + hc.assert_that( + bounded_trie_results.key, + hc.equal_to( + MetricKey('Do', MetricName(namespace, 'element_bounded_trie')))) + hc.assert_that(bounded_trie_results.committed.size(), hc.equal_to(4)) + hc.assert_that(bounded_trie_results.attempted.size(), hc.equal_to(4)) + def test_create_runner(self): self.assertTrue(isinstance(create_runner('DirectRunner'), DirectRunner)) self.assertTrue( diff --git a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner.py b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner.py index 1ed21942d28f..95bcb7567918 100644 --- a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner.py +++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner.py @@ -1536,16 +1536,18 @@ def __init__(self, step_monitoring_infos, user_metrics_only=True): self._distributions = {} self._gauges = {} self._string_sets = {} + self._bounded_tries = {} self._user_metrics_only = user_metrics_only self._monitoring_infos = step_monitoring_infos for smi in step_monitoring_infos.values(): - counters, distributions, gauges, string_sets = \ - portable_metrics.from_monitoring_infos(smi, user_metrics_only) + counters, distributions, gauges, string_sets, bounded_tries = ( + portable_metrics.from_monitoring_infos(smi, user_metrics_only)) self._counters.update(counters) self._distributions.update(distributions) self._gauges.update(gauges) self._string_sets.update(string_sets) + self._bounded_tries.update(bounded_tries) def query(self, filter=None): counters = [ @@ -1564,12 +1566,17 @@ def query(self, filter=None): MetricResult(k, v, v) for k, v in self._string_sets.items() if self.matches(filter, k) ] + bounded_tries = [ + MetricResult(k, v, v) for k, + v in self._bounded_tries.items() if self.matches(filter, k) + ] return { self.COUNTERS: counters, self.DISTRIBUTIONS: distributions, self.GAUGES: gauges, - self.STRINGSETS: string_sets + self.STRINGSETS: string_sets, + self.BOUNDED_TRIES: bounded_tries, } def monitoring_infos(self) -> List[metrics_pb2.MonitoringInfo]: diff --git a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py index 1309e7c74abc..217092fbf806 100644 --- a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py +++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py @@ -1216,6 +1216,7 @@ def test_metrics(self, check_gauge=True): distribution = beam.metrics.Metrics.distribution('ns', 'distribution') gauge = beam.metrics.Metrics.gauge('ns', 'gauge') string_set = beam.metrics.Metrics.string_set('ns', 'string_set') + bounded_trie = beam.metrics.Metrics.bounded_trie('ns', 'bounded_trie') elements = ['a', 'zzz'] pcoll = p | beam.Create(elements) @@ -1225,6 +1226,7 @@ def test_metrics(self, check_gauge=True): pcoll | 'dist' >> beam.FlatMap(lambda x: distribution.update(len(x))) pcoll | 'gauge' >> beam.FlatMap(lambda x: gauge.set(3)) pcoll | 'string_set' >> beam.FlatMap(lambda x: string_set.add(x)) + pcoll | 'bounded_trie' >> beam.FlatMap(lambda x: bounded_trie.add(tuple(x))) res = p.run() res.wait_until_finish() @@ -1248,6 +1250,12 @@ def test_metrics(self, check_gauge=True): .with_name('string_set'))['string_sets'] self.assertEqual(str_set.committed, set(elements)) + bounded_trie, = res.metrics().query(beam.metrics.MetricsFilter() + .with_name('bounded_trie'))['bounded_tries'] + self.assertEqual(bounded_trie.committed.size(), 2) + for element in elements: + self.assertTrue(bounded_trie.committed.contains(tuple(element)), element) + def test_callbacks_with_exception(self): elements_list = ['1', '2'] diff --git a/sdks/python/apache_beam/runners/portability/portable_metrics.py b/sdks/python/apache_beam/runners/portability/portable_metrics.py index 5bc3e0539181..e92d33910415 100644 --- a/sdks/python/apache_beam/runners/portability/portable_metrics.py +++ b/sdks/python/apache_beam/runners/portability/portable_metrics.py @@ -42,6 +42,7 @@ def from_monitoring_infos(monitoring_info_list, user_metrics_only=False): distributions = {} gauges = {} string_sets = {} + bounded_tries = {} for mi in monitoring_info_list: if (user_metrics_only and not monitoring_infos.is_user_monitoring_info(mi)): @@ -62,8 +63,10 @@ def from_monitoring_infos(monitoring_info_list, user_metrics_only=False): gauges[key] = metric_result elif monitoring_infos.is_string_set(mi): string_sets[key] = metric_result + elif monitoring_infos.is_bounded_trie(mi): + bounded_tries[key] = metric_result - return counters, distributions, gauges, string_sets + return counters, distributions, gauges, string_sets, bounded_tries def _create_metric_key(monitoring_info): diff --git a/sdks/python/apache_beam/runners/portability/portable_runner.py b/sdks/python/apache_beam/runners/portability/portable_runner.py index ba48bbec6d3a..fe9dcfa62b29 100644 --- a/sdks/python/apache_beam/runners/portability/portable_runner.py +++ b/sdks/python/apache_beam/runners/portability/portable_runner.py @@ -437,7 +437,7 @@ def _combine(committed, attempted, filter): ] def query(self, filter=None): - counters, distributions, gauges, stringsets = [ + counters, distributions, gauges, stringsets, bounded_tries = [ self._combine(x, y, filter) for x, y in zip(self.committed, self.attempted) ] @@ -446,7 +446,8 @@ def query(self, filter=None): self.COUNTERS: counters, self.DISTRIBUTIONS: distributions, self.GAUGES: gauges, - self.STRINGSETS: stringsets + self.STRINGSETS: stringsets, + self.BOUNDED_TRIES: bounded_tries, } From 0768e108649b64408c7bd2d41eee8c61d43b35fb Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Fri, 13 Dec 2024 16:21:03 -0800 Subject: [PATCH 116/135] Add missing pxd definition. This is needed to override _update_locked. --- sdks/python/apache_beam/metrics/cells.pxd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdks/python/apache_beam/metrics/cells.pxd b/sdks/python/apache_beam/metrics/cells.pxd index 7590bd8b5966..ebadeec97984 100644 --- a/sdks/python/apache_beam/metrics/cells.pxd +++ b/sdks/python/apache_beam/metrics/cells.pxd @@ -55,6 +55,10 @@ cdef class StringSetCell(AbstractMetricCell): pass +cdef class BoundedTrieCell(AbstractMetricCell): + pass + + cdef class DistributionData(object): cdef readonly libc.stdint.int64_t sum cdef readonly libc.stdint.int64_t count From 2aab9cd9d1bcbcce49113423adbd526bf2392ab4 Mon Sep 17 00:00:00 2001 From: Shunping Huang Date: Fri, 13 Dec 2024 20:31:09 -0500 Subject: [PATCH 117/135] Fix custom coders not being used in Reshuffle (non global window) (#33363) * Fix typehint in ReshufflePerKey on global window setting. * Only update the type hint on global window setting. Need more work in non-global windows. * Apply yapf * Fix some failed tests. * Revert change to setup.py * Fix custom coders not being used in reshuffle in non-global windows * Revert changes in setup.py. Reformat. * Make WindowedValue a generic class. Support its conversion to the correct type constraint in Beam. * Cython does not support Python generic class. Add a subclass as a workroundand keep it un-cythonized. * Add comments * Fix type error. * Remove the base class of WindowedValue in TypedWindowedValue. * Move TypedWindowedValue out from windowed_value.py * Revise the comments * Fix the module location when matching. * Fix test failure where __name__ of a type alias not found in python 3.9 * Add a note about the window coder. --------- Co-authored-by: Robert Bradshaw --- sdks/python/apache_beam/coders/coders.py | 8 +++ sdks/python/apache_beam/coders/typecoders.py | 2 + sdks/python/apache_beam/transforms/util.py | 6 +-- .../apache_beam/transforms/util_test.py | 51 ++++++++++++------- .../typehints/native_type_compatibility.py | 26 ++++++++++ .../python/apache_beam/typehints/typehints.py | 9 ++++ 6 files changed, 81 insertions(+), 21 deletions(-) diff --git a/sdks/python/apache_beam/coders/coders.py b/sdks/python/apache_beam/coders/coders.py index a0c55da81800..724f268a8312 100644 --- a/sdks/python/apache_beam/coders/coders.py +++ b/sdks/python/apache_beam/coders/coders.py @@ -1402,6 +1402,14 @@ def __hash__(self): return hash( (self.wrapped_value_coder, self.timestamp_coder, self.window_coder)) + @classmethod + def from_type_hint(cls, typehint, registry): + # type: (Any, CoderRegistry) -> WindowedValueCoder + # Ideally this'd take two parameters so that one could hint at + # the window type as well instead of falling back to the + # pickle coders. + return cls(registry.get_coder(typehint.inner_type)) + Coder.register_structured_urn( common_urns.coders.WINDOWED_VALUE.urn, WindowedValueCoder) diff --git a/sdks/python/apache_beam/coders/typecoders.py b/sdks/python/apache_beam/coders/typecoders.py index 1667cb7a916a..892f508d0136 100644 --- a/sdks/python/apache_beam/coders/typecoders.py +++ b/sdks/python/apache_beam/coders/typecoders.py @@ -94,6 +94,8 @@ def register_standard_coders(self, fallback_coder): self._register_coder_internal(str, coders.StrUtf8Coder) self._register_coder_internal(typehints.TupleConstraint, coders.TupleCoder) self._register_coder_internal(typehints.DictConstraint, coders.MapCoder) + self._register_coder_internal( + typehints.WindowedTypeConstraint, coders.WindowedValueCoder) # Default fallback coders applied in that order until the first matching # coder found. default_fallback_coders = [ diff --git a/sdks/python/apache_beam/transforms/util.py b/sdks/python/apache_beam/transforms/util.py index 43d4a6c20e94..c9fd2c76b0db 100644 --- a/sdks/python/apache_beam/transforms/util.py +++ b/sdks/python/apache_beam/transforms/util.py @@ -74,6 +74,7 @@ from apache_beam.transforms.window import TimestampedValue from apache_beam.typehints import trivial_inference from apache_beam.typehints.decorators import get_signature +from apache_beam.typehints.native_type_compatibility import TypedWindowedValue from apache_beam.typehints.sharded_key_type import ShardedKeyType from apache_beam.utils import shared from apache_beam.utils import windowed_value @@ -972,9 +973,8 @@ def restore_timestamps(element): key, windowed_values = element return [wv.with_value((key, wv.value)) for wv in windowed_values] - # TODO(https://github.com/apache/beam/issues/33356): Support reshuffling - # unpicklable objects with a non-global window setting. - ungrouped = pcoll | Map(reify_timestamps).with_output_types(Any) + ungrouped = pcoll | Map(reify_timestamps).with_input_types( + Tuple[K, V]).with_output_types(Tuple[K, TypedWindowedValue[V]]) # TODO(https://github.com/apache/beam/issues/19785) Using global window as # one of the standard window. This is to mitigate the Dataflow Java Runner diff --git a/sdks/python/apache_beam/transforms/util_test.py b/sdks/python/apache_beam/transforms/util_test.py index 7f166f78ef0a..db73310dfe25 100644 --- a/sdks/python/apache_beam/transforms/util_test.py +++ b/sdks/python/apache_beam/transforms/util_test.py @@ -1010,32 +1010,33 @@ def format_with_timestamp(element, timestamp=beam.DoFn.TimestampParam): equal_to(expected_data), label="formatted_after_reshuffle") - def test_reshuffle_unpicklable_in_global_window(self): - global _Unpicklable + global _Unpicklable + global _UnpicklableCoder - class _Unpicklable(object): - def __init__(self, value): - self.value = value + class _Unpicklable(object): + def __init__(self, value): + self.value = value - def __getstate__(self): - raise NotImplementedError() + def __getstate__(self): + raise NotImplementedError() - def __setstate__(self, state): - raise NotImplementedError() + def __setstate__(self, state): + raise NotImplementedError() - class _UnpicklableCoder(beam.coders.Coder): - def encode(self, value): - return str(value.value).encode() + class _UnpicklableCoder(beam.coders.Coder): + def encode(self, value): + return str(value.value).encode() - def decode(self, encoded): - return _Unpicklable(int(encoded.decode())) + def decode(self, encoded): + return _Unpicklable(int(encoded.decode())) - def to_type_hint(self): - return _Unpicklable + def to_type_hint(self): + return _Unpicklable - def is_deterministic(self): - return True + def is_deterministic(self): + return True + def test_reshuffle_unpicklable_in_global_window(self): beam.coders.registry.register_coder(_Unpicklable, _UnpicklableCoder) with TestPipeline() as pipeline: @@ -1049,6 +1050,20 @@ def is_deterministic(self): | beam.Map(lambda u: u.value * 10)) assert_that(result, equal_to(expected_data)) + def test_reshuffle_unpicklable_in_non_global_window(self): + beam.coders.registry.register_coder(_Unpicklable, _UnpicklableCoder) + + with TestPipeline() as pipeline: + data = [_Unpicklable(i) for i in range(5)] + expected_data = [0, 0, 0, 10, 10, 10, 20, 20, 20, 30, 30, 30, 40, 40, 40] + result = ( + pipeline + | beam.Create(data) + | beam.WindowInto(window.SlidingWindows(size=3, period=1)) + | beam.Reshuffle() + | beam.Map(lambda u: u.value * 10)) + assert_that(result, equal_to(expected_data)) + class WithKeysTest(unittest.TestCase): def setUp(self): diff --git a/sdks/python/apache_beam/typehints/native_type_compatibility.py b/sdks/python/apache_beam/typehints/native_type_compatibility.py index 6f704b37a969..381d4f7aae2b 100644 --- a/sdks/python/apache_beam/typehints/native_type_compatibility.py +++ b/sdks/python/apache_beam/typehints/native_type_compatibility.py @@ -24,9 +24,13 @@ import sys import types import typing +from typing import Generic +from typing import TypeVar from apache_beam.typehints import typehints +T = TypeVar('T') + _LOGGER = logging.getLogger(__name__) # Describes an entry in the type map in convert_to_beam_type. @@ -216,6 +220,18 @@ def convert_collections_to_typing(typ): return typ +# During type inference of WindowedValue, we need to pass in the inner value +# type. This cannot be achieved immediately with WindowedValue class because it +# is not parameterized. Changing it to a generic class (e.g. WindowedValue[T]) +# could work in theory. However, the class is cythonized and it seems that +# cython does not handle generic classes well. +# The workaround here is to create a separate class solely for the type +# inference purpose. This class should never be used for creating instances. +class TypedWindowedValue(Generic[T]): + def __init__(self, *args, **kwargs): + raise NotImplementedError("This class is solely for type inference") + + def convert_to_beam_type(typ): """Convert a given typing type to a Beam type. @@ -267,6 +283,12 @@ def convert_to_beam_type(typ): # TODO(https://github.com/apache/beam/issues/20076): Currently unhandled. _LOGGER.info('Converting NewType type hint to Any: "%s"', typ) return typehints.Any + elif typ_module == 'apache_beam.typehints.native_type_compatibility' and \ + getattr(typ, "__name__", typ.__origin__.__name__) == 'TypedWindowedValue': + # Need to pass through WindowedValue class so that it can be converted + # to the correct type constraint in Beam + # This is needed to fix https://github.com/apache/beam/issues/33356 + pass elif (typ_module != 'typing') and (typ_module != 'collections.abc'): # Only translate types from the typing and collections.abc modules. return typ @@ -324,6 +346,10 @@ def convert_to_beam_type(typ): match=_match_is_exactly_collection, arity=1, beam_type=typehints.Collection), + _TypeMapEntry( + match=_match_issubclass(TypedWindowedValue), + arity=1, + beam_type=typehints.WindowedValue), ] # Find the first matching entry. diff --git a/sdks/python/apache_beam/typehints/typehints.py b/sdks/python/apache_beam/typehints/typehints.py index 0e18e887c2a0..a65a0f753826 100644 --- a/sdks/python/apache_beam/typehints/typehints.py +++ b/sdks/python/apache_beam/typehints/typehints.py @@ -1213,6 +1213,15 @@ def type_check(self, instance): repr(self.inner_type), instance.value.__class__.__name__)) + def bind_type_variables(self, bindings): + bound_inner_type = bind_type_variables(self.inner_type, bindings) + if bound_inner_type == self.inner_type: + return self + return WindowedValue[bound_inner_type] + + def __repr__(self): + return 'WindowedValue[%s]' % repr(self.inner_type) + class GeneratorHint(IteratorHint): """A Generator type hint. From 5918e6d7993be5378af91c5d865f84fee9c7afef Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Fri, 13 Dec 2024 22:49:34 -0800 Subject: [PATCH 118/135] Skip unsupported runners. --- .../runners/portability/flink_runner_test.py | 2 +- .../portability/fn_api_runner/fn_runner_test.py | 13 +++++++------ .../runners/portability/prism_runner_test.py | 3 +++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sdks/python/apache_beam/runners/portability/flink_runner_test.py b/sdks/python/apache_beam/runners/portability/flink_runner_test.py index 4dc2446fdd9d..30f1a4c06025 100644 --- a/sdks/python/apache_beam/runners/portability/flink_runner_test.py +++ b/sdks/python/apache_beam/runners/portability/flink_runner_test.py @@ -303,7 +303,7 @@ def test_flattened_side_input(self): super().test_flattened_side_input(with_transcoding=False) def test_metrics(self): - super().test_metrics(check_gauge=False) + super().test_metrics(check_gauge=False, check_bounded_trie=False) def test_sdf_with_watermark_tracking(self): raise unittest.SkipTest("BEAM-2939") diff --git a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py index 217092fbf806..65d598171c08 100644 --- a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py +++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py @@ -1209,7 +1209,7 @@ def expand(self, pcolls): pcoll_b = p | 'b' >> beam.Create(['b']) assert_that((pcoll_a, pcoll_b) | First(), equal_to(['a'])) - def test_metrics(self, check_gauge=True): + def test_metrics(self, check_gauge=True, check_bounded_trie=False): p = self.create_pipeline() counter = beam.metrics.Metrics.counter('ns', 'counter') @@ -1250,11 +1250,12 @@ def test_metrics(self, check_gauge=True): .with_name('string_set'))['string_sets'] self.assertEqual(str_set.committed, set(elements)) - bounded_trie, = res.metrics().query(beam.metrics.MetricsFilter() - .with_name('bounded_trie'))['bounded_tries'] - self.assertEqual(bounded_trie.committed.size(), 2) - for element in elements: - self.assertTrue(bounded_trie.committed.contains(tuple(element)), element) + if check_bounded_trie: + bounded_trie, = res.metrics().query(beam.metrics.MetricsFilter() + .with_name('bounded_trie'))['bounded_tries'] + self.assertEqual(bounded_trie.committed.size(), 2) + for element in elements: + self.assertTrue(bounded_trie.committed.contains(tuple(element)), element) def test_callbacks_with_exception(self): elements_list = ['1', '2'] diff --git a/sdks/python/apache_beam/runners/portability/prism_runner_test.py b/sdks/python/apache_beam/runners/portability/prism_runner_test.py index bc72d551f966..337ac9919487 100644 --- a/sdks/python/apache_beam/runners/portability/prism_runner_test.py +++ b/sdks/python/apache_beam/runners/portability/prism_runner_test.py @@ -231,6 +231,9 @@ def test_pack_combiners(self): "Requires Prism to support coder:" + " 'beam:coder:tuple:v1'. https://github.com/apache/beam/issues/32636") + def test_metrics(self): + super().test_metrics(check_bounded_trie=False) + # Inherits all other tests. From f77efba731d748cf9958d461c9b5b9d40fc196bb Mon Sep 17 00:00:00 2001 From: Reeba Qureshi <64488642+reeba212@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:10:34 +0530 Subject: [PATCH 119/135] [yaml] Add use cases for Enrichment transform in YAML (#32289) * Create use case for enriching spanner data with bigquery End to end use case that demonstrates how spanner IO and enrichment transform coupled with other YAML transforms can be used in the real world * Create example for bigtable enrichment * Add project_id parameter to BigQueryWrapper * minor changes * remove project id being passed into bigquery wrapper * add license * add expected blocks * Update examples_test.py * Update examples_test.py * fix formatting * fix examples_test Signed-off-by: Jeffrey Kinard * Apply suggestions from code review Co-authored-by: Jeff Kinard * Update bigquery_tools.py * Update bigquery_tools.py --------- Signed-off-by: Jeffrey Kinard Co-authored-by: Jeffrey Kinard --- .../yaml/examples/testing/examples_test.py | 168 +++++++++++++++++- .../transforms/ml/bigtable_enrichment.yaml | 55 ++++++ .../ml/enrich_spanner_with_bigquery.yaml | 102 +++++++++++ 3 files changed, 318 insertions(+), 7 deletions(-) create mode 100644 sdks/python/apache_beam/yaml/examples/transforms/ml/bigtable_enrichment.yaml create mode 100644 sdks/python/apache_beam/yaml/examples/transforms/ml/enrich_spanner_with_bigquery.yaml diff --git a/sdks/python/apache_beam/yaml/examples/testing/examples_test.py b/sdks/python/apache_beam/yaml/examples/testing/examples_test.py index 3b497ed1efab..109e98410852 100644 --- a/sdks/python/apache_beam/yaml/examples/testing/examples_test.py +++ b/sdks/python/apache_beam/yaml/examples/testing/examples_test.py @@ -21,9 +21,11 @@ import os import random import unittest +from typing import Any from typing import Callable from typing import Dict from typing import List +from typing import Optional from typing import Union from unittest import mock @@ -34,11 +36,63 @@ from apache_beam.examples.snippets.util import assert_matches_stdout from apache_beam.options.pipeline_options import PipelineOptions from apache_beam.testing.test_pipeline import TestPipeline +from apache_beam.yaml import yaml_provider from apache_beam.yaml import yaml_transform from apache_beam.yaml.readme_test import TestEnvironment from apache_beam.yaml.readme_test import replace_recursive +# Used to simulate Enrichment transform during tests +# The GitHub action that invokes these tests does not +# have gcp dependencies installed which is a prerequisite +# to apache_beam.transforms.enrichment.Enrichment as a top-level +# import. +@beam.ptransform.ptransform_fn +def test_enrichment( + pcoll, + enrichment_handler: str, + handler_config: Dict[str, Any], + timeout: Optional[float] = 30): + if enrichment_handler == 'BigTable': + row_key = handler_config['row_key'] + bt_data = INPUT_TABLES[( + 'BigTable', handler_config['instance_id'], handler_config['table_id'])] + products = {str(data[row_key]): data for data in bt_data} + + def _fn(row): + left = row._asdict() + right = products[str(left[row_key])] + left['product'] = left.get('product', None) or right + return beam.Row(**left) + elif enrichment_handler == 'BigQuery': + row_key = handler_config['fields'] + dataset, table = handler_config['table_name'].split('.')[-2:] + bq_data = INPUT_TABLES[('BigQuery', str(dataset), str(table))] + bq_data = { + tuple(str(data[key]) for key in row_key): data + for data in bq_data + } + + def _fn(row): + left = row._asdict() + right = bq_data[tuple(str(left[k]) for k in row_key)] + row = { + key: left.get(key, None) or right[key] + for key in {*left.keys(), *right.keys()} + } + return beam.Row(**row) + + else: + raise ValueError(f'{enrichment_handler} is not a valid enrichment_handler.') + + return pcoll | beam.Map(_fn) + + +TEST_PROVIDERS = { + 'TestEnrichment': test_enrichment, +} + + def check_output(expected: List[str]): def _check_inner(actual: List[PCollection[str]]): formatted_actual = actual | beam.Flatten() | beam.Map( @@ -59,7 +113,31 @@ def products_csv(): ]) -def spanner_data(): +def spanner_orders_data(): + return [{ + 'order_id': 1, + 'customer_id': 1001, + 'product_id': 2001, + 'order_date': '24-03-24', + 'order_amount': 150, + }, + { + 'order_id': 2, + 'customer_id': 1002, + 'product_id': 2002, + 'order_date': '19-04-24', + 'order_amount': 90, + }, + { + 'order_id': 3, + 'customer_id': 1003, + 'product_id': 2003, + 'order_date': '7-05-24', + 'order_amount': 110, + }] + + +def spanner_shipments_data(): return [{ 'shipment_id': 'S1', 'customer_id': 'C1', @@ -110,6 +188,44 @@ def spanner_data(): }] +def bigtable_data(): + return [{ + 'product_id': '1', 'product_name': 'pixel 5', 'product_stock': '2' + }, { + 'product_id': '2', 'product_name': 'pixel 6', 'product_stock': '4' + }, { + 'product_id': '3', 'product_name': 'pixel 7', 'product_stock': '20' + }, { + 'product_id': '4', 'product_name': 'pixel 8', 'product_stock': '10' + }, { + 'product_id': '5', 'product_name': 'pixel 11', 'product_stock': '3' + }, { + 'product_id': '6', 'product_name': 'pixel 12', 'product_stock': '7' + }, { + 'product_id': '7', 'product_name': 'pixel 13', 'product_stock': '8' + }, { + 'product_id': '8', 'product_name': 'pixel 14', 'product_stock': '3' + }] + + +def bigquery_data(): + return [{ + 'customer_id': 1001, + 'customer_name': 'Alice', + 'customer_email': 'alice@gmail.com' + }, + { + 'customer_id': 1002, + 'customer_name': 'Bob', + 'customer_email': 'bob@gmail.com' + }, + { + 'customer_id': 1003, + 'customer_name': 'Claire', + 'customer_email': 'claire@gmail.com' + }] + + def create_test_method( pipeline_spec_file: str, custom_preprocessors: List[Callable[..., Union[Dict, List]]]): @@ -135,7 +251,11 @@ def test_yaml_example(self): pickle_library='cloudpickle', **yaml_transform.SafeLineLoader.strip_metadata(pipeline_spec.get( 'options', {})))) as p: - actual = [yaml_transform.expand_pipeline(p, pipeline_spec)] + actual = [ + yaml_transform.expand_pipeline( + p, + pipeline_spec, [yaml_provider.InlineProvider(TEST_PROVIDERS)]) + ] if not actual[0]: actual = list(p.transforms_stack[0].parts[-1].outputs.values()) for transform in p.transforms_stack[0].parts[:-1]: @@ -213,7 +333,8 @@ def _wordcount_test_preprocessor( 'test_simple_filter_yaml', 'test_simple_filter_and_combine_yaml', 'test_spanner_read_yaml', - 'test_spanner_write_yaml' + 'test_spanner_write_yaml', + 'test_enrich_spanner_with_bigquery_yaml' ]) def _io_write_test_preprocessor( test_spec: dict, expected: List[str], env: TestEnvironment): @@ -249,7 +370,8 @@ def _file_io_read_test_preprocessor( return test_spec -@YamlExamplesTestSuite.register_test_preprocessor(['test_spanner_read_yaml']) +@YamlExamplesTestSuite.register_test_preprocessor( + ['test_spanner_read_yaml', 'test_enrich_spanner_with_bigquery_yaml']) def _spanner_io_read_test_preprocessor( test_spec: dict, expected: List[str], env: TestEnvironment): @@ -265,14 +387,42 @@ def _spanner_io_read_test_preprocessor( k: v for k, v in config.items() if k.startswith('__') } - transform['config']['elements'] = INPUT_TABLES[( - str(instance), str(database), str(table))] + elements = INPUT_TABLES[(str(instance), str(database), str(table))] + if config.get('query', None): + config['query'].replace('select ', + 'SELECT ').replace(' from ', ' FROM ') + columns = set( + ''.join(config['query'].split('SELECT ')[1:]).split( + ' FROM', maxsplit=1)[0].split(', ')) + if columns != {'*'}: + elements = [{ + column: element[column] + for column in element if column in columns + } for element in elements] + transform['config']['elements'] = elements + + return test_spec + + +@YamlExamplesTestSuite.register_test_preprocessor( + ['test_bigtable_enrichment_yaml', 'test_enrich_spanner_with_bigquery_yaml']) +def _enrichment_test_preprocessor( + test_spec: dict, expected: List[str], env: TestEnvironment): + if pipeline := test_spec.get('pipeline', None): + for transform in pipeline.get('transforms', []): + if transform.get('type', '').startswith('Enrichment'): + transform['type'] = 'TestEnrichment' return test_spec INPUT_FILES = {'products.csv': products_csv()} -INPUT_TABLES = {('shipment-test', 'shipment', 'shipments'): spanner_data()} +INPUT_TABLES = { + ('shipment-test', 'shipment', 'shipments'): spanner_shipments_data(), + ('orders-test', 'order-database', 'orders'): spanner_orders_data(), + ('BigTable', 'beam-test', 'bigtable-enrichment-test'): bigtable_data(), + ('BigQuery', 'ALL_TEST', 'customers'): bigquery_data() +} YAML_DOCS_DIR = os.path.join(os.path.dirname(__file__)) ExamplesTest = YamlExamplesTestSuite( @@ -290,6 +440,10 @@ def _spanner_io_read_test_preprocessor( 'IOExamplesTest', os.path.join(YAML_DOCS_DIR, '../transforms/io/*.yaml')).run() +MLTest = YamlExamplesTestSuite( + 'MLExamplesTest', os.path.join(YAML_DOCS_DIR, + '../transforms/ml/*.yaml')).run() + if __name__ == '__main__': logging.getLogger().setLevel(logging.INFO) unittest.main() diff --git a/sdks/python/apache_beam/yaml/examples/transforms/ml/bigtable_enrichment.yaml b/sdks/python/apache_beam/yaml/examples/transforms/ml/bigtable_enrichment.yaml new file mode 100644 index 000000000000..788b69de7857 --- /dev/null +++ b/sdks/python/apache_beam/yaml/examples/transforms/ml/bigtable_enrichment.yaml @@ -0,0 +1,55 @@ +# coding=utf-8 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +pipeline: + type: chain + transforms: + + # Step 1: Creating a collection of elements that needs + # to be enriched. Here we are simulating sales data + - type: Create + config: + elements: + - sale_id: 1 + customer_id: 1 + product_id: 1 + quantity: 1 + + # Step 2: Enriching the data with Bigtable + # This specific bigtable stores product data in the below format + # product:product_id, product:product_name, product:product_stock + - type: Enrichment + config: + enrichment_handler: 'BigTable' + handler_config: + project_id: 'apache-beam-testing' + instance_id: 'beam-test' + table_id: 'bigtable-enrichment-test' + row_key: 'product_id' + timeout: 30 + + # Step 3: Logging for testing + # This is a simple way to view the enriched data + # We can also store it somewhere like a json file + - type: LogForTesting + +options: + yaml_experimental_features: Enrichment + +# Expected: +# Row(sale_id=1, customer_id=1, product_id=1, quantity=1, product={'product_id': '1', 'product_name': 'pixel 5', 'product_stock': '2'}) \ No newline at end of file diff --git a/sdks/python/apache_beam/yaml/examples/transforms/ml/enrich_spanner_with_bigquery.yaml b/sdks/python/apache_beam/yaml/examples/transforms/ml/enrich_spanner_with_bigquery.yaml new file mode 100644 index 000000000000..e63b3105cc0c --- /dev/null +++ b/sdks/python/apache_beam/yaml/examples/transforms/ml/enrich_spanner_with_bigquery.yaml @@ -0,0 +1,102 @@ +# coding=utf-8 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +pipeline: + transforms: + # Step 1: Read orders details from Spanner + - type: ReadFromSpanner + name: ReadOrders + config: + project_id: 'apache-beam-testing' + instance_id: 'orders-test' + database_id: 'order-database' + query: 'SELECT customer_id, product_id, order_date, order_amount FROM orders' + + # Step 2: Enrich order details with customers details from BigQuery + - type: Enrichment + name: Enriched + input: ReadOrders + config: + enrichment_handler: 'BigQuery' + handler_config: + project: "apache-beam-testing" + table_name: "apache-beam-testing.ALL_TEST.customers" + row_restriction_template: "customer_id = 1001 or customer_id = 1003" + fields: ["customer_id"] + + # Step 3: Map enriched values to Beam schema + # TODO: This should be removed when schema'd enrichment is available + - type: MapToFields + name: MapEnrichedValues + input: Enriched + config: + language: python + fields: + customer_id: + callable: 'lambda x: x.customer_id' + output_type: integer + customer_name: + callable: 'lambda x: x.customer_name' + output_type: string + customer_email: + callable: 'lambda x: x.customer_email' + output_type: string + product_id: + callable: 'lambda x: x.product_id' + output_type: integer + order_date: + callable: 'lambda x: x.order_date' + output_type: string + order_amount: + callable: 'lambda x: x.order_amount' + output_type: integer + + # Step 4: Filter orders with amount greater than 110 + - type: Filter + name: FilterHighValueOrders + input: MapEnrichedValues + config: + keep: "order_amount > 110" + language: "python" + + + # Step 6: Write processed order to another spanner table + # Note: Make sure to replace $VARS with your values. + - type: WriteToSpanner + name: WriteProcessedOrders + input: FilterHighValueOrders + config: + project_id: '$PROJECT' + instance_id: '$INSTANCE' + database_id: '$DATABASE' + table_id: '$TABLE' + error_handling: + output: my_error_output + + # Step 7: Handle write errors by writing to JSON + - type: WriteToJson + name: WriteErrorsToJson + input: WriteProcessedOrders.my_error_output + config: + path: 'errors.json' + +options: + yaml_experimental_features: Enrichment + +# Expected: +# Row(customer_id=1001, customer_name='Alice', customer_email='alice@gmail.com', product_id=2001, order_date='24-03-24', order_amount=150) From 0eb82b70acc8124d3edae761f7b85143b069c406 Mon Sep 17 00:00:00 2001 From: Kenneth Knowles Date: Mon, 16 Dec 2024 10:05:08 -0500 Subject: [PATCH 120/135] Fix duplicate yml entry causing test result dashboard sync to fail --- .github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml index 1442f5ffafc0..f79ca8747828 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink.yml @@ -93,4 +93,3 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' - large_files: true From 74b723db434f10d2fbbe53e68e9f9bdb707da936 Mon Sep 17 00:00:00 2001 From: Kenneth Knowles Date: Mon, 16 Dec 2024 10:34:00 -0500 Subject: [PATCH 121/135] Fix yaml error in Flink Java8 ValidatesRunner --- .../beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml index 0f12ce6f90ef..c51c39987236 100644 --- a/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml +++ b/.github/workflows/beam_PostCommit_Java_ValidatesRunner_Flink_Java8.yml @@ -110,4 +110,3 @@ jobs: commit: '${{ env.prsha || env.GITHUB_SHA }}' comment_mode: ${{ github.event_name == 'issue_comment' && 'always' || 'off' }} files: '**/build/test-results/**/*.xml' - large_files: true From 0f4c64768f80661ed1dfbdcd38e762c1fa52d2fe Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 16 Dec 2024 09:59:49 -0800 Subject: [PATCH 122/135] yapf --- .../runners/portability/fn_api_runner/fn_runner_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py index 65d598171c08..3f036ab27f6e 100644 --- a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py +++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py @@ -1255,7 +1255,8 @@ def test_metrics(self, check_gauge=True, check_bounded_trie=False): .with_name('bounded_trie'))['bounded_tries'] self.assertEqual(bounded_trie.committed.size(), 2) for element in elements: - self.assertTrue(bounded_trie.committed.contains(tuple(element)), element) + self.assertTrue( + bounded_trie.committed.contains(tuple(element)), element) def test_callbacks_with_exception(self): elements_list = ['1', '2'] From 4bac374df961f54ee7c7be2a8811714fb71a4a72 Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:45:22 +0000 Subject: [PATCH 123/135] chore: upgrade google_cloud_bigdataoss_version to 2.2.26 in order to be protobuf 3 and 4 compatible (#33395) --- .../main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy index a59c1d7630b0..ef934efa94be 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -606,7 +606,7 @@ class BeamModulePlugin implements Plugin { def gax_version = "2.55.0" def google_ads_version = "33.0.0" def google_clients_version = "2.0.0" - def google_cloud_bigdataoss_version = "2.2.16" + def google_cloud_bigdataoss_version = "2.2.26" // [bomupgrader] determined by: com.google.cloud:google-cloud-spanner, consistent with: google_cloud_platform_libraries_bom def google_cloud_spanner_version = "6.79.0" def google_code_gson_version = "2.10.1" From 3dc2b2edc095cc23a6a773e4064da5c2a97c96f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:22:01 -0800 Subject: [PATCH 124/135] Bump google.golang.org/protobuf from 1.35.2 to 1.36.0 in /sdks (#33397) Bumps google.golang.org/protobuf from 1.35.2 to 1.36.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 2 +- sdks/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 79b32b051df3..d0f607bd2abf 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -61,7 +61,7 @@ require ( google.golang.org/api v0.211.0 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 google.golang.org/grpc v1.67.2 - google.golang.org/protobuf v1.35.2 + google.golang.org/protobuf v1.36.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/sdks/go.sum b/sdks/go.sum index 0e7792f499f4..d4679497e21a 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1917,8 +1917,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From c5a5be5e14edb87ba055b576ada3ff260b47bcf0 Mon Sep 17 00:00:00 2001 From: claudevdm <33973061+claudevdm@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:24:55 -0500 Subject: [PATCH 125/135] Rag chunking embedding (#33364) * Add core RAG types. * Add chunking base. * Add LangChain TextSplitter chunking. * Add generic type support for embeddings. * Add base rag EmbeddingTypeAdapter. * Create HuggingfaceTextEmbeddings. * Linter fixes. * Docstring fixes. * Typehint fixes. * Docstring fix. * Add EmbeddingManager to args and more test coverage. --- sdks/python/apache_beam/ml/rag/__init__.py | 25 ++ .../apache_beam/ml/rag/chunking/__init__.py | 21 ++ .../apache_beam/ml/rag/chunking/base.py | 92 ++++++++ .../apache_beam/ml/rag/chunking/base_test.py | 139 +++++++++++ .../apache_beam/ml/rag/chunking/langchain.py | 120 ++++++++++ .../ml/rag/chunking/langchain_test.py | 217 ++++++++++++++++++ .../apache_beam/ml/rag/embeddings/__init__.py | 20 ++ .../apache_beam/ml/rag/embeddings/base.py | 55 +++++ .../ml/rag/embeddings/base_test.py | 93 ++++++++ .../ml/rag/embeddings/huggingface.py | 74 ++++++ .../ml/rag/embeddings/huggingface_test.py | 108 +++++++++ sdks/python/apache_beam/ml/rag/types.py | 73 ++++++ sdks/python/apache_beam/ml/transforms/base.py | 181 +++++++++------ .../apache_beam/ml/transforms/base_test.py | 103 ++++++--- .../ml/transforms/embeddings/huggingface.py | 4 +- 15 files changed, 1225 insertions(+), 100 deletions(-) create mode 100644 sdks/python/apache_beam/ml/rag/__init__.py create mode 100644 sdks/python/apache_beam/ml/rag/chunking/__init__.py create mode 100644 sdks/python/apache_beam/ml/rag/chunking/base.py create mode 100644 sdks/python/apache_beam/ml/rag/chunking/base_test.py create mode 100644 sdks/python/apache_beam/ml/rag/chunking/langchain.py create mode 100644 sdks/python/apache_beam/ml/rag/chunking/langchain_test.py create mode 100644 sdks/python/apache_beam/ml/rag/embeddings/__init__.py create mode 100644 sdks/python/apache_beam/ml/rag/embeddings/base.py create mode 100644 sdks/python/apache_beam/ml/rag/embeddings/base_test.py create mode 100644 sdks/python/apache_beam/ml/rag/embeddings/huggingface.py create mode 100644 sdks/python/apache_beam/ml/rag/embeddings/huggingface_test.py create mode 100644 sdks/python/apache_beam/ml/rag/types.py diff --git a/sdks/python/apache_beam/ml/rag/__init__.py b/sdks/python/apache_beam/ml/rag/__init__.py new file mode 100644 index 000000000000..554beb9d7aba --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/__init__.py @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +"""Apache Beam RAG (Retrieval Augmented Generation) components. +This package provides components for building RAG pipelines in Apache Beam, +including: +- Chunking +- Embedding generation +- Vector storage +- Vector search enrichment +""" diff --git a/sdks/python/apache_beam/ml/rag/chunking/__init__.py b/sdks/python/apache_beam/ml/rag/chunking/__init__.py new file mode 100644 index 000000000000..34a6a966b19e --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/chunking/__init__.py @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +"""Chunking components for RAG pipelines. +This module provides components for splitting text into chunks for RAG +pipelines. +""" diff --git a/sdks/python/apache_beam/ml/rag/chunking/base.py b/sdks/python/apache_beam/ml/rag/chunking/base.py new file mode 100644 index 000000000000..626a6ea8abbe --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/chunking/base.py @@ -0,0 +1,92 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +import abc +import functools +from collections.abc import Callable +from typing import Any +from typing import Dict +from typing import Optional + +import apache_beam as beam +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.transforms.base import MLTransformProvider + +ChunkIdFn = Callable[[Chunk], str] + + +def _assign_chunk_id(chunk_id_fn: ChunkIdFn, chunk: Chunk): + chunk.id = chunk_id_fn(chunk) + return chunk + + +class ChunkingTransformProvider(MLTransformProvider): + def __init__(self, chunk_id_fn: Optional[ChunkIdFn] = None): + """Base class for chunking transforms in RAG pipelines. + + ChunkingTransformProvider defines the interface for splitting documents + into chunks for embedding and retrieval. Implementations should define how + to split content while preserving metadata and managing chunk IDs. + + The transform flow: + - Takes input documents with content and metadata + - Splits content into chunks using implementation-specific logic + - Preserves document metadata in resulting chunks + - Optionally assigns unique IDs to chunks (configurable via chunk_id_fn + + Example usage: + >>> class MyChunker(ChunkingTransformProvider): + ... def get_splitter_transform(self): + ... return beam.ParDo(MySplitterDoFn()) + ... + >>> chunker = MyChunker(chunk_id_fn=my_id_function) + >>> + >>> with beam.Pipeline() as p: + ... chunks = ( + ... p + ... | beam.Create([{'text': 'document...', 'source': 'doc.txt'}]) + ... | MLTransform(...).with_transform(chunker)) + + Args: + chunk_id_fn: Optional function to generate chunk IDs. If not provided, + random UUIDs will be used. Function should take a Chunk and return str. + """ + self.assign_chunk_id_fn = functools.partial( + _assign_chunk_id, chunk_id_fn) if chunk_id_fn is not None else None + + @abc.abstractmethod + def get_splitter_transform( + self + ) -> beam.PTransform[beam.PCollection[Dict[str, Any]], + beam.PCollection[Chunk]]: + """Creates transforms that emits splits for given content.""" + raise NotImplementedError( + "Subclasses must implement get_splitter_transform") + + def get_ptransform_for_processing( + self, **kwargs + ) -> beam.PTransform[beam.PCollection[Dict[str, Any]], + beam.PCollection[Chunk]]: + """Creates transform for processing documents into chunks.""" + ptransform = ( + "Split document" >> + self.get_splitter_transform().with_output_types(Chunk)) + if self.assign_chunk_id_fn: + ptransform = ( + ptransform | "Assign chunk id" >> beam.Map( + self.assign_chunk_id_fn).with_output_types(Chunk)) + return ptransform diff --git a/sdks/python/apache_beam/ml/rag/chunking/base_test.py b/sdks/python/apache_beam/ml/rag/chunking/base_test.py new file mode 100644 index 000000000000..54e25591c348 --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/chunking/base_test.py @@ -0,0 +1,139 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +"""Tests for apache_beam.ml.rag.chunking.base.""" + +import unittest +from typing import Any +from typing import Dict +from typing import Optional + +import pytest + +import apache_beam as beam +from apache_beam.ml.rag.chunking.base import ChunkIdFn +from apache_beam.ml.rag.chunking.base import ChunkingTransformProvider +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.rag.types import Content +from apache_beam.testing.test_pipeline import TestPipeline +from apache_beam.testing.util import assert_that +from apache_beam.testing.util import equal_to + + +class WordSplitter(beam.DoFn): + def process(self, element): + words = element['text'].split() + for i, word in enumerate(words): + yield Chunk( + content=Content(text=word), + index=i, + metadata={'source': element['source']}) + + +class InvalidChunkingProvider(ChunkingTransformProvider): + def __init__(self, chunk_id_fn: Optional[ChunkIdFn] = None): + super().__init__(chunk_id_fn=chunk_id_fn) + + +class MockChunkingProvider(ChunkingTransformProvider): + def __init__(self, chunk_id_fn: Optional[ChunkIdFn] = None): + super().__init__(chunk_id_fn=chunk_id_fn) + + def get_splitter_transform( + self + ) -> beam.PTransform[beam.PCollection[Dict[str, Any]], + beam.PCollection[Chunk]]: + return beam.ParDo(WordSplitter()) + + +def chunk_equals(expected, actual): + """Custom equality function for Chunk objects.""" + if not isinstance(expected, Chunk) or not isinstance(actual, Chunk): + return False + # Don't compare IDs since they're randomly generated + return ( + expected.index == actual.index and expected.content == actual.content and + expected.metadata == actual.metadata) + + +def id_equals(expected, actual): + """Custom equality function for Chunk object id's.""" + if not isinstance(expected, Chunk) or not isinstance(actual, Chunk): + return False + return (expected.id == actual.id) + + +@pytest.mark.uses_transformers +class ChunkingTransformProviderTest(unittest.TestCase): + def setUp(self): + self.test_doc = {'text': 'hello world test', 'source': 'test.txt'} + + def test_doesnt_override_get_text_splitter_transform(self): + provider = InvalidChunkingProvider() + with self.assertRaises(NotImplementedError): + provider.get_splitter_transform() + + def test_chunking_transform(self): + """Test the complete chunking transform.""" + provider = MockChunkingProvider() + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([self.test_doc]) + | provider.get_ptransform_for_processing()) + + expected = [ + Chunk( + content=Content(text="hello"), + index=0, + metadata={'source': 'test.txt'}), + Chunk( + content=Content(text="world"), + index=1, + metadata={'source': 'test.txt'}), + Chunk( + content=Content(text="test"), + index=2, + metadata={'source': 'test.txt'}) + ] + + assert_that(chunks, equal_to(expected, equals_fn=chunk_equals)) + + def test_custom_chunk_id_fn(self): + """Test the a custom chink id function.""" + def source_index_id_fn(chunk: Chunk): + return f"{chunk.metadata['source']}_{chunk.index}" + + provider = MockChunkingProvider(chunk_id_fn=source_index_id_fn) + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([self.test_doc]) + | provider.get_ptransform_for_processing()) + + expected = [ + Chunk(content=Content(text="hello"), id="test.txt_0"), + Chunk(content=Content(text="world"), id="test.txt_1"), + Chunk(content=Content(text="test"), id="test.txt_2") + ] + + assert_that(chunks, equal_to(expected, equals_fn=id_equals)) + + +if __name__ == '__main__': + unittest.main() diff --git a/sdks/python/apache_beam/ml/rag/chunking/langchain.py b/sdks/python/apache_beam/ml/rag/chunking/langchain.py new file mode 100644 index 000000000000..9e3b6b0c8ef9 --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/chunking/langchain.py @@ -0,0 +1,120 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +import apache_beam as beam +from apache_beam.ml.rag.chunking.base import ChunkIdFn +from apache_beam.ml.rag.chunking.base import ChunkingTransformProvider +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.rag.types import Content + +try: + from langchain.text_splitter import TextSplitter +except ImportError: + TextSplitter = None + + +class LangChainChunker(ChunkingTransformProvider): + def __init__( + self, + text_splitter: TextSplitter, + document_field: str, + metadata_fields: List[str], + chunk_id_fn: Optional[ChunkIdFn] = None): + """A ChunkingTransformProvider that uses LangChain text splitters. + + This provider integrates LangChain's text splitting capabilities into + Beam's MLTransform framework. It supports various text splitting strategies + through LangChain's TextSplitter interface, including recursive character + splitting and other methods. + + The provider: + - Takes documents with text content and metadata + - Splits text using configured LangChain splitter + - Preserves document metadata in resulting chunks + - Assigns unique IDs to chunks (configurable via chunk_id_fn) + + Example usage: + ```python + from langchain.text_splitter import RecursiveCharacterTextSplitter + + splitter = RecursiveCharacterTextSplitter( + chunk_size=100, + chunk_overlap=20 + ) + + chunker = LangChainChunker(text_splitter=splitter) + + with beam.Pipeline() as p: + chunks = ( + p + | beam.Create([{'text': 'long document...', 'source': 'doc.txt'}]) + | MLTransform(...).with_transform(chunker)) + ``` + + Args: + text_splitter: A LangChain TextSplitter instance that defines how + documents are split into chunks. + metadata_fields: List of field names to copy from input documents to + chunk metadata. These fields will be preserved in each chunk created + from the document. + chunk_id_fn: Optional function that take a Chunk and return str to + generate chunk IDs. If not provided, random UUIDs will be used. + """ + if not TextSplitter: + raise ImportError( + "langchain is required to use LangChainChunker" + "Please install it with using `pip install langchain`.") + if not isinstance(text_splitter, TextSplitter): + raise TypeError("text_splitter must be a LangChain TextSplitter") + if not document_field: + raise ValueError("document_field cannot be empty") + super().__init__(chunk_id_fn) + self.text_splitter = text_splitter + self.document_field = document_field + self.metadata_fields = metadata_fields + + def get_splitter_transform( + self + ) -> beam.PTransform[beam.PCollection[Dict[str, Any]], + beam.PCollection[Chunk]]: + return "Langchain text split" >> beam.ParDo( + _LangChainTextSplitter( + text_splitter=self.text_splitter, + document_field=self.document_field, + metadata_fields=self.metadata_fields)) + + +class _LangChainTextSplitter(beam.DoFn): + def __init__( + self, + text_splitter: TextSplitter, + document_field: str, + metadata_fields: List[str]): + self.text_splitter = text_splitter + self.document_field = document_field + self.metadata_fields = metadata_fields + + def process(self, element): + text_chunks = self.text_splitter.split_text(element[self.document_field]) + metadata = {field: element[field] for field in self.metadata_fields} + for i, text_chunk in enumerate(text_chunks): + yield Chunk(content=Content(text=text_chunk), index=i, metadata=metadata) diff --git a/sdks/python/apache_beam/ml/rag/chunking/langchain_test.py b/sdks/python/apache_beam/ml/rag/chunking/langchain_test.py new file mode 100644 index 000000000000..83a4fc1a778f --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/chunking/langchain_test.py @@ -0,0 +1,217 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +"""Tests for apache_beam.ml.rag.chunking.langchain.""" + +import unittest + +import apache_beam as beam +from apache_beam.ml.rag.types import Chunk +from apache_beam.testing.test_pipeline import TestPipeline +from apache_beam.testing.util import assert_that +from apache_beam.testing.util import equal_to + +try: + from apache_beam.ml.rag.chunking.langchain import LangChainChunker + + from langchain.text_splitter import ( + CharacterTextSplitter, RecursiveCharacterTextSplitter) + LANGCHAIN_AVAILABLE = True +except ImportError: + LANGCHAIN_AVAILABLE = False + +# Import optional dependencies +try: + from transformers import AutoTokenizer + TRANSFORMERS_AVAILABLE = True +except ImportError: + TRANSFORMERS_AVAILABLE = False + + +def chunk_equals(expected, actual): + """Custom equality function for Chunk objects.""" + if not isinstance(expected, Chunk) or not isinstance(actual, Chunk): + return False + return ( + expected.content == actual.content and expected.index == actual.index and + expected.metadata == actual.metadata) + + +@unittest.skipIf(not LANGCHAIN_AVAILABLE, 'langchain is not installed.') +class LangChainChunkingTest(unittest.TestCase): + def setUp(self): + self.simple_text = { + 'content': 'This is a simple test document. It has multiple sentences. ' + 'We will use it to test basic splitting.', + 'source': 'simple.txt', + 'language': 'en' + } + + self.complex_text = { + 'content': ( + 'The patient arrived at 2 p.m. yesterday. ' + 'Initial assessment was completed. ' + 'Lab results showed normal ranges. ' + 'Follow-up scheduled for next week.'), + 'source': 'medical.txt', + 'language': 'en' + } + + def test_no_metadata_fields(self): + """Test chunking with no metadata fields specified.""" + splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20) + provider = LangChainChunker( + document_field='content', metadata_fields=[], text_splitter=splitter) + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([self.simple_text]) + | provider.get_ptransform_for_processing()) + chunks_count = chunks | beam.combiners.Count.Globally() + + assert_that(chunks_count, lambda x: x[0] > 0, 'Has chunks') + + assert_that(chunks, lambda x: all(c.metadata == {} for c in x)) + + def test_multiple_metadata_fields(self): + """Test chunking with multiple metadata fields.""" + splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20) + provider = LangChainChunker( + document_field='content', + metadata_fields=['source', 'language'], + text_splitter=splitter) + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([self.simple_text]) + | provider.get_ptransform_for_processing()) + chunks_count = chunks | beam.combiners.Count.Globally() + + assert_that(chunks_count, lambda x: x[0] > 0, 'Has chunks') + assert_that( + chunks, + lambda x: all( + c.metadata == { + 'source': 'simple.txt', 'language': 'en' + } for c in x)) + + def test_recursive_splitter_no_overlap(self): + """Test RecursiveCharacterTextSplitter with no overlap.""" + splitter = RecursiveCharacterTextSplitter( + chunk_size=30, chunk_overlap=0, separators=[". "]) + provider = LangChainChunker( + document_field='content', + metadata_fields=['source'], + text_splitter=splitter) + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([self.simple_text]) + | provider.get_ptransform_for_processing()) + chunks_count = chunks | beam.combiners.Count.Globally() + + assert_that(chunks_count, lambda x: x[0] > 0, 'Has chunks') + assert_that(chunks, lambda x: all(len(c.content.text) <= 30 for c in x)) + + @unittest.skipIf(not TRANSFORMERS_AVAILABLE, "transformers not available") + def test_huggingface_tokenizer_splitter(self): + """Test text splitter created from HuggingFace tokenizer.""" + tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") + splitter = RecursiveCharacterTextSplitter.from_huggingface_tokenizer( + tokenizer, + chunk_size=10, # tokens + chunk_overlap=2 # tokens + ) + + provider = LangChainChunker( + document_field='content', + metadata_fields=['source'], + text_splitter=splitter) + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([self.simple_text]) + | provider.get_ptransform_for_processing()) + + def check_token_lengths(chunks): + for chunk in chunks: + # Verify each chunk's token length is within limits + num_tokens = len(tokenizer.encode(chunk.content.text)) + if not num_tokens <= 10: + raise AssertionError( + f"Chunk has {num_tokens} tokens, expected <= 10") + return True + + chunks_count = chunks | beam.combiners.Count.Globally() + + assert_that(chunks_count, lambda x: x[0] > 0, 'Has chunks') + assert_that(chunks, check_token_lengths) + + def test_invalid_document_field(self): + """Test that using an invalid document field raises KeyError.""" + splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20) + provider = LangChainChunker( + document_field='nonexistent', + metadata_fields={}, + text_splitter=splitter) + + with self.assertRaises(KeyError): + with TestPipeline() as p: + _ = ( + p + | beam.Create([self.simple_text]) + | provider.get_ptransform_for_processing()) + + def test_empty_document_field(self): + """Test that using an invalid document field raises KeyError.""" + splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20) + + with self.assertRaises(ValueError): + _ = LangChainChunker( + document_field='', metadata_fields={}, text_splitter=splitter) + + def test_invalid_text_splitter(self): + """Test that using an invalid document field raises KeyError.""" + + with self.assertRaises(TypeError): + _ = LangChainChunker( + document_field='nonexistent', text_splitter="Not a text splitter!") + + def test_empty_text(self): + """Test that empty text produces no chunks.""" + empty_doc = {'content': '', 'source': 'empty.txt'} + + splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20) + provider = LangChainChunker( + document_field='content', + metadata_fields=['source'], + text_splitter=splitter) + + with TestPipeline() as p: + chunks = ( + p + | beam.Create([empty_doc]) + | provider.get_ptransform_for_processing()) + + assert_that(chunks, equal_to([])) + + +if __name__ == '__main__': + unittest.main() diff --git a/sdks/python/apache_beam/ml/rag/embeddings/__init__.py b/sdks/python/apache_beam/ml/rag/embeddings/__init__.py new file mode 100644 index 000000000000..d2cdb63c0bde --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/embeddings/__init__.py @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +"""Embedding components for RAG pipelines. +This module provides components for generating embeddings in RAG pipelines. +""" diff --git a/sdks/python/apache_beam/ml/rag/embeddings/base.py b/sdks/python/apache_beam/ml/rag/embeddings/base.py new file mode 100644 index 000000000000..25dc3ee47e80 --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/embeddings/base.py @@ -0,0 +1,55 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +from collections.abc import Sequence +from typing import List + +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.rag.types import Embedding +from apache_beam.ml.transforms.base import EmbeddingTypeAdapter + + +def create_rag_adapter() -> EmbeddingTypeAdapter[Chunk, Chunk]: + """Creates adapter for converting between Chunk and Embedding types. + + The adapter: + - Extracts text from Chunk.content.text for embedding + - Creates Embedding objects from model output + - Sets Embedding in Chunk.embedding + + Returns: + EmbeddingTypeAdapter configured for RAG pipeline types + """ + return EmbeddingTypeAdapter( + input_fn=_extract_chunk_text, output_fn=_add_embedding_fn) + + +def _extract_chunk_text(chunks: Sequence[Chunk]) -> List[str]: + """Extract text from chunks for embedding.""" + chunk_texts = [] + for chunk in chunks: + if not chunk.content.text: + raise ValueError("Expected chunk text content.") + chunk_texts.append(chunk.content.text) + return chunk_texts + + +def _add_embedding_fn( + chunks: Sequence[Chunk], embeddings: Sequence[List[float]]) -> List[Chunk]: + """Create Embeddings from chunks and embedding vectors.""" + for chunk, embedding in zip(chunks, embeddings): + chunk.embedding = Embedding(dense_embedding=embedding) + return list(chunks) diff --git a/sdks/python/apache_beam/ml/rag/embeddings/base_test.py b/sdks/python/apache_beam/ml/rag/embeddings/base_test.py new file mode 100644 index 000000000000..3a27ae8e7ebb --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/embeddings/base_test.py @@ -0,0 +1,93 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +import unittest + +from apache_beam.ml.rag.embeddings.base import create_rag_adapter +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.rag.types import Content +from apache_beam.ml.rag.types import Embedding + + +class RAGBaseEmbeddingsTest(unittest.TestCase): + def setUp(self): + self.test_chunks = [ + Chunk( + content=Content(text="This is a test sentence."), + id="1", + metadata={ + "source": "test.txt", "language": "en" + }), + Chunk( + content=Content(text="Another example."), + id="2", + metadata={ + "source": "test2.txt", "language": "en" + }) + ] + + def test_adapter_input_conversion(self): + """Test the RAG type adapter converts correctly.""" + adapter = create_rag_adapter() + + # Test input conversion + texts = adapter.input_fn(self.test_chunks) + self.assertEqual(texts, ["This is a test sentence.", "Another example."]) + + def test_adapter_input_conversion_missing_text_content(self): + """Test the RAG type adapter converts correctly.""" + adapter = create_rag_adapter() + + # Test input conversion + with self.assertRaisesRegex(ValueError, "Expected chunk text content"): + adapter.input_fn([ + Chunk( + content=Content(), + id="1", + metadata={ + "source": "test.txt", "language": "en" + }) + ]) + + def test_adapter_output_conversion(self): + """Test the RAG type adapter converts correctly.""" + # Test output conversion + mock_embeddings = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]] + # Expected outputs + expected = [ + Chunk( + id="1", + embedding=Embedding(dense_embedding=[0.1, 0.2, 0.3]), + metadata={ + 'source': 'test.txt', 'language': 'en' + }, + content=Content(text='This is a test sentence.')), + Chunk( + id="2", + embedding=Embedding(dense_embedding=[0.4, 0.5, 0.6]), + metadata={ + 'source': 'test2.txt', 'language': 'en' + }, + content=Content(text='Another example.')), + ] + adapter = create_rag_adapter() + + embeddings = adapter.output_fn(self.test_chunks, mock_embeddings) + self.assertListEqual(embeddings, expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/sdks/python/apache_beam/ml/rag/embeddings/huggingface.py b/sdks/python/apache_beam/ml/rag/embeddings/huggingface.py new file mode 100644 index 000000000000..4cb0aecd6e82 --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/embeddings/huggingface.py @@ -0,0 +1,74 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +"""RAG-specific embedding implementations using HuggingFace models.""" + +from typing import Optional + +import apache_beam as beam +from apache_beam.ml.inference.base import RunInference +from apache_beam.ml.rag.embeddings.base import create_rag_adapter +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.transforms.base import EmbeddingsManager +from apache_beam.ml.transforms.base import _TextEmbeddingHandler +from apache_beam.ml.transforms.embeddings.huggingface import _SentenceTransformerModelHandler + +try: + from sentence_transformers import SentenceTransformer +except ImportError: + SentenceTransformer = None + + +class HuggingfaceTextEmbeddings(EmbeddingsManager): + def __init__( + self, model_name: str, *, max_seq_length: Optional[int] = None, **kwargs): + """Utilizes huggingface SentenceTransformer embeddings for RAG pipeline. + + Args: + model_name: Name of the sentence-transformers model to use + max_seq_length: Maximum sequence length for the model + **kwargs: Additional arguments passed to + :class:`~apache_beam.ml.transforms.base.EmbeddingsManager` + constructor including ModelHandler arguments + """ + if not SentenceTransformer: + raise ImportError( + "sentence-transformers is required to use " + "HuggingfaceTextEmbeddings." + "Please install it with using `pip install sentence-transformers`.") + super().__init__(type_adapter=create_rag_adapter(), **kwargs) + self.model_name = model_name + self.max_seq_length = max_seq_length + self.model_class = SentenceTransformer + + def get_model_handler(self): + """Returns model handler configured with RAG adapter.""" + return _SentenceTransformerModelHandler( + model_class=self.model_class, + max_seq_length=self.max_seq_length, + model_name=self.model_name, + load_model_args=self.load_model_args, + min_batch_size=self.min_batch_size, + max_batch_size=self.max_batch_size, + large_model=self.large_model) + + def get_ptransform_for_processing( + self, **kwargs + ) -> beam.PTransform[beam.PCollection[Chunk], beam.PCollection[Chunk]]: + """Returns PTransform that uses the RAG adapter.""" + return RunInference( + model_handler=_TextEmbeddingHandler(self), + inference_args=self.inference_args).with_output_types(Chunk) diff --git a/sdks/python/apache_beam/ml/rag/embeddings/huggingface_test.py b/sdks/python/apache_beam/ml/rag/embeddings/huggingface_test.py new file mode 100644 index 000000000000..aa63d13025a1 --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/embeddings/huggingface_test.py @@ -0,0 +1,108 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +"""Tests for apache_beam.ml.rag.embeddings.huggingface.""" + +import tempfile +import unittest + +import pytest + +import apache_beam as beam +from apache_beam.ml.rag.embeddings.huggingface import HuggingfaceTextEmbeddings +from apache_beam.ml.rag.types import Chunk +from apache_beam.ml.rag.types import Content +from apache_beam.ml.rag.types import Embedding +from apache_beam.ml.transforms.base import MLTransform +from apache_beam.testing.test_pipeline import TestPipeline +from apache_beam.testing.util import assert_that +from apache_beam.testing.util import equal_to + +# pylint: disable=unused-import +try: + from sentence_transformers import SentenceTransformer + SENTENCE_TRANSFORMERS_AVAILABLE = True +except ImportError: + SENTENCE_TRANSFORMERS_AVAILABLE = False + + +def chunk_approximately_equals(expected, actual): + """Compare embeddings allowing for numerical differences.""" + if not isinstance(expected, Chunk) or not isinstance(actual, Chunk): + return False + + return ( + expected.id == actual.id and expected.metadata == actual.metadata and + expected.content == actual.content and + len(expected.embedding.dense_embedding) == len( + actual.embedding.dense_embedding) and + all(isinstance(x, float) for x in actual.embedding.dense_embedding)) + + +@pytest.mark.uses_transformers +@unittest.skipIf( + not SENTENCE_TRANSFORMERS_AVAILABLE, "sentence-transformers not available") +class HuggingfaceTextEmbeddingsTest(unittest.TestCase): + def setUp(self): + self.artifact_location = tempfile.mkdtemp(prefix='sentence_transformers_') + self.test_chunks = [ + Chunk( + content=Content(text="This is a test sentence."), + id="1", + metadata={ + "source": "test.txt", "language": "en" + }), + Chunk( + content=Content(text="Another example."), + id="2", + metadata={ + "source": "test.txt", "language": "en" + }) + ] + + def test_embedding_pipeline(self): + expected = [ + Chunk( + id="1", + embedding=Embedding(dense_embedding=[0.0] * 384), + metadata={ + "source": "test.txt", "language": "en" + }, + content=Content(text="This is a test sentence.")), + Chunk( + id="2", + embedding=Embedding(dense_embedding=[0.0] * 384), + metadata={ + "source": "test.txt", "language": "en" + }, + content=Content(text="Another example.")) + ] + embedder = HuggingfaceTextEmbeddings( + model_name="sentence-transformers/all-MiniLM-L6-v2") + + with TestPipeline() as p: + embeddings = ( + p + | beam.Create(self.test_chunks) + | MLTransform(write_artifact_location=self.artifact_location). + with_transform(embedder)) + + assert_that( + embeddings, equal_to(expected, equals_fn=chunk_approximately_equals)) + + +if __name__ == '__main__': + unittest.main() diff --git a/sdks/python/apache_beam/ml/rag/types.py b/sdks/python/apache_beam/ml/rag/types.py new file mode 100644 index 000000000000..79429899e4c1 --- /dev/null +++ b/sdks/python/apache_beam/ml/rag/types.py @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +"""Core types for RAG pipelines. +This module contains the core dataclasses used throughout the RAG pipeline +implementation, including Chunk and Embedding types that define the data +contracts between different stages of the pipeline. +""" + +import uuid +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple + + +@dataclass +class Content: + """Container for embeddable content. Add new types as when as necessary. + + Args: + text: Text content to be embedded + """ + text: Optional[str] = None + + +@dataclass +class Embedding: + """Represents vector embeddings. + + Args: + dense_embedding: Dense vector representation + sparse_embedding: Optional sparse vector representation for hybrid + search + """ + dense_embedding: Optional[List[float]] = None + # For hybrid search + sparse_embedding: Optional[Tuple[List[int], List[float]]] = None + + +@dataclass +class Chunk: + """Represents a chunk of embeddable content with metadata. + + Args: + content: The actual content of the chunk + id: Unique identifier for the chunk + index: Index of this chunk within the original document + metadata: Additional metadata about the chunk (e.g., document source) + embedding: Vector embeddings of the content + """ + content: Content + id: str = field(default_factory=lambda: str(uuid.uuid4())) + index: int = 0 + metadata: Dict[str, Any] = field(default_factory=dict) + embedding: Optional[Embedding] = None diff --git a/sdks/python/apache_beam/ml/transforms/base.py b/sdks/python/apache_beam/ml/transforms/base.py index a963f602a06d..57a5efd3ff0e 100644 --- a/sdks/python/apache_beam/ml/transforms/base.py +++ b/sdks/python/apache_beam/ml/transforms/base.py @@ -15,18 +15,24 @@ # limitations under the License. import abc -import collections +import functools import logging import os import tempfile import uuid +from collections.abc import Callable from collections.abc import Mapping from collections.abc import Sequence +from dataclasses import dataclass from typing import Any +from typing import Dict from typing import Generic +from typing import Iterable +from typing import List from typing import Optional from typing import TypeVar from typing import Union +from typing import cast import jsonpickle import numpy as np @@ -62,36 +68,31 @@ # Output of the apply() method of BaseOperation. OperationOutputT = TypeVar('OperationOutputT') +# Input to the EmbeddingTypeAdapter input_fn +EmbeddingTypeAdapterInputT = TypeVar( + 'EmbeddingTypeAdapterInputT') # e.g., Chunk +# Output of the EmbeddingTypeAdapter output_fn +EmbeddingTypeAdapterOutputT = TypeVar( + 'EmbeddingTypeAdapterOutputT') # e.g., Embedding -def _convert_list_of_dicts_to_dict_of_lists( - list_of_dicts: Sequence[dict[str, Any]]) -> dict[str, list[Any]]: - keys_to_element_list = collections.defaultdict(list) - input_keys = list_of_dicts[0].keys() - for d in list_of_dicts: - if set(d.keys()) != set(input_keys): - extra_keys = set(d.keys()) - set(input_keys) if len( - d.keys()) > len(input_keys) else set(input_keys) - set(d.keys()) - raise RuntimeError( - f'All the dicts in the input data should have the same keys. ' - f'Got: {extra_keys} instead.') - for key, value in d.items(): - keys_to_element_list[key].append(value) - return keys_to_element_list - - -def _convert_dict_of_lists_to_lists_of_dict( - dict_of_lists: dict[str, list[Any]]) -> list[dict[str, Any]]: - batch_length = len(next(iter(dict_of_lists.values()))) - result: list[dict[str, Any]] = [{} for _ in range(batch_length)] - # all the values in the dict_of_lists should have same length - for key, values in dict_of_lists.items(): - assert len(values) == batch_length, ( - "This function expects all the values " - "in the dict_of_lists to have same length." - ) - for i in range(len(values)): - result[i][key] = values[i] - return result + +@dataclass +class EmbeddingTypeAdapter(Generic[EmbeddingTypeAdapterInputT, + EmbeddingTypeAdapterOutputT]): + """Adapts input types to text for embedding and converts output embeddings. + + Args: + input_fn: Function to extract text for embedding from input type + output_fn: Function to create output type from input and embeddings + """ + input_fn: Callable[[Sequence[EmbeddingTypeAdapterInputT]], List[str]] + output_fn: Callable[[Sequence[EmbeddingTypeAdapterInputT], Sequence[Any]], + List[EmbeddingTypeAdapterOutputT]] + + def __reduce__(self): + """Custom serialization that preserves type information during + jsonpickle.""" + return (self.__class__, (self.input_fn, self.output_fn)) def _map_errors_to_beam_row(element, cls_name=None): @@ -182,13 +183,74 @@ def append_transform(self, transform: BaseOperation): """ +def _dict_input_fn(columns: Sequence[str], + batch: Sequence[Dict[str, Any]]) -> List[str]: + """Extract text from specified columns in batch.""" + if not batch or not isinstance(batch[0], dict): + raise TypeError( + 'Expected data to be dicts, got ' + f'{type(batch[0])} instead.') + + result = [] + expected_keys = set(batch[0].keys()) + expected_columns = set(columns) + # Process one batch item at a time + for item in batch: + item_keys = item.keys() + if set(item_keys) != expected_keys: + extra_keys = item_keys - expected_keys + missing_keys = expected_keys - item_keys + raise RuntimeError( + f'All dicts in batch must have the same keys. ' + f'extra keys: {extra_keys}, ' + f'missing keys: {missing_keys}') + missing_columns = expected_columns - item_keys + if (missing_columns): + raise RuntimeError( + f'Data does not contain the following columns ' + f': {missing_columns}.') + + # Get all columns for this item + for col in columns: + result.append(item[col]) + return result + + +def _dict_output_fn( + columns: Sequence[str], + batch: Sequence[Dict[str, Any]], + embeddings: Sequence[Any]) -> List[Dict[str, Any]]: + """Map embeddings back to columns in batch.""" + result = [] + for batch_idx, item in enumerate(batch): + for col_idx, col in enumerate(columns): + embedding_idx = batch_idx * len(columns) + col_idx + item[col] = embeddings[embedding_idx] + result.append(item) + return result + + +def _create_dict_adapter( + columns: List[str]) -> EmbeddingTypeAdapter[Dict[str, Any], Dict[str, Any]]: + """Create adapter for dict-based processing.""" + return EmbeddingTypeAdapter[Dict[str, Any], Dict[str, Any]]( + input_fn=cast( + Callable[[Sequence[Dict[str, Any]]], List[str]], + functools.partial(_dict_input_fn, columns)), + output_fn=cast( + Callable[[Sequence[Dict[str, Any]], Sequence[Any]], + List[Dict[str, Any]]], + functools.partial(_dict_output_fn, columns))) + + # TODO:https://github.com/apache/beam/issues/29356 # Add support for inference_fn class EmbeddingsManager(MLTransformProvider): def __init__( self, - columns: list[str], *, + columns: Optional[list[str]] = None, + type_adapter: Optional[EmbeddingTypeAdapter] = None, # common args for all ModelHandlers. load_model_args: Optional[dict[str, Any]] = None, min_batch_size: Optional[int] = None, @@ -200,6 +262,12 @@ def __init__( self.max_batch_size = max_batch_size self.large_model = large_model self.columns = columns + if columns is not None: + self.type_adapter = _create_dict_adapter(columns) + elif type_adapter is not None: + self.type_adapter = type_adapter + else: + raise ValueError("Either columns or type_adapter must be specified") self.inference_args = kwargs.pop('inference_args', {}) if kwargs: @@ -616,38 +684,6 @@ def load_model(self): def _validate_column_data(self, batch): pass - def _validate_batch(self, batch: Sequence[dict[str, Any]]): - if not batch or not isinstance(batch[0], dict): - raise TypeError( - 'Expected data to be dicts, got ' - f'{type(batch[0])} instead.') - - def _process_batch( - self, - dict_batch: dict[str, list[Any]], - model: ModelT, - inference_args: Optional[dict[str, Any]]) -> dict[str, list[Any]]: - result: dict[str, list[Any]] = collections.defaultdict(list) - input_keys = dict_batch.keys() - missing_columns_in_data = set(self.columns) - set(input_keys) - if missing_columns_in_data: - raise RuntimeError( - f'Data does not contain the following columns ' - f': {missing_columns_in_data}.') - for key, batch in dict_batch.items(): - if key in self.columns: - self._validate_column_data(batch) - prediction = self._underlying.run_inference( - batch, model, inference_args) - if isinstance(prediction, np.ndarray): - prediction = prediction.tolist() - result[key] = prediction # type: ignore[assignment] - else: - result[key] = prediction # type: ignore[assignment] - else: - result[key] = batch - return result - def run_inference( self, batch: Sequence[dict[str, list[str]]], @@ -659,12 +695,19 @@ def run_inference( a list of dicts. Each dict should have the same keys, and the shape should be of the same size for a single key across the batch. """ - self._validate_batch(batch) - dict_batch = _convert_list_of_dicts_to_dict_of_lists(list_of_dicts=batch) - transformed_batch = self._process_batch(dict_batch, model, inference_args) - return _convert_dict_of_lists_to_lists_of_dict( - dict_of_lists=transformed_batch, - ) + embedding_input = self.embedding_config.type_adapter.input_fn(batch) + self._validate_column_data(batch=embedding_input) + prediction = self._underlying.run_inference( + embedding_input, model, inference_args) + # Convert prediction to Sequence[Any] + if isinstance(prediction, np.ndarray): + prediction_seq = prediction.tolist() + elif isinstance(prediction, Iterable) and not isinstance(prediction, + (str, bytes)): + prediction_seq = list(prediction) + else: + prediction_seq = [prediction] + return self.embedding_config.type_adapter.output_fn(batch, prediction_seq) def get_metrics_namespace(self) -> str: return ( diff --git a/sdks/python/apache_beam/ml/transforms/base_test.py b/sdks/python/apache_beam/ml/transforms/base_test.py index 3db5a63b9542..1ef01acca18a 100644 --- a/sdks/python/apache_beam/ml/transforms/base_test.py +++ b/sdks/python/apache_beam/ml/transforms/base_test.py @@ -78,6 +78,10 @@ def setUp(self) -> None: def tearDown(self): shutil.rmtree(self.artifact_location) + def test_ml_transform_no_read_or_write_artifact_lcoation(self): + with self.assertRaises(ValueError): + _ = base.MLTransform(transforms=[]) + @unittest.skipIf(tft is None, 'tft module is not installed.') def test_ml_transform_appends_transforms_to_process_handler_correctly(self): fake_fn_1 = _FakeOperation(name='fake_fn_1', columns=['x']) @@ -354,6 +358,21 @@ def __repr__(self): return 'FakeEmbeddingsManager' +class InvalidEmbeddingsManager(base.EmbeddingsManager): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def get_model_handler(self) -> ModelHandler: + InvalidEmbeddingsManager.__repr__ = lambda x: 'InvalidEmbeddingsManager' # type: ignore[method-assign] + return FakeModelHandler() + + def get_ptransform_for_processing(self, **kwargs) -> beam.PTransform: + return (RunInference(model_handler=base._TextEmbeddingHandler(self))) + + def __repr__(self): + return 'InvalidEmbeddingsManager' + + class TextEmbeddingHandlerTest(unittest.TestCase): def setUp(self) -> None: self.embedding_conig = FakeEmbeddingsManager(columns=['x']) @@ -362,6 +381,10 @@ def setUp(self) -> None: def tearDown(self) -> None: shutil.rmtree(self.artifact_location) + def test_no_columns_or_type_adapter(self): + with self.assertRaises(ValueError): + _ = InvalidEmbeddingsManager() + def test_handler_with_incompatible_datatype(self): text_handler = base._TextEmbeddingHandler( embeddings_manager=self.embedding_conig) @@ -430,9 +453,9 @@ def test_handler_on_multiple_columns(self): 'x': "Apache Beam", 'y': "Hello world", 'z': 'unchanged' }, ] - self.embedding_conig.columns = ['x', 'y'] + embedding_config = FakeEmbeddingsManager(columns=['x', 'y']) expected_data = [{ - key: (value[::-1] if key in self.embedding_conig.columns else value) + key: (value[::-1] if key in embedding_config.columns else value) for key, value in d.items() } for d in data] @@ -440,9 +463,8 @@ def test_handler_on_multiple_columns(self): result = ( p | beam.Create(data) - | base.MLTransform( - write_artifact_location=self.artifact_location).with_transform( - self.embedding_conig)) + | base.MLTransform(write_artifact_location=self.artifact_location). + with_transform(embedding_config)) assert_that( result, equal_to(expected_data), @@ -457,16 +479,15 @@ def test_handler_on_columns_not_exist_in_input_data(self): 'x': "Apache Beam", 'y': "Hello world" }, ] - self.embedding_conig.columns = ['x', 'y', 'a'] + embedding_config = FakeEmbeddingsManager(columns=['x', 'y', 'a']) with self.assertRaises(RuntimeError): with beam.Pipeline() as p: _ = ( p | beam.Create(data) - | base.MLTransform( - write_artifact_location=self.artifact_location).with_transform( - self.embedding_conig)) + | base.MLTransform(write_artifact_location=self.artifact_location). + with_transform(embedding_config)) def test_handler_with_list_data(self): data = [{ @@ -550,7 +571,7 @@ def tearDown(self) -> None: shutil.rmtree(self.artifact_location) @unittest.skipIf(PIL is None, 'PIL module is not installed.') - def test_handler_with_incompatible_datatype(self): + def test_handler_with_non_dict_datatype(self): image_handler = base._ImageEmbeddingHandler( embeddings_manager=self.embedding_config) data = [ @@ -561,6 +582,24 @@ def test_handler_with_incompatible_datatype(self): with self.assertRaises(TypeError): image_handler.run_inference(data, None, None) + @unittest.skipIf(PIL is None, 'PIL module is not installed.') + def test_handler_with_non_image_datatype(self): + image_handler = base._ImageEmbeddingHandler( + embeddings_manager=self.embedding_config) + data = [ + { + 'x': 'hi there' + }, + { + 'x': 'not an image' + }, + { + 'x': 'image_path.jpg' + }, + ] + with self.assertRaises(TypeError): + image_handler.run_inference(data, None, None) + @unittest.skipIf(PIL is None, 'PIL module is not installed.') def test_handler_with_dict_inputs(self): img_one = PIL.Image.new(mode='RGB', size=(1, 1)) @@ -588,31 +627,37 @@ def test_handler_with_dict_inputs(self): class TestUtilFunctions(unittest.TestCase): - def test_list_of_dicts_to_dict_of_lists_normal(self): + def test_dict_input_fn_normal(self): + input_list = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}] + columns = ['a', 'b'] + + expected_output = [1, 2, 3, 4] + self.assertEqual(base._dict_input_fn(columns, input_list), expected_output) + + def test_dict_output_fn_normal(self): input_list = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}] - expected_output = {'a': [1, 3], 'b': [2, 4]} + columns = ['a', 'b'] + embeddings = [1.1, 2.2, 3.3, 4.4] + + expected_output = [{'a': 1.1, 'b': 2.2}, {'a': 3.3, 'b': 4.4}] self.assertEqual( - base._convert_list_of_dicts_to_dict_of_lists(input_list), - expected_output) + base._dict_output_fn(columns, input_list, embeddings), expected_output) - def test_list_of_dicts_to_dict_of_lists_on_list_inputs(self): + def test_dict_input_fn_on_list_inputs(self): input_list = [{'a': [1, 2, 10], 'b': 3}, {'a': [1], 'b': 5}] - expected_output = {'a': [[1, 2, 10], [1]], 'b': [3, 5]} - self.assertEqual( - base._convert_list_of_dicts_to_dict_of_lists(input_list), - expected_output) + columns = ['a', 'b'] - def test_dict_of_lists_to_lists_of_dict_normal(self): - input_dict = {'a': [1, 3], 'b': [2, 4]} - expected_output = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}] - self.assertEqual( - base._convert_dict_of_lists_to_lists_of_dict(input_dict), - expected_output) + expected_output = [[1, 2, 10], 3, [1], 5] + self.assertEqual(base._dict_input_fn(columns, input_list), expected_output) - def test_dict_of_lists_to_lists_of_dict_unequal_length(self): - input_dict = {'a': [1, 3], 'b': [2]} - with self.assertRaises(AssertionError): - base._convert_dict_of_lists_to_lists_of_dict(input_dict) + def test_dict_output_fn_on_list_inputs(self): + input_list = [{'a': [1, 2, 10], 'b': 3}, {'a': [1], 'b': 5}] + columns = ['a', 'b'] + embeddings = [1.1, 2.2, 3.3, 4.4] + + expected_output = [{'a': 1.1, 'b': 2.2}, {'a': 3.3, 'b': 4.4}] + self.assertEqual( + base._dict_output_fn(columns, input_list, embeddings), expected_output) class TestJsonPickleTransformAttributeManager(unittest.TestCase): diff --git a/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py b/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py index 2162ed050c42..e492cb164222 100644 --- a/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py +++ b/sdks/python/apache_beam/ml/transforms/embeddings/huggingface.py @@ -133,7 +133,7 @@ def __init__( max_batch_size: The maximum batch size to be used for inference. large_model: Whether to share the model across processes. """ - super().__init__(columns, **kwargs) + super().__init__(columns=columns, **kwargs) self.model_name = model_name self.max_seq_length = max_seq_length self.image_model = image_model @@ -219,7 +219,7 @@ def __init__( api_url: Optional[str] = None, **kwargs, ): - super().__init__(columns, **kwargs) + super().__init__(columns=columns, **kwargs) self._authorization_token = {"Authorization": f"Bearer {hf_token}"} self._model_name = model_name self.hf_token = hf_token From d34409e27c42d1ac16d4f2912e436195147f17bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:37:50 -0500 Subject: [PATCH 126/135] Bump google.golang.org/api from 0.211.0 to 0.212.0 in /sdks (#33398) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.211.0 to 0.212.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.211.0...v0.212.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 6 +++--- sdks/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index d0f607bd2abf..ffe9d942f799 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -58,7 +58,7 @@ require ( golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 - google.golang.org/api v0.211.0 + google.golang.org/api v0.212.0 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 google.golang.org/grpc v1.67.2 google.golang.org/protobuf v1.36.0 @@ -75,7 +75,7 @@ require ( require ( cel.dev/expr v0.16.1 // indirect - cloud.google.com/go/auth v0.12.1 // indirect + cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/monitoring v1.21.2 // indirect dario.cat/mergo v1.0.0 // indirect @@ -123,7 +123,7 @@ require ( require ( cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/compute/metadata v0.5.2 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/iam v1.2.2 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect diff --git a/sdks/go.sum b/sdks/go.sum index d4679497e21a..a32b86613ba7 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -101,8 +101,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4= -cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= @@ -188,8 +188,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= -cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -1707,8 +1707,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= -google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= +google.golang.org/api v0.212.0 h1:BcRj3MJfHF3FYD29rk7u9kuu1SyfGqfHcA0hSwKqkHg= +google.golang.org/api v0.212.0/go.mod h1:gICpLlpp12/E8mycRMzgy3SQ9cFh2XnVJ6vJi/kQbvI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From f7a7bddb3bad80275e59591d8444e68b61ece760 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Tue, 17 Dec 2024 08:43:35 -0800 Subject: [PATCH 127/135] Faster default coder for unknown windows. (#33382) This will get used in a windowed reshuffle, among other places. --- sdks/python/apache_beam/coders/coder_impl.pxd | 12 ++++++ sdks/python/apache_beam/coders/coder_impl.py | 31 +++++++++++++++ sdks/python/apache_beam/coders/coders.py | 38 ++++++++++++++++++- .../apache_beam/coders/coders_test_common.py | 8 ++++ 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/coders/coder_impl.pxd b/sdks/python/apache_beam/coders/coder_impl.pxd index 52889fa2fd92..8a28499555c1 100644 --- a/sdks/python/apache_beam/coders/coder_impl.pxd +++ b/sdks/python/apache_beam/coders/coder_impl.pxd @@ -219,6 +219,18 @@ cdef libc.stdint.int64_t MIN_TIMESTAMP_micros cdef libc.stdint.int64_t MAX_TIMESTAMP_micros +cdef class _OrderedUnionCoderImpl(StreamCoderImpl): + cdef tuple _types + cdef tuple _coder_impls + cdef CoderImpl _fallback_coder_impl + + @cython.locals(ix=int, c=CoderImpl) + cpdef encode_to_stream(self, value, OutputStream stream, bint nested) + + @cython.locals(ix=int, c=CoderImpl) + cpdef decode_from_stream(self, InputStream stream, bint nested) + + cdef class WindowedValueCoderImpl(StreamCoderImpl): """A coder for windowed values.""" cdef CoderImpl _value_coder diff --git a/sdks/python/apache_beam/coders/coder_impl.py b/sdks/python/apache_beam/coders/coder_impl.py index 5262e6adf8a6..5dff35052901 100644 --- a/sdks/python/apache_beam/coders/coder_impl.py +++ b/sdks/python/apache_beam/coders/coder_impl.py @@ -1421,6 +1421,37 @@ def estimate_size(self, value, nested=False): return size +class _OrderedUnionCoderImpl(StreamCoderImpl): + def __init__(self, coder_impl_types, fallback_coder_impl): + assert len(coder_impl_types) < 128 + self._types, self._coder_impls = zip(*coder_impl_types) + self._fallback_coder_impl = fallback_coder_impl + + def encode_to_stream(self, value, out, nested): + value_t = type(value) + for (ix, t) in enumerate(self._types): + if value_t is t: + out.write_byte(ix) + c = self._coder_impls[ix] # for typing + c.encode_to_stream(value, out, nested) + break + else: + if self._fallback_coder_impl is None: + raise ValueError("No fallback.") + out.write_byte(0xFF) + self._fallback_coder_impl.encode_to_stream(value, out, nested) + + def decode_from_stream(self, in_stream, nested): + ix = in_stream.read_byte() + if ix == 0xFF: + if self._fallback_coder_impl is None: + raise ValueError("No fallback.") + return self._fallback_coder_impl.decode_from_stream(in_stream, nested) + else: + c = self._coder_impls[ix] # for typing + return c.decode_from_stream(in_stream, nested) + + class WindowedValueCoderImpl(StreamCoderImpl): """For internal use only; no backwards-compatibility guarantees. diff --git a/sdks/python/apache_beam/coders/coders.py b/sdks/python/apache_beam/coders/coders.py index 724f268a8312..e52c6048a15c 100644 --- a/sdks/python/apache_beam/coders/coders.py +++ b/sdks/python/apache_beam/coders/coders.py @@ -1350,12 +1350,48 @@ def __hash__(self): common_urns.coders.INTERVAL_WINDOW.urn, IntervalWindowCoder) +class _OrderedUnionCoder(FastCoder): + def __init__( + self, *coder_types: Tuple[type, Coder], fallback_coder: Optional[Coder]): + self._coder_types = coder_types + self._fallback_coder = fallback_coder + + def _create_impl(self): + return coder_impl._OrderedUnionCoderImpl( + [(t, c.get_impl()) for t, c in self._coder_types], + fallback_coder_impl=self._fallback_coder.get_impl() + if self._fallback_coder else None) + + def is_deterministic(self) -> bool: + return ( + all(c.is_deterministic for _, c in self._coder_types) and ( + self._fallback_coder is None or + self._fallback_coder.is_deterministic())) + + def to_type_hint(self): + return Any + + def __eq__(self, other): + return ( + type(self) == type(other) and + self._coder_types == other._coder_types and + self._fallback_coder == other._fallback_coder) + + def __hash__(self): + return hash((type(self), tuple(self._coder_types), self._fallback_coder)) + + class WindowedValueCoder(FastCoder): """Coder for windowed values.""" def __init__(self, wrapped_value_coder, window_coder=None): # type: (Coder, Optional[Coder]) -> None if not window_coder: - window_coder = PickleCoder() + # Avoid circular imports. + from apache_beam.transforms import window + window_coder = _OrderedUnionCoder( + (window.GlobalWindow, GlobalWindowCoder()), + (window.IntervalWindow, IntervalWindowCoder()), + fallback_coder=PickleCoder()) self.wrapped_value_coder = wrapped_value_coder self.timestamp_coder = TimestampCoder() self.window_coder = window_coder diff --git a/sdks/python/apache_beam/coders/coders_test_common.py b/sdks/python/apache_beam/coders/coders_test_common.py index 4bd9698dd57b..f3381cdb1d69 100644 --- a/sdks/python/apache_beam/coders/coders_test_common.py +++ b/sdks/python/apache_beam/coders/coders_test_common.py @@ -769,6 +769,14 @@ def test_decimal_coder(self): test_encodings[idx], base64.b64encode(test_coder.encode(value)).decode().rstrip("=")) + def test_OrderedUnionCoder(self): + test_coder = coders._OrderedUnionCoder((str, coders.StrUtf8Coder()), + (int, coders.VarIntCoder()), + fallback_coder=coders.FloatCoder()) + self.check_coder(test_coder, 's') + self.check_coder(test_coder, 123) + self.check_coder(test_coder, 1.5) + if __name__ == '__main__': logging.getLogger().setLevel(logging.INFO) From e50a136219e3b5f45467d173ff29e4a22ec499de Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:10:01 -0500 Subject: [PATCH 128/135] Support creating BigLake managed tables (#33125) * create managed biglake tables * add to translation * add to changes.md * adjust changes description --- CHANGES.md | 1 + .../beam/sdk/io/gcp/bigquery/BigQueryIO.java | 50 +++++++++++- .../gcp/bigquery/BigQueryIOTranslation.java | 8 ++ .../io/gcp/bigquery/CreateTableHelpers.java | 32 +++++++- .../sdk/io/gcp/bigquery/CreateTables.java | 3 +- .../sdk/io/gcp/bigquery/StorageApiLoads.java | 13 ++- .../StorageApiWriteRecordsInconsistent.java | 9 ++- .../StorageApiWriteUnshardedRecords.java | 16 +++- .../StorageApiWritesShardedRecords.java | 8 +- .../bigquery/BigQueryIOTranslationTest.java | 1 + .../io/gcp/bigquery/BigQueryIOWriteTest.java | 34 ++++++++ .../StorageApiSinkCreateIfNeededIT.java | 80 ++++++++++++++++--- 12 files changed, 226 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0cfac6c73c1e..7a8ed493c216 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -66,6 +66,7 @@ * gcs-connector config options can be set via GcsOptions (Java) ([#32769](https://github.com/apache/beam/pull/32769)). * Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). * Upgraded the default version of Hadoop dependencies to 3.4.1. Hadoop 2.10.2 is still supported (Java) ([#33011](https://github.com/apache/beam/issues/33011)). +* [BigQueryIO] Create managed BigLake tables dynamically ([#33125](https://github.com/apache/beam/pull/33125)) ## New Features / Improvements diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java index 9a7f3a05556c..30626da31c7c 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIO.java @@ -54,6 +54,7 @@ import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -629,6 +630,9 @@ public class BigQueryIO { private static final SerializableFunction DEFAULT_AVRO_SCHEMA_FACTORY = BigQueryAvroUtils::toGenericAvroSchema; + static final String CONNECTION_ID = "connectionId"; + static final String STORAGE_URI = "storageUri"; + /** * @deprecated Use {@link #read(SerializableFunction)} or {@link #readTableRows} instead. {@link * #readTableRows()} does exactly the same as {@link #read}, however {@link @@ -2372,6 +2376,8 @@ public enum Method { /** Table description. Default is empty. */ abstract @Nullable String getTableDescription(); + abstract @Nullable Map getBigLakeConfiguration(); + /** An option to indicate if table validation is desired. Default is true. */ abstract boolean getValidate(); @@ -2484,6 +2490,8 @@ abstract Builder setAvroSchemaFactory( abstract Builder setTableDescription(String tableDescription); + abstract Builder setBigLakeConfiguration(Map bigLakeConfiguration); + abstract Builder setValidate(boolean validate); abstract Builder setBigQueryServices(BigQueryServices bigQueryServices); @@ -2909,6 +2917,30 @@ public Write withTableDescription(String tableDescription) { return toBuilder().setTableDescription(tableDescription).build(); } + /** + * Specifies a configuration to create BigLake tables. The following options are available: + * + *

    + *
  • connectionId (REQUIRED): the name of your cloud resource connection. + *
  • storageUri (REQUIRED): the path to your GCS folder where data will be written to. This + * sink will create sub-folders for each project, dataset, and table destination. Example: + * if you specify a storageUri of {@code "gs://foo/bar"} and writing to table {@code + * "my_project.my_dataset.my_table"}, your data will be written under {@code + * "gs://foo/bar/my_project/my_dataset/my_table/"} + *
  • fileFormat (OPTIONAL): defaults to {@code "parquet"} + *
  • tableFormat (OPTIONAL): defaults to {@code "iceberg"} + *
+ * + *

NOTE: This is only supported with the Storage Write API methods. + * + * @see BigQuery Tables for + * Apache Iceberg documentation + */ + public Write withBigLakeConfiguration(Map bigLakeConfiguration) { + checkArgument(bigLakeConfiguration != null, "bigLakeConfiguration can not be null"); + return toBuilder().setBigLakeConfiguration(bigLakeConfiguration).build(); + } + /** * Specifies a policy for handling failed inserts. * @@ -3454,8 +3486,21 @@ && getStorageApiTriggeringFrequency(bqOptions) != null) { checkArgument( !getAutoSchemaUpdate(), "withAutoSchemaUpdate only supported when using STORAGE_WRITE_API or STORAGE_API_AT_LEAST_ONCE."); - } else if (getWriteDisposition() == WriteDisposition.WRITE_TRUNCATE) { - LOG.error("The Storage API sink does not support the WRITE_TRUNCATE write disposition."); + checkArgument( + getBigLakeConfiguration() == null, + "bigLakeConfiguration is only supported when using STORAGE_WRITE_API or STORAGE_API_AT_LEAST_ONCE."); + } else { + if (getWriteDisposition() == WriteDisposition.WRITE_TRUNCATE) { + LOG.error("The Storage API sink does not support the WRITE_TRUNCATE write disposition."); + } + if (getBigLakeConfiguration() != null) { + checkArgument( + Arrays.stream(new String[] {CONNECTION_ID, STORAGE_URI}) + .allMatch(getBigLakeConfiguration()::containsKey), + String.format( + "bigLakeConfiguration must contain keys '%s' and '%s'", + CONNECTION_ID, STORAGE_URI)); + } } if (getRowMutationInformationFn() != null) { checkArgument( @@ -3905,6 +3950,7 @@ private WriteResult continueExpandTyped( getPropagateSuccessfulStorageApiWritesPredicate(), getRowMutationInformationFn() != null, getDefaultMissingValueInterpretation(), + getBigLakeConfiguration(), getBadRecordRouter(), getBadRecordErrorHandler()); return input.apply("StorageApiLoads", storageApiLoads); diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslation.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslation.java index 561f5ccfc457..1da47156dda7 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslation.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslation.java @@ -393,6 +393,7 @@ static class BigQueryIOWriteTranslator implements TransformPayloadTranslator transform) { if (transform.getTableDescription() != null) { fieldValues.put("table_description", transform.getTableDescription()); } + if (transform.getBigLakeConfiguration() != null) { + fieldValues.put("biglake_configuration", transform.getBigLakeConfiguration()); + } fieldValues.put("validate", transform.getValidate()); if (transform.getBigQueryServices() != null) { fieldValues.put("bigquery_services", toByteArray(transform.getBigQueryServices())); @@ -719,6 +723,10 @@ public Write fromConfigRow(Row configRow, PipelineOptions options) { if (tableDescription != null) { builder = builder.setTableDescription(tableDescription); } + Map biglakeConfiguration = configRow.getMap("biglake_configuration"); + if (biglakeConfiguration != null) { + builder = builder.setBigLakeConfiguration(biglakeConfiguration); + } Boolean validate = configRow.getBoolean("validate"); if (validate != null) { builder = builder.setValidate(validate); diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTableHelpers.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTableHelpers.java index 7a94657107ec..7c428917503f 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTableHelpers.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTableHelpers.java @@ -17,11 +17,14 @@ */ package org.apache.beam.sdk.io.gcp.bigquery; +import static org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.CONNECTION_ID; +import static org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.STORAGE_URI; import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument; import com.google.api.client.util.BackOff; import com.google.api.client.util.BackOffUtils; import com.google.api.gax.rpc.ApiException; +import com.google.api.services.bigquery.model.BigLakeConfiguration; import com.google.api.services.bigquery.model.Clustering; import com.google.api.services.bigquery.model.EncryptionConfiguration; import com.google.api.services.bigquery.model.Table; @@ -31,6 +34,7 @@ import com.google.api.services.bigquery.model.TimePartitioning; import io.grpc.StatusRuntimeException; import java.util.Collections; +import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; @@ -41,6 +45,7 @@ import org.apache.beam.sdk.util.FluentBackoff; import org.apache.beam.sdk.util.Preconditions; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.MoreObjects; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Strings; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Supplier; import org.checkerframework.checker.nullness.qual.Nullable; @@ -91,7 +96,8 @@ static TableDestination possiblyCreateTable( CreateDisposition createDisposition, @Nullable Coder tableDestinationCoder, @Nullable String kmsKey, - BigQueryServices bqServices) { + BigQueryServices bqServices, + @Nullable Map bigLakeConfiguration) { checkArgument( tableDestination.getTableSpec() != null, "DynamicDestinations.getTable() must return a TableDestination " @@ -132,7 +138,8 @@ static TableDestination possiblyCreateTable( createDisposition, tableSpec, kmsKey, - bqServices); + bqServices, + bigLakeConfiguration); } } } @@ -147,7 +154,8 @@ private static void tryCreateTable( CreateDisposition createDisposition, String tableSpec, @Nullable String kmsKey, - BigQueryServices bqServices) { + BigQueryServices bqServices, + @Nullable Map bigLakeConfiguration) { TableReference tableReference = tableDestination.getTableReference().clone(); tableReference.setTableId(BigQueryHelpers.stripPartitionDecorator(tableReference.getTableId())); try (DatasetService datasetService = bqServices.getDatasetService(options)) { @@ -189,6 +197,24 @@ private static void tryCreateTable( if (kmsKey != null) { table.setEncryptionConfiguration(new EncryptionConfiguration().setKmsKeyName(kmsKey)); } + if (bigLakeConfiguration != null) { + TableReference ref = table.getTableReference(); + table.setBiglakeConfiguration( + new BigLakeConfiguration() + .setTableFormat( + MoreObjects.firstNonNull(bigLakeConfiguration.get("tableFormat"), "iceberg")) + .setFileFormat( + MoreObjects.firstNonNull(bigLakeConfiguration.get("fileFormat"), "parquet")) + .setConnectionId( + Preconditions.checkArgumentNotNull(bigLakeConfiguration.get(CONNECTION_ID))) + .setStorageUri( + String.format( + "%s/%s/%s/%s", + Preconditions.checkArgumentNotNull(bigLakeConfiguration.get(STORAGE_URI)), + ref.getProjectId(), + ref.getDatasetId(), + ref.getTableId()))); + } datasetService.createTable(table); } } catch (Exception e) { diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTables.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTables.java index 1bbd4e756084..7008c049a4a5 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTables.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/CreateTables.java @@ -132,7 +132,8 @@ public void processElement(ProcessContext context) { createDisposition, dynamicDestinations.getDestinationCoder(), kmsKey, - bqServices); + bqServices, + null); }); context.output(KV.of(tableDestination, context.element().getValue())); diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiLoads.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiLoads.java index 4ca9d5035c81..0bc60e98b253 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiLoads.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiLoads.java @@ -23,6 +23,7 @@ import com.google.cloud.bigquery.storage.v1.AppendRowsRequest; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Predicate; import javax.annotation.Nullable; @@ -76,6 +77,7 @@ public class StorageApiLoads private final boolean usesCdc; private final AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation; + private final Map bigLakeConfiguration; private final BadRecordRouter badRecordRouter; @@ -98,6 +100,7 @@ public StorageApiLoads( Predicate propagateSuccessfulStorageApiWritesPredicate, boolean usesCdc, AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation, + Map bigLakeConfiguration, BadRecordRouter badRecordRouter, ErrorHandler badRecordErrorHandler) { this.destinationCoder = destinationCoder; @@ -118,6 +121,7 @@ public StorageApiLoads( this.successfulRowsPredicate = propagateSuccessfulStorageApiWritesPredicate; this.usesCdc = usesCdc; this.defaultMissingValueInterpretation = defaultMissingValueInterpretation; + this.bigLakeConfiguration = bigLakeConfiguration; this.badRecordRouter = badRecordRouter; this.badRecordErrorHandler = badRecordErrorHandler; } @@ -186,7 +190,8 @@ public WriteResult expandInconsistent( createDisposition, kmsKey, usesCdc, - defaultMissingValueInterpretation)); + defaultMissingValueInterpretation, + bigLakeConfiguration)); PCollection insertErrors = PCollectionList.of(convertMessagesResult.get(failedRowsTag)) @@ -279,7 +284,8 @@ public WriteResult expandTriggered( successfulRowsPredicate, autoUpdateSchema, ignoreUnknownValues, - defaultMissingValueInterpretation)); + defaultMissingValueInterpretation, + bigLakeConfiguration)); PCollection insertErrors = PCollectionList.of(convertMessagesResult.get(failedRowsTag)) @@ -372,7 +378,8 @@ public WriteResult expandUntriggered( createDisposition, kmsKey, usesCdc, - defaultMissingValueInterpretation)); + defaultMissingValueInterpretation, + bigLakeConfiguration)); PCollection insertErrors = PCollectionList.of(convertMessagesResult.get(failedRowsTag)) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWriteRecordsInconsistent.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWriteRecordsInconsistent.java index 0860b4eda8a2..58bbed8ba5a9 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWriteRecordsInconsistent.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWriteRecordsInconsistent.java @@ -19,6 +19,7 @@ import com.google.api.services.bigquery.model.TableRow; import com.google.cloud.bigquery.storage.v1.AppendRowsRequest; +import java.util.Map; import java.util.function.Predicate; import javax.annotation.Nullable; import org.apache.beam.sdk.coders.Coder; @@ -55,6 +56,7 @@ public class StorageApiWriteRecordsInconsistent private final @Nullable String kmsKey; private final boolean usesCdc; private final AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation; + private final @Nullable Map bigLakeConfiguration; public StorageApiWriteRecordsInconsistent( StorageApiDynamicDestinations dynamicDestinations, @@ -69,7 +71,8 @@ public StorageApiWriteRecordsInconsistent( BigQueryIO.Write.CreateDisposition createDisposition, @Nullable String kmsKey, boolean usesCdc, - AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation) { + AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation, + @Nullable Map bigLakeConfiguration) { this.dynamicDestinations = dynamicDestinations; this.bqServices = bqServices; this.failedRowsTag = failedRowsTag; @@ -83,6 +86,7 @@ public StorageApiWriteRecordsInconsistent( this.kmsKey = kmsKey; this.usesCdc = usesCdc; this.defaultMissingValueInterpretation = defaultMissingValueInterpretation; + this.bigLakeConfiguration = bigLakeConfiguration; } @Override @@ -116,7 +120,8 @@ public PCollectionTuple expand(PCollection private final @Nullable String kmsKey; private final boolean usesCdc; private final AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation; + private final @Nullable Map bigLakeConfiguration; /** * The Guava cache object is thread-safe. However our protocol requires that client pin the @@ -179,7 +180,8 @@ public StorageApiWriteUnshardedRecords( BigQueryIO.Write.CreateDisposition createDisposition, @Nullable String kmsKey, boolean usesCdc, - AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation) { + AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation, + @Nullable Map bigLakeConfiguration) { this.dynamicDestinations = dynamicDestinations; this.bqServices = bqServices; this.failedRowsTag = failedRowsTag; @@ -193,6 +195,7 @@ public StorageApiWriteUnshardedRecords( this.kmsKey = kmsKey; this.usesCdc = usesCdc; this.defaultMissingValueInterpretation = defaultMissingValueInterpretation; + this.bigLakeConfiguration = bigLakeConfiguration; } @Override @@ -228,7 +231,8 @@ public PCollectionTuple expand(PCollection bigLakeConfiguration; WriteRecordsDoFn( String operationName, @@ -973,7 +978,8 @@ void postFlush() { @Nullable String kmsKey, boolean usesCdc, AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation, - int maxRetries) { + int maxRetries, + @Nullable Map bigLakeConfiguration) { this.messageConverters = new TwoLevelMessageConverterCache<>(operationName); this.dynamicDestinations = dynamicDestinations; this.bqServices = bqServices; @@ -992,6 +998,7 @@ void postFlush() { this.usesCdc = usesCdc; this.defaultMissingValueInterpretation = defaultMissingValueInterpretation; this.maxRetries = maxRetries; + this.bigLakeConfiguration = bigLakeConfiguration; } boolean shouldFlush() { @@ -1098,7 +1105,8 @@ DestinationState createDestinationState( createDisposition, destinationCoder, kmsKey, - bqServices); + bqServices, + bigLakeConfiguration); return true; }; diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWritesShardedRecords.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWritesShardedRecords.java index e2674fe34f2e..738a52b69cb7 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWritesShardedRecords.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiWritesShardedRecords.java @@ -131,6 +131,7 @@ public class StorageApiWritesShardedRecords bigLakeConfiguration; private final Duration streamIdleTime = DEFAULT_STREAM_IDLE_TIME; private final TupleTag failedRowsTag; @@ -232,7 +233,8 @@ public StorageApiWritesShardedRecords( Predicate successfulRowsPredicate, boolean autoUpdateSchema, boolean ignoreUnknownValues, - AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation) { + AppendRowsRequest.MissingValueInterpretation defaultMissingValueInterpretation, + @Nullable Map bigLakeConfiguration) { this.dynamicDestinations = dynamicDestinations; this.createDisposition = createDisposition; this.kmsKey = kmsKey; @@ -246,6 +248,7 @@ public StorageApiWritesShardedRecords( this.autoUpdateSchema = autoUpdateSchema; this.ignoreUnknownValues = ignoreUnknownValues; this.defaultMissingValueInterpretation = defaultMissingValueInterpretation; + this.bigLakeConfiguration = bigLakeConfiguration; } @Override @@ -499,7 +502,8 @@ public void process( createDisposition, destinationCoder, kmsKey, - bqServices); + bqServices, + bigLakeConfiguration); return true; }; diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslationTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslationTest.java index e15258e6ab40..5b7b5d473190 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslationTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOTranslationTest.java @@ -96,6 +96,7 @@ public class BigQueryIOTranslationTest { WRITE_TRANSFORM_SCHEMA_MAPPING.put("getWriteDisposition", "write_disposition"); WRITE_TRANSFORM_SCHEMA_MAPPING.put("getSchemaUpdateOptions", "schema_update_options"); WRITE_TRANSFORM_SCHEMA_MAPPING.put("getTableDescription", "table_description"); + WRITE_TRANSFORM_SCHEMA_MAPPING.put("getBigLakeConfiguration", "biglake_configuration"); WRITE_TRANSFORM_SCHEMA_MAPPING.put("getValidate", "validate"); WRITE_TRANSFORM_SCHEMA_MAPPING.put("getBigQueryServices", "bigquery_services"); WRITE_TRANSFORM_SCHEMA_MAPPING.put("getMaxFilesPerBundle", "max_files_per_bundle"); diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOWriteTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOWriteTest.java index d96e22f84907..69994c019509 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOWriteTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOWriteTest.java @@ -2257,6 +2257,40 @@ public void testUpdateTableSchemaNoUnknownValues() throws Exception { p.run(); } + @Test + public void testBigLakeConfigurationFailsForNonStorageApiWrites() { + assumeTrue(!useStorageApi); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage( + "bigLakeConfiguration is only supported when using STORAGE_WRITE_API or STORAGE_API_AT_LEAST_ONCE"); + + p.apply(Create.empty(TableRowJsonCoder.of())) + .apply( + BigQueryIO.writeTableRows() + .to("project-id:dataset-id.table") + .withBigLakeConfiguration( + ImmutableMap.of( + "connectionId", "some-connection", + "storageUri", "gs://bucket")) + .withTestServices(fakeBqServices)); + p.run(); + } + + @Test + public void testBigLakeConfigurationFailsForMissingProperties() { + assumeTrue(useStorageApi); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("bigLakeConfiguration must contain keys 'connectionId' and 'storageUri'"); + + p.apply(Create.empty(TableRowJsonCoder.of())) + .apply( + BigQueryIO.writeTableRows() + .to("project-id:dataset-id.table") + .withBigLakeConfiguration(ImmutableMap.of("connectionId", "some-connection")) + .withTestServices(fakeBqServices)); + p.run(); + } + @SuppressWarnings({"unused"}) static class UpdateTableSchemaDoFn extends DoFn, TableRow> { @TimerId("updateTimer") diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiSinkCreateIfNeededIT.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiSinkCreateIfNeededIT.java index 18c832f0c54b..858921e19ced 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiSinkCreateIfNeededIT.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/StorageApiSinkCreateIfNeededIT.java @@ -17,23 +17,32 @@ */ package org.apache.beam.sdk.io.gcp.bigquery; +import static org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.CONNECTION_ID; +import static org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.STORAGE_URI; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import com.google.api.services.bigquery.model.TableFieldSchema; import com.google.api.services.bigquery.model.TableRow; import com.google.api.services.bigquery.model.TableSchema; +import com.google.api.services.storage.model.Objects; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.LongStream; import org.apache.beam.sdk.Pipeline; import org.apache.beam.sdk.extensions.gcp.options.GcpOptions; +import org.apache.beam.sdk.extensions.gcp.options.GcsOptions; +import org.apache.beam.sdk.extensions.gcp.util.GcsUtil; import org.apache.beam.sdk.io.gcp.testing.BigqueryClient; import org.apache.beam.sdk.testing.TestPipeline; import org.apache.beam.sdk.transforms.Create; import org.apache.beam.sdk.values.PCollection; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hamcrest.Matchers; import org.joda.time.Duration; import org.junit.AfterClass; @@ -57,11 +66,16 @@ public static Iterable data() { private static final Logger LOG = LoggerFactory.getLogger(StorageApiSinkCreateIfNeededIT.class); - private static final BigqueryClient BQ_CLIENT = new BigqueryClient("StorageApiSinkFailedRowsIT"); + private static final BigqueryClient BQ_CLIENT = + new BigqueryClient("StorageApiSinkCreateIfNeededIT"); private static final String PROJECT = TestPipeline.testingPipelineOptions().as(GcpOptions.class).getProject(); private static final String BIG_QUERY_DATASET_ID = - "storage_api_sink_failed_rows" + System.nanoTime(); + "storage_api_sink_create_tables_" + System.nanoTime(); + private static final String TEST_CONNECTION_ID = + "projects/apache-beam-testing/locations/us/connections/apache-beam-testing-storageapi-biglake-nodelete"; + private static final String TEST_STORAGE_URI = + "gs://apache-beam-testing-bq-biglake/" + StorageApiSinkCreateIfNeededIT.class.getSimpleName(); private static final List FIELDS = ImmutableList.builder() .add(new TableFieldSchema().setType("STRING").setName("str")) @@ -96,19 +110,55 @@ public void testCreateManyTables() throws IOException, InterruptedException { String table = "table" + System.nanoTime(); String tableSpecBase = PROJECT + "." + BIG_QUERY_DATASET_ID + "." + table; - runPipeline(getMethod(), tableSpecBase, inputs); - assertTablesCreated(tableSpecBase, 100); + runPipeline(getMethod(), tableSpecBase, inputs, null); + assertTablesCreated(tableSpecBase, 100, true); } - private void assertTablesCreated(String tableSpecPrefix, int expectedRows) + @Test + public void testCreateBigLakeTables() throws IOException, InterruptedException { + int numTables = 5; + List inputs = + LongStream.range(0, numTables) + .mapToObj(l -> new TableRow().set("str", "foo").set("tablenum", l)) + .collect(Collectors.toList()); + + String table = "iceberg_table_" + System.nanoTime() + "_"; + String tableSpecBase = PROJECT + "." + BIG_QUERY_DATASET_ID + "." + table; + Map bigLakeConfiguration = + ImmutableMap.of( + CONNECTION_ID, TEST_CONNECTION_ID, + STORAGE_URI, TEST_STORAGE_URI); + runPipeline(getMethod(), tableSpecBase, inputs, bigLakeConfiguration); + assertTablesCreated(tableSpecBase, numTables, false); + assertIcebergTablesCreated(table, numTables); + } + + private void assertIcebergTablesCreated(String tablePrefix, int expectedRows) throws IOException, InterruptedException { + GcsUtil gcsUtil = TestPipeline.testingPipelineOptions().as(GcsOptions.class).getGcsUtil(); + + Objects objects = + gcsUtil.listObjects( + "apache-beam-testing-bq-biglake", + String.format( + "%s/%s/%s/%s", + getClass().getSimpleName(), PROJECT, BIG_QUERY_DATASET_ID, tablePrefix), + null); + + assertEquals(expectedRows, objects.getItems().size()); + } + + private void assertTablesCreated(String tableSpecPrefix, int expectedRows, boolean useWildCard) + throws IOException, InterruptedException { + String query = String.format("SELECT COUNT(*) FROM `%s`", tableSpecPrefix + "*"); + if (!useWildCard) { + query = String.format("SELECT (SELECT COUNT(*) FROM `%s`)", tableSpecPrefix + 0); + for (int i = 1; i < expectedRows; i++) { + query += String.format(" + (SELECT COUNT(*) FROM `%s`)", tableSpecPrefix + i); + } + } TableRow queryResponse = - Iterables.getOnlyElement( - BQ_CLIENT.queryUnflattened( - String.format("SELECT COUNT(*) FROM `%s`", tableSpecPrefix + "*"), - PROJECT, - true, - true)); + Iterables.getOnlyElement(BQ_CLIENT.queryUnflattened(query, PROJECT, true, true)); int numRowsWritten = Integer.parseInt((String) queryResponse.get("f0_")); if (useAtLeastOnce) { assertThat(numRowsWritten, Matchers.greaterThanOrEqualTo(expectedRows)); @@ -118,7 +168,10 @@ private void assertTablesCreated(String tableSpecPrefix, int expectedRows) } private static void runPipeline( - BigQueryIO.Write.Method method, String tableSpecBase, Iterable tableRows) { + BigQueryIO.Write.Method method, + String tableSpecBase, + Iterable tableRows, + @Nullable Map bigLakeConfiguration) { Pipeline p = Pipeline.create(); BigQueryIO.Write write = @@ -131,6 +184,9 @@ private static void runPipeline( write = write.withNumStorageWriteApiStreams(1); write = write.withTriggeringFrequency(Duration.standardSeconds(1)); } + if (bigLakeConfiguration != null) { + write = write.withBigLakeConfiguration(bigLakeConfiguration); + } PCollection input = p.apply("Create test cases", Create.of(tableRows)); input = input.setIsBoundedInternal(PCollection.IsBounded.UNBOUNDED); input.apply("Write using Storage Write API", write); From c83444dbb9454320c9dc46a1c8a9eb0cbca1b0ee Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Tue, 17 Dec 2024 10:44:23 -0800 Subject: [PATCH 129/135] Docstring fix. --- sdks/python/apache_beam/metrics/monitoring_infos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/metrics/monitoring_infos.py b/sdks/python/apache_beam/metrics/monitoring_infos.py index e343793b3ad4..cb4e60e218f6 100644 --- a/sdks/python/apache_beam/metrics/monitoring_infos.py +++ b/sdks/python/apache_beam/metrics/monitoring_infos.py @@ -392,7 +392,7 @@ def is_string_set(monitoring_info_proto): def is_bounded_trie(monitoring_info_proto): - """Returns true if the monitoring info is a StringSet metric.""" + """Returns true if the monitoring info is a BoundedTrie metric.""" return monitoring_info_proto.type in BOUNDED_TRIE_TYPES From 8e1e12453baa8ffa564922496994446c9e41003c Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Tue, 17 Dec 2024 10:45:33 -0800 Subject: [PATCH 130/135] [#32929] Add OrderedListState support to Prism. (#33350) --- CHANGES.md | 1 + runners/prism/java/build.gradle | 4 - .../runners/prism/internal/engine/data.go | 97 ++++++++ .../prism/internal/engine/data_test.go | 222 ++++++++++++++++++ .../prism/internal/engine/elementmanager.go | 11 +- .../beam/runners/prism/internal/execute.go | 2 +- .../prism/internal/jobservices/management.go | 3 +- .../pkg/beam/runners/prism/internal/stage.go | 37 +++ .../beam/runners/prism/internal/urns/urns.go | 5 +- .../runners/prism/internal/worker/worker.go | 14 ++ 10 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 sdks/go/pkg/beam/runners/prism/internal/engine/data_test.go diff --git a/CHANGES.md b/CHANGES.md index 7a8ed493c216..deaa8bfcd471 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -74,6 +74,7 @@ * X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). * Support OnWindowExpiration in Prism ([#32211](https://github.com/apache/beam/issues/32211)). * This enables initial Java GroupIntoBatches support. +* Support OrderedListState in Prism ([#32929](https://github.com/apache/beam/issues/32929)). ## Breaking Changes diff --git a/runners/prism/java/build.gradle b/runners/prism/java/build.gradle index ce71151099bd..cd2e90fde67c 100644 --- a/runners/prism/java/build.gradle +++ b/runners/prism/java/build.gradle @@ -233,10 +233,6 @@ def createPrismValidatesRunnerTask = { name, environmentType -> excludeCategories 'org.apache.beam.sdk.testing.UsesExternalService' excludeCategories 'org.apache.beam.sdk.testing.UsesSdkHarnessEnvironment' - // Not yet implemented in Prism - // https://github.com/apache/beam/issues/32929 - excludeCategories 'org.apache.beam.sdk.testing.UsesOrderedListState' - // Not supported in Portable Java SDK yet. // https://github.com/apache/beam/issues?q=is%3Aissue+is%3Aopen+MultimapState excludeCategories 'org.apache.beam.sdk.testing.UsesMultimapState' diff --git a/sdks/go/pkg/beam/runners/prism/internal/engine/data.go b/sdks/go/pkg/beam/runners/prism/internal/engine/data.go index 7b8689f95112..380b6e2f31d1 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/engine/data.go +++ b/sdks/go/pkg/beam/runners/prism/internal/engine/data.go @@ -17,13 +17,17 @@ package engine import ( "bytes" + "cmp" "fmt" "log/slog" + "slices" + "sort" "github.com/apache/beam/sdks/v2/go/pkg/beam/core/graph/coder" "github.com/apache/beam/sdks/v2/go/pkg/beam/core/graph/window" "github.com/apache/beam/sdks/v2/go/pkg/beam/core/runtime/exec" "github.com/apache/beam/sdks/v2/go/pkg/beam/core/typex" + "google.golang.org/protobuf/encoding/protowire" ) // StateData is a "union" between Bag state and MultiMap state to increase common code. @@ -42,6 +46,10 @@ type TimerKey struct { type TentativeData struct { Raw map[string][][]byte + // stateTypeLen is a map from LinkID to valueLen function for parsing data. + // Only used by OrderedListState, since Prism must manipulate these datavalues, + // which isn't expected, or a requirement of other state values. + stateTypeLen map[LinkID]func([]byte) int // state is a map from transformID + UserStateID, to window, to userKey, to datavalues. state map[LinkID]map[typex.Window]map[string]StateData // timers is a map from the Timer transform+family to the encoded timer. @@ -220,3 +228,92 @@ func (d *TentativeData) ClearMultimapKeysState(stateID LinkID, wKey, uKey []byte kmap[string(uKey)] = StateData{} slog.Debug("State() MultimapKeys.Clear", slog.Any("StateID", stateID), slog.Any("UserKey", uKey), slog.Any("WindowKey", wKey)) } + +// AppendOrderedListState appends the incoming timestamped data to the existing tentative data bundle. +// Assumes the data is TimestampedValue encoded, which has a BigEndian int64 suffixed to the data. +// This means we may always use the last 8 bytes to determine the value sorting. +// +// The stateID has the Transform and Local fields populated, for the Transform and UserStateID respectively. +func (d *TentativeData) AppendOrderedListState(stateID LinkID, wKey, uKey []byte, data []byte) { + kmap := d.appendState(stateID, wKey) + typeLen := d.stateTypeLen[stateID] + var datums [][]byte + + // We need to parse out all values individually for later sorting. + // + // OrderedListState is encoded as KVs with varint encoded millis followed by the value. + // This is not the standard TimestampValueCoder encoding, which + // uses a big-endian long as a suffix to the value. This is important since + // values may be concatenated, and we'll need to split them out out. + // + // The TentativeData.stateTypeLen is populated with a function to extract + // the length of a the next value, so we can skip through elements individually. + for i := 0; i < len(data); { + // Get the length of the VarInt for the timestamp. + _, tn := protowire.ConsumeVarint(data[i:]) + + // Get the length of the encoded value. + vn := typeLen(data[i+tn:]) + prev := i + i += tn + vn + datums = append(datums, data[prev:i]) + } + + s := StateData{Bag: append(kmap[string(uKey)].Bag, datums...)} + sort.SliceStable(s.Bag, func(i, j int) bool { + vi := s.Bag[i] + vj := s.Bag[j] + return compareTimestampSuffixes(vi, vj) + }) + kmap[string(uKey)] = s + slog.Debug("State() OrderedList.Append", slog.Any("StateID", stateID), slog.Any("UserKey", uKey), slog.Any("Window", wKey), slog.Any("NewData", s)) +} + +func compareTimestampSuffixes(vi, vj []byte) bool { + ims, _ := protowire.ConsumeVarint(vi) + jms, _ := protowire.ConsumeVarint(vj) + return (int64(ims)) < (int64(jms)) +} + +// GetOrderedListState available state from the tentative bundle data. +// The stateID has the Transform and Local fields populated, for the Transform and UserStateID respectively. +func (d *TentativeData) GetOrderedListState(stateID LinkID, wKey, uKey []byte, start, end int64) [][]byte { + winMap := d.state[stateID] + w := d.toWindow(wKey) + data := winMap[w][string(uKey)] + + lo, hi := findRange(data.Bag, start, end) + slog.Debug("State() OrderedList.Get", slog.Any("StateID", stateID), slog.Any("UserKey", uKey), slog.Any("Window", wKey), slog.Group("range", slog.Int64("start", start), slog.Int64("end", end)), slog.Group("outrange", slog.Int("lo", lo), slog.Int("hi", hi)), slog.Any("Data", data.Bag[lo:hi])) + return data.Bag[lo:hi] +} + +func cmpSuffix(vs [][]byte, target int64) func(i int) int { + return func(i int) int { + v := vs[i] + ims, _ := protowire.ConsumeVarint(v) + tvsbi := cmp.Compare(target, int64(ims)) + slog.Debug("cmpSuffix", "target", target, "bi", ims, "tvsbi", tvsbi) + return tvsbi + } +} + +func findRange(bag [][]byte, start, end int64) (int, int) { + lo, _ := sort.Find(len(bag), cmpSuffix(bag, start)) + hi, _ := sort.Find(len(bag), cmpSuffix(bag, end)) + return lo, hi +} + +func (d *TentativeData) ClearOrderedListState(stateID LinkID, wKey, uKey []byte, start, end int64) { + winMap := d.state[stateID] + w := d.toWindow(wKey) + kMap := winMap[w] + data := kMap[string(uKey)] + + lo, hi := findRange(data.Bag, start, end) + slog.Debug("State() OrderedList.Clear", slog.Any("StateID", stateID), slog.Any("UserKey", uKey), slog.Any("Window", wKey), slog.Group("range", slog.Int64("start", start), slog.Int64("end", end)), "lo", lo, "hi", hi, slog.Any("PreClearData", data.Bag)) + + cleared := slices.Delete(data.Bag, lo, hi) + // Zero the current entry to clear. + // Delete makes it difficult to delete the persisted stage state for the key. + kMap[string(uKey)] = StateData{Bag: cleared} +} diff --git a/sdks/go/pkg/beam/runners/prism/internal/engine/data_test.go b/sdks/go/pkg/beam/runners/prism/internal/engine/data_test.go new file mode 100644 index 000000000000..1d0497104182 --- /dev/null +++ b/sdks/go/pkg/beam/runners/prism/internal/engine/data_test.go @@ -0,0 +1,222 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You 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. + +package engine + +import ( + "bytes" + "encoding/binary" + "math" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/encoding/protowire" +) + +func TestCompareTimestampSuffixes(t *testing.T) { + t.Run("simple", func(t *testing.T) { + loI := int64(math.MinInt64) + hiI := int64(math.MaxInt64) + + loB := binary.BigEndian.AppendUint64(nil, uint64(loI)) + hiB := binary.BigEndian.AppendUint64(nil, uint64(hiI)) + + if compareTimestampSuffixes(loB, hiB) != (loI < hiI) { + t.Errorf("lo vs Hi%v < %v: bytes %v vs %v, %v %v", loI, hiI, loB, hiB, loI < hiI, compareTimestampSuffixes(loB, hiB)) + } + }) +} + +func TestOrderedListState(t *testing.T) { + time1 := protowire.AppendVarint(nil, 11) + time2 := protowire.AppendVarint(nil, 22) + time3 := protowire.AppendVarint(nil, 33) + time4 := protowire.AppendVarint(nil, 44) + time5 := protowire.AppendVarint(nil, 55) + + wKey := []byte{} // global window. + uKey := []byte("\u0007userkey") + linkID := LinkID{ + Transform: "dofn", + Local: "localStateName", + } + cc := func(a []byte, b ...byte) []byte { + return bytes.Join([][]byte{a, b}, []byte{}) + } + + t.Run("bool", func(t *testing.T) { + d := TentativeData{ + stateTypeLen: map[LinkID]func([]byte) int{ + linkID: func(_ []byte) int { + return 1 + }, + }, + } + + d.AppendOrderedListState(linkID, wKey, uKey, cc(time3, 1)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time2, 0)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time5, 1)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time1, 1)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time4, 0)) + + got := d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want := [][]byte{ + cc(time1, 1), + cc(time2, 0), + cc(time3, 1), + cc(time4, 0), + cc(time5, 1), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList booleans \n%v", d) + } + + d.ClearOrderedListState(linkID, wKey, uKey, 12, 54) + got = d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want = [][]byte{ + cc(time1, 1), + cc(time5, 1), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList booleans, after clear\n%v", d) + } + }) + t.Run("float64", func(t *testing.T) { + d := TentativeData{ + stateTypeLen: map[LinkID]func([]byte) int{ + linkID: func(_ []byte) int { + return 8 + }, + }, + } + + d.AppendOrderedListState(linkID, wKey, uKey, cc(time5, 0, 0, 0, 0, 0, 0, 0, 1)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time1, 0, 0, 0, 0, 0, 0, 1, 0)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time3, 0, 0, 0, 0, 0, 1, 0, 0)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time2, 0, 0, 0, 0, 1, 0, 0, 0)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time4, 0, 0, 0, 1, 0, 0, 0, 0)) + + got := d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want := [][]byte{ + cc(time1, 0, 0, 0, 0, 0, 0, 1, 0), + cc(time2, 0, 0, 0, 0, 1, 0, 0, 0), + cc(time3, 0, 0, 0, 0, 0, 1, 0, 0), + cc(time4, 0, 0, 0, 1, 0, 0, 0, 0), + cc(time5, 0, 0, 0, 0, 0, 0, 0, 1), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList float64s \n%v", d) + } + + d.ClearOrderedListState(linkID, wKey, uKey, 11, 12) + d.ClearOrderedListState(linkID, wKey, uKey, 33, 34) + d.ClearOrderedListState(linkID, wKey, uKey, 55, 56) + + got = d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want = [][]byte{ + cc(time2, 0, 0, 0, 0, 1, 0, 0, 0), + cc(time4, 0, 0, 0, 1, 0, 0, 0, 0), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList float64s, after clear \n%v", d) + } + }) + + t.Run("varint", func(t *testing.T) { + d := TentativeData{ + stateTypeLen: map[LinkID]func([]byte) int{ + linkID: func(b []byte) int { + _, n := protowire.ConsumeVarint(b) + return int(n) + }, + }, + } + + d.AppendOrderedListState(linkID, wKey, uKey, cc(time2, protowire.AppendVarint(nil, 56)...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time4, protowire.AppendVarint(nil, 20067)...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time3, protowire.AppendVarint(nil, 7777777)...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time1, protowire.AppendVarint(nil, 424242)...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time5, protowire.AppendVarint(nil, 0)...)) + + got := d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want := [][]byte{ + cc(time1, protowire.AppendVarint(nil, 424242)...), + cc(time2, protowire.AppendVarint(nil, 56)...), + cc(time3, protowire.AppendVarint(nil, 7777777)...), + cc(time4, protowire.AppendVarint(nil, 20067)...), + cc(time5, protowire.AppendVarint(nil, 0)...), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList int32 \n%v", d) + } + }) + t.Run("lp", func(t *testing.T) { + d := TentativeData{ + stateTypeLen: map[LinkID]func([]byte) int{ + linkID: func(b []byte) int { + l, n := protowire.ConsumeVarint(b) + return int(l) + n + }, + }, + } + + d.AppendOrderedListState(linkID, wKey, uKey, cc(time1, []byte("\u0003one")...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time2, []byte("\u0003two")...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time3, []byte("\u0005three")...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time4, []byte("\u0004four")...)) + d.AppendOrderedListState(linkID, wKey, uKey, cc(time5, []byte("\u0019FourHundredAndEleventyTwo")...)) + + got := d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want := [][]byte{ + cc(time1, []byte("\u0003one")...), + cc(time2, []byte("\u0003two")...), + cc(time3, []byte("\u0005three")...), + cc(time4, []byte("\u0004four")...), + cc(time5, []byte("\u0019FourHundredAndEleventyTwo")...), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList int32 \n%v", d) + } + }) + t.Run("lp_onecall", func(t *testing.T) { + d := TentativeData{ + stateTypeLen: map[LinkID]func([]byte) int{ + linkID: func(b []byte) int { + l, n := protowire.ConsumeVarint(b) + return int(l) + n + }, + }, + } + d.AppendOrderedListState(linkID, wKey, uKey, bytes.Join([][]byte{ + time5, []byte("\u0019FourHundredAndEleventyTwo"), + time3, []byte("\u0005three"), + time2, []byte("\u0003two"), + time1, []byte("\u0003one"), + time4, []byte("\u0004four"), + }, nil)) + + got := d.GetOrderedListState(linkID, wKey, uKey, 0, 60) + want := [][]byte{ + cc(time1, []byte("\u0003one")...), + cc(time2, []byte("\u0003two")...), + cc(time3, []byte("\u0005three")...), + cc(time4, []byte("\u0004four")...), + cc(time5, []byte("\u0019FourHundredAndEleventyTwo")...), + } + if d := cmp.Diff(want, got); d != "" { + t.Errorf("OrderedList int32 \n%v", d) + } + }) +} diff --git a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go index 00e18c669afa..7180bb456f1a 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go +++ b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go @@ -269,8 +269,10 @@ func (em *ElementManager) StageAggregates(ID string) { // StageStateful marks the given stage as stateful, which means elements are // processed by key. -func (em *ElementManager) StageStateful(ID string) { - em.stages[ID].stateful = true +func (em *ElementManager) StageStateful(ID string, stateTypeLen map[LinkID]func([]byte) int) { + ss := em.stages[ID] + ss.stateful = true + ss.stateTypeLen = stateTypeLen } // StageOnWindowExpiration marks the given stage as stateful, which means elements are @@ -669,7 +671,9 @@ func (em *ElementManager) StateForBundle(rb RunBundle) TentativeData { ss := em.stages[rb.StageID] ss.mu.Lock() defer ss.mu.Unlock() - var ret TentativeData + ret := TentativeData{ + stateTypeLen: ss.stateTypeLen, + } keys := ss.inprogressKeysByBundle[rb.BundleID] // TODO(lostluck): Also track windows per bundle, to reduce copying. if len(ss.state) > 0 { @@ -1136,6 +1140,7 @@ type stageState struct { inprogressKeys set[string] // all keys that are assigned to bundles. inprogressKeysByBundle map[string]set[string] // bundle to key assignments. state map[LinkID]map[typex.Window]map[string]StateData // state data for this stage, from {tid, stateID} -> window -> userKey + stateTypeLen map[LinkID]func([]byte) int // map from state to a function that will produce the total length of a single value in bytes. // Accounting for handling watermark holds for timers. // We track the count of timers with the same hold, and clear it from diff --git a/sdks/go/pkg/beam/runners/prism/internal/execute.go b/sdks/go/pkg/beam/runners/prism/internal/execute.go index fde62f00c7c1..8b56c30eb61b 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/execute.go +++ b/sdks/go/pkg/beam/runners/prism/internal/execute.go @@ -316,7 +316,7 @@ func executePipeline(ctx context.Context, wks map[string]*worker.W, j *jobservic sort.Strings(outputs) em.AddStage(stage.ID, []string{stage.primaryInput}, outputs, stage.sideInputs) if stage.stateful { - em.StageStateful(stage.ID) + em.StageStateful(stage.ID, stage.stateTypeLen) } if stage.onWindowExpiration.TimerFamily != "" { slog.Debug("OnWindowExpiration", slog.String("stage", stage.ID), slog.Any("values", stage.onWindowExpiration)) diff --git a/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go b/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go index 894a6e1427a2..af559a92ab46 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go +++ b/sdks/go/pkg/beam/runners/prism/internal/jobservices/management.go @@ -174,7 +174,8 @@ func (s *Server) Prepare(ctx context.Context, req *jobpb.PrepareJobRequest) (_ * // Validate all the state features for _, spec := range pardo.GetStateSpecs() { isStateful = true - check("StateSpec.Protocol.Urn", spec.GetProtocol().GetUrn(), urns.UserStateBag, urns.UserStateMultiMap) + check("StateSpec.Protocol.Urn", spec.GetProtocol().GetUrn(), + urns.UserStateBag, urns.UserStateMultiMap, urns.UserStateOrderedList) } // Validate all the timer features for _, spec := range pardo.GetTimerFamilySpecs() { diff --git a/sdks/go/pkg/beam/runners/prism/internal/stage.go b/sdks/go/pkg/beam/runners/prism/internal/stage.go index 9dd6cbdafec8..e1e942a06f0c 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/stage.go +++ b/sdks/go/pkg/beam/runners/prism/internal/stage.go @@ -35,6 +35,7 @@ import ( "github.com/apache/beam/sdks/v2/go/pkg/beam/runners/prism/internal/worker" "golang.org/x/exp/maps" "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/proto" ) @@ -73,6 +74,10 @@ type stage struct { hasTimers []engine.StaticTimerID processingTimeTimers map[string]bool + // stateTypeLen maps state values to encoded lengths for the type. + // Only used for OrderedListState which must manipulate individual state datavalues. + stateTypeLen map[engine.LinkID]func([]byte) int + exe transformExecuter inputTransformID string inputInfo engine.PColInfo @@ -438,6 +443,38 @@ func buildDescriptor(stg *stage, comps *pipepb.Components, wk *worker.W, em *eng rewriteCoder(&s.SetSpec.ElementCoderId) case *pipepb.StateSpec_OrderedListSpec: rewriteCoder(&s.OrderedListSpec.ElementCoderId) + // Add the length determination helper for OrderedList state values. + if stg.stateTypeLen == nil { + stg.stateTypeLen = map[engine.LinkID]func([]byte) int{} + } + linkID := engine.LinkID{ + Transform: tid, + Local: stateID, + } + var fn func([]byte) int + switch v := coders[s.OrderedListSpec.GetElementCoderId()]; v.GetSpec().GetUrn() { + case urns.CoderBool: + fn = func(_ []byte) int { + return 1 + } + case urns.CoderDouble: + fn = func(_ []byte) int { + return 8 + } + case urns.CoderVarInt: + fn = func(b []byte) int { + _, n := protowire.ConsumeVarint(b) + return int(n) + } + case urns.CoderLengthPrefix, urns.CoderBytes, urns.CoderStringUTF8: + fn = func(b []byte) int { + l, n := protowire.ConsumeVarint(b) + return int(l) + n + } + default: + rewriteErr = fmt.Errorf("unknown coder used for ordered list state after re-write id: %v coder: %v, for state %v for transform %v in stage %v", s.OrderedListSpec.GetElementCoderId(), v, stateID, tid, stg.ID) + } + stg.stateTypeLen[linkID] = fn case *pipepb.StateSpec_CombiningSpec: rewriteCoder(&s.CombiningSpec.AccumulatorCoderId) case *pipepb.StateSpec_MapSpec: diff --git a/sdks/go/pkg/beam/runners/prism/internal/urns/urns.go b/sdks/go/pkg/beam/runners/prism/internal/urns/urns.go index 5312fd799c89..12e62ef84a81 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/urns/urns.go +++ b/sdks/go/pkg/beam/runners/prism/internal/urns/urns.go @@ -95,8 +95,9 @@ var ( SideInputMultiMap = siUrn(pipepb.StandardSideInputTypes_MULTIMAP) // UserState kinds - UserStateBag = usUrn(pipepb.StandardUserStateTypes_BAG) - UserStateMultiMap = usUrn(pipepb.StandardUserStateTypes_MULTIMAP) + UserStateBag = usUrn(pipepb.StandardUserStateTypes_BAG) + UserStateMultiMap = usUrn(pipepb.StandardUserStateTypes_MULTIMAP) + UserStateOrderedList = usUrn(pipepb.StandardUserStateTypes_ORDERED_LIST) // WindowsFns WindowFnGlobal = quickUrn(pipepb.GlobalWindowsPayload_PROPERTIES) diff --git a/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go b/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go index c2c988aa097f..9d9058975b26 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go +++ b/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go @@ -554,6 +554,11 @@ func (wk *W) State(state fnpb.BeamFnState_StateServer) error { case *fnpb.StateKey_MultimapKeysUserState_: mmkey := key.GetMultimapKeysUserState() data = b.OutputData.GetMultimapKeysState(engine.LinkID{Transform: mmkey.GetTransformId(), Local: mmkey.GetUserStateId()}, mmkey.GetWindow(), mmkey.GetKey()) + case *fnpb.StateKey_OrderedListUserState_: + olkey := key.GetOrderedListUserState() + data = b.OutputData.GetOrderedListState( + engine.LinkID{Transform: olkey.GetTransformId(), Local: olkey.GetUserStateId()}, + olkey.GetWindow(), olkey.GetKey(), olkey.GetRange().GetStart(), olkey.GetRange().GetEnd()) default: panic(fmt.Sprintf("unsupported StateKey Get type: %T: %v", key.GetType(), prototext.Format(key))) } @@ -578,6 +583,11 @@ func (wk *W) State(state fnpb.BeamFnState_StateServer) error { case *fnpb.StateKey_MultimapUserState_: mmkey := key.GetMultimapUserState() b.OutputData.AppendMultimapState(engine.LinkID{Transform: mmkey.GetTransformId(), Local: mmkey.GetUserStateId()}, mmkey.GetWindow(), mmkey.GetKey(), mmkey.GetMapKey(), req.GetAppend().GetData()) + case *fnpb.StateKey_OrderedListUserState_: + olkey := key.GetOrderedListUserState() + b.OutputData.AppendOrderedListState( + engine.LinkID{Transform: olkey.GetTransformId(), Local: olkey.GetUserStateId()}, + olkey.GetWindow(), olkey.GetKey(), req.GetAppend().GetData()) default: panic(fmt.Sprintf("unsupported StateKey Append type: %T: %v", key.GetType(), prototext.Format(key))) } @@ -601,6 +611,10 @@ func (wk *W) State(state fnpb.BeamFnState_StateServer) error { case *fnpb.StateKey_MultimapKeysUserState_: mmkey := key.GetMultimapUserState() b.OutputData.ClearMultimapKeysState(engine.LinkID{Transform: mmkey.GetTransformId(), Local: mmkey.GetUserStateId()}, mmkey.GetWindow(), mmkey.GetKey()) + case *fnpb.StateKey_OrderedListUserState_: + olkey := key.GetOrderedListUserState() + b.OutputData.ClearOrderedListState(engine.LinkID{Transform: olkey.GetTransformId(), Local: olkey.GetUserStateId()}, + olkey.GetWindow(), olkey.GetKey(), olkey.GetRange().GetStart(), olkey.GetRange().GetEnd()) default: panic(fmt.Sprintf("unsupported StateKey Clear type: %T: %v", key.GetType(), prototext.Format(key))) } From 0e375012dbe954e6892a53a2623125e5b74daecb Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:44:13 -0500 Subject: [PATCH 131/135] Add TF MNIST classification cost benchmark (#33391) * Add TF MNIST classification cost benchmark * linting * Generalize to single workflow file for cost benchmarks * fix incorrect UTC time in comment * move wordcount to same workflow * update workflow job name --- ...> beam_Python_CostBenchmarks_Dataflow.yml} | 28 +++++++++---- .../python_tf_mnist_classification.txt | 29 +++++++++++++ ...low_mnist_classification_cost_benchmark.py | 41 +++++++++++++++++++ 3 files changed, 91 insertions(+), 7 deletions(-) rename .github/workflows/{beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml => beam_Python_CostBenchmarks_Dataflow.yml} (69%) create mode 100644 .github/workflows/cost-benchmarks-pipeline-options/python_tf_mnist_classification.txt create mode 100644 sdks/python/apache_beam/testing/benchmarks/inference/tensorflow_mnist_classification_cost_benchmark.py diff --git a/.github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml b/.github/workflows/beam_Python_CostBenchmarks_Dataflow.yml similarity index 69% rename from .github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml rename to .github/workflows/beam_Python_CostBenchmarks_Dataflow.yml index 51d1005affbc..18fe37e142ac 100644 --- a/.github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml +++ b/.github/workflows/beam_Python_CostBenchmarks_Dataflow.yml @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -name: Wordcount Python Cost Benchmarks Dataflow +name: Python Cost Benchmarks Dataflow on: + schedule: + - cron: '30 18 * * 6' # Run at 6:30 pm UTC on Saturdays workflow_dispatch: #Setting explicit permissions for the action to avoid the default permissions which are `write-all` in case of pull_request_target event @@ -47,16 +49,17 @@ env: INFLUXDB_USER_PASSWORD: ${{ secrets.INFLUXDB_USER_PASSWORD }} jobs: - beam_Inference_Python_Benchmarks_Dataflow: + beam_Python_Cost_Benchmarks_Dataflow: if: | - github.event_name == 'workflow_dispatch' + github.event_name == 'workflow_dispatch' || + (github.event_name == 'schedule' && github.repository == 'apache/beam') runs-on: [self-hosted, ubuntu-20.04, main] timeout-minutes: 900 name: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) strategy: matrix: - job_name: ["beam_Wordcount_Python_Cost_Benchmarks_Dataflow"] - job_phrase: ["Run Wordcount Cost Benchmark"] + job_name: ["beam_Python_CostBenchmark_Dataflow"] + job_phrase: ["Run Python Dataflow Cost Benchmarks"] steps: - uses: actions/checkout@v4 - name: Setup repository @@ -76,10 +79,11 @@ jobs: test-language: python argument-file-paths: | ${{ github.workspace }}/.github/workflows/cost-benchmarks-pipeline-options/python_wordcount.txt + ${{ github.workspace }}/.github/workflows/cost-benchmarks-pipeline-options/python_tf_mnist_classification.txt # The env variables are created and populated in the test-arguments-action as "_test_arguments_" - name: get current time run: echo "NOW_UTC=$(date '+%m%d%H%M%S' --utc)" >> $GITHUB_ENV - - name: run wordcount on Dataflow Python + - name: Run wordcount on Dataflow uses: ./.github/actions/gradle-command-self-hosted-action timeout-minutes: 30 with: @@ -88,4 +92,14 @@ jobs: -PloadTest.mainClass=apache_beam.testing.benchmarks.wordcount.wordcount \ -Prunner=DataflowRunner \ -PpythonVersion=3.10 \ - '-PloadTest.args=${{ env.beam_Inference_Python_Benchmarks_Dataflow_test_arguments_1 }} --job_name=benchmark-tests-wordcount-python-${{env.NOW_UTC}} --output=gs://temp-storage-for-end-to-end-tests/wordcount/result_wordcount-${{env.NOW_UTC}}.txt' \ \ No newline at end of file + '-PloadTest.args=${{ env.beam_Inference_Python_Benchmarks_Dataflow_test_arguments_1 }} --job_name=benchmark-tests-wordcount-python-${{env.NOW_UTC}} --output_file=gs://temp-storage-for-end-to-end-tests/wordcount/result_wordcount-${{env.NOW_UTC}}.txt' \ + - name: Run Tensorflow MNIST Image Classification on Dataflow + uses: ./.github/actions/gradle-command-self-hosted-action + timeout-minutes: 30 + with: + gradle-command: :sdks:python:apache_beam:testing:load_tests:run + arguments: | + -PloadTest.mainClass=apache_beam.testing.benchmarks.inference.tensorflow_mnist_classification_cost_benchmark \ + -Prunner=DataflowRunner \ + -PpythonVersion=3.10 \ + '-PloadTest.args=${{ env.beam_Inference_Python_Benchmarks_Dataflow_test_arguments_2 }} --job_name=benchmark-tests-tf-mnist-classification-python-${{env.NOW_UTC}} --input_file=gs://apache-beam-ml/testing/inputs/it_mnist_data.csv --output_file=gs://temp-storage-for-end-to-end-tests/wordcount/result_tf_mnist-${{env.NOW_UTC}}.txt --model=gs://apache-beam-ml/models/tensorflow/mnist/' \ \ No newline at end of file diff --git a/.github/workflows/cost-benchmarks-pipeline-options/python_tf_mnist_classification.txt b/.github/workflows/cost-benchmarks-pipeline-options/python_tf_mnist_classification.txt new file mode 100644 index 000000000000..01f4460b8c7e --- /dev/null +++ b/.github/workflows/cost-benchmarks-pipeline-options/python_tf_mnist_classification.txt @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--region=us-central1 +--machine_type=n1-standard-2 +--num_workers=1 +--disk_size_gb=50 +--autoscaling_algorithm=NONE +--input_options={} +--staging_location=gs://temp-storage-for-perf-tests/loadtests +--temp_location=gs://temp-storage-for-perf-tests/loadtests +--requirements_file=apache_beam/ml/inference/tensorflow_tests_requirements.txt +--publish_to_big_query=true +--metrics_dataset=beam_run_inference +--metrics_table=tf_mnist_classification +--runner=DataflowRunner \ No newline at end of file diff --git a/sdks/python/apache_beam/testing/benchmarks/inference/tensorflow_mnist_classification_cost_benchmark.py b/sdks/python/apache_beam/testing/benchmarks/inference/tensorflow_mnist_classification_cost_benchmark.py new file mode 100644 index 000000000000..f7e12dcead03 --- /dev/null +++ b/sdks/python/apache_beam/testing/benchmarks/inference/tensorflow_mnist_classification_cost_benchmark.py @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# pytype: skip-file + +import logging + +from apache_beam.examples.inference import tensorflow_mnist_classification +from apache_beam.testing.load_tests.dataflow_cost_benchmark import DataflowCostBenchmark + + +class TensorflowMNISTClassificationCostBenchmark(DataflowCostBenchmark): + def __init__(self): + super().__init__() + + def test(self): + extra_opts = {} + extra_opts['input'] = self.pipeline.get_option('input_file') + extra_opts['output'] = self.pipeline.get_option('output_file') + extra_opts['model_path'] = self.pipeline.get_option('model') + tensorflow_mnist_classification.run( + self.pipeline.get_full_options_as_args(**extra_opts), + save_main_session=False) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + TensorflowMNISTClassificationCostBenchmark().run() From e27ecedfda5ad83a07935d26a95849e86896885e Mon Sep 17 00:00:00 2001 From: Nick Anikin <52892974+an2x@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:56:51 -0500 Subject: [PATCH 132/135] Do no throw exceptions if commitSync fails in KafkaUnboundedReader. (#33402) Committing consumer offsets to Kafka is not critical for KafkaIO because it relies on the offsets stored in KafkaCheckpointMark, but throwing an exception makes Dataflow retry the same work item unnecessarily. --- .../sdk/io/kafka/KafkaUnboundedReader.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaUnboundedReader.java b/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaUnboundedReader.java index 069607955c6d..ab9e26b3b740 100644 --- a/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaUnboundedReader.java +++ b/sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/KafkaUnboundedReader.java @@ -606,13 +606,20 @@ private void commitCheckpointMark() { LOG.debug("{}: Committing finalized checkpoint {}", this, checkpointMark); Consumer consumer = Preconditions.checkStateNotNull(this.consumer); - consumer.commitSync( - checkpointMark.getPartitions().stream() - .filter(p -> p.getNextOffset() != UNINITIALIZED_OFFSET) - .collect( - Collectors.toMap( - p -> new TopicPartition(p.getTopic(), p.getPartition()), - p -> new OffsetAndMetadata(p.getNextOffset())))); + try { + consumer.commitSync( + checkpointMark.getPartitions().stream() + .filter(p -> p.getNextOffset() != UNINITIALIZED_OFFSET) + .collect( + Collectors.toMap( + p -> new TopicPartition(p.getTopic(), p.getPartition()), + p -> new OffsetAndMetadata(p.getNextOffset())))); + } catch (Exception e) { + // Log but ignore the exception. Committing consumer offsets to Kafka is not critical for + // KafkaIO because it relies on the offsets stored in KafkaCheckpointMark. + LOG.warn( + String.format("%s: Could not commit finalized checkpoint %s", this, checkpointMark), e); + } } } From 470d7d6194749ab0de9fdc39d3e45ed5a9d76aff Mon Sep 17 00:00:00 2001 From: Shunping Huang Date: Tue, 17 Dec 2024 16:27:28 -0500 Subject: [PATCH 133/135] Add missing to_type_hint to WindowedValueCoder (#33403) * Add missing to_type_hint to WindowedValueCoder * Add type ignore to make mypy happy. --- sdks/python/apache_beam/coders/coders.py | 3 +++ sdks/python/apache_beam/coders/coders_test.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/sdks/python/apache_beam/coders/coders.py b/sdks/python/apache_beam/coders/coders.py index e52c6048a15c..22d041f34f8b 100644 --- a/sdks/python/apache_beam/coders/coders.py +++ b/sdks/python/apache_beam/coders/coders.py @@ -1446,6 +1446,9 @@ def from_type_hint(cls, typehint, registry): # pickle coders. return cls(registry.get_coder(typehint.inner_type)) + def to_type_hint(self): + return typehints.WindowedValue[self.wrapped_value_coder.to_type_hint()] + Coder.register_structured_urn( common_urns.coders.WINDOWED_VALUE.urn, WindowedValueCoder) diff --git a/sdks/python/apache_beam/coders/coders_test.py b/sdks/python/apache_beam/coders/coders_test.py index dc9780e36be3..bddd2cb57e06 100644 --- a/sdks/python/apache_beam/coders/coders_test.py +++ b/sdks/python/apache_beam/coders/coders_test.py @@ -258,6 +258,12 @@ def test_numpy_int(self): _ = indata | "CombinePerKey" >> beam.CombinePerKey(sum) +class WindowedValueCoderTest(unittest.TestCase): + def test_to_type_hint(self): + coder = coders.WindowedValueCoder(coders.VarIntCoder()) + self.assertEqual(coder.to_type_hint(), typehints.WindowedValue[int]) # type: ignore[misc] + + if __name__ == '__main__': logging.getLogger().setLevel(logging.INFO) unittest.main() From a9f50fae94b49a4e83acb131c04a34731a4a6604 Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:30:21 -0500 Subject: [PATCH 134/135] Python ExternalTransformProvider improvements (#33359) --- .../python/apache_beam/transforms/external.py | 14 ++-- .../transforms/external_transform_provider.py | 65 +++++++++++++------ 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/sdks/python/apache_beam/transforms/external.py b/sdks/python/apache_beam/transforms/external.py index e44f7482dc61..fb37a8fd974d 100644 --- a/sdks/python/apache_beam/transforms/external.py +++ b/sdks/python/apache_beam/transforms/external.py @@ -962,14 +962,14 @@ def __init__( self, path_to_jar, extra_args=None, classpath=None, append_args=None): if extra_args and append_args: raise ValueError('Only one of extra_args or append_args may be provided') - self._path_to_jar = path_to_jar + self.path_to_jar = path_to_jar self._extra_args = extra_args self._classpath = classpath or [] self._service_count = 0 self._append_args = append_args or [] def is_existing_service(self): - return subprocess_server.is_service_endpoint(self._path_to_jar) + return subprocess_server.is_service_endpoint(self.path_to_jar) @staticmethod def _expand_jars(jar): @@ -997,7 +997,7 @@ def _expand_jars(jar): def _default_args(self): """Default arguments to be used by `JavaJarExpansionService`.""" - to_stage = ','.join([self._path_to_jar] + sum(( + to_stage = ','.join([self.path_to_jar] + sum(( JavaJarExpansionService._expand_jars(jar) for jar in self._classpath or []), [])) args = ['{{PORT}}', f'--filesToStage={to_stage}'] @@ -1009,8 +1009,8 @@ def _default_args(self): def __enter__(self): if self._service_count == 0: - self._path_to_jar = subprocess_server.JavaJarServer.local_jar( - self._path_to_jar) + self.path_to_jar = subprocess_server.JavaJarServer.local_jar( + self.path_to_jar) if self._extra_args is None: self._extra_args = self._default_args() + self._append_args # Consider memoizing these servers (with some timeout). @@ -1018,7 +1018,7 @@ def __enter__(self): 'Starting a JAR-based expansion service from JAR %s ' + ( 'and with classpath: %s' % self._classpath if self._classpath else ''), - self._path_to_jar) + self.path_to_jar) classpath_urls = [ subprocess_server.JavaJarServer.local_jar(path) for jar in self._classpath @@ -1026,7 +1026,7 @@ def __enter__(self): ] self._service_provider = subprocess_server.JavaJarServer( ExpansionAndArtifactRetrievalStub, - self._path_to_jar, + self.path_to_jar, self._extra_args, classpath=classpath_urls) self._service = self._service_provider.__enter__() diff --git a/sdks/python/apache_beam/transforms/external_transform_provider.py b/sdks/python/apache_beam/transforms/external_transform_provider.py index 117c7f7c9b93..b22cd4b24cb6 100644 --- a/sdks/python/apache_beam/transforms/external_transform_provider.py +++ b/sdks/python/apache_beam/transforms/external_transform_provider.py @@ -26,6 +26,7 @@ from apache_beam.transforms import PTransform from apache_beam.transforms.external import BeamJarExpansionService +from apache_beam.transforms.external import JavaJarExpansionService from apache_beam.transforms.external import SchemaAwareExternalTransform from apache_beam.transforms.external import SchemaTransformsConfig from apache_beam.typehints.schemas import named_tuple_to_schema @@ -133,37 +134,57 @@ class ExternalTransformProvider: (see the `urn_pattern` parameter). These classes are generated when :class:`ExternalTransformProvider` is - initialized. We need to give it one or more expansion service addresses that - are already up and running: - >>> provider = ExternalTransformProvider(["localhost:12345", - ... "localhost:12121"]) - We can also give it the gradle target of a standard Beam expansion service: - >>> provider = ExternalTransform(BeamJarExpansionService( - ... "sdks:java:io:google-cloud-platform:expansion-service:shadowJar")) - Let's take a look at the output of :func:`get_available()` to know the - available transforms in the expansion service(s) we provided: + initialized. You can give it an expansion service address that is already + up and running: + + >>> provider = ExternalTransformProvider("localhost:12345") + + Or you can give it the path to an expansion service Jar file: + + >>> provider = ExternalTransformProvider(JavaJarExpansionService( + "path/to/expansion-service.jar")) + + Or you can give it the gradle target of a standard Beam expansion service: + + >>> provider = ExternalTransformProvider(BeamJarExpansionService( + "sdks:java:io:google-cloud-platform:expansion-service:shadowJar")) + + Note that you can provide a list of these services: + + >>> provider = ExternalTransformProvider([ + "localhost:12345", + JavaJarExpansionService("path/to/expansion-service.jar"), + BeamJarExpansionService( + "sdks:java:io:google-cloud-platform:expansion-service:shadowJar")]) + + The output of :func:`get_available()` provides a list of available transforms + in the provided expansion service(s): + >>> provider.get_available() [('JdbcWrite', 'beam:schematransform:org.apache.beam:jdbc_write:v1'), ('BigtableRead', 'beam:schematransform:org.apache.beam:bigtable_read:v1'), ...] - Then retrieve a transform by :func:`get()`, :func:`get_urn()`, or by directly - accessing it as an attribute of :class:`ExternalTransformProvider`. - All of the following commands do the same thing: + You can retrieve a transform with :func:`get()`, :func:`get_urn()`, or by + directly accessing it as an attribute. The following lines all do the same + thing: + >>> provider.get('BigqueryStorageRead') >>> provider.get_urn( - ... 'beam:schematransform:org.apache.beam:bigquery_storage_read:v1') + 'beam:schematransform:org.apache.beam:bigquery_storage_read:v1') >>> provider.BigqueryStorageRead - You can inspect the transform's documentation to know more about it. This - returns some documentation only IF the underlying SchemaTransform - implementation provides any. + You can inspect the transform's documentation for more details. The following + returns the documentation provided by the underlying SchemaTransform. If no + such documentation is provided, this will be empty. + >>> import inspect >>> inspect.getdoc(provider.BigqueryStorageRead) Similarly, you can inspect the transform's signature to know more about its parameters, including their names, types, and any documentation that the underlying SchemaTransform may provide: + >>> inspect.signature(provider.BigqueryStorageRead) (query: 'typing.Union[str, NoneType]: The SQL query to be executed to...', row_restriction: 'typing.Union[str, NoneType]: Read only rows that match...', @@ -178,8 +199,6 @@ class ExternalTransformProvider: query=query, row_restriction=restriction) | 'Some processing' >> beam.Map(...)) - - Experimental; no backwards compatibility guarantees. """ def __init__(self, expansion_services, urn_pattern=STANDARD_URN_PATTERN): f"""Initialize an ExternalTransformProvider @@ -188,6 +207,7 @@ def __init__(self, expansion_services, urn_pattern=STANDARD_URN_PATTERN): A list of expansion services to discover transforms from. Supported forms: * a string representing the expansion service address + * a :attr:`JavaJarExpansionService` pointing to the path of a Java Jar * a :attr:`BeamJarExpansionService` pointing to a gradle target :param urn_pattern: The regular expression used to match valid transforms. In addition to @@ -213,11 +233,14 @@ def _create_wrappers(self): target = service if isinstance(service, BeamJarExpansionService): target = service.gradle_target + if isinstance(service, JavaJarExpansionService): + target = service.path_to_jar try: schematransform_configs = SchemaAwareExternalTransform.discover(service) except Exception as e: logging.exception( - "Encountered an error while discovering expansion service %s:\n%s", + "Encountered an error while discovering " + "expansion service at '%s':\n%s", target, e) continue @@ -249,7 +272,7 @@ def _create_wrappers(self): if skipped_urns: logging.info( - "Skipped URN(s) in %s that don't follow the pattern \"%s\": %s", + "Skipped URN(s) in '%s' that don't follow the pattern \"%s\": %s", target, self._urn_pattern, skipped_urns) @@ -262,7 +285,7 @@ def get_available(self) -> List[Tuple[str, str]]: return list(self._name_to_urn.items()) def get_all(self) -> Dict[str, ExternalTransform]: - """Get all ExternalTransform""" + """Get all ExternalTransforms""" return self._transforms def get(self, name) -> ExternalTransform: From 286e29cb7a57c7a716de080167bfaf729251fd45 Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:33:50 -0500 Subject: [PATCH 135/135] [Managed Iceberg] Support partitioning time types (year, month, day, hour) (#32939) --- .../IO_Iceberg_Integration_Tests.json | 2 +- CHANGES.md | 1 + .../sdk/io/iceberg/RecordWriterManager.java | 79 +++++- .../sdk/io/iceberg/SerializableDataFile.java | 5 +- .../beam/sdk/io/iceberg/IcebergIOIT.java | 2 +- ...ebergWriteSchemaTransformProviderTest.java | 98 ++++++++ .../io/iceberg/RecordWriterManagerTest.java | 224 +++++++++++++++++- 7 files changed, 402 insertions(+), 9 deletions(-) diff --git a/.github/trigger_files/IO_Iceberg_Integration_Tests.json b/.github/trigger_files/IO_Iceberg_Integration_Tests.json index bbdc3a3910ef..2160d3c68005 100644 --- a/.github/trigger_files/IO_Iceberg_Integration_Tests.json +++ b/.github/trigger_files/IO_Iceberg_Integration_Tests.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 3 + "modification": 5 } diff --git a/CHANGES.md b/CHANGES.md index deaa8bfcd471..7707e252961b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -65,6 +65,7 @@ * gcs-connector config options can be set via GcsOptions (Java) ([#32769](https://github.com/apache/beam/pull/32769)). * Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). +* [Managed Iceberg] Support partitioning by time (year, month, day, hour) for types `date`, `time`, `timestamp`, and `timestamp(tz)` ([#32939](https://github.com/apache/beam/pull/32939)) * Upgraded the default version of Hadoop dependencies to 3.4.1. Hadoop 2.10.2 is still supported (Java) ([#33011](https://github.com/apache/beam/issues/33011)). * [BigQueryIO] Create managed BigLake tables dynamically ([#33125](https://github.com/apache/beam/pull/33125)) diff --git a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java index 255fce9ece4e..4c21a0175ab0 100644 --- a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java +++ b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java @@ -21,6 +21,11 @@ import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -31,6 +36,7 @@ import org.apache.beam.sdk.util.WindowedValue; import org.apache.beam.sdk.values.Row; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Splitter; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.Cache; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.CacheBuilder; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.RemovalNotification; @@ -38,14 +44,20 @@ import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Maps; import org.apache.iceberg.DataFile; import org.apache.iceberg.ManifestFile; +import org.apache.iceberg.PartitionField; import org.apache.iceberg.PartitionKey; import org.apache.iceberg.PartitionSpec; import org.apache.iceberg.Table; import org.apache.iceberg.catalog.Catalog; import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.data.GenericRecord; import org.apache.iceberg.data.Record; import org.apache.iceberg.exceptions.AlreadyExistsException; import org.apache.iceberg.exceptions.NoSuchTableException; +import org.apache.iceberg.expressions.Literal; +import org.apache.iceberg.transforms.Transform; +import org.apache.iceberg.transforms.Transforms; +import org.apache.iceberg.types.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,6 +102,7 @@ class DestinationState { final Cache writers; private final List dataFiles = Lists.newArrayList(); @VisibleForTesting final Map writerCounts = Maps.newHashMap(); + private final Map partitionFieldMap = Maps.newHashMap(); private final List exceptions = Lists.newArrayList(); DestinationState(IcebergDestination icebergDestination, Table table) { @@ -98,6 +111,9 @@ class DestinationState { this.spec = table.spec(); this.partitionKey = new PartitionKey(spec, schema); this.table = table; + for (PartitionField partitionField : spec.fields()) { + partitionFieldMap.put(partitionField.name(), partitionField); + } // build a cache of RecordWriters. // writers will expire after 1 min of idle time. @@ -123,7 +139,9 @@ class DestinationState { throw rethrow; } openWriters--; - dataFiles.add(SerializableDataFile.from(recordWriter.getDataFile(), pk)); + String partitionPath = getPartitionDataPath(pk.toPath(), partitionFieldMap); + dataFiles.add( + SerializableDataFile.from(recordWriter.getDataFile(), partitionPath)); }) .build(); } @@ -136,7 +154,7 @@ class DestinationState { * can't create a new writer, the {@link Record} is rejected and {@code false} is returned. */ boolean write(Record record) { - partitionKey.partition(record); + partitionKey.partition(getPartitionableRecord(record)); if (!writers.asMap().containsKey(partitionKey) && openWriters >= maxNumWriters) { return false; @@ -185,8 +203,65 @@ private RecordWriter createWriter(PartitionKey partitionKey) { e); } } + + /** + * Resolves an input {@link Record}'s partition values and returns another {@link Record} that + * can be applied to the destination's {@link PartitionSpec}. + */ + private Record getPartitionableRecord(Record record) { + if (spec.isUnpartitioned()) { + return record; + } + Record output = GenericRecord.create(schema); + for (PartitionField partitionField : spec.fields()) { + Transform transform = partitionField.transform(); + Types.NestedField field = schema.findField(partitionField.sourceId()); + String name = field.name(); + Object value = record.getField(name); + @Nullable Literal literal = Literal.of(value.toString()).to(field.type()); + if (literal == null || transform.isVoid() || transform.isIdentity()) { + output.setField(name, value); + } else { + output.setField(name, literal.value()); + } + } + return output; + } } + /** + * Returns an equivalent partition path that is made up of partition data. Needed to reconstruct a + * {@link DataFile}. + */ + @VisibleForTesting + static String getPartitionDataPath( + String partitionPath, Map partitionFieldMap) { + if (partitionPath.isEmpty() || partitionFieldMap.isEmpty()) { + return partitionPath; + } + List resolved = new ArrayList<>(); + for (String partition : Splitter.on('/').splitToList(partitionPath)) { + List nameAndValue = Splitter.on('=').splitToList(partition); + String name = nameAndValue.get(0); + String value = nameAndValue.get(1); + String transformName = + Preconditions.checkArgumentNotNull(partitionFieldMap.get(name)).transform().toString(); + if (Transforms.month().toString().equals(transformName)) { + int month = YearMonth.parse(value).getMonthValue(); + value = String.valueOf(month); + } else if (Transforms.hour().toString().equals(transformName)) { + long hour = ChronoUnit.HOURS.between(EPOCH, LocalDateTime.parse(value, HOUR_FORMATTER)); + value = String.valueOf(hour); + } + resolved.add(name + "=" + value); + } + return String.join("/", resolved); + } + + private static final DateTimeFormatter HOUR_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd-HH"); + private static final LocalDateTime EPOCH = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC); + private final Catalog catalog; private final String filePrefix; private final long maxFileSize; diff --git a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SerializableDataFile.java b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SerializableDataFile.java index 59b456162008..eef2b154d243 100644 --- a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SerializableDataFile.java +++ b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SerializableDataFile.java @@ -116,13 +116,14 @@ abstract static class Builder { * Create a {@link SerializableDataFile} from a {@link DataFile} and its associated {@link * PartitionKey}. */ - static SerializableDataFile from(DataFile f, PartitionKey key) { + static SerializableDataFile from(DataFile f, String partitionPath) { + return SerializableDataFile.builder() .setPath(f.path().toString()) .setFileFormat(f.format().toString()) .setRecordCount(f.recordCount()) .setFileSizeInBytes(f.fileSizeInBytes()) - .setPartitionPath(key.toPath()) + .setPartitionPath(partitionPath) .setPartitionSpecId(f.specId()) .setKeyMetadata(f.keyMetadata()) .setSplitOffsets(f.splitOffsets()) diff --git a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java index c79b0a550051..a060bc16d6c7 100644 --- a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java +++ b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOIT.java @@ -354,7 +354,7 @@ public void testWritePartitionedData() { PartitionSpec partitionSpec = PartitionSpec.builderFor(ICEBERG_SCHEMA) .identity("bool") - .identity("modulo_5") + .hour("datetime") .truncate("str", "value_x".length()) .build(); Table table = diff --git a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProviderTest.java b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProviderTest.java index 47dc9aa425dd..9834547c4741 100644 --- a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProviderTest.java +++ b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProviderTest.java @@ -23,14 +23,19 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.apache.beam.sdk.Pipeline; import org.apache.beam.sdk.managed.Managed; import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.schemas.logicaltypes.SqlTypes; import org.apache.beam.sdk.testing.PAssert; import org.apache.beam.sdk.testing.TestPipeline; import org.apache.beam.sdk.testing.TestStream; @@ -49,12 +54,16 @@ import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap; import org.apache.iceberg.CatalogUtil; +import org.apache.iceberg.PartitionSpec; import org.apache.iceberg.Table; import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.data.IcebergGenerics; import org.apache.iceberg.data.Record; +import org.apache.iceberg.util.DateTimeUtil; import org.checkerframework.checker.nullness.qual.Nullable; import org.hamcrest.Matchers; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.joda.time.Duration; import org.joda.time.Instant; import org.junit.ClassRule; @@ -360,4 +369,93 @@ public Void apply(Iterable input) { return null; } } + + @Test + public void testWritePartitionedData() { + Schema schema = + Schema.builder() + .addStringField("str") + .addInt32Field("int") + .addLogicalTypeField("y_date", SqlTypes.DATE) + .addLogicalTypeField("y_datetime", SqlTypes.DATETIME) + .addDateTimeField("y_datetime_tz") + .addLogicalTypeField("m_date", SqlTypes.DATE) + .addLogicalTypeField("m_datetime", SqlTypes.DATETIME) + .addDateTimeField("m_datetime_tz") + .addLogicalTypeField("d_date", SqlTypes.DATE) + .addLogicalTypeField("d_datetime", SqlTypes.DATETIME) + .addDateTimeField("d_datetime_tz") + .addLogicalTypeField("h_datetime", SqlTypes.DATETIME) + .addDateTimeField("h_datetime_tz") + .build(); + org.apache.iceberg.Schema icebergSchema = IcebergUtils.beamSchemaToIcebergSchema(schema); + PartitionSpec spec = + PartitionSpec.builderFor(icebergSchema) + .identity("str") + .bucket("int", 5) + .year("y_date") + .year("y_datetime") + .year("y_datetime_tz") + .month("m_date") + .month("m_datetime") + .month("m_datetime_tz") + .day("d_date") + .day("d_datetime") + .day("d_datetime_tz") + .hour("h_datetime") + .hour("h_datetime_tz") + .build(); + String identifier = "default.table_" + Long.toString(UUID.randomUUID().hashCode(), 16); + + warehouse.createTable(TableIdentifier.parse(identifier), icebergSchema, spec); + Map config = + ImmutableMap.of( + "table", + identifier, + "catalog_properties", + ImmutableMap.of("type", "hadoop", "warehouse", warehouse.location)); + + List rows = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + long millis = i * 100_00_000_000L; + LocalDate localDate = DateTimeUtil.dateFromDays(i * 100); + LocalDateTime localDateTime = DateTimeUtil.timestampFromMicros(millis * 1000); + DateTime dateTime = new DateTime(millis).withZone(DateTimeZone.forOffsetHoursMinutes(3, 25)); + Row row = + Row.withSchema(schema) + .addValues( + "str_" + i, + i, + localDate, + localDateTime, + dateTime, + localDate, + localDateTime, + dateTime, + localDate, + localDateTime, + dateTime, + localDateTime, + dateTime) + .build(); + rows.add(row); + } + + PCollection result = + testPipeline + .apply("Records To Add", Create.of(rows)) + .setRowSchema(schema) + .apply(Managed.write(Managed.ICEBERG).withConfig(config)) + .get(SNAPSHOTS_TAG); + + PAssert.that(result) + .satisfies(new VerifyOutputs(Collections.singletonList(identifier), "append")); + testPipeline.run().waitUntilFinish(); + + Pipeline p = Pipeline.create(TestPipeline.testingPipelineOptions()); + PCollection readRows = + p.apply(Managed.read(Managed.ICEBERG).withConfig(config)).getSinglePCollection(); + PAssert.that(readRows).containsInAnyOrder(rows); + p.run(); + } } diff --git a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java index 2bce390e0992..5168f71fef99 100644 --- a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java +++ b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java @@ -27,9 +27,14 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.schemas.logicaltypes.SqlTypes; import org.apache.beam.sdk.transforms.windowing.GlobalWindow; import org.apache.beam.sdk.transforms.windowing.PaneInfo; import org.apache.beam.sdk.util.WindowedValue; @@ -39,6 +44,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.iceberg.DataFile; import org.apache.iceberg.FileFormat; +import org.apache.iceberg.PartitionField; import org.apache.iceberg.PartitionKey; import org.apache.iceberg.PartitionSpec; import org.apache.iceberg.Table; @@ -46,6 +52,8 @@ import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.hadoop.HadoopCatalog; import org.checkerframework.checker.nullness.qual.Nullable; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; @@ -85,9 +93,14 @@ public void setUp() { private WindowedValue getWindowedDestination( String tableName, @Nullable PartitionSpec partitionSpec) { + return getWindowedDestination(tableName, ICEBERG_SCHEMA, partitionSpec); + } + + private WindowedValue getWindowedDestination( + String tableName, org.apache.iceberg.Schema schema, @Nullable PartitionSpec partitionSpec) { TableIdentifier tableIdentifier = TableIdentifier.of("default", tableName); - warehouse.createTable(tableIdentifier, ICEBERG_SCHEMA, partitionSpec); + warehouse.createTable(tableIdentifier, schema, partitionSpec); IcebergDestination icebergDestination = IcebergDestination.builder() @@ -314,8 +327,15 @@ public void testSerializableDataFileRoundTripEquality() throws IOException { DataFile datafile = writer.getDataFile(); assertEquals(2L, datafile.recordCount()); + Map partitionFieldMap = new HashMap<>(); + for (PartitionField partitionField : PARTITION_SPEC.fields()) { + partitionFieldMap.put(partitionField.name(), partitionField); + } + + String partitionPath = + RecordWriterManager.getPartitionDataPath(partitionKey.toPath(), partitionFieldMap); DataFile roundTripDataFile = - SerializableDataFile.from(datafile, partitionKey) + SerializableDataFile.from(datafile, partitionPath) .createDataFile(ImmutableMap.of(PARTITION_SPEC.specId(), PARTITION_SPEC)); checkDataFileEquality(datafile, roundTripDataFile); @@ -347,8 +367,14 @@ public void testRecreateSerializableDataAfterUpdatingPartitionSpec() throws IOEx writer.close(); // fetch data file and its serializable version + Map partitionFieldMap = new HashMap<>(); + for (PartitionField partitionField : PARTITION_SPEC.fields()) { + partitionFieldMap.put(partitionField.name(), partitionField); + } + String partitionPath = + RecordWriterManager.getPartitionDataPath(partitionKey.toPath(), partitionFieldMap); DataFile datafile = writer.getDataFile(); - SerializableDataFile serializableDataFile = SerializableDataFile.from(datafile, partitionKey); + SerializableDataFile serializableDataFile = SerializableDataFile.from(datafile, partitionPath); assertEquals(2L, datafile.recordCount()); assertEquals(serializableDataFile.getPartitionSpecId(), datafile.specId()); @@ -415,6 +441,198 @@ public void testWriterKeepsUpWithUpdatingPartitionSpec() throws IOException { } } + @Test + public void testIdentityPartitioning() throws IOException { + Schema primitiveTypeSchema = + Schema.builder() + .addBooleanField("bool") + .addInt32Field("int") + .addInt64Field("long") + .addFloatField("float") + .addDoubleField("double") + .addStringField("str") + .build(); + + Row row = + Row.withSchema(primitiveTypeSchema).addValues(true, 1, 1L, 1.23f, 4.56, "str").build(); + org.apache.iceberg.Schema icebergSchema = + IcebergUtils.beamSchemaToIcebergSchema(primitiveTypeSchema); + PartitionSpec spec = + PartitionSpec.builderFor(icebergSchema) + .identity("bool") + .identity("int") + .identity("long") + .identity("float") + .identity("double") + .identity("str") + .build(); + WindowedValue dest = + getWindowedDestination("identity_partitioning", icebergSchema, spec); + + RecordWriterManager writer = + new RecordWriterManager(catalog, "test_prefix", Long.MAX_VALUE, Integer.MAX_VALUE); + writer.write(dest, row); + writer.close(); + List files = writer.getSerializableDataFiles().get(dest); + assertEquals(1, files.size()); + SerializableDataFile dataFile = files.get(0); + assertEquals(1, dataFile.getRecordCount()); + // build this string: bool=true/int=1/long=1/float=1.0/double=1.0/str=str + List expectedPartitions = new ArrayList<>(); + for (Schema.Field field : primitiveTypeSchema.getFields()) { + Object val = row.getValue(field.getName()); + expectedPartitions.add(field.getName() + "=" + val); + } + String expectedPartitionPath = String.join("/", expectedPartitions); + assertEquals(expectedPartitionPath, dataFile.getPartitionPath()); + assertThat(dataFile.getPath(), containsString(expectedPartitionPath)); + } + + @Test + public void testBucketPartitioning() throws IOException { + Schema bucketSchema = + Schema.builder() + .addInt32Field("int") + .addInt64Field("long") + .addStringField("str") + .addLogicalTypeField("date", SqlTypes.DATE) + .addLogicalTypeField("time", SqlTypes.TIME) + .addLogicalTypeField("datetime", SqlTypes.DATETIME) + .addDateTimeField("datetime_tz") + .build(); + + String timestamp = "2024-10-08T13:18:20.053"; + LocalDateTime localDateTime = LocalDateTime.parse(timestamp); + + Row row = + Row.withSchema(bucketSchema) + .addValues( + 1, + 1L, + "str", + localDateTime.toLocalDate(), + localDateTime.toLocalTime(), + localDateTime, + DateTime.parse(timestamp)) + .build(); + org.apache.iceberg.Schema icebergSchema = IcebergUtils.beamSchemaToIcebergSchema(bucketSchema); + PartitionSpec spec = + PartitionSpec.builderFor(icebergSchema) + .bucket("int", 2) + .bucket("long", 2) + .bucket("str", 2) + .bucket("date", 2) + .bucket("time", 2) + .bucket("datetime", 2) + .bucket("datetime_tz", 2) + .build(); + WindowedValue dest = + getWindowedDestination("bucket_partitioning", icebergSchema, spec); + + RecordWriterManager writer = + new RecordWriterManager(catalog, "test_prefix", Long.MAX_VALUE, Integer.MAX_VALUE); + writer.write(dest, row); + writer.close(); + List files = writer.getSerializableDataFiles().get(dest); + assertEquals(1, files.size()); + SerializableDataFile dataFile = files.get(0); + assertEquals(1, dataFile.getRecordCount()); + for (Schema.Field field : bucketSchema.getFields()) { + String expectedPartition = field.getName() + "_bucket"; + assertThat(dataFile.getPartitionPath(), containsString(expectedPartition)); + assertThat(dataFile.getPath(), containsString(expectedPartition)); + } + } + + @Test + public void testTimePartitioning() throws IOException { + Schema timePartitioningSchema = + Schema.builder() + .addLogicalTypeField("y_date", SqlTypes.DATE) + .addLogicalTypeField("y_datetime", SqlTypes.DATETIME) + .addDateTimeField("y_datetime_tz") + .addLogicalTypeField("m_date", SqlTypes.DATE) + .addLogicalTypeField("m_datetime", SqlTypes.DATETIME) + .addDateTimeField("m_datetime_tz") + .addLogicalTypeField("d_date", SqlTypes.DATE) + .addLogicalTypeField("d_datetime", SqlTypes.DATETIME) + .addDateTimeField("d_datetime_tz") + .addLogicalTypeField("h_datetime", SqlTypes.DATETIME) + .addDateTimeField("h_datetime_tz") + .build(); + org.apache.iceberg.Schema icebergSchema = + IcebergUtils.beamSchemaToIcebergSchema(timePartitioningSchema); + PartitionSpec spec = + PartitionSpec.builderFor(icebergSchema) + .year("y_date") + .year("y_datetime") + .year("y_datetime_tz") + .month("m_date") + .month("m_datetime") + .month("m_datetime_tz") + .day("d_date") + .day("d_datetime") + .day("d_datetime_tz") + .hour("h_datetime") + .hour("h_datetime_tz") + .build(); + + WindowedValue dest = + getWindowedDestination("time_partitioning", icebergSchema, spec); + + String timestamp = "2024-10-08T13:18:20.053"; + LocalDateTime localDateTime = LocalDateTime.parse(timestamp); + LocalDate localDate = localDateTime.toLocalDate(); + String timestamptz = "2024-10-08T13:18:20.053+03:27"; + DateTime dateTime = DateTime.parse(timestamptz); + + Row row = + Row.withSchema(timePartitioningSchema) + .addValues(localDate, localDateTime, dateTime) // year + .addValues(localDate, localDateTime, dateTime) // month + .addValues(localDate, localDateTime, dateTime) // day + .addValues(localDateTime, dateTime) // hour + .build(); + + // write some rows + RecordWriterManager writer = + new RecordWriterManager(catalog, "test_prefix", Long.MAX_VALUE, Integer.MAX_VALUE); + writer.write(dest, row); + writer.close(); + List files = writer.getSerializableDataFiles().get(dest); + assertEquals(1, files.size()); + SerializableDataFile serializableDataFile = files.get(0); + assertEquals(1, serializableDataFile.getRecordCount()); + + int year = localDateTime.getYear(); + int month = localDateTime.getMonthValue(); + int day = localDateTime.getDayOfMonth(); + int hour = localDateTime.getHour(); + List expectedPartitions = new ArrayList<>(); + for (Schema.Field field : timePartitioningSchema.getFields()) { + String name = field.getName(); + String expected = ""; + if (name.startsWith("y_")) { + expected = String.format("%s_year=%s", name, year); + } else if (name.startsWith("m_")) { + expected = String.format("%s_month=%s-%02d", name, year, month); + } else if (name.startsWith("d_")) { + expected = String.format("%s_day=%s-%02d-%02d", name, year, month, day); + } else if (name.startsWith("h_")) { + if (name.contains("tz")) { + hour = dateTime.withZone(DateTimeZone.UTC).getHourOfDay(); + } + expected = String.format("%s_hour=%s-%02d-%02d-%02d", name, year, month, day, hour); + } + expectedPartitions.add(expected); + } + String expectedPartition = String.join("/", expectedPartitions); + DataFile dataFile = + serializableDataFile.createDataFile( + catalog.loadTable(dest.getValue().getTableIdentifier()).specs()); + assertThat(dataFile.path().toString(), containsString(expectedPartition)); + } + @Rule public ExpectedException thrown = ExpectedException.none(); @Test

A message receiver is closed when it is no longer able to receive messages. + */ + boolean isClosed(); + /** * Receives a message from the broker. * diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java index 6dcd0b652616..84a876a9d0bc 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SessionService.java @@ -120,6 +120,13 @@ public abstract class SessionService implements Serializable { /** Gracefully closes the connection to the service. */ public abstract void close(); + /** + * Checks whether the connection to the service is currently closed. This method is called when an + * `UnboundedSolaceReader` is starting to read messages - a session will be created if this + * returns true. + */ + public abstract boolean isClosed(); + /** * Returns a MessageReceiver object for receiving messages from Solace. If it is the first time * this method is used, the receiver is created from the session instance, otherwise it returns diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java index d74f3cae89fe..d548d2049a5b 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/SolaceMessageReceiver.java @@ -24,8 +24,12 @@ import java.io.IOException; import org.apache.beam.sdk.io.solace.RetryCallableManager; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SolaceMessageReceiver implements MessageReceiver { + private static final Logger LOG = LoggerFactory.getLogger(SolaceMessageReceiver.class); + public static final int DEFAULT_ADVANCE_TIMEOUT_IN_MILLIS = 100; private final FlowReceiver flowReceiver; private final RetryCallableManager retryCallableManager = RetryCallableManager.create(); @@ -48,14 +52,19 @@ private void startFlowReceiver() { ImmutableSet.of(JCSMPException.class)); } + @Override + public boolean isClosed() { + return flowReceiver == null || flowReceiver.isClosed(); + } + @Override public BytesXMLMessage receive() throws IOException { try { return flowReceiver.receive(DEFAULT_ADVANCE_TIMEOUT_IN_MILLIS); } catch (StaleSessionException e) { + LOG.warn("SolaceIO: Caught StaleSessionException, restarting the FlowReceiver."); startFlowReceiver(); - throw new IOException( - "SolaceIO: Caught StaleSessionException, restarting the FlowReceiver.", e); + throw new IOException(e); } catch (JCSMPException e) { throw new IOException(e); } @@ -63,6 +72,8 @@ public BytesXMLMessage receive() throws IOException { @Override public void close() { - flowReceiver.close(); + if (!isClosed()) { + this.flowReceiver.close(); + } } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java index a913fd6133ea..77f6eed8f62c 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/SolaceCheckpointMark.java @@ -18,16 +18,17 @@ package org.apache.beam.sdk.io.solace.read; import com.solacesystems.jcsmp.BytesXMLMessage; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; -import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.beam.sdk.annotations.Internal; import org.apache.beam.sdk.coders.DefaultCoder; import org.apache.beam.sdk.extensions.avro.coders.AvroCoder; import org.apache.beam.sdk.io.UnboundedSource; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; import org.checkerframework.checker.nullness.qual.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Checkpoint for an unbounded Solace source. Consists of the Solace messages waiting to be @@ -37,8 +38,10 @@ @Internal @VisibleForTesting public class SolaceCheckpointMark implements UnboundedSource.CheckpointMark { - private static final Logger LOG = LoggerFactory.getLogger(SolaceCheckpointMark.class); - private transient Queue safeToAck; + private transient AtomicBoolean activeReader; + // BytesXMLMessage is not serializable so if a job restarts from the checkpoint, we cannot retry + // these messages here. We relay on Solace's retry mechanism. + private transient ArrayDeque ackQueue; @SuppressWarnings("initialization") // Avro will set the fields by breaking abstraction private SolaceCheckpointMark() {} @@ -46,24 +49,25 @@ private SolaceCheckpointMark() {} /** * Creates a new {@link SolaceCheckpointMark}. * - * @param safeToAck - a queue of {@link BytesXMLMessage} to be acknowledged. + * @param activeReader {@link AtomicBoolean} indicating if the related reader is active. The + * reader creating the messages has to be active to acknowledge the messages. + * @param ackQueue {@link List} of {@link BytesXMLMessage} to be acknowledged. */ - SolaceCheckpointMark(Queue safeToAck) { - this.safeToAck = safeToAck; + SolaceCheckpointMark(AtomicBoolean activeReader, List ackQueue) { + this.activeReader = activeReader; + this.ackQueue = new ArrayDeque<>(ackQueue); } @Override public void finalizeCheckpoint() { - BytesXMLMessage msg; - while ((msg = safeToAck.poll()) != null) { - try { + if (activeReader == null || !activeReader.get() || ackQueue == null) { + return; + } + + while (!ackQueue.isEmpty()) { + BytesXMLMessage msg = ackQueue.poll(); + if (msg != null) { msg.ackMessage(); - } catch (IllegalStateException e) { - LOG.error( - "SolaceIO.Read: cannot acknowledge the message with applicationMessageId={}, ackMessageId={}. It will not be retried.", - msg.getApplicationMessageId(), - msg.getAckMessageId(), - e); } } } @@ -80,11 +84,15 @@ public boolean equals(@Nullable Object o) { return false; } SolaceCheckpointMark that = (SolaceCheckpointMark) o; - return Objects.equals(safeToAck, that.safeToAck); + // Needed to convert to ArrayList because ArrayDeque.equals checks only for reference, not + // content. + ArrayList ackList = new ArrayList<>(ackQueue); + ArrayList thatAckList = new ArrayList<>(that.ackQueue); + return Objects.equals(activeReader, that.activeReader) && Objects.equals(ackList, thatAckList); } @Override public int hashCode() { - return Objects.hash(safeToAck); + return Objects.hash(activeReader, ackQueue); } } diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java index dc84e0a07017..a421970370da 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/read/UnboundedSolaceReader.java @@ -22,26 +22,17 @@ import com.solacesystems.jcsmp.BytesXMLMessage; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; -import java.util.Queue; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.beam.sdk.io.UnboundedSource; import org.apache.beam.sdk.io.UnboundedSource.UnboundedReader; import org.apache.beam.sdk.io.solace.broker.SempClient; import org.apache.beam.sdk.io.solace.broker.SessionService; -import org.apache.beam.sdk.io.solace.broker.SessionServiceFactory; import org.apache.beam.sdk.transforms.windowing.BoundedWindow; import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.Cache; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.CacheBuilder; -import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.cache.RemovalNotification; import org.checkerframework.checker.nullness.qual.Nullable; import org.joda.time.Instant; import org.slf4j.Logger; @@ -55,92 +46,48 @@ class UnboundedSolaceReader extends UnboundedReader { private final UnboundedSolaceSource currentSource; private final WatermarkPolicy watermarkPolicy; private final SempClient sempClient; - private final UUID readerUuid; - private final SessionServiceFactory sessionServiceFactory; private @Nullable BytesXMLMessage solaceOriginalRecord; private @Nullable T solaceMappedRecord; + private @Nullable SessionService sessionService; + AtomicBoolean active = new AtomicBoolean(true); /** - * Queue to place advanced messages before {@link #getCheckpointMark()} is called. CAUTION: - * Accessed by both reader and checkpointing threads. + * Queue to place advanced messages before {@link #getCheckpointMark()} be called non-concurrent + * queue, should only be accessed by the reader thread A given {@link UnboundedReader} object will + * only be accessed by a single thread at once. */ - private final Queue safeToAckMessages = new ConcurrentLinkedQueue<>(); - - /** - * Queue for messages that were ingested in the {@link #advance()} method, but not sent yet to a - * {@link SolaceCheckpointMark}. - */ - private final Queue receivedMessages = new ArrayDeque<>(); - - private static final Cache sessionServiceCache; - private static final ScheduledExecutorService cleanUpThread = Executors.newScheduledThreadPool(1); - - static { - Duration cacheExpirationTimeout = Duration.ofMinutes(1); - sessionServiceCache = - CacheBuilder.newBuilder() - .expireAfterAccess(cacheExpirationTimeout) - .removalListener( - (RemovalNotification notification) -> { - LOG.info( - "SolaceIO.Read: Closing session for the reader with uuid {} as it has been idle for over {}.", - notification.getKey(), - cacheExpirationTimeout); - SessionService sessionService = notification.getValue(); - if (sessionService != null) { - sessionService.close(); - } - }) - .build(); - - startCleanUpThread(); - } - - @SuppressWarnings("FutureReturnValueIgnored") - private static void startCleanUpThread() { - cleanUpThread.scheduleAtFixedRate(sessionServiceCache::cleanUp, 1, 1, TimeUnit.MINUTES); - } + private final java.util.Queue elementsToCheckpoint = new ArrayDeque<>(); public UnboundedSolaceReader(UnboundedSolaceSource currentSource) { this.currentSource = currentSource; this.watermarkPolicy = WatermarkPolicy.create( currentSource.getTimestampFn(), currentSource.getWatermarkIdleDurationThreshold()); - this.sessionServiceFactory = currentSource.getSessionServiceFactory(); + this.sessionService = currentSource.getSessionServiceFactory().create(); this.sempClient = currentSource.getSempClientFactory().create(); - this.readerUuid = UUID.randomUUID(); - } - - private SessionService getSessionService() { - try { - return sessionServiceCache.get( - readerUuid, - () -> { - LOG.info("SolaceIO.Read: creating a new session for reader with uuid {}.", readerUuid); - SessionService sessionService = sessionServiceFactory.create(); - sessionService.connect(); - sessionService.getReceiver().start(); - return sessionService; - }); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } } @Override public boolean start() { - // Create and initialize SessionService with Receiver - getSessionService(); + populateSession(); + checkNotNull(sessionService).getReceiver().start(); return advance(); } + public void populateSession() { + if (sessionService == null) { + sessionService = getCurrentSource().getSessionServiceFactory().create(); + } + if (sessionService.isClosed()) { + checkNotNull(sessionService).connect(); + } + } + @Override public boolean advance() { - finalizeReadyMessages(); - BytesXMLMessage receivedXmlMessage; try { - receivedXmlMessage = getSessionService().getReceiver().receive(); + receivedXmlMessage = checkNotNull(sessionService).getReceiver().receive(); } catch (IOException e) { LOG.warn("SolaceIO.Read: Exception when pulling messages from the broker.", e); return false; @@ -149,40 +96,23 @@ public boolean advance() { if (receivedXmlMessage == null) { return false; } + elementsToCheckpoint.add(receivedXmlMessage); solaceOriginalRecord = receivedXmlMessage; solaceMappedRecord = getCurrentSource().getParseFn().apply(receivedXmlMessage); - receivedMessages.add(receivedXmlMessage); - + watermarkPolicy.update(solaceMappedRecord); return true; } @Override public void close() { - finalizeReadyMessages(); - sessionServiceCache.invalidate(readerUuid); - } - - public void finalizeReadyMessages() { - BytesXMLMessage msg; - while ((msg = safeToAckMessages.poll()) != null) { - try { - msg.ackMessage(); - } catch (IllegalStateException e) { - LOG.error( - "SolaceIO.Read: failed to acknowledge the message with applicationMessageId={}, ackMessageId={}. Returning the message to queue to retry.", - msg.getApplicationMessageId(), - msg.getAckMessageId(), - e); - safeToAckMessages.add(msg); // In case the error was transient, might succeed later - break; // Commit is only best effort - } - } + active.set(false); + checkNotNull(sessionService).close(); } @Override public Instant getWatermark() { // should be only used by a test receiver - if (getSessionService().getReceiver().isEOF()) { + if (checkNotNull(sessionService).getReceiver().isEOF()) { return BoundedWindow.TIMESTAMP_MAX_VALUE; } return watermarkPolicy.getWatermark(); @@ -190,9 +120,14 @@ public Instant getWatermark() { @Override public UnboundedSource.CheckpointMark getCheckpointMark() { - safeToAckMessages.addAll(receivedMessages); - receivedMessages.clear(); - return new SolaceCheckpointMark(safeToAckMessages); + List ackQueue = new ArrayList<>(); + while (!elementsToCheckpoint.isEmpty()) { + BytesXMLMessage msg = elementsToCheckpoint.poll(); + if (msg != null) { + ackQueue.add(msg); + } + } + return new SolaceCheckpointMark(active, ackQueue); } @Override diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java index 7631d32f63cc..38b4953a5984 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockEmptySessionService.java @@ -40,6 +40,11 @@ public void close() { throw new UnsupportedOperationException(exceptionMessage); } + @Override + public boolean isClosed() { + throw new UnsupportedOperationException(exceptionMessage); + } + @Override public MessageReceiver getReceiver() { throw new UnsupportedOperationException(exceptionMessage); diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java index 6d28bcefc84c..bd52dee7ea86 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/MockSessionService.java @@ -77,6 +77,11 @@ public abstract Builder mockProducerFn( @Override public void close() {} + @Override + public boolean isClosed() { + return false; + } + @Override public MessageReceiver getReceiver() { if (messageReceiver == null) { @@ -126,6 +131,11 @@ public MockReceiver( @Override public void start() {} + @Override + public boolean isClosed() { + return false; + } + @Override public BytesXMLMessage receive() throws IOException { return getRecordFn.apply(counter.getAndIncrement()); diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java index a1f80932eddf..c718c55e1b48 100644 --- a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/SolaceIOReadTest.java @@ -447,29 +447,25 @@ public void testCheckpointMarkAndFinalizeSeparately() throws Exception { // start the reader and move to the first record assertTrue(reader.start()); - // consume 3 messages (NB: #start() already consumed the first message) + // consume 3 messages (NB: start already consumed the first message) for (int i = 0; i < 3; i++) { assertTrue(String.format("Failed at %d-th message", i), reader.advance()); } - // #advance() was called, but the messages were not ready to be acknowledged. - assertEquals(0, countAckMessages.get()); - - // mark all consumed messages as ready to be acknowledged + // create checkpoint but don't finalize yet CheckpointMark checkpointMark = reader.getCheckpointMark(); - // consume 1 more message. This will call #ackMsg() on messages that were ready to be acked. + // consume 2 more messages reader.advance(); - assertEquals(4, countAckMessages.get()); - - // consume 1 more message. No change in the acknowledged messages. reader.advance(); - assertEquals(4, countAckMessages.get()); + + // check if messages are still not acknowledged + assertEquals(0, countAckMessages.get()); // acknowledge from the first checkpoint checkpointMark.finalizeCheckpoint(); - // No change in the acknowledged messages, because they were acknowledged in the #advance() - // method. + + // only messages from the first checkpoint are acknowledged assertEquals(4, countAckMessages.get()); } From 9f165576e24701135cf26c62e08abea55c436e23 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 2 Dec 2024 13:25:14 -0500 Subject: [PATCH 050/135] Clean up changelog (#33260) --- CHANGES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c120906fdd0d..4f56b4d49e45 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -98,7 +98,6 @@ ## I/Os -* Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). * [Managed Iceberg] Support creating tables if needed ([#32686](https://github.com/apache/beam/pull/32686)) * [Managed Iceberg] Now available in Python SDK ([#31495](https://github.com/apache/beam/pull/31495)) * [Managed Iceberg] Add support for TIMESTAMP, TIME, and DATE types ([#32688](https://github.com/apache/beam/pull/32688)) From afdc5eaed8c42f6d7136102829bccc00160d28af Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 2 Dec 2024 16:11:49 -0500 Subject: [PATCH 051/135] Revert "Revert "Update vllm colab to work with asyncio requirements"" (#33171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "Revert "Update vllm colab to work with asyncio requirements (#33122)"…" This reverts commit baa15911679e068db0314b4fe712ea5e97867119. * Install triton as well --- .../beam-ml/run_inference_vllm.ipynb | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/examples/notebooks/beam-ml/run_inference_vllm.ipynb b/examples/notebooks/beam-ml/run_inference_vllm.ipynb index c4803eccebff..eaa72a613bfc 100644 --- a/examples/notebooks/beam-ml/run_inference_vllm.ipynb +++ b/examples/notebooks/beam-ml/run_inference_vllm.ipynb @@ -100,7 +100,9 @@ "source": [ "!pip install openai>=1.52.2\n", "!pip install vllm>=0.6.3\n", - "!pip install apache-beam[gcp]==2.60.0\n", + "!pip install triton>=3.1.0\n", + "!pip install apache-beam[gcp]==2.61.0\n", + "!pip install nest_asyncio # only needed in colab\n", "!pip check" ] }, @@ -109,6 +111,30 @@ "metadata": { "id": "3xz8zuA7vcS4" }, + "source": [ + "## Colab only: allow nested asyncio\n", + "\n", + "The vLLM model handler logic below uses asyncio to feed vLLM records. This only works if we are not already in an asyncio event loop. Most of the time, this is fine, but colab already operates in an event loop. To work around this, we can use nest_asyncio to make things work smoothly in colab. Do not include this step outside of colab." + ], + "metadata": { + "id": "3xz8zuA7vcS3" + } + }, + { + "cell_type": "code", + "source": [ + "# This should not be necessary outside of colab.\n", + "import nest_asyncio\n", + "nest_asyncio.apply()\n" + ], + "metadata": { + "id": "sUqjOzw3wpI3" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", "source": [ "## Run locally without Apache Beam\n", "\n", @@ -315,7 +341,7 @@ "COPY --from=apache/beam_python3.10_sdk:2.60.0 /opt/apache/beam /opt/apache/beam\n", "\n", "RUN pip install --no-cache-dir -vvv apache-beam[gcp]==2.60.0\n", - "RUN pip install openai>=1.52.2 vllm>=0.6.3\n", + "RUN pip install openai>=1.52.2 vllm>=0.6.3 triton>=3.1.0\n", "\n", "RUN apt install libcairo2-dev pkg-config python3-dev -y\n", "RUN pip install pycairo\n", From ccffdbab96c1bebc18e35cd80be9b30ce0a51e9b Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 2 Dec 2024 16:18:17 -0500 Subject: [PATCH 052/135] Remove dupe metadata (#33261) * Remove dupe metadata * Fix metadata --- examples/notebooks/beam-ml/run_inference_vllm.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/notebooks/beam-ml/run_inference_vllm.ipynb b/examples/notebooks/beam-ml/run_inference_vllm.ipynb index eaa72a613bfc..40eff1af5155 100644 --- a/examples/notebooks/beam-ml/run_inference_vllm.ipynb +++ b/examples/notebooks/beam-ml/run_inference_vllm.ipynb @@ -108,9 +108,6 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "3xz8zuA7vcS4" - }, "source": [ "## Colab only: allow nested asyncio\n", "\n", @@ -135,6 +132,9 @@ }, { "cell_type": "markdown", + "metadata": { + "id": "sUqjOzw3wpI4" + }, "source": [ "## Run locally without Apache Beam\n", "\n", From abd03feaf6ce51e987282051a1e7513a552ddde7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:22:27 -0500 Subject: [PATCH 053/135] Bump org.javacc.javacc from 3.0.2 to 3.0.3 (#33139) Bumps org.javacc.javacc from 3.0.2 to 3.0.3. --- updated-dependencies: - dependency-name: org.javacc.javacc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index ca30a5ea750a..d90bb3fb5b82 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ import com.gradle.enterprise.gradleplugin.internal.extension.BuildScanExtensionW pluginManagement { plugins { - id("org.javacc.javacc") version "3.0.2" // enable the JavaCC parser generator + id("org.javacc.javacc") version "3.0.3" // enable the JavaCC parser generator } } From 160ffd53ce0f5ac2b91f917abba42dda635841d9 Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Mon, 2 Dec 2024 16:27:35 -0800 Subject: [PATCH 054/135] [CHANGES] Add prism note to 2.61 release blog and CHANGES.md (#33262) * add prism note to 2.61 release blog * Update CHANGES.md * Update beam-2.61.0.md - style * Update CHANGES.md - style --- CHANGES.md | 3 ++- website/www/site/content/en/blog/beam-2.61.0.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4f56b4d49e45..3bb490cc83c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -109,9 +109,10 @@ ## New Features / Improvements * Added support for read with metadata in MqttIO (Java) ([#32195](https://github.com/apache/beam/issues/32195)) -* Added support for processing events which use a global sequence to "ordered" extension (Java) [#32540](https://github.com/apache/beam/pull/32540) +* Added support for processing events which use a global sequence to "ordered" extension (Java) ([#32540](https://github.com/apache/beam/pull/32540)) * Add new meta-transform FlattenWith and Tee that allow one to introduce branching without breaking the linear/chaining style of pipeline construction. +* Use Prism as a fallback to the Python Portable runner when running a pipeline with the Python Direct runner ([#32876](https://github.com/apache/beam/pull/32876)) ## Deprecations diff --git a/website/www/site/content/en/blog/beam-2.61.0.md b/website/www/site/content/en/blog/beam-2.61.0.md index 6f0404af7846..a4c7ac0cefbd 100644 --- a/website/www/site/content/en/blog/beam-2.61.0.md +++ b/website/www/site/content/en/blog/beam-2.61.0.md @@ -45,9 +45,10 @@ For more information on changes in 2.61.0, check out the [detailed release notes ## New Features / Improvements * Added support for read with metadata in MqttIO (Java) ([#32195](https://github.com/apache/beam/issues/32195)) -* Added support for processing events which use a global sequence to "ordered" extension (Java) [#32540](https://github.com/apache/beam/pull/32540) +* Added support for processing events which use a global sequence to "ordered" extension (Java) ([#32540](https://github.com/apache/beam/pull/32540)) * Add new meta-transform FlattenWith and Tee that allow one to introduce branching without breaking the linear/chaining style of pipeline construction. +* Use Prism as a fallback to the Python Portable runner when running a pipeline with the Python Direct runner ([#32876](https://github.com/apache/beam/pull/32876)) ## Deprecations From 63b8dd17d6cba28cbcc415981bab423767ad7c60 Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:57:06 -0500 Subject: [PATCH 055/135] Remove unnecessary branches on Python version in typehint code (#33258) * Remove unneccessary branches on Python version in typehint code * remove unused imports --- .../apache_beam/typehints/decorators.py | 4 - .../apache_beam/typehints/decorators_test.py | 10 +- .../typehints/native_type_compatibility.py | 15 +- .../native_type_compatibility_test.py | 195 +++++++++--------- sdks/python/apache_beam/typehints/opcodes.py | 12 +- .../typehints/typed_pipeline_test.py | 8 +- .../python/apache_beam/typehints/typehints.py | 8 +- .../apache_beam/typehints/typehints_test.py | 140 +++++-------- 8 files changed, 149 insertions(+), 243 deletions(-) diff --git a/sdks/python/apache_beam/typehints/decorators.py b/sdks/python/apache_beam/typehints/decorators.py index 9c0cc2b8af4e..7050df7016e5 100644 --- a/sdks/python/apache_beam/typehints/decorators.py +++ b/sdks/python/apache_beam/typehints/decorators.py @@ -82,7 +82,6 @@ def foo((a, b)): import inspect import itertools import logging -import sys import traceback import types from typing import Any @@ -686,9 +685,6 @@ def get_type_hints(fn: Any) -> IOTypeHints: # Can't add arbitrary attributes to this object, # but might have some restrictions anyways... hints = IOTypeHints.empty() - # Python 3.7 introduces annotations for _MethodDescriptorTypes. - if isinstance(fn, _MethodDescriptorType) and sys.version_info < (3, 7): - hints = hints.with_input_types(fn.__objclass__) # type: ignore return hints return fn._type_hints # pylint: enable=protected-access diff --git a/sdks/python/apache_beam/typehints/decorators_test.py b/sdks/python/apache_beam/typehints/decorators_test.py index 71edc75f31a6..dd110ced5bb8 100644 --- a/sdks/python/apache_beam/typehints/decorators_test.py +++ b/sdks/python/apache_beam/typehints/decorators_test.py @@ -20,7 +20,6 @@ # pytype: skip-file import functools -import sys import typing import unittest @@ -70,14 +69,7 @@ def test_from_callable_builtin(self): def test_from_callable_method_descriptor(self): # from_callable() injects an annotation in this special type of builtin. th = decorators.IOTypeHints.from_callable(str.strip) - if sys.version_info >= (3, 7): - self.assertEqual(th.input_types, ((str, Any), {})) - else: - self.assertEqual( - th.input_types, - ((str, decorators._ANY_VAR_POSITIONAL), { - '__unknown__keywords': decorators._ANY_VAR_KEYWORD - })) + self.assertEqual(th.input_types, ((str, Any), {})) self.assertEqual(th.output_types, ((Any, ), {})) def test_strip_iterable_not_simple_output_noop(self): diff --git a/sdks/python/apache_beam/typehints/native_type_compatibility.py b/sdks/python/apache_beam/typehints/native_type_compatibility.py index 621adc44507e..6f704b37a969 100644 --- a/sdks/python/apache_beam/typehints/native_type_compatibility.py +++ b/sdks/python/apache_beam/typehints/native_type_compatibility.py @@ -101,10 +101,7 @@ def _match_issubclass(match_against): def _match_is_exactly_mapping(user_type): # Avoid unintentionally catching all subtypes (e.g. strings and mappings). - if sys.version_info < (3, 7): - expected_origin = typing.Mapping - else: - expected_origin = collections.abc.Mapping + expected_origin = collections.abc.Mapping return getattr(user_type, '__origin__', None) is expected_origin @@ -112,10 +109,7 @@ def _match_is_exactly_iterable(user_type): if user_type is typing.Iterable: return True # Avoid unintentionally catching all subtypes (e.g. strings and mappings). - if sys.version_info < (3, 7): - expected_origin = typing.Iterable - else: - expected_origin = collections.abc.Iterable + expected_origin = collections.abc.Iterable return getattr(user_type, '__origin__', None) is expected_origin @@ -244,11 +238,10 @@ def convert_to_beam_type(typ): sys.version_info.minor >= 10) and (isinstance(typ, types.UnionType)): typ = typing.Union[typ] - if sys.version_info >= (3, 9) and isinstance(typ, types.GenericAlias): + if isinstance(typ, types.GenericAlias): typ = convert_builtin_to_typing(typ) - if sys.version_info >= (3, 9) and getattr(typ, '__module__', - None) == 'collections.abc': + if getattr(typ, '__module__', None) == 'collections.abc': typ = convert_collections_to_typing(typ) typ_module = getattr(typ, '__module__', None) diff --git a/sdks/python/apache_beam/typehints/native_type_compatibility_test.py b/sdks/python/apache_beam/typehints/native_type_compatibility_test.py index 2e6db6a7733c..ae8e1a0b2906 100644 --- a/sdks/python/apache_beam/typehints/native_type_compatibility_test.py +++ b/sdks/python/apache_beam/typehints/native_type_compatibility_test.py @@ -21,7 +21,6 @@ import collections.abc import enum -import sys import typing import unittest @@ -128,105 +127,98 @@ def test_convert_to_beam_type(self): self.assertEqual(converted_typing_type, typing_type, description) def test_convert_to_beam_type_with_builtin_types(self): - if sys.version_info >= (3, 9): - test_cases = [ - ('builtin dict', dict[str, int], typehints.Dict[str, int]), - ('builtin list', list[str], typehints.List[str]), - ('builtin tuple', tuple[str], - typehints.Tuple[str]), ('builtin set', set[str], typehints.Set[str]), - ('builtin frozenset', frozenset[int], typehints.FrozenSet[int]), - ( - 'nested builtin', - dict[str, list[tuple[float]]], - typehints.Dict[str, typehints.List[typehints.Tuple[float]]]), - ( - 'builtin nested tuple', - tuple[str, list], - typehints.Tuple[str, typehints.List[typehints.Any]], - ) - ] - - for test_case in test_cases: - description = test_case[0] - builtins_type = test_case[1] - expected_beam_type = test_case[2] - converted_beam_type = convert_to_beam_type(builtins_type) - self.assertEqual(converted_beam_type, expected_beam_type, description) + test_cases = [ + ('builtin dict', dict[str, int], typehints.Dict[str, int]), + ('builtin list', list[str], typehints.List[str]), + ('builtin tuple', tuple[str], + typehints.Tuple[str]), ('builtin set', set[str], typehints.Set[str]), + ('builtin frozenset', frozenset[int], typehints.FrozenSet[int]), + ( + 'nested builtin', + dict[str, list[tuple[float]]], + typehints.Dict[str, typehints.List[typehints.Tuple[float]]]), + ( + 'builtin nested tuple', + tuple[str, list], + typehints.Tuple[str, typehints.List[typehints.Any]], + ) + ] + + for test_case in test_cases: + description = test_case[0] + builtins_type = test_case[1] + expected_beam_type = test_case[2] + converted_beam_type = convert_to_beam_type(builtins_type) + self.assertEqual(converted_beam_type, expected_beam_type, description) def test_convert_to_beam_type_with_collections_types(self): - if sys.version_info >= (3, 9): - test_cases = [ - ( - 'collection iterable', - collections.abc.Iterable[int], - typehints.Iterable[int]), - ( - 'collection generator', - collections.abc.Generator[int], - typehints.Generator[int]), - ( - 'collection iterator', - collections.abc.Iterator[int], - typehints.Iterator[int]), - ( - 'nested iterable', - tuple[bytes, collections.abc.Iterable[int]], - typehints.Tuple[bytes, typehints.Iterable[int]]), - ( - 'iterable over tuple', - collections.abc.Iterable[tuple[str, int]], - typehints.Iterable[typehints.Tuple[str, int]]), - ( - 'mapping not caught', - collections.abc.Mapping[str, int], - collections.abc.Mapping[str, int]), - ('set', collections.abc.Set[str], typehints.Set[str]), - ('mutable set', collections.abc.MutableSet[int], typehints.Set[int]), - ( - 'enum set', - collections.abc.Set[_TestEnum], - typehints.Set[_TestEnum]), - ( - 'enum mutable set', - collections.abc.MutableSet[_TestEnum], - typehints.Set[_TestEnum]), - ( - 'collection enum', - collections.abc.Collection[_TestEnum], - typehints.Collection[_TestEnum]), - ( - 'collection of tuples', - collections.abc.Collection[tuple[str, int]], - typehints.Collection[typehints.Tuple[str, int]]), - ] - - for test_case in test_cases: - description = test_case[0] - builtins_type = test_case[1] - expected_beam_type = test_case[2] - converted_beam_type = convert_to_beam_type(builtins_type) - self.assertEqual(converted_beam_type, expected_beam_type, description) + test_cases = [ + ( + 'collection iterable', + collections.abc.Iterable[int], + typehints.Iterable[int]), + ( + 'collection generator', + collections.abc.Generator[int], + typehints.Generator[int]), + ( + 'collection iterator', + collections.abc.Iterator[int], + typehints.Iterator[int]), + ( + 'nested iterable', + tuple[bytes, collections.abc.Iterable[int]], + typehints.Tuple[bytes, typehints.Iterable[int]]), + ( + 'iterable over tuple', + collections.abc.Iterable[tuple[str, int]], + typehints.Iterable[typehints.Tuple[str, int]]), + ( + 'mapping not caught', + collections.abc.Mapping[str, int], + collections.abc.Mapping[str, int]), + ('set', collections.abc.Set[str], typehints.Set[str]), + ('mutable set', collections.abc.MutableSet[int], typehints.Set[int]), + ('enum set', collections.abc.Set[_TestEnum], typehints.Set[_TestEnum]), + ( + 'enum mutable set', + collections.abc.MutableSet[_TestEnum], + typehints.Set[_TestEnum]), + ( + 'collection enum', + collections.abc.Collection[_TestEnum], + typehints.Collection[_TestEnum]), + ( + 'collection of tuples', + collections.abc.Collection[tuple[str, int]], + typehints.Collection[typehints.Tuple[str, int]]), + ] + + for test_case in test_cases: + description = test_case[0] + builtins_type = test_case[1] + expected_beam_type = test_case[2] + converted_beam_type = convert_to_beam_type(builtins_type) + self.assertEqual(converted_beam_type, expected_beam_type, description) def test_convert_builtin_to_typing(self): - if sys.version_info >= (3, 9): - test_cases = [ - ('dict', dict[str, int], typing.Dict[str, int]), - ('list', list[str], typing.List[str]), - ('tuple', tuple[str], typing.Tuple[str]), - ('set', set[str], typing.Set[str]), - ( - 'nested', - dict[str, list[tuple[float]]], - typing.Dict[str, typing.List[typing.Tuple[float]]]), - ] - - for test_case in test_cases: - description = test_case[0] - builtin_type = test_case[1] - expected_typing_type = test_case[2] - converted_typing_type = convert_builtin_to_typing(builtin_type) - self.assertEqual( - converted_typing_type, expected_typing_type, description) + test_cases = [ + ('dict', dict[str, int], typing.Dict[str, int]), + ('list', list[str], typing.List[str]), + ('tuple', tuple[str], typing.Tuple[str]), + ('set', set[str], typing.Set[str]), + ( + 'nested', + dict[str, list[tuple[float]]], + typing.Dict[str, typing.List[typing.Tuple[float]]]), + ] + + for test_case in test_cases: + description = test_case[0] + builtin_type = test_case[1] + expected_typing_type = test_case[2] + converted_typing_type = convert_builtin_to_typing(builtin_type) + self.assertEqual(converted_typing_type, expected_typing_type, description) def test_generator_converted_to_iterator(self): self.assertEqual( @@ -293,14 +285,11 @@ def test_convert_bare_types(self): typing.Tuple[typing.Iterator], typehints.Tuple[typehints.Iterator[typehints.TypeVariable('T_co')]] ), + ( + 'bare generator', + typing.Generator, + typehints.Generator[typehints.TypeVariable('T_co')]), ] - if sys.version_info >= (3, 7): - test_cases += [ - ( - 'bare generator', - typing.Generator, - typehints.Generator[typehints.TypeVariable('T_co')]), - ] for test_case in test_cases: description = test_case[0] typing_type = test_case[1] diff --git a/sdks/python/apache_beam/typehints/opcodes.py b/sdks/python/apache_beam/typehints/opcodes.py index 62c7a8fadc35..7bea621841f6 100644 --- a/sdks/python/apache_beam/typehints/opcodes.py +++ b/sdks/python/apache_beam/typehints/opcodes.py @@ -246,14 +246,10 @@ def set_add(state, arg): def map_add(state, arg): - if sys.version_info >= (3, 8): - # PEP 572 The MAP_ADD expects the value as the first element in the stack - # and the key as the second element. - new_value_type = Const.unwrap(state.stack.pop()) - new_key_type = Const.unwrap(state.stack.pop()) - else: - new_key_type = Const.unwrap(state.stack.pop()) - new_value_type = Const.unwrap(state.stack.pop()) + # PEP 572 The MAP_ADD expects the value as the first element in the stack + # and the key as the second element. + new_value_type = Const.unwrap(state.stack.pop()) + new_key_type = Const.unwrap(state.stack.pop()) state.stack[-arg] = Dict[Union[state.stack[-arg].key_type, new_key_type], Union[state.stack[-arg].value_type, new_value_type]] diff --git a/sdks/python/apache_beam/typehints/typed_pipeline_test.py b/sdks/python/apache_beam/typehints/typed_pipeline_test.py index 57e7f44f6922..44318fa44a8c 100644 --- a/sdks/python/apache_beam/typehints/typed_pipeline_test.py +++ b/sdks/python/apache_beam/typehints/typed_pipeline_test.py @@ -19,7 +19,6 @@ # pytype: skip-file -import sys import typing import unittest @@ -874,12 +873,7 @@ def test_flat_type_hint(self): class AnnotationsTest(unittest.TestCase): def test_pardo_wrapper_builtin_method(self): th = beam.ParDo(str.strip).get_type_hints() - if sys.version_info < (3, 7): - self.assertEqual(th.input_types, ((str, ), {})) - else: - # Python 3.7+ has annotations for CPython builtins - # (_MethodDescriptorType). - self.assertEqual(th.input_types, ((str, typehints.Any), {})) + self.assertEqual(th.input_types, ((str, typehints.Any), {})) self.assertEqual(th.output_types, ((typehints.Any, ), {})) def test_pardo_wrapper_builtin_type(self): diff --git a/sdks/python/apache_beam/typehints/typehints.py b/sdks/python/apache_beam/typehints/typehints.py index 912cb78dc095..0e18e887c2a0 100644 --- a/sdks/python/apache_beam/typehints/typehints.py +++ b/sdks/python/apache_beam/typehints/typehints.py @@ -391,12 +391,6 @@ def validate_composite_type_param(type_param, error_msg_prefix): if sys.version_info.major == 3 and sys.version_info.minor >= 10: if isinstance(type_param, types.UnionType): is_not_type_constraint = False - # Pre-Python 3.9 compositve type-hinting with built-in types was not - # supported, the typing module equivalents should be used instead. - if sys.version_info.major == 3 and sys.version_info.minor < 9: - is_not_type_constraint = is_not_type_constraint or ( - isinstance(type_param, type) and - type_param in DISALLOWED_PRIMITIVE_TYPES) if is_not_type_constraint: raise TypeError( @@ -1266,7 +1260,7 @@ def normalize(x, none_as_type=False): # Avoid circular imports from apache_beam.typehints import native_type_compatibility - if sys.version_info >= (3, 9) and isinstance(x, types.GenericAlias): + if isinstance(x, types.GenericAlias): x = native_type_compatibility.convert_builtin_to_typing(x) if none_as_type and x is None: diff --git a/sdks/python/apache_beam/typehints/typehints_test.py b/sdks/python/apache_beam/typehints/typehints_test.py index 843c1498cac5..6611dcecab01 100644 --- a/sdks/python/apache_beam/typehints/typehints_test.py +++ b/sdks/python/apache_beam/typehints/typehints_test.py @@ -388,15 +388,10 @@ def test_getitem_params_must_be_type_or_constraint(self): typehints.Tuple[5, [1, 3]] self.assertTrue(e.exception.args[0].startswith(expected_error_prefix)) - if sys.version_info < (3, 9): - with self.assertRaises(TypeError) as e: - typehints.Tuple[list, dict] - self.assertTrue(e.exception.args[0].startswith(expected_error_prefix)) - else: - try: - typehints.Tuple[list, dict] - except TypeError: - self.fail("built-in composite raised TypeError unexpectedly") + try: + typehints.Tuple[list, dict] + except TypeError: + self.fail("built-in composite raised TypeError unexpectedly") def test_compatibility_arbitrary_length(self): self.assertNotCompatible( @@ -548,15 +543,13 @@ def test_type_check_invalid_composite_type_arbitrary_length(self): e.exception.args[0]) def test_normalize_with_builtin_tuple(self): - if sys.version_info >= (3, 9): - expected_beam_type = typehints.Tuple[int, int] - converted_beam_type = typehints.normalize(tuple[int, int], False) - self.assertEqual(converted_beam_type, expected_beam_type) + expected_beam_type = typehints.Tuple[int, int] + converted_beam_type = typehints.normalize(tuple[int, int], False) + self.assertEqual(converted_beam_type, expected_beam_type) def test_builtin_and_type_compatibility(self): - if sys.version_info >= (3, 9): - self.assertCompatible(tuple, typing.Tuple) - self.assertCompatible(tuple[int, int], typing.Tuple[int, int]) + self.assertCompatible(tuple, typing.Tuple) + self.assertCompatible(tuple[int, int], typing.Tuple[int, int]) class ListHintTestCase(TypeHintTestCase): @@ -618,22 +611,19 @@ def test_enforce_list_type_constraint_invalid_composite_type(self): e.exception.args[0]) def test_normalize_with_builtin_list(self): - if sys.version_info >= (3, 9): - expected_beam_type = typehints.List[int] - converted_beam_type = typehints.normalize(list[int], False) - self.assertEqual(converted_beam_type, expected_beam_type) + expected_beam_type = typehints.List[int] + converted_beam_type = typehints.normalize(list[int], False) + self.assertEqual(converted_beam_type, expected_beam_type) def test_builtin_and_type_compatibility(self): - if sys.version_info >= (3, 9): - self.assertCompatible(list, typing.List) - self.assertCompatible(list[int], typing.List[int]) + self.assertCompatible(list, typing.List) + self.assertCompatible(list[int], typing.List[int]) def test_is_typing_generic(self): self.assertTrue(typehints.is_typing_generic(typing.List[str])) def test_builtin_is_typing_generic(self): - if sys.version_info >= (3, 9): - self.assertTrue(typehints.is_typing_generic(list[str])) + self.assertTrue(typehints.is_typing_generic(list[str])) class KVHintTestCase(TypeHintTestCase): @@ -687,14 +677,10 @@ def test_getitem_param_must_have_length_2(self): e.exception.args[0]) def test_key_type_must_be_valid_composite_param(self): - if sys.version_info < (3, 9): - with self.assertRaises(TypeError): - typehints.Dict[list, int] - else: - try: - typehints.Tuple[list, int] - except TypeError: - self.fail("built-in composite raised TypeError unexpectedly") + try: + typehints.Tuple[list, int] + except TypeError: + self.fail("built-in composite raised TypeError unexpectedly") def test_value_type_must_be_valid_composite_param(self): with self.assertRaises(TypeError): @@ -777,35 +763,24 @@ def test_match_type_variables(self): hint.match_type_variables(typehints.Dict[int, str])) def test_normalize_with_builtin_dict(self): - if sys.version_info >= (3, 9): - expected_beam_type = typehints.Dict[str, int] - converted_beam_type = typehints.normalize(dict[str, int], False) - self.assertEqual(converted_beam_type, expected_beam_type) + expected_beam_type = typehints.Dict[str, int] + converted_beam_type = typehints.normalize(dict[str, int], False) + self.assertEqual(converted_beam_type, expected_beam_type) def test_builtin_and_type_compatibility(self): - if sys.version_info >= (3, 9): - self.assertCompatible(dict, typing.Dict) - self.assertCompatible(dict[str, int], typing.Dict[str, int]) - self.assertCompatible( - dict[str, list[int]], typing.Dict[str, typing.List[int]]) + self.assertCompatible(dict, typing.Dict) + self.assertCompatible(dict[str, int], typing.Dict[str, int]) + self.assertCompatible( + dict[str, list[int]], typing.Dict[str, typing.List[int]]) class BaseSetHintTest: class CommonTests(TypeHintTestCase): def test_getitem_invalid_composite_type_param(self): - if sys.version_info < (3, 9): - with self.assertRaises(TypeError) as e: - self.beam_type[list] - self.assertEqual( - "Parameter to a {} hint must be a non-sequence, a " - "type, or a TypeConstraint. {} is an instance of " - "type.".format(self.string_type, list), - e.exception.args[0]) - else: - try: - self.beam_type[list] - except TypeError: - self.fail("built-in composite raised TypeError unexpectedly") + try: + self.beam_type[list] + except TypeError: + self.fail("built-in composite raised TypeError unexpectedly") def test_non_typing_generic(self): testCase = DummyTestClass1() @@ -855,16 +830,14 @@ class SetHintTestCase(BaseSetHintTest.CommonTests): string_type = 'Set' def test_builtin_compatibility(self): - if sys.version_info >= (3, 9): - self.assertCompatible(set[int], collections.abc.Set[int]) - self.assertCompatible(set[int], collections.abc.MutableSet[int]) + self.assertCompatible(set[int], collections.abc.Set[int]) + self.assertCompatible(set[int], collections.abc.MutableSet[int]) def test_collections_compatibility(self): - if sys.version_info >= (3, 9): - self.assertCompatible( - collections.abc.Set[int], collections.abc.MutableSet[int]) - self.assertCompatible( - collections.abc.MutableSet[int], collections.abc.Set[int]) + self.assertCompatible( + collections.abc.Set[int], collections.abc.MutableSet[int]) + self.assertCompatible( + collections.abc.MutableSet[int], collections.abc.Set[int]) class FrozenSetHintTestCase(BaseSetHintTest.CommonTests): @@ -1416,37 +1389,16 @@ def func(a, b_c, *d): func, *[Any, Any, Tuple[str, ...], int])) def test_getcallargs_forhints_builtins(self): - if sys.version_info < (3, 7): - # Signatures for builtins are not supported in 3.5 and 3.6. - self.assertEqual({ - '_': str, - '__unknown__varargs': Tuple[Any, ...], - '__unknown__keywords': typehints.Dict[Any, Any] - }, - getcallargs_forhints(str.upper, str)) - self.assertEqual({ - '_': str, - '__unknown__varargs': Tuple[str, ...], - '__unknown__keywords': typehints.Dict[Any, Any] - }, - getcallargs_forhints(str.strip, str, str)) - self.assertEqual({ - '_': str, - '__unknown__varargs': Tuple[typehints.List[int], ...], - '__unknown__keywords': typehints.Dict[Any, Any] - }, - getcallargs_forhints(str.join, str, typehints.List[int])) - else: - self.assertEqual({'self': str}, getcallargs_forhints(str.upper, str)) - # str.strip has an optional second argument. - self.assertEqual({ - 'self': str, 'chars': Any - }, - getcallargs_forhints(str.strip, str)) - self.assertEqual({ - 'self': str, 'iterable': typehints.List[int] - }, - getcallargs_forhints(str.join, str, typehints.List[int])) + self.assertEqual({'self': str}, getcallargs_forhints(str.upper, str)) + # str.strip has an optional second argument. + self.assertEqual({ + 'self': str, 'chars': Any + }, + getcallargs_forhints(str.strip, str)) + self.assertEqual({ + 'self': str, 'iterable': typehints.List[int] + }, + getcallargs_forhints(str.join, str, typehints.List[int])) class TestGetYieldedType(unittest.TestCase): From a938fa54a910446093be990e4e5cb488118ea9ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:04:55 -0500 Subject: [PATCH 056/135] Bump github.com/aws/aws-sdk-go-v2 from 1.32.4 to 1.32.6 in /sdks (#33264) Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.32.4 to 1.32.6. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.32.4...v1.32.6) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 4 ++-- sdks/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index fc4c60006fe1..587decf4197f 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -30,12 +30,12 @@ require ( cloud.google.com/go/pubsub v1.45.1 cloud.google.com/go/spanner v1.73.0 cloud.google.com/go/storage v1.45.0 - github.com/aws/aws-sdk-go-v2 v1.32.4 + github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.28.4 github.com/aws/aws-sdk-go-v2/credentials v1.17.45 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38 github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0 - github.com/aws/smithy-go v1.22.0 + github.com/aws/smithy-go v1.22.1 github.com/docker/go-connections v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/go-sql-driver/mysql v1.8.1 diff --git a/sdks/go.sum b/sdks/go.sum index bbc0d0d22204..71dfce6e8636 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -689,8 +689,8 @@ github.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo= github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= -github.com/aws/aws-sdk-go-v2 v1.32.4 h1:S13INUiTxgrPueTmrm5DZ+MiAo99zYzHEFh1UNkOxNE= -github.com/aws/aws-sdk-go-v2 v1.32.4/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= +github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA= @@ -737,8 +737,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BV github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 h1:s7LRgBqhwLaxcocnAniBJp7gaAB+4I4vHzqUqjH18yc= github.com/aws/aws-sdk-go-v2/service/sts v1.33.0/go.mod h1:9XEUty5v5UAsMiFOBJrNibZgwCeOma73jgGwwhgffa8= github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= -github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= From 532880aac1bc03a1d1b869c9aaa5de443bad5596 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:05:24 -0500 Subject: [PATCH 057/135] Bump golang.org/x/oauth2 from 0.23.0 to 0.24.0 in /sdks (#33141) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.23.0 to 0.24.0. - [Commits](https://github.com/golang/oauth2/compare/v0.23.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 2 +- sdks/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 587decf4197f..ef966f0dcef4 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -54,7 +54,7 @@ require ( github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c go.mongodb.org/mongo-driver v1.17.1 golang.org/x/net v0.31.0 - golang.org/x/oauth2 v0.23.0 + golang.org/x/oauth2 v0.24.0 golang.org/x/sync v0.9.0 golang.org/x/sys v0.27.0 golang.org/x/text v0.20.0 diff --git a/sdks/go.sum b/sdks/go.sum index 71dfce6e8636..5527a59f4c52 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1417,8 +1417,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 63d89cd047334cfbedceb20eec7d9f41e0e1d8c5 Mon Sep 17 00:00:00 2001 From: Claire McGinty Date: Tue, 3 Dec 2024 11:06:55 -0500 Subject: [PATCH 058/135] Propagate gcs-connector options to GcsUtil (#32769) * Propagate gcs-connector options to GcsUtil * newline * Update CHANGES.md * Remove Hadoop dependency * Remove unused deps * Drop googleCloudStorageReadOptions member variable * add missing package * fixup CHANGES.md --- CHANGES.md | 1 + .../extensions/gcp/options/GcsOptions.java | 10 ++++++ .../beam/sdk/extensions/gcp/util/GcsUtil.java | 25 ++++++++++--- .../extensions/gcp/GcpCoreApiSurfaceTest.java | 2 ++ .../sdk/extensions/gcp/util/GcsUtilTest.java | 35 +++++++++++++++++-- 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3bb490cc83c4..fc32398a7a5a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -62,6 +62,7 @@ ## I/Os +* gcs-connector config options can be set via GcsOptions (Java) ([#32769](https://github.com/apache/beam/pull/32769)). * Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). ## New Features / Improvements diff --git a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java index 18d637254115..1285b88663e7 100644 --- a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java +++ b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/options/GcsOptions.java @@ -18,6 +18,7 @@ package org.apache.beam.sdk.extensions.gcp.options; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.cloud.hadoop.gcsio.GoogleCloudStorageReadOptions; import com.google.cloud.hadoop.util.AsyncWriteChannelOptions; import java.util.concurrent.ExecutorService; import org.apache.beam.sdk.extensions.gcp.storage.GcsPathValidator; @@ -44,6 +45,15 @@ public interface GcsOptions extends ApplicationNameOptions, GcpOptions, Pipeline void setGcsUtil(GcsUtil value); + @JsonIgnore + @Description( + "The GoogleCloudStorageReadOptions instance that should be used to read from Google Cloud Storage.") + @Default.InstanceFactory(GcsUtil.GcsReadOptionsFactory.class) + @Hidden + GoogleCloudStorageReadOptions getGoogleCloudStorageReadOptions(); + + void setGoogleCloudStorageReadOptions(GoogleCloudStorageReadOptions value); + /** * The ExecutorService instance to use to create threads, can be overridden to specify an * ExecutorService that is compatible with the user's environment. If unset, the default is to use diff --git a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java index 8d3596f17b3b..d58154132a72 100644 --- a/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java +++ b/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtil.java @@ -123,6 +123,14 @@ public static GcsCountersOptions create( } } + public static class GcsReadOptionsFactory + implements DefaultValueFactory { + @Override + public GoogleCloudStorageReadOptions create(PipelineOptions options) { + return GoogleCloudStorageReadOptions.DEFAULT; + } + } + /** * This is a {@link DefaultValueFactory} able to create a {@link GcsUtil} using any transport * flags specified on the {@link PipelineOptions}. @@ -153,7 +161,8 @@ public GcsUtil create(PipelineOptions options) { : null, gcsOptions.getEnableBucketWriteMetricCounter() ? gcsOptions.getGcsWriteCounterPrefix() - : null)); + : null), + gcsOptions.getGoogleCloudStorageReadOptions()); } /** Returns an instance of {@link GcsUtil} based on the given parameters. */ @@ -164,7 +173,8 @@ public static GcsUtil create( ExecutorService executorService, Credentials credentials, @Nullable Integer uploadBufferSizeBytes, - GcsCountersOptions gcsCountersOptions) { + GcsCountersOptions gcsCountersOptions, + GoogleCloudStorageReadOptions gcsReadOptions) { return new GcsUtil( storageClient, httpRequestInitializer, @@ -173,7 +183,8 @@ public static GcsUtil create( credentials, uploadBufferSizeBytes, null, - gcsCountersOptions); + gcsCountersOptions, + gcsReadOptions); } } @@ -249,7 +260,8 @@ public static boolean isWildcard(GcsPath spec) { Credentials credentials, @Nullable Integer uploadBufferSizeBytes, @Nullable Integer rewriteDataOpBatchLimit, - GcsCountersOptions gcsCountersOptions) { + GcsCountersOptions gcsCountersOptions, + GoogleCloudStorageReadOptions gcsReadOptions) { this.storageClient = storageClient; this.httpRequestInitializer = httpRequestInitializer; this.uploadBufferSizeBytes = uploadBufferSizeBytes; @@ -260,6 +272,7 @@ public static boolean isWildcard(GcsPath spec) { googleCloudStorageOptions = GoogleCloudStorageOptions.builder() .setAppName("Beam") + .setReadChannelOptions(gcsReadOptions) .setGrpcEnabled(shouldUseGrpc) .build(); googleCloudStorage = @@ -565,7 +578,9 @@ private SeekableByteChannel wrapInCounting( public SeekableByteChannel open(GcsPath path) throws IOException { String bucket = path.getBucket(); SeekableByteChannel channel = - googleCloudStorage.open(new StorageResourceId(path.getBucket(), path.getObject())); + googleCloudStorage.open( + new StorageResourceId(path.getBucket(), path.getObject()), + this.googleCloudStorageOptions.getReadChannelOptions()); return wrapInCounting(channel, bucket); } diff --git a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/GcpCoreApiSurfaceTest.java b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/GcpCoreApiSurfaceTest.java index f5075a3f2c55..26d98125a3af 100644 --- a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/GcpCoreApiSurfaceTest.java +++ b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/GcpCoreApiSurfaceTest.java @@ -55,6 +55,8 @@ public void testGcpCoreApiSurface() throws Exception { classesInPackage("com.google.api.services.storage"), classesInPackage("com.google.auth"), classesInPackage("com.fasterxml.jackson.annotation"), + classesInPackage("com.google.cloud.hadoop.gcsio"), + classesInPackage("com.google.common.collect"), // Via gcs-connector ReadOptions builder classesInPackage("java"), classesInPackage("javax"), classesInPackage("org.apache.beam.sdk"), diff --git a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java index bd7f46ec8951..97082572ce41 100644 --- a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java +++ b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcsUtilTest.java @@ -177,6 +177,32 @@ public void testCreationWithGcsUtilProvided() { assertSame(gcsUtil, pipelineOptions.getGcsUtil()); } + @Test + public void testCreationWithExplicitGoogleCloudStorageReadOptions() throws Exception { + GoogleCloudStorageReadOptions readOptions = + GoogleCloudStorageReadOptions.builder() + .setFadvise(GoogleCloudStorageReadOptions.Fadvise.AUTO) + .setSupportGzipEncoding(true) + .setFastFailOnNotFound(false) + .build(); + + GcsOptions pipelineOptions = PipelineOptionsFactory.as(GcsOptions.class); + pipelineOptions.setGoogleCloudStorageReadOptions(readOptions); + + GcsUtil gcsUtil = pipelineOptions.getGcsUtil(); + GoogleCloudStorage googleCloudStorageMock = Mockito.spy(GoogleCloudStorage.class); + Mockito.when(googleCloudStorageMock.open(Mockito.any(), Mockito.any())) + .thenReturn(Mockito.mock(SeekableByteChannel.class)); + gcsUtil.setCloudStorageImpl(googleCloudStorageMock); + + assertEquals(readOptions, pipelineOptions.getGoogleCloudStorageReadOptions()); + + // Assert read options are passed to GCS calls + pipelineOptions.getGcsUtil().open(GcsPath.fromUri("gs://bucket/path")); + Mockito.verify(googleCloudStorageMock, Mockito.times(1)) + .open(StorageResourceId.fromStringPath("gs://bucket/path"), readOptions); + } + @Test public void testMultipleThreadsCanCompleteOutOfOrderWithDefaultThreadPool() throws Exception { GcsOptions pipelineOptions = PipelineOptionsFactory.as(GcsOptions.class); @@ -1630,7 +1656,8 @@ public static GcsUtilMock createMock(PipelineOptions options) { : null, gcsOptions.getEnableBucketWriteMetricCounter() ? gcsOptions.getGcsWriteCounterPrefix() - : null)); + : null), + gcsOptions.getGoogleCloudStorageReadOptions()); } private GcsUtilMock( @@ -1641,7 +1668,8 @@ private GcsUtilMock( Credentials credentials, @Nullable Integer uploadBufferSizeBytes, @Nullable Integer rewriteDataOpBatchLimit, - GcsCountersOptions gcsCountersOptions) { + GcsCountersOptions gcsCountersOptions, + GoogleCloudStorageReadOptions gcsReadOptions) { super( storageClient, httpRequestInitializer, @@ -1650,7 +1678,8 @@ private GcsUtilMock( credentials, uploadBufferSizeBytes, rewriteDataOpBatchLimit, - gcsCountersOptions); + gcsCountersOptions, + gcsReadOptions); } @Override From 2fe725d92a0311677b3e6f5ffcd3838064ca2932 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Tue, 3 Dec 2024 11:44:41 -0500 Subject: [PATCH 059/135] Update republish workflow with latest beam (#33194) --- .github/workflows/republish_released_docker_containers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/republish_released_docker_containers.yml b/.github/workflows/republish_released_docker_containers.yml index d359ba2c4489..ed6e74ecf13d 100644 --- a/.github/workflows/republish_released_docker_containers.yml +++ b/.github/workflows/republish_released_docker_containers.yml @@ -34,8 +34,8 @@ on: - cron: "0 6 * * 1" env: docker_registry: gcr.io - release: ${{ github.event.inputs.RELEASE || "2.60.0" }} - rc: ${{ github.event.inputs.RC || "2" }} + release: ${{ github.event.inputs.RELEASE || "2.61.0" }} + rc: ${{ github.event.inputs.RC || "3" }} jobs: From 95a08451c58cf95a1c844e17e08832532a796bd7 Mon Sep 17 00:00:00 2001 From: Ravi Magham Date: Tue, 3 Dec 2024 14:59:05 -0800 Subject: [PATCH 060/135] jdbcio write batch config changes (#33205) * jdbcio write batch config changes * fixup: lint fixes * fixup: format fixes * fixup: support batch size for yaml based pipeline * fixup: review comments --------- Co-authored-by: Ravi Magham Co-authored-by: PoojaS2010 --- .../beam/sdk/io/jdbc/JdbcSchemaIOProvider.java | 5 +++++ .../jdbc/JdbcWriteSchemaTransformProvider.java | 11 +++++++++++ .../org/apache/beam/sdk/io/jdbc/JdbcIOTest.java | 16 ++++++++++++++++ .../sdk/io/jdbc/JdbcSchemaIOProviderTest.java | 2 ++ .../JdbcWriteSchemaTransformProviderTest.java | 1 + sdks/python/apache_beam/io/jdbc.py | 9 ++++++++- sdks/python/apache_beam/yaml/standard_io.yaml | 1 + 7 files changed, 44 insertions(+), 1 deletion(-) diff --git a/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java b/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java index 30012465eb9e..11034aee1cdf 100644 --- a/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java +++ b/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java @@ -68,6 +68,7 @@ public Schema configurationSchema() { .addNullableField("disableAutoCommit", FieldType.BOOLEAN) .addNullableField("outputParallelization", FieldType.BOOLEAN) .addNullableField("autosharding", FieldType.BOOLEAN) + .addNullableField("writeBatchSize", FieldType.INT64) // Partitioning support. If you specify a partition column we will use that instead of // readQuery .addNullableField("partitionColumn", FieldType.STRING) @@ -194,6 +195,10 @@ public PDone expand(PCollection input) { if (autosharding != null && autosharding) { writeRows = writeRows.withAutoSharding(); } + @Nullable Long writeBatchSize = config.getInt64("writeBatchSize"); + if (writeBatchSize != null) { + writeRows = writeRows.withBatchSize(writeBatchSize); + } return input.apply(writeRows); } }; diff --git a/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProvider.java b/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProvider.java index a409b604b11f..1f970ba0624f 100644 --- a/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProvider.java +++ b/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProvider.java @@ -141,6 +141,12 @@ public PCollectionRowTuple expand(PCollectionRowTuple input) { if (autosharding != null && autosharding) { writeRows = writeRows.withAutoSharding(); } + + Long writeBatchSize = config.getBatchSize(); + if (writeBatchSize != null) { + writeRows = writeRows.withBatchSize(writeBatchSize); + } + PCollection postWrite = input .get("input") @@ -205,6 +211,9 @@ public abstract static class JdbcWriteSchemaTransformConfiguration implements Se @Nullable public abstract String getDriverJars(); + @Nullable + public abstract Long getBatchSize(); + public void validate() throws IllegalArgumentException { if (Strings.isNullOrEmpty(getJdbcUrl())) { throw new IllegalArgumentException("JDBC URL cannot be blank"); @@ -268,6 +277,8 @@ public abstract Builder setConnectionInitSql( public abstract Builder setDriverJars(String value); + public abstract Builder setBatchSize(Long value); + public abstract JdbcWriteSchemaTransformConfiguration build(); } } diff --git a/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcIOTest.java b/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcIOTest.java index a04f8c4e762f..8725ef4b3f78 100644 --- a/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcIOTest.java +++ b/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcIOTest.java @@ -549,6 +549,22 @@ public void testWrite() throws Exception { } } + @Test + public void testWriteWithBatchSize() throws Exception { + String tableName = DatabaseTestHelper.getTestTableName("UT_WRITE"); + DatabaseTestHelper.createTable(DATA_SOURCE, tableName); + try { + ArrayList> data = getDataToWrite(EXPECTED_ROW_COUNT); + pipeline.apply(Create.of(data)).apply(getJdbcWrite(tableName).withBatchSize(10L)); + + pipeline.run(); + + assertRowCount(DATA_SOURCE, tableName, EXPECTED_ROW_COUNT); + } finally { + DatabaseTestHelper.deleteTable(DATA_SOURCE, tableName); + } + } + @Test public void testWriteWithAutosharding() throws Exception { String tableName = DatabaseTestHelper.getTestTableName("UT_WRITE"); diff --git a/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProviderTest.java b/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProviderTest.java index ed380d813625..193a1f0c3477 100644 --- a/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProviderTest.java +++ b/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProviderTest.java @@ -133,6 +133,7 @@ public void testAbleToReadDataSourceConfiguration() { .withFieldValue("connectionInitSqls", new ArrayList<>(Collections.singleton("initSql"))) .withFieldValue("maxConnections", (short) 3) .withFieldValue("driverJars", "test.jar") + .withFieldValue("writeBatchSize", 10L) .build(); JdbcSchemaIOProvider.JdbcSchemaIO schemaIO = provider.from(READ_TABLE_NAME, config, Schema.builder().build()); @@ -148,6 +149,7 @@ public void testAbleToReadDataSourceConfiguration() { Objects.requireNonNull(dataSourceConf.getConnectionInitSqls()).get()); assertEquals(3, (int) dataSourceConf.getMaxConnections().get()); assertEquals("test.jar", Objects.requireNonNull(dataSourceConf.getDriverJars()).get()); + assertEquals(10L, schemaIO.config.getInt64("writeBatchSize").longValue()); } /** Create test data that is consistent with that generated by TestRow. */ diff --git a/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProviderTest.java b/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProviderTest.java index f66a143323e5..d6be4d9f89c8 100644 --- a/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProviderTest.java +++ b/sdks/java/io/jdbc/src/test/java/org/apache/beam/sdk/io/jdbc/JdbcWriteSchemaTransformProviderTest.java @@ -175,6 +175,7 @@ public void testWriteToTable() throws SQLException { .setDriverClassName(DATA_SOURCE_CONFIGURATION.getDriverClassName().get()) .setJdbcUrl(DATA_SOURCE_CONFIGURATION.getUrl().get()) .setLocation(writeTableName) + .setBatchSize(1L) .build())); pipeline.run(); DatabaseTestHelper.assertRowCount(DATA_SOURCE, writeTableName, 2); diff --git a/sdks/python/apache_beam/io/jdbc.py b/sdks/python/apache_beam/io/jdbc.py index d4ece0c7bc29..11570680a2f3 100644 --- a/sdks/python/apache_beam/io/jdbc.py +++ b/sdks/python/apache_beam/io/jdbc.py @@ -131,7 +131,8 @@ def default_io_expansion_service(classpath=None): ('partition_column', typing.Optional[str]), ('partitions', typing.Optional[np.int16]), ('max_connections', typing.Optional[np.int16]), - ('driver_jars', typing.Optional[str])], + ('driver_jars', typing.Optional[str]), + ('write_batch_size', typing.Optional[np.int64])], ) DEFAULT_JDBC_CLASSPATH = ['org.postgresql:postgresql:42.2.16'] @@ -187,6 +188,7 @@ def __init__( driver_jars=None, expansion_service=None, classpath=None, + write_batch_size=None, ): """ Initializes a write operation to Jdbc. @@ -218,6 +220,9 @@ def __init__( package (e.g. "org.postgresql:postgresql:42.3.1"). By default, this argument includes a Postgres SQL JDBC driver. + :param write_batch_size: sets the maximum size in number of SQL statement + for the batch. + default is {@link JdbcIO.DEFAULT_BATCH_SIZE} """ classpath = classpath or DEFAULT_JDBC_CLASSPATH super().__init__( @@ -235,6 +240,7 @@ def __init__( connection_properties=connection_properties, connection_init_sqls=connection_init_sqls, write_statement=statement, + write_batch_size=write_batch_size, read_query=None, fetch_size=None, disable_autocommit=None, @@ -352,6 +358,7 @@ def __init__( connection_properties=connection_properties, connection_init_sqls=connection_init_sqls, write_statement=None, + write_batch_size=None, read_query=query, fetch_size=fetch_size, disable_autocommit=disable_autocommit, diff --git a/sdks/python/apache_beam/yaml/standard_io.yaml b/sdks/python/apache_beam/yaml/standard_io.yaml index 269c14e17baa..305e6877ad90 100644 --- a/sdks/python/apache_beam/yaml/standard_io.yaml +++ b/sdks/python/apache_beam/yaml/standard_io.yaml @@ -225,6 +225,7 @@ driver_jars: 'driver_jars' connection_properties: 'connection_properties' connection_init_sql: 'connection_init_sql' + batch_size: 'batch_size' 'ReadFromMySql': 'ReadFromJdbc' 'WriteToMySql': 'WriteToJdbc' 'ReadFromPostgres': 'ReadFromJdbc' From 697a84393e932aada3947c19d61c9b34a2b1ded9 Mon Sep 17 00:00:00 2001 From: Gabija Balvociute <47227438+thread-sleep@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:59:56 -0800 Subject: [PATCH 061/135] Tour of Beam: formatting/typos (#33249) * common transforms * core transforms --- .../aggregation/count/description.md | 4 ++-- .../aggregation/max/description.md | 4 ++-- .../aggregation/min/description.md | 6 +++--- .../common-transforms/filter/description.md | 12 ++---------- .../additional-outputs/description.md | 4 ++-- .../core-transforms/branching/description.md | 2 +- .../combine/combine-per-key/description.md | 8 ++++---- .../combine/simple-function/description.md | 2 +- .../core-transforms/composite/description.md | 6 +++--- .../core-transforms/flatten/description.md | 2 +- .../map/co-group-by-key/description.md | 2 +- .../map/group-by-key/description.md | 16 ++++++++-------- .../map/map-elements/description.md | 2 +- .../map/pardo-one-to-one/description.md | 10 +++++----- .../core-transforms/partition/description.md | 2 +- .../core-transforms/side-inputs/description.md | 4 ++-- .../runner-concepts/description.md | 2 +- 17 files changed, 40 insertions(+), 48 deletions(-) diff --git a/learning/tour-of-beam/learning-content/common-transforms/aggregation/count/description.md b/learning/tour-of-beam/learning-content/common-transforms/aggregation/count/description.md index 43ab5503240c..60fd0cc9f216 100644 --- a/learning/tour-of-beam/learning-content/common-transforms/aggregation/count/description.md +++ b/learning/tour-of-beam/learning-content/common-transforms/aggregation/count/description.md @@ -238,11 +238,11 @@ PCollection> input = pipeline.apply( And replace `Count.globally` with `Count.perKey` it will output the count numbers by key. It is also necessary to replace the generic type: ``` -PCollection> output = applyTransform(input); +PCollection> output = applyTransform(input); ``` ``` -static PCollection> applyTransform(PCollection> input) { +static PCollection> applyTransform(PCollection> input) { return input.apply(Count.globally()); } ``` diff --git a/learning/tour-of-beam/learning-content/common-transforms/aggregation/max/description.md b/learning/tour-of-beam/learning-content/common-transforms/aggregation/max/description.md index 92a0ace73b4d..9b8cfbea8b11 100644 --- a/learning/tour-of-beam/learning-content/common-transforms/aggregation/max/description.md +++ b/learning/tour-of-beam/learning-content/common-transforms/aggregation/max/description.md @@ -42,11 +42,11 @@ func ApplyTransform(s beam.Scope, input beam.PCollection) beam.PCollection { ``` {{end}} {{if (eq .Sdk "java")}} -You can find the global maximum value from the `PCollection` by using `Max.doublesGlobally()` +You can find the global maximum value from the `PCollection` by using `Max.integersGlobally()` ``` PCollection input = pipeline.apply(Create.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); -PCollection max = input.apply(Max.doublesGlobally()); +PCollection max = input.apply(Max.integersGlobally()); ``` Output diff --git a/learning/tour-of-beam/learning-content/common-transforms/aggregation/min/description.md b/learning/tour-of-beam/learning-content/common-transforms/aggregation/min/description.md index 1343b9d8c85f..138c8aef640e 100644 --- a/learning/tour-of-beam/learning-content/common-transforms/aggregation/min/description.md +++ b/learning/tour-of-beam/learning-content/common-transforms/aggregation/min/description.md @@ -41,11 +41,11 @@ func ApplyTransform(s beam.Scope, input beam.PCollection) beam.PCollection { ``` {{end}} {{if (eq .Sdk "java")}} -You can find the global minimum value from the `PCollection` by using `Min.doublesGlobally()` +You can find the global minimum value from the `PCollection` by using `Min.integersGlobally()` ``` PCollection input = pipeline.apply(Create.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); -PCollection min = input.apply(Min.doublesGlobally()); +PCollection min = input.apply(Min.integersGlobally()); ``` Output @@ -165,7 +165,7 @@ PCollection> output = applyTransform(input); ``` static PCollection> applyTransform(PCollection> input) { - return input.apply(Sum.integersPerKey()); + return input.apply(Min.integersPerKey()); } ``` {{end}} diff --git a/learning/tour-of-beam/learning-content/common-transforms/filter/description.md b/learning/tour-of-beam/learning-content/common-transforms/filter/description.md index 7a5b9522926d..96f4b549625b 100644 --- a/learning/tour-of-beam/learning-content/common-transforms/filter/description.md +++ b/learning/tour-of-beam/learning-content/common-transforms/filter/description.md @@ -51,7 +51,7 @@ world ### Built-in filters -The Java SDK has several filter methods built-in, like `Filter.greaterThan` and `Filter.lessThen` With `Filter.greaterThan`, the input `PCollection` can be filtered so that only the elements whose values are greater than the specified amount remain. Similarly, you can use `Filter.lessThen` to filter out elements of the input `PCollection` whose values are greater than the specified amount. +The Java SDK has several filter methods built-in, like `Filter.greaterThan` and `Filter.lessThan` With `Filter.greaterThan`, the input `PCollection` can be filtered so that only the elements whose values are greater than the specified amount remain. Similarly, you can use `Filter.lessThan` to filter out elements of the input `PCollection` whose values are greater than the specified amount. Other built-in filters are: @@ -62,7 +62,7 @@ Other built-in filters are: * Filter.equal -## Example 2: Filtering with a built-in methods +## Example 2: Filtering with built-in methods ``` // List of integers @@ -404,11 +404,3 @@ func applyTransform(s beam.Scope, input beam.PCollection) beam.PCollection { }) } ``` - -### Playground exercise - -You can find the complete code of the above example using 'Filter' in the playground window, which you can run and experiment with. - -Filter transform can be used with both text and numerical collection. For example, let's try filtering the input collection that contains words so that only words that start with the letter 'a' are returned. - -You can also chain several filter transforms to form more complex filtering based on several simple filters or implement more complex filtering logic within a single filter transform. For example, try both approaches to filter the same list of words such that only ones that start with a letter 'a' (regardless of the case) and containing more than three symbols are returned. diff --git a/learning/tour-of-beam/learning-content/core-transforms/additional-outputs/description.md b/learning/tour-of-beam/learning-content/core-transforms/additional-outputs/description.md index d228e6537498..7c4922314521 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/additional-outputs/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/additional-outputs/description.md @@ -104,7 +104,7 @@ func extractWordsFn(pn beam.PaneInfo, line string, emitWords func(string)) { ``` {{end}} {{if (eq .Sdk "java")}} -While `ParDo` always outputs the main output of `PCollection` (as a return value from apply), you can also force your `ParDo` to output any number of additional `PCollection` outputs. If you decide to have multiple outputs, your `ParDo` will return all the `PCollection` output (including the main output) combined. This will be useful when you are working with big data or a database that needs to be divided into different collections. You get a combined `PCollectionTuple`, you can use `TupleTag` to get a `PCollection`. +While `ParDo` always outputs the main output of `PCollection` (as a return value from apply), you can also force your `ParDo` to output any number of additional `PCollection` outputs. If you decide to have multiple outputs, your `ParDo` will return all the `PCollection` outputs (including the main output) combined. This will be useful when you are working with big data or a database that needs to be divided into different collections. You get a combined `PCollectionTuple`, you can use `TupleTag` to get a `PCollection`. A `PCollectionTuple` is an immutable tuple of heterogeneously typed `PCollection`, "with keys" `TupleTags`. A `PCollectionTuple` can be used as input or output for `PTransform` receiving or creating multiple `PCollection` inputs or outputs, which can be of different types, for example, `ParDo` with multiple outputs. @@ -202,7 +202,7 @@ tens = results[None] # the undeclared main output You can find the full code of this example in the playground window, which you can run and experiment with. -The `applyTransform()` accepts a list of integers at the output two `PCollection` one `PCollection` above 100 and second below 100. +The `applyTransform()` accepts a list of integers and outputs two `PCollections`: one `PCollection` above 100 and second below 100. You can also work with strings: {{if (eq .Sdk "go")}} diff --git a/learning/tour-of-beam/learning-content/core-transforms/branching/description.md b/learning/tour-of-beam/learning-content/core-transforms/branching/description.md index a01022de97ab..8adff770cdd5 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/branching/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/branching/description.md @@ -86,7 +86,7 @@ starts_with_b = input | beam.Filter(lambda x: x.startswith('B')) You can find the full code of this example in the playground window, which you can run and experiment with. -Accepts a `PCollection` consisting of strings. Without modification, it returns a new "PCollection". In this case, one `PCollection` includes elements in uppercase. The other `PCollection' stores inverted elements. +Accepts a `PCollection` consisting of strings. Without modification, it returns a new `PCollection`. In this case, one `PCollection` includes elements in uppercase. The other `PCollection` stores inverted elements. You can use a different method of branching. Since `applyTransforms` performs 2 conversions, it takes a lot of time. It is possible to convert `PCollection` separately. {{if (eq .Sdk "go")}} diff --git a/learning/tour-of-beam/learning-content/core-transforms/combine/combine-per-key/description.md b/learning/tour-of-beam/learning-content/core-transforms/combine/combine-per-key/description.md index 1f44151337dd..af6f9e7aa197 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/combine/combine-per-key/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/combine/combine-per-key/description.md @@ -14,9 +14,9 @@ limitations under the License. # CombinePerKey -CombinePerKey is a transform in Apache Beam that applies a `CombineFn` function to each key of a PCollection of key-value pairs. The `CombineFn` function can be used to aggregate, sum, or combine the values associated with each key in the input `PCollection`. +`CombinePerKey` is a transform in Apache Beam that applies a `CombineFn` function to each key of a `PCollection` of key-value pairs. The `CombineFn` function can be used to aggregate, sum, or combine the values associated with each key in the input `PCollection`. -The `CombinePerKey` transform takes in an instance of a `CombineFn` class and applies it to the input `PCollection`. The output of the transform is a new PCollection where each element is a key-value pair, where the key is the same as the input key, and the value is the result of applying the `CombineFn` function to all the values associated with that key in the input `PCollection`. +The `CombinePerKey` transform takes in an instance of a `CombineFn` class and applies it to the input `PCollection`. The output of the transform is a new `PCollection` where each element is a key-value pair, where the key is the same as the input key, and the value is the result of applying the `CombineFn` function to all the values associated with that key in the input `PCollection`. {{if (eq .Sdk "go")}} ``` @@ -81,7 +81,7 @@ func applyTransform(s beam.Scope, input beam.PCollection) beam.PCollection { {{if (eq .Sdk "java")}} ``` PCollection> input = pipeline - .apply("ParseCitiesToTimeKV", Create.of( + .apply(Create.of( KV.of("a", "apple"), KV.of("o", "orange"), KV.of("a", "avocado), @@ -93,7 +93,7 @@ static PCollection> applyTransform(PCollection { + static class SumStringBinaryCombineFn extends BinaryCombineFn { @Override public String apply(String left, String right) { diff --git a/learning/tour-of-beam/learning-content/core-transforms/combine/simple-function/description.md b/learning/tour-of-beam/learning-content/core-transforms/combine/simple-function/description.md index 8eda136e7931..1946265fa66c 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/combine/simple-function/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/combine/simple-function/description.md @@ -14,7 +14,7 @@ limitations under the License. # Combine -`Combine` is a Beam transform for combining collections of elements or values in your data. Combine has variants that work on entire PCollections, and some that combine the values for each key in `PCollections` of **key/value** pairs. +`Combine` is a Beam transform for combining collections of elements or values in your data. Combine has variants that work on entire `PCollections`, and some that combine the values for each key in `PCollections` of **key/value** pairs. When you apply a `Combine` transform, you must provide the function that contains the logic for combining the elements or values. The combining function should be commutative and associative, as the function is not necessarily invoked exactly once on all values with a given key. Because the input data (including the value collection) may be distributed across multiple workers, the combining function might be called multiple times to perform partial combining on subsets of the value collection. The Beam SDK also provides some pre-built combine functions for common numeric combination operations such as sum, min, and max. diff --git a/learning/tour-of-beam/learning-content/core-transforms/composite/description.md b/learning/tour-of-beam/learning-content/core-transforms/composite/description.md index 774f5deae924..c61dcee8950b 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/composite/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/composite/description.md @@ -195,7 +195,7 @@ func extractWords(s beam.Scope, input beam.PCollection) beam.PCollection { } ``` -You can use other transformations you can replace `Count` with `Filter` to output words starting with **p**: +You can use other transformations, i.e. you can replace `Count` with `Filter` to output words starting with **p**: ``` func applyTransform(s beam.Scope, input beam.PCollection) beam.PCollection { @@ -224,7 +224,7 @@ PCollection words = input })); ``` -You can use other transformations you can replace `Count` with `Filter` to output words starting with **p**: +You can use other transformations, i.e. you can replace `Count` with `Filter` to output words starting with **p**: ``` PCollection filtered = input @@ -252,7 +252,7 @@ PCollection filtered = input words = input | 'ExtractWords' >> beam.FlatMap(lambda line: [word for word in line.split() if word]) ``` -You can use other transformations you can replace `Count` with `Filter` to output words starting with **p**: +You can use other transformations, i.e. you can replace `Count` with `Filter` to output words starting with **p**: ``` filtered = (input | 'ExtractNonSpaceCharacters' >> beam.FlatMap(lambda line: [word for word in line.split() if word]) diff --git a/learning/tour-of-beam/learning-content/core-transforms/flatten/description.md b/learning/tour-of-beam/learning-content/core-transforms/flatten/description.md index 19618f02c9e3..db9cbc31dc65 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/flatten/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/flatten/description.md @@ -56,7 +56,7 @@ By default, the coder for the output `PCollection` is the same as the coder for When using `Flatten` to merge `PCollection` objects that have a windowing strategy applied, all of the `PCollection` objects you want to merge must use a compatible windowing strategy and window sizing. For example, all the collections you’re merging must all use (hypothetically) identical 5-minute fixed windows or 4-minute sliding windows starting every 30 seconds. -If your pipeline attempts to use `Flatten` to merge `PCollection` objects with incompatible windows, Beam generates an IllegalStateException error when your pipeline is constructed. +If your pipeline attempts to use `Flatten` to merge `PCollection` objects with incompatible windows, Beam generates an `IllegalStateException` error when your pipeline is constructed. ### Playground exercise diff --git a/learning/tour-of-beam/learning-content/core-transforms/map/co-group-by-key/description.md b/learning/tour-of-beam/learning-content/core-transforms/map/co-group-by-key/description.md index a15321cd8ea3..739f43f96201 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/map/co-group-by-key/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/map/co-group-by-key/description.md @@ -75,7 +75,7 @@ func formatCoGBKResults(key string, emailIter, phoneIter func(*string) bool) str {{if (eq .Sdk "java")}} You can use the `CoGroupByKey` transformation for a tuple of tables. `CoGroupByKey` groups results from all tables by similar keys in `CoGbkResults`, from which results for any particular table can be accessed using the `TupleTag` tag supplied with the source table. -For type safety, the Jav SDK requires you to pass each `PCollection` as part of a `KeyedPCollectionTuple`. You must declare a `TupleTag` for each input `PCollection` in the `KeyedPCollectionTuple` that you want to pass to `CoGroupByKey`. As output, `CoGroupByKey` returns a `PCollection>`, which groups values from all the input `PCollections` by their common keys. Each key (all of type K) will have a different `CoGbkResult`, which is a map from `TupleTag to Iterable`. You can access a specific collection in an `CoGbkResult` object by using the `TupleTag` that you supplied with the initial collection. +For type safety, the Java SDK requires you to pass each `PCollection` as part of a `KeyedPCollectionTuple`. You must declare a `TupleTag` for each input `PCollection` in the `KeyedPCollectionTuple` that you want to pass to `CoGroupByKey`. As output, `CoGroupByKey` returns a `PCollection>`, which groups values from all the input `PCollections` by their common keys. Each key (all of type K) will have a different `CoGbkResult`, which is a map from `TupleTag to Iterable`. You can access a specific collection in an `CoGbkResult` object by using the `TupleTag` that you supplied with the initial collection. ``` // Mock data diff --git a/learning/tour-of-beam/learning-content/core-transforms/map/group-by-key/description.md b/learning/tour-of-beam/learning-content/core-transforms/map/group-by-key/description.md index c6041905a2d8..d8c396bf3a75 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/map/group-by-key/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/map/group-by-key/description.md @@ -11,7 +11,7 @@ limitations under the License. --> # GroupByKey -`GroupByKey` is a transform that is used to group elements in a `PCollection` by key. The input to `GroupByKey` is a `PCollection` of key-value pairs, where the keys are used to group the elements. The output of `GroupByKey` is a PCollection of key-value pairs, where the keys are the same as the input, and the values are lists of all the elements with that key. +`GroupByKey` is a transform that is used to group elements in a `PCollection` by key. The input to `GroupByKey` is a `PCollection` of key-value pairs, where the keys are used to group the elements. The output of `GroupByKey` is a `PCollection` of key-value pairs, where the keys are the same as the input, and the values are lists of all the elements with that key. Let’s examine the mechanics of `GroupByKey` with a simple example case, where our data set consists of words from a text file and the line number on which they appear. We want to group together all the line numbers (values) that share the same word (key), letting us see all the places in the text where a particular word appears. @@ -30,7 +30,7 @@ cat, 9 and, 6 ``` -`GroupByKey` gathers up all the values with the same key and outputs a new pair consisting of the unique key and a collection of all of the values that were associated with that key in the input collection. If we apply GroupByKey to our input collection above, the output collection would look like this: +`GroupByKey` gathers up all the values with the same key and outputs a new pair consisting of the unique key and a collection of all of the values that were associated with that key in the input collection. If we apply `GroupByKey` to our input collection above, the output collection would look like this: ``` cat, [1,5,9] @@ -61,11 +61,11 @@ PCollection> input = ...; // Apply GroupByKey to the PCollection input. // Save the result as the PCollection reduced. -PCollection>> reduced = mapped.apply(GroupByKey.create()); +PCollection>> reduced = input.apply(GroupByKey.create()); ``` {{end}} {{if (eq .Sdk "python")}} -While all SDKs have a GroupByKey transform, using GroupBy is generally more natural. The `GroupBy` transform can be parameterized by the name(s) of properties on which to group the elements of the PCollection, or a function taking the each element as input that maps to a key on which to do grouping. +While all SDKs have a `GroupByKey` transform, using `GroupBy` is generally more natural. The `GroupBy` transform can be parameterized by the name(s) of properties on which to group the elements of the `PCollection`, or a function taking the each element as input that maps to a key on which to do grouping. ``` input = ... @@ -77,11 +77,11 @@ grouped_words = input | beam.GroupByKey() If you are using unbounded `PCollections`, you must use either non-global windowing or an aggregation trigger in order to perform a `GroupByKey` or `CoGroupByKey`. This is because a bounded `GroupByKey` or `CoGroupByKey` must wait for all the data with a certain key to be collected, but with unbounded collections, the data is unlimited. Windowing and/or triggers allow grouping to operate on logical, finite bundles of data within the unbounded data streams. -If you do apply `GroupByKey` or `CoGroupByKey` to a group of unbounded `PCollections` without setting either a non-global windowing strategy, a trigger strategy, or both for each collection, Beam generates an IllegalStateException error at pipeline construction time. +If you do apply `GroupByKey` or `CoGroupByKey` to a group of unbounded `PCollections` without setting either a non-global windowing strategy, a trigger strategy, or both for each collection, Beam generates an `IllegalStateException` error at pipeline construction time. -When using `GroupByKey` or `CoGroupByKey` to group PCollections that have a windowing strategy applied, all of the `PCollections` you want to group must use the same windowing strategy and window sizing. For example, all the collections you are merging must use (hypothetically) identical 5-minute fixed windows, or 4-minute sliding windows starting every 30 seconds. +When using `GroupByKey` or `CoGroupByKey` to group `PCollections` that have a windowing strategy applied, all of the `PCollections` you want to group must use the same windowing strategy and window sizing. For example, all the collections you are merging must use (hypothetically) identical 5-minute fixed windows, or 4-minute sliding windows starting every 30 seconds. -If your pipeline attempts to use `GroupByKey` or `CoGroupByKey` to merge `PCollections` with incompatible windows, Beam generates an IllegalStateException error at pipeline construction time. +If your pipeline attempts to use `GroupByKey` or `CoGroupByKey` to merge `PCollections` with incompatible windows, Beam generates an `IllegalStateException` error at pipeline construction time. ### Playground exercise @@ -118,7 +118,7 @@ func applyTransform(s beam.Scope, input beam.PCollection) beam.PCollection { {{if (eq .Sdk "java")}} ``` PCollection> input = pipeline - .apply("ParseCitiesToTimeKV", Create.of( + .apply(Create.of( KV.of("banana", 2), KV.of("apple", 4), KV.of("lemon", 3), diff --git a/learning/tour-of-beam/learning-content/core-transforms/map/map-elements/description.md b/learning/tour-of-beam/learning-content/core-transforms/map/map-elements/description.md index 27a07e4849fb..51c38a44b771 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/map/map-elements/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/map/map-elements/description.md @@ -28,7 +28,7 @@ PCollection wordLengths = input.apply( })); ``` -If your `ParDo` performs a one-to-one mapping of input elements to output elements–that is, for each input element, it applies a function that produces exactly one output element, you can use the higher-level `MapElements` transform.MapElements can accept an anonymous Java 8 lambda function for additional brevity. +If your `ParDo` performs a one-to-one mapping of input elements to output elements–that is, for each input element, it applies a function that produces exactly one output element, you can use the higher-level `MapElements` transform. `MapElements` can accept an anonymous Java 8 lambda function for additional brevity. Here’s the previous example using `MapElements` : diff --git a/learning/tour-of-beam/learning-content/core-transforms/map/pardo-one-to-one/description.md b/learning/tour-of-beam/learning-content/core-transforms/map/pardo-one-to-one/description.md index e7bdbc6565ed..0f3dd6f580a5 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/map/pardo-one-to-one/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/map/pardo-one-to-one/description.md @@ -264,9 +264,9 @@ wordLengths := beam.ParDo(s, func(word string) int { {{if (eq .Sdk "java")}} ### Accessing additional parameters in your DoFn -In addition to the element and the OutputReceiver, Beam will populate other parameters to your DoFn’s @ProcessElement method. Any combination of these parameters can be added to your process method in any order. +In addition to the element and the `OutputReceiver`, Beam will populate other parameters to your DoFn’s `@ProcessElement` method. Any combination of these parameters can be added to your process method in any order. -**Timestamp**: To access the timestamp of an input element, add a parameter annotated with @Timestamp of type Instant. For example: +**Timestamp**: To access the timestamp of an input element, add a parameter annotated with `@Timestamp` of type `Instant`. For example: ``` .of(new DoFn() { @@ -274,7 +274,7 @@ In addition to the element and the OutputReceiver, Beam will populate other para }}) ``` -**Window**: To access the window an input element falls into, add a parameter of the type of the window used for the input `PCollection`. If the parameter is a window type (a subclass of BoundedWindow) that does not match the input `PCollection`, then an error will be raised. If an element falls in multiple windows (for example, this will happen when using `SlidingWindows`), then the `@ProcessElement` method will be invoked multiple time for the element, once for each window. For example, when fixed windows are being used, the window is of type `IntervalWindow`. +**Window**: To access the window an input element falls into, add a parameter of the type of the window used for the input `PCollection`. If the parameter is a window type (a subclass of `BoundedWindow`) that does not match the input `PCollection`, then an error will be raised. If an element falls in multiple windows (for example, this will happen when using `SlidingWindows`), then the `@ProcessElement` method will be invoked multiple time for the element, once for each window. For example, when fixed windows are being used, the window is of type `IntervalWindow`. ``` .of(new DoFn() { @@ -298,7 +298,7 @@ In addition to the element and the OutputReceiver, Beam will populate other para }}) ``` -`@OnTimer` methods can also access many of these parameters. Timestamp, Window, key, `PipelineOptions`, `OutputReceiver`, and `MultiOutputReceiver` parameters can all be accessed in an @OnTimer method. In addition, an `@OnTimer` method can take a parameter of type `TimeDomain` which tells whether the timer is based on event time or processing time. Timers are explained in more detail in the Timely (and Stateful) Processing with Apache Beam blog post. +`@OnTimer` methods can also access many of these parameters. Timestamp, Window, key, `PipelineOptions`, `OutputReceiver`, and `MultiOutputReceiver` parameters can all be accessed in an `@OnTimer` method. In addition, an `@OnTimer` method can take a parameter of type `TimeDomain` which tells whether the timer is based on event time or processing time. Timers are explained in more detail in the [Timely (and Stateful) Processing with Apache Beam blog post](https://beam.apache.org/blog/timely-processing/). {{end}} @@ -401,7 +401,7 @@ class StatefulDoFn(beam.DoFn): You can find the full code of this example in the playground window, which you can run and experiment with. -You can work with any type of object.For example String: +You can work with any type of object. For example `String`: {{if (eq .Sdk "go")}} ``` diff --git a/learning/tour-of-beam/learning-content/core-transforms/partition/description.md b/learning/tour-of-beam/learning-content/core-transforms/partition/description.md index 59f6ad5f5c33..e951a38c1750 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/partition/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/partition/description.md @@ -70,7 +70,7 @@ fortieth_percentile = by_decile[4] You can find the full code of this example in the playground window, which you can run and experiment with. -The `applyTransforms` returns a slice of the PCollection, you can access it by index. In this case, we have two `PCollections`, one consists of numbers that are less than 100, the second is more than 100. +The `applyTransforms` returns a slice of the `PCollection`, you can access it by index. In this case, we have two `PCollections`, one consists of numbers that are less than 100, the second is more than 100. You can also divide other types into parts, for example: "strings" and others. diff --git a/learning/tour-of-beam/learning-content/core-transforms/side-inputs/description.md b/learning/tour-of-beam/learning-content/core-transforms/side-inputs/description.md index a64ccde52950..ae5bdffc827d 100644 --- a/learning/tour-of-beam/learning-content/core-transforms/side-inputs/description.md +++ b/learning/tour-of-beam/learning-content/core-transforms/side-inputs/description.md @@ -11,7 +11,7 @@ limitations under the License. --> # Side inputs -In addition to the main input `PCollection`, you can provide additional inputs to a `ParDo` transform in the form of side inputs. A side input is an additional input that your `DoFn` can access each time it processes an element in the input PCollection. When you specify a side input, you create a view of some other data that can be read from within the `ParDo` transform’s `DoFn` while processing each element. +In addition to the main input `PCollection`, you can provide additional inputs to a `ParDo` transform in the form of side inputs. A side input is an additional input that your `DoFn` can access each time it processes an element in the input `PCollection`. When you specify a side input, you create a view of some other data that can be read from within the `ParDo` transform’s `DoFn` while processing each element. Side inputs are useful if your `ParDo` needs to inject additional data when processing each element in the input `PCollection`, but the additional data needs to be determined at runtime (and not hard-coded). Such values might be determined by the input data, or depend on a different branch of your pipeline. {{if (eq .Sdk "go")}} @@ -172,7 +172,7 @@ If the side input has multiple trigger firings, Beam uses the value from the lat You can find the full code of this example in the playground window, which you can run and experiment with. -At the entrance we have a map whose key is the city of the country value. And we also have a `Person` structure with his name and city. We can compare cities and embed countries in `Person`. +At the entrance we have a map whose key is the city of the country value. And we also have a `Person` structure with their name and city. We can compare cities and embed countries in `Person`. You can also use it as a variable for mathematical calculations. diff --git a/learning/tour-of-beam/learning-content/introduction/introduction-concepts/runner-concepts/description.md b/learning/tour-of-beam/learning-content/introduction/introduction-concepts/runner-concepts/description.md index 71abe616f1ad..d7c4cb4137dc 100644 --- a/learning/tour-of-beam/learning-content/introduction/introduction-concepts/runner-concepts/description.md +++ b/learning/tour-of-beam/learning-content/introduction/introduction-concepts/runner-concepts/description.md @@ -53,7 +53,7 @@ When using Java, you must specify your dependency on the Direct Runner in your p #### Set runner -In java, you need to set runner to `args` when you start the program. +In Java, you need to set runner to `args` when you start the program. ``` --runner=DirectRunner From 3201c575405eb97bf821bb138920151a3138e44e Mon Sep 17 00:00:00 2001 From: Jeffrey Kinard Date: Fri, 8 Nov 2024 16:12:31 -0500 Subject: [PATCH 062/135] [yaml] error_handling normalization follow-up Signed-off-by: Jeffrey Kinard --- sdks/python/apache_beam/yaml/generate_yaml_docs.py | 5 +++-- sdks/python/apache_beam/yaml/yaml_errors.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdks/python/apache_beam/yaml/generate_yaml_docs.py b/sdks/python/apache_beam/yaml/generate_yaml_docs.py index 2123c7a9f202..e0a9f3f5e6c5 100644 --- a/sdks/python/apache_beam/yaml/generate_yaml_docs.py +++ b/sdks/python/apache_beam/yaml/generate_yaml_docs.py @@ -154,8 +154,9 @@ def normalize_error_handling(f): PythonCallableWithSource.load_from_expression( param.type_name), param.description) for param in doc.params - ]))), - description=f.description) + ])), + nullable=True), + description=f.description or doc.short_description) return f def lines(): diff --git a/sdks/python/apache_beam/yaml/yaml_errors.py b/sdks/python/apache_beam/yaml/yaml_errors.py index c0d448473f42..dace44ca09f6 100644 --- a/sdks/python/apache_beam/yaml/yaml_errors.py +++ b/sdks/python/apache_beam/yaml/yaml_errors.py @@ -24,7 +24,7 @@ class ErrorHandlingConfig(NamedTuple): - """Class to define Error Handling parameters. + """This option specifies whether and where to output error rows. Args: output (str): Name to use for the output error collection From edd352801d1f50638f1536be9ee9e979dcb4b4d0 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 4 Dec 2024 06:06:44 -0800 Subject: [PATCH 063/135] Add a pointer to java provider example. (#33240) --- website/www/site/content/en/documentation/sdks/yaml.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/www/site/content/en/documentation/sdks/yaml.md b/website/www/site/content/en/documentation/sdks/yaml.md index d4829c163522..a27b47a2b415 100644 --- a/website/www/site/content/en/documentation/sdks/yaml.md +++ b/website/www/site/content/en/documentation/sdks/yaml.md @@ -662,6 +662,9 @@ providers: MyCustomTransform: "urn:registered:in:expansion:service" ``` +A full example of how to build a java provider can be found +[here](https://github.com/Polber/beam-yaml-xlang). + Arbitrary Python transforms can be provided as well, using the syntax ``` From a9a82a1c1ae7a702f25dc98fd3aaca16f50209e3 Mon Sep 17 00:00:00 2001 From: Jeff Kinard Date: Wed, 4 Dec 2024 10:28:46 -0500 Subject: [PATCH 064/135] [yaml] remove PubSubLite from API docs (#33184) * [yaml] remove PubSubLite from API docs Signed-off-by: Jeffrey Kinard * add deprecation note Signed-off-by: Jeffrey Kinard --------- Signed-off-by: Jeffrey Kinard --- .../PubsubLiteReadSchemaTransformProvider.java | 7 +++++++ .../PubsubLiteWriteSchemaTransformProvider.java | 7 +++++++ sdks/python/apache_beam/yaml/generate_yaml_docs.py | 10 ++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteReadSchemaTransformProvider.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteReadSchemaTransformProvider.java index 9e83619f7b8d..61b94aeee445 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteReadSchemaTransformProvider.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteReadSchemaTransformProvider.java @@ -87,6 +87,13 @@ protected Class configurationClass() return PubsubLiteReadSchemaTransformConfiguration.class; } + @Override + public String description() { + return "Performs a read from Google Pub/Sub Lite.\n" + + "\n" + + "**Note**: This provider is deprecated. See Pub/Sub Lite documentation for more information."; + } + public static class ErrorFn extends DoFn { private final SerializableFunction valueMapper; private final Counter errorCounter; diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteWriteSchemaTransformProvider.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteWriteSchemaTransformProvider.java index ebca921c57e1..54ed7ac495d9 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteWriteSchemaTransformProvider.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsublite/PubsubLiteWriteSchemaTransformProvider.java @@ -82,6 +82,13 @@ protected Class configurationClass( return PubsubLiteWriteSchemaTransformConfiguration.class; } + @Override + public String description() { + return "Performs a write to Google Pub/Sub Lite.\n" + + "\n" + + "**Note**: This provider is deprecated. See Pub/Sub Lite documentation for more information."; + } + public static class ErrorCounterFn extends DoFn { private final SerializableFunction toBytesFn; private final Counter errorCounter; diff --git a/sdks/python/apache_beam/yaml/generate_yaml_docs.py b/sdks/python/apache_beam/yaml/generate_yaml_docs.py index 2123c7a9f202..38a888fdebe3 100644 --- a/sdks/python/apache_beam/yaml/generate_yaml_docs.py +++ b/sdks/python/apache_beam/yaml/generate_yaml_docs.py @@ -189,11 +189,8 @@ def io_grouping_key(transform_name): return 0, transform_name -SKIP = [ - 'Combine', - 'Filter', - 'MapToFields', -] +# Exclude providers +SKIP = {} def transform_docs(transform_base, transforms, providers, extra_docs=''): @@ -236,7 +233,8 @@ def main(): options = parser.parse_args() include = re.compile(options.include).match exclude = ( - re.compile(options.exclude).match if options.exclude else lambda _: False) + re.compile(options.exclude).match + if options.exclude else lambda x: x in SKIP) with subprocess_server.SubprocessServer.cache_subprocesses(): json_config_schemas = [] From 2f788f674f377f857748c761abe98397038a8979 Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:46:47 -0500 Subject: [PATCH 065/135] use standard sql (#33278) --- .../BigQueryDirectReadSchemaTransformProvider.java | 2 +- .../io/gcp/bigquery/providers/BigQueryManagedIT.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryDirectReadSchemaTransformProvider.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryDirectReadSchemaTransformProvider.java index 15b1b01d7f6c..073de40038b3 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryDirectReadSchemaTransformProvider.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryDirectReadSchemaTransformProvider.java @@ -233,7 +233,7 @@ BigQueryIO.TypedRead createDirectReadTransform() { read = read.withSelectedFields(configuration.getSelectedFields()); } } else { - read = read.fromQuery(configuration.getQuery()); + read = read.fromQuery(configuration.getQuery()).usingStandardSql(); } if (!Strings.isNullOrEmpty(configuration.getKmsKey())) { read = read.withKmsKey(configuration.getKmsKey()); diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryManagedIT.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryManagedIT.java index 16ce2f049dcf..6a422f1832d8 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryManagedIT.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/providers/BigQueryManagedIT.java @@ -96,8 +96,8 @@ public static void cleanup() { @Test public void testBatchFileLoadsWriteRead() { String table = - String.format("%s:%s.%s", PROJECT, BIG_QUERY_DATASET_ID, testName.getMethodName()); - Map config = ImmutableMap.of("table", table); + String.format("%s.%s.%s", PROJECT, BIG_QUERY_DATASET_ID, testName.getMethodName()); + Map writeConfig = ImmutableMap.of("table", table); // file loads requires a GCS temp location String tempLocation = writePipeline.getOptions().as(TestPipelineOptions.class).getTempRoot(); @@ -105,13 +105,15 @@ public void testBatchFileLoadsWriteRead() { // batch write PCollectionRowTuple.of("input", getInput(writePipeline, false)) - .apply(Managed.write(Managed.BIGQUERY).withConfig(config)); + .apply(Managed.write(Managed.BIGQUERY).withConfig(writeConfig)); writePipeline.run().waitUntilFinish(); + Map readConfig = + ImmutableMap.of("query", String.format("SELECT * FROM `%s`", table)); // read and validate PCollection outputRows = readPipeline - .apply(Managed.read(Managed.BIGQUERY).withConfig(config)) + .apply(Managed.read(Managed.BIGQUERY).withConfig(readConfig)) .getSinglePCollection(); PAssert.that(outputRows).containsInAnyOrder(ROWS); readPipeline.run().waitUntilFinish(); From 78bde63b4f4d6158584faa2362760e5260336a15 Mon Sep 17 00:00:00 2001 From: Jeff Kinard Date: Wed, 4 Dec 2024 12:08:24 -0500 Subject: [PATCH 066/135] [yaml] Fix YAML join with upstream ReadFromPubSub (#33273) --- sdks/python/apache_beam/yaml/yaml_join.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdks/python/apache_beam/yaml/yaml_join.py b/sdks/python/apache_beam/yaml/yaml_join.py index 5124ef56b49c..b22e452b27f9 100644 --- a/sdks/python/apache_beam/yaml/yaml_join.py +++ b/sdks/python/apache_beam/yaml/yaml_join.py @@ -62,9 +62,11 @@ def _validate_equalities(equalities, pcolls): error_prefix = f'Invalid value "{equalities}" for "equalities".' valid_cols = { - name: set(dict(pcoll.element_type._fields).keys()) - for name, - pcoll in pcolls.items() + name: set( + dict(fields).keys() if fields and all( + isinstance(field, tuple) for field in fields) else fields) + for (name, pcoll) in pcolls.items() + for fields in [getattr(pcoll.element_type, '_fields', [])] } if isinstance(equalities, str): From f080be0fbdc68dbbe629caad6bb8b730d6ff362d Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 4 Dec 2024 10:49:47 -0800 Subject: [PATCH 067/135] [YAML] Make datetime available for jinja templatization. --- CHANGES.md | 1 + sdks/python/apache_beam/yaml/main_test.py | 13 +++++++++++++ sdks/python/apache_beam/yaml/yaml_transform.py | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fc32398a7a5a..5258183140a2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,6 +31,7 @@ ## New Features / Improvements +* The datetime module is now available for use in jinja templatization for yaml. * X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). ## Breaking Changes diff --git a/sdks/python/apache_beam/yaml/main_test.py b/sdks/python/apache_beam/yaml/main_test.py index 1a3da6443b72..d5fbfedc0349 100644 --- a/sdks/python/apache_beam/yaml/main_test.py +++ b/sdks/python/apache_beam/yaml/main_test.py @@ -15,6 +15,7 @@ # limitations under the License. # +import datetime import glob import logging import os @@ -100,6 +101,18 @@ def test_preparse_jinja_flags(self): 'pos_arg', ]) + def test_jinja_datetime(self): + with tempfile.TemporaryDirectory() as tmpdir: + out_path = os.path.join(tmpdir, 'out.txt') + main.run([ + '--yaml_pipeline', + TEST_PIPELINE.replace('PATH', out_path).replace( + 'ELEMENT', '"{{datetime.datetime.now().strftime("%Y-%m-%d")}}"'), + ]) + with open(glob.glob(out_path + '*')[0], 'rt') as fin: + self.assertEqual( + fin.read().strip(), datetime.datetime.now().strftime("%Y-%m-%d")) + if __name__ == '__main__': logging.getLogger().setLevel(logging.INFO) diff --git a/sdks/python/apache_beam/yaml/yaml_transform.py b/sdks/python/apache_beam/yaml/yaml_transform.py index 0190fe20413f..82cd404b4ddc 100644 --- a/sdks/python/apache_beam/yaml/yaml_transform.py +++ b/sdks/python/apache_beam/yaml/yaml_transform.py @@ -16,6 +16,7 @@ # import collections +import datetime import functools import json import logging @@ -992,7 +993,7 @@ def expand_jinja( jinja2.Environment( undefined=jinja2.StrictUndefined, loader=_BeamFileIOLoader()) .from_string(jinja_template) - .render(**jinja_variables)) + .render(datetime=datetime, **jinja_variables)) class YamlTransform(beam.PTransform): From 56d6a4670eee61c3a8186a0f5f2d9d78b9249ca4 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 4 Dec 2024 11:03:12 -0800 Subject: [PATCH 068/135] [YAML] Document the yaml provider include syntax. --- .../www/site/content/en/documentation/sdks/yaml.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/website/www/site/content/en/documentation/sdks/yaml.md b/website/www/site/content/en/documentation/sdks/yaml.md index a27b47a2b415..1b68db19de3c 100644 --- a/website/www/site/content/en/documentation/sdks/yaml.md +++ b/website/www/site/content/en/documentation/sdks/yaml.md @@ -678,6 +678,19 @@ providers: MyCustomTransform: "pkg.subpkg.PTransformClassOrCallable" ``` +One can additionally reference an external listings of providers as follows + +``` +providers: + - include: "file:///path/to/local/providers.yaml" + - include: "gs://path/to/remote/providers.yaml" + - include: "https://example.com/hosted/providers.yaml" + ... +``` + +where `providers.yaml` is simply a yaml file containing a list of providers +in the same format as those inlined in this providers block. + ## Pipeline options [Pipeline options](https://beam.apache.org/documentation/programming-guide/#configuring-pipeline-options) From 40423bce99d271250509f1e10ebee5b32f53a0a7 Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:30:38 -0500 Subject: [PATCH 069/135] add iceberg gcp dependency (#33281) --- sdks/java/io/iceberg/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/sdks/java/io/iceberg/build.gradle b/sdks/java/io/iceberg/build.gradle index 6754b0aecf50..a2d192b67208 100644 --- a/sdks/java/io/iceberg/build.gradle +++ b/sdks/java/io/iceberg/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation "org.apache.iceberg:iceberg-api:$iceberg_version" implementation "org.apache.iceberg:iceberg-parquet:$iceberg_version" implementation "org.apache.iceberg:iceberg-orc:$iceberg_version" + runtimeOnly "org.apache.iceberg:iceberg-gcp:$iceberg_version" implementation library.java.hadoop_common testImplementation project(":sdks:java:managed") From 122368f028ad4b6781c6da677334db21027d9edb Mon Sep 17 00:00:00 2001 From: Damon Date: Wed, 4 Dec 2024 13:04:40 -0800 Subject: [PATCH 070/135] Fix TypeError: 'type' object is not subscriptable (#33280) Internal dataflow tests reference this code line with the error `TypeError: 'type' object is not subscriptable`. --- sdks/python/apache_beam/metrics/cells.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/metrics/cells.py b/sdks/python/apache_beam/metrics/cells.py index 10ac7b3a1e69..5802c6914eb2 100644 --- a/sdks/python/apache_beam/metrics/cells.py +++ b/sdks/python/apache_beam/metrics/cells.py @@ -570,7 +570,7 @@ def __repr__(self) -> str: def get_cumulative(self) -> "StringSetData": return StringSetData(set(self.string_set), self.string_size) - def get_result(self) -> set[str]: + def get_result(self) -> Set[str]: return set(self.string_set) def add(self, *strings): From 2e43e29e44c2cb90c8e59a64c441cb37a0ffd44f Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Wed, 4 Dec 2024 14:04:23 -0800 Subject: [PATCH 071/135] [#32222] Actually maintain the heap invariant for timers. (#33270) --- CHANGES.md | 1 + runners/prism/java/build.gradle | 9 +-------- .../beam/runners/prism/internal/engine/elementmanager.go | 7 +++++-- .../runners/portability/fn_api_runner/fn_runner_test.py | 9 ++++++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fc32398a7a5a..1b943a99f8a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -81,6 +81,7 @@ ## Bugfixes * Fixed X (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). +* Fixed EventTimeTimer ordering in Prism. ([#32222](https://github.com/apache/beam/issues/32222)). ## Security Fixes * Fixed (CVE-YYYY-NNNN)[https://www.cve.org/CVERecord?id=CVE-YYYY-NNNN] (Java/Python/Go) ([#X](https://github.com/apache/beam/issues/X)). diff --git a/runners/prism/java/build.gradle b/runners/prism/java/build.gradle index f2dfa2bb1a28..82eb62b9e207 100644 --- a/runners/prism/java/build.gradle +++ b/runners/prism/java/build.gradle @@ -178,10 +178,7 @@ def sickbayTests = [ 'org.apache.beam.sdk.transforms.ParDoTest$StateTests.testTwoRequiresTimeSortedInputWithLateData', 'org.apache.beam.sdk.transforms.ParDoTest$StateTests.testRequiresTimeSortedInputWithLateData', - // Timer race condition/ordering issue in Prism. - 'org.apache.beam.sdk.transforms.ParDoTest$TimerTests.testTwoTimersSettingEachOtherWithCreateAsInputUnbounded', - - // Missing output due to timer skew. + // Missing output due to processing time timer skew. 'org.apache.beam.sdk.transforms.ParDoTest$TimestampTests.testProcessElementSkew', // TestStream + BundleFinalization. @@ -241,10 +238,6 @@ def createPrismValidatesRunnerTask = { name, environmentType -> excludeCategories 'org.apache.beam.sdk.testing.UsesMultimapState' } filter { - // Hangs forever with prism. Put here instead of sickbay to allow sickbay runs to terminate. - // https://github.com/apache/beam/issues/32222 - excludeTestsMatching 'org.apache.beam.sdk.transforms.ParDoTest$TimerTests.testEventTimeTimerOrderingWithCreate' - for (String test : sickbayTests) { excludeTestsMatching test } diff --git a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go index 3cfde4701a8f..1739efdb742a 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go +++ b/sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go @@ -1159,7 +1159,7 @@ func (ss *stageState) AddPending(newPending []element) int { } ss.pendingByKeys[string(e.keyBytes)] = dnt } - dnt.elements.Push(e) + heap.Push(&dnt.elements, e) if e.IsTimer() { if lastSet, ok := dnt.timers[timerKey{family: e.family, tag: e.tag, window: e.window}]; ok { @@ -1576,6 +1576,8 @@ func (ss *stageState) updateWatermarks(em *ElementManager) set[string] { // They'll never be read in again. for _, wins := range ss.sideInputs { for win := range wins { + // TODO(#https://github.com/apache/beam/issues/31438): + // Adjust with AllowedLateness // Clear out anything we've already used. if win.MaxTimestamp() < newOut { delete(wins, win) @@ -1584,7 +1586,8 @@ func (ss *stageState) updateWatermarks(em *ElementManager) set[string] { } for _, wins := range ss.state { for win := range wins { - // Clear out anything we've already used. + // TODO(#https://github.com/apache/beam/issues/31438): + // Adjust with AllowedLateness if win.MaxTimestamp() < newOut { delete(wins, win) } diff --git a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py index 4a737feaf288..1309e7c74abc 100644 --- a/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py +++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/fn_runner_test.py @@ -776,7 +776,8 @@ def process( state.clear() yield buffer else: - timer.set(ts + 1) + # Set the timer to fire within it's window. + timer.set(ts + (1 - timestamp.Duration(micros=1000))) @userstate.on_timer(timer_spec) def process_timer(self, state=beam.DoFn.StateParam(state_spec)): @@ -790,8 +791,10 @@ def is_buffered_correctly(actual): # Acutal should be a grouping of the inputs into batches of size # at most buffer_size, but the actual batching is nondeterministic # based on ordering and trigger firing timing. - self.assertEqual(sorted(sum((list(b) for b in actual), [])), elements) - self.assertEqual(max(len(list(buffer)) for buffer in actual), buffer_size) + self.assertEqual( + sorted(sum((list(b) for b in actual), [])), elements, actual) + self.assertEqual( + max(len(list(buffer)) for buffer in actual), buffer_size, actual) if windowed: # Elements were assigned to windows based on their parity. # Assert that each grouping consists of elements belonging to the From 7ef6d36d70dc61c6e997fb31166364bd587a2941 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 4 Dec 2024 16:01:24 -0800 Subject: [PATCH 072/135] [YAML] Document jinja templatization features. This resolves #32873. --- .../content/en/documentation/sdks/yaml.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/website/www/site/content/en/documentation/sdks/yaml.md b/website/www/site/content/en/documentation/sdks/yaml.md index a27b47a2b415..4f9ee4081439 100644 --- a/website/www/site/content/en/documentation/sdks/yaml.md +++ b/website/www/site/content/en/documentation/sdks/yaml.md @@ -708,6 +708,49 @@ options: streaming: true ``` +## Jinja Templatization + +It is a common to want to run a single Beam pipeline in different contexts +and/or with different configurations. +When running a YAML pipeline either locally or via the gcloud, +the yaml file can be parameterized with externally provided variables using +the [jinja variable syntax](https://jinja.palletsprojects.com/en/stable/templates/#variables). +The values are then passed via a `--jinja_variables` command line flag. + +For example, one could start a pipeline with + +``` +pipeline: + transforms: + - type: ReadFromCsv + config: + path: {{input_pattern}} +``` + +and then run it with + +```sh +python -m apache_beam.yaml.main \ + --yaml_pipeline_file=pipeline.yaml \ + --jinja_variables='{"input_pattern": "gs://path/to/this/runs/files*.csv"}' +``` + +Arbitrary [jinja control structures](https://jinja.palletsprojects.com/en/stable/templates/#list-of-control-structures), +such as looping and conditionals, can be used as well if desired as long as the +output results in a valid Beam YAML pipeline. + +We also expose the [`datetime`](https://docs.python.org/3/library/datetime.html) +module as a variable by default, which can be particularly useful in reading +or writing dated sources and sinks, e.g. + +``` +- type: WriteToJson + config: + path: "gs://path/to/{{ datetime.datetime.now().strftime('%Y/%m/%d') }}/dated-output.json" +``` + +would write to files like `gs://path/to/2016/08/04/dated-output*.json`. + ## Other Resources * [Example pipeline](https://github.com/apache/beam/tree/master/sdks/python/apache_beam/yaml/examples) From 2bdbbdcfdd324b9316306ab44508b41cb1bb356f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:15:27 -0800 Subject: [PATCH 073/135] Bump cloud.google.com/go/pubsub from 1.45.1 to 1.45.3 in /sdks (#33289) Bumps [cloud.google.com/go/pubsub](https://github.com/googleapis/google-cloud-go) from 1.45.1 to 1.45.3. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.45.1...pubsub/v1.45.3) --- updated-dependencies: - dependency-name: cloud.google.com/go/pubsub dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 24 ++++++++++---------- sdks/go.sum | 64 ++++++++++++++++++++++++++--------------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index ef966f0dcef4..acf52d7a62cb 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -27,7 +27,7 @@ require ( cloud.google.com/go/bigtable v1.33.0 cloud.google.com/go/datastore v1.20.0 cloud.google.com/go/profiler v0.4.1 - cloud.google.com/go/pubsub v1.45.1 + cloud.google.com/go/pubsub v1.45.3 cloud.google.com/go/spanner v1.73.0 cloud.google.com/go/storage v1.45.0 github.com/aws/aws-sdk-go-v2 v1.32.6 @@ -58,8 +58,8 @@ require ( golang.org/x/sync v0.9.0 golang.org/x/sys v0.27.0 golang.org/x/text v0.20.0 - google.golang.org/api v0.203.0 - google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 + google.golang.org/api v0.210.0 + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.2 gopkg.in/yaml.v2 v2.4.0 @@ -75,9 +75,9 @@ require ( require ( cel.dev/expr v0.16.1 // indirect - cloud.google.com/go/auth v0.9.9 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/monitoring v1.21.1 // indirect + cloud.google.com/go/auth v0.11.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect @@ -117,15 +117,15 @@ require ( go.opentelemetry.io/otel/sdk v1.29.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.8.0 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect ) require ( cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/longrunning v0.6.1 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/longrunning v0.6.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/apache/arrow/go/arrow v0.0.0-20200730104253-651201b0f516 // indirect @@ -163,7 +163,7 @@ require ( github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -194,6 +194,6 @@ require ( golang.org/x/mod v0.20.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect ) diff --git a/sdks/go.sum b/sdks/go.sum index 5527a59f4c52..d8eb73149c76 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -101,10 +101,10 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= -cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= +cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -210,8 +210,8 @@ cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOX cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/datacatalog v1.22.1 h1:i0DyKb/o7j+0vgaFtimcRFjYsD6wFw1jpnODYUyiYRs= -cloud.google.com/go/datacatalog v1.22.1/go.mod h1:MscnJl9B2lpYlFoxRjicw19kFTwEke8ReKL5Y/6TWg8= +cloud.google.com/go/datacatalog v1.23.0 h1:9F2zIbWNNmtrSkPIyGRQNsIugG5VgVVFip6+tXSdWLg= +cloud.google.com/go/datacatalog v1.23.0/go.mod h1:9Wamq8TDfL2680Sav7q3zEhBJSPBrDxJU8WtPJ25dBM= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= @@ -327,8 +327,8 @@ cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGE cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -348,8 +348,8 @@ cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4 cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/kms v1.20.0 h1:uKUvjGqbBlI96xGE669hcVnEMw1Px/Mvfa62dhM5UrY= -cloud.google.com/go/kms v1.20.0/go.mod h1:/dMbFF1tLLFnQV44AoI2GlotbjowyUfgVwezxW291fM= +cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg= +cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -360,13 +360,13 @@ cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6 cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= -cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= @@ -390,8 +390,8 @@ cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhI cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= -cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -451,8 +451,8 @@ cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcd cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsub v1.45.1 h1:ZC/UzYcrmK12THWn1P72z+Pnp2vu/zCZRXyhAfP1hJY= -cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= +cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow= +cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= @@ -582,8 +582,8 @@ cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= -cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= @@ -978,8 +978,8 @@ github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqE github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -1560,8 +1560,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1705,8 +1705,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= -google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= +google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1846,12 +1846,12 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From d24d838bd838427a545592ad21d9623fb1272fb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:47:44 -0800 Subject: [PATCH 074/135] Bump cloud.google.com/go/storage from 1.45.0 to 1.47.0 in /sdks (#33266) Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.45.0 to 1.47.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/pubsub/v1.45.0...spanner/v1.47.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/storage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 2 +- sdks/go.sum | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index acf52d7a62cb..5ea567825135 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -29,7 +29,7 @@ require ( cloud.google.com/go/profiler v0.4.1 cloud.google.com/go/pubsub v1.45.3 cloud.google.com/go/spanner v1.73.0 - cloud.google.com/go/storage v1.45.0 + cloud.google.com/go/storage v1.47.0 github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.28.4 github.com/aws/aws-sdk-go-v2/credentials v1.17.45 diff --git a/sdks/go.sum b/sdks/go.sum index d8eb73149c76..d5747e210a5a 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -561,8 +561,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storage v1.45.0 h1:5av0QcIVj77t+44mV4gffFC/LscFRUhto6UBMB5SimM= -cloud.google.com/go/storage v1.45.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= +cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= +cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1239,6 +1239,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= From 68e3c0de9c570ea8e137e1c4f1a2296397ad855e Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Thu, 5 Dec 2024 14:32:28 -0500 Subject: [PATCH 075/135] Small string fix for error message (#33295) --- sdks/python/apache_beam/yaml/yaml_transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/yaml/yaml_transform.py b/sdks/python/apache_beam/yaml/yaml_transform.py index 0190fe20413f..b8e49e81c579 100644 --- a/sdks/python/apache_beam/yaml/yaml_transform.py +++ b/sdks/python/apache_beam/yaml/yaml_transform.py @@ -493,7 +493,7 @@ def expand_leaf_transform(spec, scope): outputs = inputs | scope.unique_name(spec, ptransform) >> ptransform except Exception as exn: raise ValueError( - f"Error apply transform {identify_object(spec)}: {exn}") from exn + f"Error applying transform {identify_object(spec)}: {exn}") from exn if isinstance(outputs, dict): # TODO: Handle (or at least reject) nested case. return outputs From 2344fdc4cb8608fad8bb42d2ce5b2e910e6c8098 Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Thu, 5 Dec 2024 12:47:05 -0800 Subject: [PATCH 076/135] [#32211] Fail job on SDK worker disconnect. (#33298) Co-authored-by: lostluck <13907733+lostluck@users.noreply.github.com> --- .../runners/prism/internal/worker/bundle.go | 28 ++++++++-- .../runners/prism/internal/worker/worker.go | 53 +++++++++++++++---- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go b/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go index 55cdb97f258c..83ad1bda9841 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go +++ b/sdks/go/pkg/beam/runners/prism/internal/worker/bundle.go @@ -126,7 +126,7 @@ func (b *B) ProcessOn(ctx context.Context, wk *W) <-chan struct{} { slog.Debug("processing", "bundle", b, "worker", wk) // Tell the SDK to start processing the bundle. - wk.InstReqs <- &fnpb.InstructionRequest{ + req := &fnpb.InstructionRequest{ InstructionId: b.InstID, Request: &fnpb.InstructionRequest_ProcessBundle{ ProcessBundle: &fnpb.ProcessBundleRequest{ @@ -134,6 +134,18 @@ func (b *B) ProcessOn(ctx context.Context, wk *W) <-chan struct{} { }, }, } + select { + case <-wk.StoppedChan: + // The worker was stopped before req was sent. + // Quit to avoid sending on a closed channel. + outCap := b.OutputCount + len(b.HasTimers) + for i := 0; i < outCap; i++ { + b.DataOrTimerDone() + } + return b.DataWait + case wk.InstReqs <- req: + // desired outcome + } // TODO: make batching decisions on the maxium to send per elements block, to reduce processing time overhead. for _, block := range b.Input { @@ -163,10 +175,13 @@ func (b *B) ProcessOn(ctx context.Context, wk *W) <-chan struct{} { } select { - case wk.DataReqs <- elms: + case <-wk.StoppedChan: + b.DataOrTimerDone() + return b.DataWait case <-ctx.Done(): b.DataOrTimerDone() return b.DataWait + case wk.DataReqs <- elms: } } @@ -181,6 +196,12 @@ func (b *B) ProcessOn(ctx context.Context, wk *W) <-chan struct{} { }) } select { + case <-wk.StoppedChan: + b.DataOrTimerDone() + return b.DataWait + case <-ctx.Done(): + b.DataOrTimerDone() + return b.DataWait case wk.DataReqs <- &fnpb.Elements{ Timers: timers, Data: []*fnpb.Elements_Data{ @@ -191,9 +212,6 @@ func (b *B) ProcessOn(ctx context.Context, wk *W) <-chan struct{} { }, }, }: - case <-ctx.Done(): - b.DataOrTimerDone() - return b.DataWait } return b.DataWait diff --git a/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go b/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go index 1f129595abef..c2c988aa097f 100644 --- a/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go +++ b/sdks/go/pkg/beam/runners/prism/internal/worker/worker.go @@ -68,6 +68,7 @@ type W struct { // These are the ID sources inst uint64 connected, stopped atomic.Bool + StoppedChan chan struct{} // Channel to Broadcast stopped state. InstReqs chan *fnpb.InstructionRequest DataReqs chan *fnpb.Elements @@ -96,8 +97,9 @@ func New(id, env string) *W { lis: lis, server: grpc.NewServer(opts...), - InstReqs: make(chan *fnpb.InstructionRequest, 10), - DataReqs: make(chan *fnpb.Elements, 10), + InstReqs: make(chan *fnpb.InstructionRequest, 10), + DataReqs: make(chan *fnpb.Elements, 10), + StoppedChan: make(chan struct{}), activeInstructions: make(map[string]controlResponder), Descriptors: make(map[string]*fnpb.ProcessBundleDescriptor), @@ -132,12 +134,26 @@ func (wk *W) LogValue() slog.Value { ) } +// shutdown safely closes channels, and can be called in the event of SDK crashes. +// +// Splitting this logic from the GRPC server Stop is necessary, since a worker +// crash would be handled in a streaming RPC context, which will block GRPC +// stop calls. +func (wk *W) shutdown() { + // If this is the first call to "stop" this worker, also close the channels. + if wk.stopped.CompareAndSwap(false, true) { + slog.Debug("shutdown", "worker", wk, "firstTime", true) + close(wk.StoppedChan) + close(wk.InstReqs) + close(wk.DataReqs) + } else { + slog.Debug("shutdown", "worker", wk, "firstTime", false) + } +} + // Stop the GRPC server. func (wk *W) Stop() { - slog.Debug("stopping", "worker", wk) - wk.stopped.Store(true) - close(wk.InstReqs) - close(wk.DataReqs) + wk.shutdown() // Give the SDK side 5 seconds to gracefully stop, before // hard stopping all RPCs. @@ -331,17 +347,21 @@ func (wk *W) Control(ctrl fnpb.BeamFnControl_ControlServer) error { case <-ctrl.Context().Done(): wk.mu.Lock() // Fail extant instructions - slog.Debug("SDK Disconnected", "worker", wk, "ctx_error", ctrl.Context().Err(), "outstanding_instructions", len(wk.activeInstructions)) + err := context.Cause(ctrl.Context()) + slog.Debug("SDK Disconnected", "worker", wk, "ctx_error", err, "outstanding_instructions", len(wk.activeInstructions)) - msg := fmt.Sprintf("SDK worker disconnected: %v, %v active instructions", wk.String(), len(wk.activeInstructions)) + msg := fmt.Sprintf("SDK worker disconnected: %v, %v active instructions, error: %v", wk.String(), len(wk.activeInstructions), err) for instID, b := range wk.activeInstructions { b.Respond(&fnpb.InstructionResponse{ InstructionId: instID, Error: msg, }) } + // Soft shutdown to prevent GRPC shutdown from being blocked by this + // streaming call. + wk.shutdown() wk.mu.Unlock() - return context.Cause(ctrl.Context()) + return err case err := <-done: if err != nil { slog.Warn("Control done", "error", err, "worker", wk) @@ -639,9 +659,22 @@ func (wk *W) sendInstruction(ctx context.Context, req *fnpb.InstructionRequest) if wk.Stopped() { return nil } - wk.InstReqs <- req + select { + case <-wk.StoppedChan: + return &fnpb.InstructionResponse{ + InstructionId: progInst, + Error: "worker stopped before send", + } + case wk.InstReqs <- req: + // desired outcome + } select { + case <-wk.StoppedChan: + return &fnpb.InstructionResponse{ + InstructionId: progInst, + Error: "worker stopped before receive", + } case <-ctx.Done(): return &fnpb.InstructionResponse{ InstructionId: progInst, From e509e703a504d74da778ce16541d3ee6f918a646 Mon Sep 17 00:00:00 2001 From: Damon Date: Thu, 5 Dec 2024 13:00:54 -0800 Subject: [PATCH 077/135] Synchronize JdbcSchemaIO provider and Python transform configuration field order (#33288) * Move order of write_batch_size * Reorder Java schema instead * Revert jdbc.py changes --- .github/trigger_files/beam_PostCommit_Python.json | 2 +- .../java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Python.json b/.github/trigger_files/beam_PostCommit_Python.json index 2d7af65a3815..00bd9e035648 100644 --- a/.github/trigger_files/beam_PostCommit_Python.json +++ b/.github/trigger_files/beam_PostCommit_Python.json @@ -1,5 +1,5 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run.", - "modification": 5 + "modification": 6 } diff --git a/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java b/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java index 11034aee1cdf..23221042938b 100644 --- a/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java +++ b/sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcSchemaIOProvider.java @@ -68,13 +68,13 @@ public Schema configurationSchema() { .addNullableField("disableAutoCommit", FieldType.BOOLEAN) .addNullableField("outputParallelization", FieldType.BOOLEAN) .addNullableField("autosharding", FieldType.BOOLEAN) - .addNullableField("writeBatchSize", FieldType.INT64) // Partitioning support. If you specify a partition column we will use that instead of // readQuery .addNullableField("partitionColumn", FieldType.STRING) .addNullableField("partitions", FieldType.INT16) .addNullableField("maxConnections", FieldType.INT16) .addNullableField("driverJars", FieldType.STRING) + .addNullableField("writeBatchSize", FieldType.INT64) .build(); } From 7df49a520b5660aadfe8ee6edfa26076277cb072 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Thu, 5 Dec 2024 13:46:15 -0800 Subject: [PATCH 078/135] Clarificaiton on yaml invocation. --- website/www/site/content/en/documentation/sdks/yaml.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/www/site/content/en/documentation/sdks/yaml.md b/website/www/site/content/en/documentation/sdks/yaml.md index 4f9ee4081439..3559a18076ba 100644 --- a/website/www/site/content/en/documentation/sdks/yaml.md +++ b/website/www/site/content/en/documentation/sdks/yaml.md @@ -712,7 +712,7 @@ options: It is a common to want to run a single Beam pipeline in different contexts and/or with different configurations. -When running a YAML pipeline either locally or via the gcloud, +When running a YAML pipeline using `apache_beam.yaml.main` or via gcloud, the yaml file can be parameterized with externally provided variables using the [jinja variable syntax](https://jinja.palletsprojects.com/en/stable/templates/#variables). The values are then passed via a `--jinja_variables` command line flag. From d379968e7ee8a108b2f019702ffb4525f153e070 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:32:14 -0800 Subject: [PATCH 079/135] Bump github.com/aws/aws-sdk-go-v2/credentials in /sdks (#33304) Bumps [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) from 1.17.45 to 1.17.47. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.45...credentials/v1.17.47) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/credentials dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 18 +++++++++--------- sdks/go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 5ea567825135..dc380debf3ed 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -32,7 +32,7 @@ require ( cloud.google.com/go/storage v1.47.0 github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.28.4 - github.com/aws/aws-sdk-go-v2/credentials v1.17.45 + github.com/aws/aws-sdk-go-v2/credentials v1.17.47 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38 github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0 github.com/aws/smithy-go v1.22.1 @@ -132,18 +132,18 @@ require ( github.com/apache/thrift v0.17.0 // indirect github.com/aws/aws-sdk-go v1.34.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/sdks/go.sum b/sdks/go.sum index d5747e210a5a..eae2e9ad22b6 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -697,31 +697,31 @@ github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/y github.com/aws/aws-sdk-go-v2/config v1.28.4 h1:qgD0MKmkIzZR2DrAjWJcI9UkndjR+8f6sjUQvXh0mb0= github.com/aws/aws-sdk-go-v2/config v1.28.4/go.mod h1:LgnWnNzHZw4MLplSyEGia0WgJ/kCGD86zGCjvNpehJs= github.com/aws/aws-sdk-go-v2/credentials v1.3.1/go.mod h1:r0n73xwsIVagq8RsxmZbGSRQFj9As3je72C2WzUIToc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.45 h1:DUgm5lFso57E7150RBgu1JpVQoF8fAPretiDStIuVjg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.45/go.mod h1:dnBpENcPC1ekZrGpSWspX+ZRGzhkvqngT2Qp5xBR1dY= +github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= +github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDucWfA2zqQCYCOMCDHiCOciALyNw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 h1:woXadbf0c7enQ2UGCi8gW/WuKmE0xIzxBF/eD94jMKQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19/go.mod h1:zminj5ucw7w0r65bP6nhyOd3xL6veAUMc3ElGMoLVb4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.3.2/go.mod h1:qaqQiHSrOUVOfKe6fhgQ6UzhxjwqVW8aHNegd6Ws4w4= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38 h1:xN0PViSptTHJ7QIKyWeWntuTCZoejutTPfhsZIoMDy0= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38/go.mod h1:orUzUoWBICDyc+hz49KpySb3sa2Tw3c0IaFqrH4c4dg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 h1:A2w6m6Tmr+BNXjDsr7M90zkWjsu4JXHwrzPg235STs4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23/go.mod h1:35EVp9wyeANdujZruvHiQUAo9E3vbhnIO1mTCAxMlY0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 h1:pgYW9FCabt2M25MoHYCfMrVY2ghiiBKYWUVXfwZs+sU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23/go.mod h1:c48kLgzO19wAu3CPkDWC28JbaJ+hfQlsdl7I2+oqIbk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 h1:1SZBDiRzzs3sNhOMVApyWPduWYGAX0imGy06XiBnCAM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23/go.mod h1:i9TkxgbZmHVh2S0La6CAXtnyFhlCX/pJ0JsOvBAS6Mk= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.1/go.mod h1:v33JQ57i2nekYTA70Mb+O18KeH4KqhdqxTJZNK1zdRE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 h1:aaPpoG15S2qHkWm4KlEyF01zovK1nW4BBbyXuHNSE90= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4/go.mod h1:eD9gS2EARTKgGr/W5xwgY/ik9z/zqpW+m/xOQbVxrMk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 h1:tHxQi/XHPK0ctd/wdOw0t7Xrc2OxcRCnVzv8lwWPu0c= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4/go.mod h1:4GQbF1vJzG60poZqWatZlhP31y8PGCCVTvIGPdaaYJ0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.1/go.mod h1:6EQZIwNNvHpq/2/QSJnp4+ECvqIy55w95Ofs0ze+nGQ= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 h1:E5ZAVOmI2apR8ADb72Q63KqwwwdW1XcMeXIlrZ1Psjg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4/go.mod h1:wezzqVUOVVdk+2Z/JzQT4NxAU0NbhRe5W8pIE72jsWI= @@ -729,13 +729,13 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.11.1/go.mod h1:XLAGFrEjbvMCLvAtWLLP32 github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0 h1:SwaJ0w0MOp0pBTIKTamLVeTKD+iOWyNJRdJ2KCQRg6Q= github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0/go.mod h1:TMhLIyRIyoGVlaEMAt+ITMbwskSTpcGsCPDq91/ihY0= github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 h1:HJwZwRt2Z2Tdec+m+fPjvdmkq2s9Ra+VR0hjF7V2o40= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.5/go.mod h1:wrMCEwjFPms+V86TCQQeOxQF/If4vT44FGIOFiMC2ck= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 h1:zcx9LiGWZ6i6pjdcoE9oXAB6mUdeyC36Ia/QEiIvYdg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4/go.mod h1:Tp/ly1cTjRLGBBmNccFumbZ8oqpZlpdhFf80SrRh4is= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 h1:s7LRgBqhwLaxcocnAniBJp7gaAB+4I4vHzqUqjH18yc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.0/go.mod h1:9XEUty5v5UAsMiFOBJrNibZgwCeOma73jgGwwhgffa8= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= From 222086bf7448caf268b2c44bcf5d51ec4a156394 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:38:30 -0500 Subject: [PATCH 080/135] Bump golang.org/x/text from 0.20.0 to 0.21.0 in /sdks (#33305) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.20.0 to 0.21.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 4 ++-- sdks/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index dc380debf3ed..9e259d8ede9d 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -55,9 +55,9 @@ require ( go.mongodb.org/mongo-driver v1.17.1 golang.org/x/net v0.31.0 golang.org/x/oauth2 v0.24.0 - golang.org/x/sync v0.9.0 + golang.org/x/sync v0.10.0 golang.org/x/sys v0.27.0 - golang.org/x/text v0.20.0 + golang.org/x/text v0.21.0 google.golang.org/api v0.210.0 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 google.golang.org/grpc v1.67.1 diff --git a/sdks/go.sum b/sdks/go.sum index eae2e9ad22b6..bf0bfe08db1b 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -1437,8 +1437,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1554,8 +1554,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 288c1569d1eca4e8e431255ab74c1ffb3d9b05fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:39:03 -0500 Subject: [PATCH 081/135] Bump github.com/aws/aws-sdk-go-v2/feature/s3/manager in /sdks (#33303) Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.17.38 to 1.17.43. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.38...credentials/v1.17.43) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sdks/go.mod | 14 +++++++------- sdks/go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/sdks/go.mod b/sdks/go.mod index 9e259d8ede9d..a73cd530325f 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -31,10 +31,10 @@ require ( cloud.google.com/go/spanner v1.73.0 cloud.google.com/go/storage v1.47.0 github.com/aws/aws-sdk-go-v2 v1.32.6 - github.com/aws/aws-sdk-go-v2/config v1.28.4 + github.com/aws/aws-sdk-go-v2/config v1.28.6 github.com/aws/aws-sdk-go-v2/credentials v1.17.47 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38 - github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.43 + github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 github.com/aws/smithy-go v1.22.1 github.com/docker/go-connections v0.5.0 github.com/dustin/go-humanize v1.0.1 @@ -131,16 +131,16 @@ require ( github.com/apache/arrow/go/arrow v0.0.0-20200730104253-651201b0f516 // indirect github.com/apache/thrift v0.17.0 // indirect github.com/aws/aws-sdk-go v1.34.0 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect diff --git a/sdks/go.sum b/sdks/go.sum index bf0bfe08db1b..a0f784b9789e 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -691,11 +691,11 @@ github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA= -github.com/aws/aws-sdk-go-v2/config v1.28.4 h1:qgD0MKmkIzZR2DrAjWJcI9UkndjR+8f6sjUQvXh0mb0= -github.com/aws/aws-sdk-go-v2/config v1.28.4/go.mod h1:LgnWnNzHZw4MLplSyEGia0WgJ/kCGD86zGCjvNpehJs= +github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= +github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= github.com/aws/aws-sdk-go-v2/credentials v1.3.1/go.mod h1:r0n73xwsIVagq8RsxmZbGSRQFj9As3je72C2WzUIToc= github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= @@ -703,8 +703,8 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDu github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.3.2/go.mod h1:qaqQiHSrOUVOfKe6fhgQ6UzhxjwqVW8aHNegd6Ws4w4= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38 h1:xN0PViSptTHJ7QIKyWeWntuTCZoejutTPfhsZIoMDy0= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.38/go.mod h1:orUzUoWBICDyc+hz49KpySb3sa2Tw3c0IaFqrH4c4dg= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.43 h1:iLdpkYZ4cXIQMO7ud+cqMWR1xK5ESbt1rvN77tRi1BY= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.43/go.mod h1:OgbsKPAswXDd5kxnR4vZov69p3oYjbvUyIRBAAV0y9o= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= @@ -712,22 +712,22 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 h1:1SZBDiRzzs3sNhOMVApyWPduWYGAX0imGy06XiBnCAM= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23/go.mod h1:i9TkxgbZmHVh2S0La6CAXtnyFhlCX/pJ0JsOvBAS6Mk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.1/go.mod h1:v33JQ57i2nekYTA70Mb+O18KeH4KqhdqxTJZNK1zdRE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 h1:aaPpoG15S2qHkWm4KlEyF01zovK1nW4BBbyXuHNSE90= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4/go.mod h1:eD9gS2EARTKgGr/W5xwgY/ik9z/zqpW+m/xOQbVxrMk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.1/go.mod h1:6EQZIwNNvHpq/2/QSJnp4+ECvqIy55w95Ofs0ze+nGQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 h1:E5ZAVOmI2apR8ADb72Q63KqwwwdW1XcMeXIlrZ1Psjg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4/go.mod h1:wezzqVUOVVdk+2Z/JzQT4NxAU0NbhRe5W8pIE72jsWI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= github.com/aws/aws-sdk-go-v2/service/s3 v1.11.1/go.mod h1:XLAGFrEjbvMCLvAtWLLP32yTv8GpBquCApZEycDLunI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0 h1:SwaJ0w0MOp0pBTIKTamLVeTKD+iOWyNJRdJ2KCQRg6Q= -github.com/aws/aws-sdk-go-v2/service/s3 v1.67.0/go.mod h1:TMhLIyRIyoGVlaEMAt+ITMbwskSTpcGsCPDq91/ihY0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= From cd02853f7fcf085136909353d5b6fc1c3cbe9879 Mon Sep 17 00:00:00 2001 From: Jan Lukavsky Date: Thu, 5 Dec 2024 16:16:16 +0100 Subject: [PATCH 082/135] [infra] #33309 automatically apply spotless --- build.gradle.kts | 16 ++++++++++++++++ runners/flink/flink_runner.gradle | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index d96e77a4c78c..0adb29058479 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -647,6 +647,22 @@ tasks.register("checkSetup") { dependsOn(":examples:java:wordCount") } +// if not disabled make spotlessApply dependency of compileJava and compileTestJava +val disableSpotlessCheck: String by project +val isSpotlessDisabled = project.hasProperty("disableSpotlessCheck") && + disableSpotlessCheck == "true" +if (!isSpotlessDisabled) { + subprojects { + afterEvaluate { + tasks.findByName("spotlessApply")?.let { + listOf("compileJava", "compileTestJava").forEach { + t -> tasks.findByName(t)?.let { f -> f.dependsOn("spotlessApply") } + } + } + } + } +} + // Generates external transform config project.tasks.register("generateExternalTransformsConfig") { dependsOn(":sdks:python:generateExternalTransformsConfig") diff --git a/runners/flink/flink_runner.gradle b/runners/flink/flink_runner.gradle index d13e1c5faf6e..be39d4e0b012 100644 --- a/runners/flink/flink_runner.gradle +++ b/runners/flink/flink_runner.gradle @@ -422,3 +422,8 @@ createPipelineOptionsTableTask('Python') // Update the pipeline options documentation before running the tests test.dependsOn(generatePipelineOptionsTableJava) test.dependsOn(generatePipelineOptionsTablePython) + +// delegate spotlessApply to :runners:flink:spotlessApply +tasks.named("spotlessApply") { + dependsOn ":runners:flink:spotlessApply" +} From 89ae2c104dfba9ddbf79315857c96e1c121254fa Mon Sep 17 00:00:00 2001 From: Ahmed Abualsaud <65791736+ahmedabu98@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:05:29 -0500 Subject: [PATCH 083/135] Enable managed service in Python tests (#33190) * enable managed service * format fix * trigger xlang tests --- ...m_PostCommit_Python_Xlang_IO_Dataflow.json | 2 +- ...eam_PostCommit_Python_Xlang_IO_Direct.json | 2 +- .../transforms/managed_iceberg_it_test.py | 30 ++++++++----------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Dataflow.json b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Dataflow.json index c537844dc84a..b26833333238 100644 --- a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Dataflow.json +++ b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Dataflow.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 3 + "modification": 2 } diff --git a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json index b26833333238..c537844dc84a 100644 --- a/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json +++ b/.github/trigger_files/beam_PostCommit_Python_Xlang_IO_Direct.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 2 + "modification": 3 } diff --git a/sdks/python/apache_beam/transforms/managed_iceberg_it_test.py b/sdks/python/apache_beam/transforms/managed_iceberg_it_test.py index 0dfa2aa19c51..a09203f313eb 100644 --- a/sdks/python/apache_beam/transforms/managed_iceberg_it_test.py +++ b/sdks/python/apache_beam/transforms/managed_iceberg_it_test.py @@ -16,15 +16,13 @@ # import os -import secrets -import shutil -import tempfile import time import unittest import pytest import apache_beam as beam +from apache_beam.testing.test_pipeline import TestPipeline from apache_beam.testing.util import assert_that from apache_beam.testing.util import equal_to @@ -35,17 +33,15 @@ "EXPANSION_JARS environment var is not provided, " "indicating that jars have not been built") class ManagedIcebergIT(unittest.TestCase): - def setUp(self): - self._tempdir = tempfile.mkdtemp() - if not os.path.exists(self._tempdir): - os.mkdir(self._tempdir) - test_warehouse_name = 'test_warehouse_%d_%s' % ( - int(time.time()), secrets.token_hex(3)) - self.warehouse_path = os.path.join(self._tempdir, test_warehouse_name) - os.mkdir(self.warehouse_path) + WAREHOUSE = "gs://temp-storage-for-end-to-end-tests/xlang-python-using-java" - def tearDown(self): - shutil.rmtree(self._tempdir, ignore_errors=False) + def setUp(self): + self.test_pipeline = TestPipeline(is_integration_test=True) + self.args = self.test_pipeline.get_full_options_as_args() + self.args.extend([ + '--experiments=enable_managed_transforms', + '--dataflow_endpoint=https://dataflow-staging.sandbox.googleapis.com', + ]) def _create_row(self, num: int): return beam.Row( @@ -57,24 +53,24 @@ def _create_row(self, num: int): def test_write_read_pipeline(self): iceberg_config = { - "table": "test.write_read", + "table": "test_iceberg_write_read.test_" + str(int(time.time())), "catalog_name": "default", "catalog_properties": { "type": "hadoop", - "warehouse": f"file://{self.warehouse_path}", + "warehouse": self.WAREHOUSE, } } rows = [self._create_row(i) for i in range(100)] expected_dicts = [row.as_dict() for row in rows] - with beam.Pipeline() as write_pipeline: + with beam.Pipeline(argv=self.args) as write_pipeline: _ = ( write_pipeline | beam.Create(rows) | beam.managed.Write(beam.managed.ICEBERG, config=iceberg_config)) - with beam.Pipeline() as read_pipeline: + with beam.Pipeline(argv=self.args) as read_pipeline: output_dicts = ( read_pipeline | beam.managed.Read(beam.managed.ICEBERG, config=iceberg_config) From 0bf6f69c8bb1670795d86cd3fdfa5b2a402f963e Mon Sep 17 00:00:00 2001 From: Jeff Kinard Date: Fri, 6 Dec 2024 15:49:47 -0500 Subject: [PATCH 084/135] [yaml] various inline provider doc fixes (#33296) * [yaml] various inline provider doc fixes Signed-off-by: Jeffrey Kinard * Update yaml_combine.py --------- Signed-off-by: Jeffrey Kinard Co-authored-by: Robert Bradshaw --- sdks/python/apache_beam/yaml/yaml_combine.py | 6 ++++ sdks/python/apache_beam/yaml/yaml_mapping.py | 29 +++++++++++--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/sdks/python/apache_beam/yaml/yaml_combine.py b/sdks/python/apache_beam/yaml/yaml_combine.py index a28bef52ea31..b7499f3b0c7a 100644 --- a/sdks/python/apache_beam/yaml/yaml_combine.py +++ b/sdks/python/apache_beam/yaml/yaml_combine.py @@ -94,6 +94,12 @@ class PyJsYamlCombine(beam.PTransform): See also the documentation on [YAML Aggregation](https://beam.apache.org/documentation/sdks/yaml-combine/). + + Args: + group_by: The field(s) to aggregate on. + combine: The aggregation function to use. + language: The language used to define (and execute) the + custom callables in `combine`. Defaults to generic. """ def __init__( self, diff --git a/sdks/python/apache_beam/yaml/yaml_mapping.py b/sdks/python/apache_beam/yaml/yaml_mapping.py index 9f92f59f42b6..8f4a2118c236 100644 --- a/sdks/python/apache_beam/yaml/yaml_mapping.py +++ b/sdks/python/apache_beam/yaml/yaml_mapping.py @@ -428,19 +428,19 @@ class _StripErrorMetadata(beam.PTransform): For example, in the following pipeline snippet:: - - name: MyMappingTransform - type: MapToFields - input: SomeInput - config: - language: python - fields: - ... - error_handling: - output: errors - - - name: RecoverOriginalElements - type: StripErrorMetadata - input: MyMappingTransform.errors + - name: MyMappingTransform + type: MapToFields + input: SomeInput + config: + language: python + fields: + ... + error_handling: + output: errors + + - name: RecoverOriginalElements + type: StripErrorMetadata + input: MyMappingTransform.errors the output of `RecoverOriginalElements` will contain exactly those elements from SomeInput that failed to processes (whereas `MyMappingTransform.errors` @@ -453,6 +453,9 @@ class _StripErrorMetadata(beam.PTransform): _ERROR_FIELD_NAMES = ('failed_row', 'element', 'record') + def __init__(self): + super().__init__(label=None) + def expand(self, pcoll): try: existing_fields = { From d138b7501022fb63b9dde427c42eeabddd5c2243 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 6 Dec 2024 16:20:50 -0500 Subject: [PATCH 085/135] Add throttling metrics and retries to vertex embeddings (#33311) * Add throttling metrics and retries to vertex embeddings * Format + run postcommits * fix + lint --- .../trigger_files/beam_PostCommit_Python.json | 2 +- .../ml/transforms/embeddings/vertex_ai.py | 103 +++++++++++++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/.github/trigger_files/beam_PostCommit_Python.json b/.github/trigger_files/beam_PostCommit_Python.json index 00bd9e035648..9c7a70ceed74 100644 --- a/.github/trigger_files/beam_PostCommit_Python.json +++ b/.github/trigger_files/beam_PostCommit_Python.json @@ -1,5 +1,5 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run.", - "modification": 6 + "modification": 7 } diff --git a/sdks/python/apache_beam/ml/transforms/embeddings/vertex_ai.py b/sdks/python/apache_beam/ml/transforms/embeddings/vertex_ai.py index 6fe8320e758b..6df505508ae9 100644 --- a/sdks/python/apache_beam/ml/transforms/embeddings/vertex_ai.py +++ b/sdks/python/apache_beam/ml/transforms/embeddings/vertex_ai.py @@ -19,20 +19,27 @@ # Follow https://cloud.google.com/vertex-ai/docs/python-sdk/use-vertex-ai-python-sdk # pylint: disable=line-too-long # to install Vertex AI Python SDK. +import logging +import time from collections.abc import Iterable from collections.abc import Sequence from typing import Any from typing import Optional +from google.api_core.exceptions import ServerError +from google.api_core.exceptions import TooManyRequests from google.auth.credentials import Credentials import apache_beam as beam import vertexai +from apache_beam.io.components.adaptive_throttler import AdaptiveThrottler +from apache_beam.metrics.metric import Metrics from apache_beam.ml.inference.base import ModelHandler from apache_beam.ml.inference.base import RunInference from apache_beam.ml.transforms.base import EmbeddingsManager from apache_beam.ml.transforms.base import _ImageEmbeddingHandler from apache_beam.ml.transforms.base import _TextEmbeddingHandler +from apache_beam.utils import retry from vertexai.language_models import TextEmbeddingInput from vertexai.language_models import TextEmbeddingModel from vertexai.vision_models import Image @@ -51,6 +58,26 @@ "CLUSTERING" ] _BATCH_SIZE = 5 # Vertex AI limits requests to 5 at a time. +_MSEC_TO_SEC = 1000 + +LOGGER = logging.getLogger("VertexAIEmbeddings") + + +def _retry_on_appropriate_gcp_error(exception): + """ + Retry filter that returns True if a returned HTTP error code is 5xx or 429. + This is used to retry remote requests that fail, most notably 429 + (TooManyRequests.) + + Args: + exception: the returned exception encountered during the request/response + loop. + + Returns: + boolean indication whether or not the exception is a Server Error (5xx) or + a TooManyRequests (429) error. + """ + return isinstance(exception, (TooManyRequests, ServerError)) class _VertexAITextEmbeddingHandler(ModelHandler): @@ -74,6 +101,41 @@ def __init__( self.task_type = task_type self.title = title + # Configure AdaptiveThrottler and throttling metrics for client-side + # throttling behavior. + # See https://docs.google.com/document/d/1ePorJGZnLbNCmLD9mR7iFYOdPsyDA1rDnTpYnbdrzSU/edit?usp=sharing + # for more details. + self.throttled_secs = Metrics.counter( + VertexAIImageEmbeddings, "cumulativeThrottlingSeconds") + self.throttler = AdaptiveThrottler( + window_ms=1, bucket_ms=1, overload_ratio=2) + + @retry.with_exponential_backoff( + num_retries=5, retry_filter=_retry_on_appropriate_gcp_error) + def get_request( + self, + text_batch: Sequence[TextEmbeddingInput], + model: MultiModalEmbeddingModel, + throttle_delay_secs: int): + while self.throttler.throttle_request(time.time() * _MSEC_TO_SEC): + LOGGER.info( + "Delaying request for %d seconds due to previous failures", + throttle_delay_secs) + time.sleep(throttle_delay_secs) + self.throttled_secs.inc(throttle_delay_secs) + + try: + req_time = time.time() + prediction = model.get_embeddings(text_batch) + self.throttler.successful_request(req_time * _MSEC_TO_SEC) + return prediction + except TooManyRequests as e: + LOGGER.warning("request was limited by the service with code %i", e.code) + raise + except Exception as e: + LOGGER.error("unexpected exception raised as part of request, got %s", e) + raise + def run_inference( self, batch: Sequence[str], @@ -89,7 +151,8 @@ def run_inference( text=text, title=self.title, task_type=self.task_type) for text in text_batch ] - embeddings_batch = model.get_embeddings(text_batch) + embeddings_batch = self.get_request( + text_batch=text_batch, model=model, throttle_delay_secs=5) embeddings.extend([el.values for el in embeddings_batch]) return embeddings @@ -173,6 +236,41 @@ def __init__( self.model_name = model_name self.dimension = dimension + # Configure AdaptiveThrottler and throttling metrics for client-side + # throttling behavior. + # See https://docs.google.com/document/d/1ePorJGZnLbNCmLD9mR7iFYOdPsyDA1rDnTpYnbdrzSU/edit?usp=sharing + # for more details. + self.throttled_secs = Metrics.counter( + VertexAIImageEmbeddings, "cumulativeThrottlingSeconds") + self.throttler = AdaptiveThrottler( + window_ms=1, bucket_ms=1, overload_ratio=2) + + @retry.with_exponential_backoff( + num_retries=5, retry_filter=_retry_on_appropriate_gcp_error) + def get_request( + self, + img: Image, + model: MultiModalEmbeddingModel, + throttle_delay_secs: int): + while self.throttler.throttle_request(time.time() * _MSEC_TO_SEC): + LOGGER.info( + "Delaying request for %d seconds due to previous failures", + throttle_delay_secs) + time.sleep(throttle_delay_secs) + self.throttled_secs.inc(throttle_delay_secs) + + try: + req_time = time.time() + prediction = model.get_embeddings(image=img, dimension=self.dimension) + self.throttler.successful_request(req_time * _MSEC_TO_SEC) + return prediction + except TooManyRequests as e: + LOGGER.warning("request was limited by the service with code %i", e.code) + raise + except Exception as e: + LOGGER.error("unexpected exception raised as part of request, got %s", e) + raise + def run_inference( self, batch: Sequence[Image], @@ -182,8 +280,7 @@ def run_inference( embeddings = [] # Maximum request size for muli-model embedding models is 1. for img in batch: - embedding_response = model.get_embeddings( - image=img, dimension=self.dimension) + embedding_response = self.get_request(img, model, throttle_delay_secs=5) embeddings.append(embedding_response.image_embedding) return embeddings From 17129643d307e55670de28ec6f07ad326d666b77 Mon Sep 17 00:00:00 2001 From: Hai Joey Tran Date: Fri, 6 Dec 2024 17:03:32 -0500 Subject: [PATCH 086/135] Fix FlatMapTuple typehint bug (#33307) * create unit test * minimize to not using flatmaptuple * fix by adding a tuple conersion in flatmaptuple * add comment referring to ticket * remove extra pipeline * manually isort * retrigger builder * retrigger builder * isort? * try manually isorting again * Revert "try manually isorting again" This reverts commit a0fac321a15a07169fb27e217b61be3edc73d157. * manually fix isort --- sdks/python/apache_beam/transforms/core.py | 2 +- .../typehints/typed_pipeline_test.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/transforms/core.py b/sdks/python/apache_beam/transforms/core.py index 9c798d3ce6dc..4d1678d72a69 100644 --- a/sdks/python/apache_beam/transforms/core.py +++ b/sdks/python/apache_beam/transforms/core.py @@ -2238,7 +2238,7 @@ def FlatMapTuple(fn, *args, **kwargs): # pylint: disable=invalid-name if defaults or args or kwargs: wrapper = lambda x, *args, **kwargs: fn(*(tuple(x) + args), **kwargs) else: - wrapper = lambda x: fn(*x) + wrapper = lambda x: fn(*tuple(x)) # Proxy the type-hint information from the original function to this new # wrapped function. diff --git a/sdks/python/apache_beam/typehints/typed_pipeline_test.py b/sdks/python/apache_beam/typehints/typed_pipeline_test.py index 44318fa44a8c..820f78fa9ef5 100644 --- a/sdks/python/apache_beam/typehints/typed_pipeline_test.py +++ b/sdks/python/apache_beam/typehints/typed_pipeline_test.py @@ -21,6 +21,7 @@ import typing import unittest +from typing import Tuple import apache_beam as beam from apache_beam import pvalue @@ -999,5 +1000,22 @@ def filter_fn(element: int) -> bool: self.assertEqual(th.output_types, ((int, ), {})) +class TestFlatMapTuple(unittest.TestCase): + def test_flatmaptuple(self): + # Regression test. See + # https://github.com/apache/beam/issues/33014 + + def identity(x: Tuple[str, int]) -> Tuple[str, int]: + return x + + with beam.Pipeline() as p: + # Just checking that this doesn't raise an exception. + ( + p + | "Generate input" >> beam.Create([('P1', [2])]) + | "Flat" >> beam.FlatMapTuple(lambda k, vs: [(k, v) for v in vs]) + | "Identity" >> beam.Map(identity)) + + if __name__ == '__main__': unittest.main() From 0dae9c99427bfe484c26c138854d71ff9bfdeea3 Mon Sep 17 00:00:00 2001 From: Hai Joey Tran Date: Fri, 6 Dec 2024 20:56:38 -0500 Subject: [PATCH 087/135] Polish FlatMapTuple and MapTuple docstrings (#33316) --- sdks/python/apache_beam/transforms/core.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/sdks/python/apache_beam/transforms/core.py b/sdks/python/apache_beam/transforms/core.py index 4d1678d72a69..b420d1d66d09 100644 --- a/sdks/python/apache_beam/transforms/core.py +++ b/sdks/python/apache_beam/transforms/core.py @@ -2117,15 +2117,13 @@ def MapTuple(fn, *args, **kwargs): # pylint: disable=invalid-name r""":func:`MapTuple` is like :func:`Map` but expects tuple inputs and flattens them into multiple input arguments. - beam.MapTuple(lambda a, b, ...: ...) - In other words - beam.MapTuple(fn) + "SwapKV" >> beam.Map(lambda kv: (kv[1], kv[0])) is equivalent to - beam.Map(lambda element, ...: fn(\*element, ...)) + "SwapKV" >> beam.MapTuple(lambda k, v: (v, k)) This can be useful when processing a PCollection of tuples (e.g. key-value pairs). @@ -2191,19 +2189,13 @@ def FlatMapTuple(fn, *args, **kwargs): # pylint: disable=invalid-name r""":func:`FlatMapTuple` is like :func:`FlatMap` but expects tuple inputs and flattens them into multiple input arguments. - beam.FlatMapTuple(lambda a, b, ...: ...) - - is equivalent to Python 2 - - beam.FlatMap(lambda (a, b, ...), ...: ...) - In other words - beam.FlatMapTuple(fn) + beam.FlatMap(lambda start_end: range(start_end[0], start_end[1])) is equivalent to - beam.FlatMap(lambda element, ...: fn(\*element, ...)) + beam.FlatMapTuple(lambda start, end: range(start, end)) This can be useful when processing a PCollection of tuples (e.g. key-value pairs). From 85bff0d2c1adb743c4738f51428c88fb74532021 Mon Sep 17 00:00:00 2001 From: Yi Hu Date: Mon, 9 Dec 2024 11:59:38 -0500 Subject: [PATCH 088/135] Reapply "bump hadoop version (#33011)" (#33312) * Reapply "bump hadoop version (#33011)" (#33257) This reverts commit 7e25649b88b1ac08e48db28f8af09978b649da17. * Fix hbase and hcatalog test dependencies * Add missing pinned hadoop dependency version for compat test target --- .../beam_PostCommit_Java_Hadoop_Versions.json | 2 +- .../beam_PreCommit_Java_HBase_IO_Direct.json | 1 + CHANGES.md | 1 + .../org/apache/beam/gradle/BeamModulePlugin.groovy | 2 +- sdks/java/io/hadoop-common/build.gradle | 2 +- sdks/java/io/hadoop-file-system/build.gradle | 2 +- sdks/java/io/hadoop-format/build.gradle | 2 +- sdks/java/io/hbase/build.gradle | 9 ++------- sdks/java/io/hcatalog/build.gradle | 10 +++++++++- sdks/java/io/iceberg/build.gradle | 8 ++++++++ 10 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 .github/trigger_files/beam_PreCommit_Java_HBase_IO_Direct.json diff --git a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json b/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json index 920c8d132e4a..8784d0786c02 100644 --- a/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json +++ b/.github/trigger_files/beam_PostCommit_Java_Hadoop_Versions.json @@ -1,4 +1,4 @@ { "comment": "Modify this file in a trivial way to cause this test suite to run", - "modification": 1 + "modification": 2 } \ No newline at end of file diff --git a/.github/trigger_files/beam_PreCommit_Java_HBase_IO_Direct.json b/.github/trigger_files/beam_PreCommit_Java_HBase_IO_Direct.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/.github/trigger_files/beam_PreCommit_Java_HBase_IO_Direct.json @@ -0,0 +1 @@ +{} diff --git a/CHANGES.md b/CHANGES.md index 1b943a99f8a0..dbadd588ae3f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -64,6 +64,7 @@ * gcs-connector config options can be set via GcsOptions (Java) ([#32769](https://github.com/apache/beam/pull/32769)). * Support for X source added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)). +* Upgraded the default version of Hadoop dependencies to 3.4.1. Hadoop 2.10.2 is still supported (Java) ([#33011](https://github.com/apache/beam/issues/33011)). ## New Features / Improvements diff --git a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy index 84c7c3ecfd4a..2abd43a5d4cc 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -614,7 +614,7 @@ class BeamModulePlugin implements Plugin { // [bomupgrader] determined by: io.grpc:grpc-netty, consistent with: google_cloud_platform_libraries_bom def grpc_version = "1.67.1" def guava_version = "33.1.0-jre" - def hadoop_version = "2.10.2" + def hadoop_version = "3.4.1" def hamcrest_version = "2.1" def influxdb_version = "2.19" def httpclient_version = "4.5.13" diff --git a/sdks/java/io/hadoop-common/build.gradle b/sdks/java/io/hadoop-common/build.gradle index b0303d29ff98..4375001ffa81 100644 --- a/sdks/java/io/hadoop-common/build.gradle +++ b/sdks/java/io/hadoop-common/build.gradle @@ -28,7 +28,7 @@ def hadoopVersions = [ "2102": "2.10.2", "324": "3.2.4", "336": "3.3.6", - "341": "3.4.1", + // "341": "3.4.1", // tests already exercised on the default version ] hadoopVersions.each {kv -> configurations.create("hadoopVersion$kv.key")} diff --git a/sdks/java/io/hadoop-file-system/build.gradle b/sdks/java/io/hadoop-file-system/build.gradle index fafa8b5c7e34..b4ebbfa08c5e 100644 --- a/sdks/java/io/hadoop-file-system/build.gradle +++ b/sdks/java/io/hadoop-file-system/build.gradle @@ -29,7 +29,7 @@ def hadoopVersions = [ "2102": "2.10.2", "324": "3.2.4", "336": "3.3.6", - "341": "3.4.1", + // "341": "3.4.1", // tests already exercised on the default version ] hadoopVersions.each {kv -> configurations.create("hadoopVersion$kv.key")} diff --git a/sdks/java/io/hadoop-format/build.gradle b/sdks/java/io/hadoop-format/build.gradle index 4664005a1fc8..73fc44a0f311 100644 --- a/sdks/java/io/hadoop-format/build.gradle +++ b/sdks/java/io/hadoop-format/build.gradle @@ -33,7 +33,7 @@ def hadoopVersions = [ "2102": "2.10.2", "324": "3.2.4", "336": "3.3.6", - "341": "3.4.1", + // "341": "3.4.1", // tests already exercised on the default version ] hadoopVersions.each {kv -> configurations.create("hadoopVersion$kv.key")} diff --git a/sdks/java/io/hbase/build.gradle b/sdks/java/io/hbase/build.gradle index d85c0fc610bb..07014f2d5e3b 100644 --- a/sdks/java/io/hbase/build.gradle +++ b/sdks/java/io/hbase/build.gradle @@ -34,7 +34,7 @@ test { jvmArgs "-Dtest.build.data.basedirectory=build/test-data" } -def hbase_version = "2.5.5" +def hbase_version = "2.6.1-hadoop3" dependencies { implementation library.java.vendored_guava_32_1_2_jre @@ -46,12 +46,7 @@ dependencies { testImplementation project(path: ":sdks:java:core", configuration: "shadowTest") testImplementation library.java.junit testImplementation library.java.hamcrest - testImplementation library.java.hadoop_minicluster - testImplementation library.java.hadoop_hdfs - testImplementation library.java.hadoop_common + // shaded-testing-utils has shaded all Hadoop/HBase dependencies testImplementation("org.apache.hbase:hbase-shaded-testing-util:$hbase_version") - testImplementation "org.apache.hbase:hbase-hadoop-compat:$hbase_version:tests" - testImplementation "org.apache.hbase:hbase-hadoop2-compat:$hbase_version:tests" testRuntimeOnly project(path: ":runners:direct-java", configuration: "shadow") } - diff --git a/sdks/java/io/hcatalog/build.gradle b/sdks/java/io/hcatalog/build.gradle index 364c10fa738b..d07904f3465e 100644 --- a/sdks/java/io/hcatalog/build.gradle +++ b/sdks/java/io/hcatalog/build.gradle @@ -33,7 +33,7 @@ def hadoopVersions = [ "2102": "2.10.2", "324": "3.2.4", "336": "3.3.6", - "341": "3.4.1", + // "341": "3.4.1", // tests already exercised on the default version ] hadoopVersions.each {kv -> configurations.create("hadoopVersion$kv.key")} @@ -71,13 +71,21 @@ dependencies { testRuntimeOnly project(path: ":runners:direct-java", configuration: "shadow") hadoopVersions.each {kv -> "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-common:$kv.value" + "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-hdfs:$kv.value" + "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-hdfs-client:$kv.value" + "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-mapreduce-client-core:$kv.value" } } hadoopVersions.each {kv -> configurations."hadoopVersion$kv.key" { resolutionStrategy { + force "org.apache.hadoop:hadoop-client:$kv.value" force "org.apache.hadoop:hadoop-common:$kv.value" + force "org.apache.hadoop:hadoop-mapreduce-client-core:$kv.value" + force "org.apache.hadoop:hadoop-minicluster:$kv.value" + force "org.apache.hadoop:hadoop-hdfs:$kv.value" + force "org.apache.hadoop:hadoop-hdfs-client:$kv.value" } } } diff --git a/sdks/java/io/iceberg/build.gradle b/sdks/java/io/iceberg/build.gradle index a2d192b67208..fa1e2426ce69 100644 --- a/sdks/java/io/iceberg/build.gradle +++ b/sdks/java/io/iceberg/build.gradle @@ -71,6 +71,9 @@ dependencies { testRuntimeOnly project(path: ":runners:google-cloud-dataflow-java") hadoopVersions.each {kv -> "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-client:$kv.value" + "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-minicluster:$kv.value" + "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-hdfs-client:$kv.value" + "hadoopVersion$kv.key" "org.apache.hadoop:hadoop-mapreduce-client-core:$kv.value" } } @@ -78,6 +81,11 @@ hadoopVersions.each {kv -> configurations."hadoopVersion$kv.key" { resolutionStrategy { force "org.apache.hadoop:hadoop-client:$kv.value" + force "org.apache.hadoop:hadoop-common:$kv.value" + force "org.apache.hadoop:hadoop-mapreduce-client-core:$kv.value" + force "org.apache.hadoop:hadoop-minicluster:$kv.value" + force "org.apache.hadoop:hadoop-hdfs:$kv.value" + force "org.apache.hadoop:hadoop-hdfs-client:$kv.value" } } } From a806c0ed3a9ead3b7738ae94bf323865abbd5e73 Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:20:12 -0500 Subject: [PATCH 089/135] Add Dataflow Cost Benchmark framework to Beam Python (#33297) * initial benchmark framework code * Implement Dataflow cost benchmark framework + add wordcount example * formatting * move to base wordcount instead * add comment for pipeline execution in wordcount --- ...rdcount_Python_Cost_Benchmark_Dataflow.yml | 91 ++++++++++++++ .../python_wordcount.txt | 28 +++++ sdks/python/apache_beam/examples/wordcount.py | 39 +++--- .../testing/benchmarks/wordcount/__init__.py | 16 +++ .../testing/benchmarks/wordcount/wordcount.py | 39 ++++++ .../load_tests/dataflow_cost_benchmark.py | 113 ++++++++++++++++++ .../load_tests/dataflow_cost_consts.py | 59 +++++++++ 7 files changed, 368 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml create mode 100644 .github/workflows/cost-benchmarks-pipeline-options/python_wordcount.txt create mode 100644 sdks/python/apache_beam/testing/benchmarks/wordcount/__init__.py create mode 100644 sdks/python/apache_beam/testing/benchmarks/wordcount/wordcount.py create mode 100644 sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py create mode 100644 sdks/python/apache_beam/testing/load_tests/dataflow_cost_consts.py diff --git a/.github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml b/.github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml new file mode 100644 index 000000000000..51d1005affbc --- /dev/null +++ b/.github/workflows/beam_Wordcount_Python_Cost_Benchmark_Dataflow.yml @@ -0,0 +1,91 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +name: Wordcount Python Cost Benchmarks Dataflow + +on: + workflow_dispatch: + +#Setting explicit permissions for the action to avoid the default permissions which are `write-all` in case of pull_request_target event +permissions: + actions: write + pull-requests: read + checks: read + contents: read + deployments: read + id-token: none + issues: read + discussions: read + packages: read + pages: read + repository-projects: read + security-events: read + statuses: read + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.issue.number || github.sha || github.head_ref || github.ref }}-${{ github.event.schedule || github.event.comment.id || github.event.sender.login }}' + cancel-in-progress: true + +env: + DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} + INFLUXDB_USER: ${{ secrets.INFLUXDB_USER }} + INFLUXDB_USER_PASSWORD: ${{ secrets.INFLUXDB_USER_PASSWORD }} + +jobs: + beam_Inference_Python_Benchmarks_Dataflow: + if: | + github.event_name == 'workflow_dispatch' + runs-on: [self-hosted, ubuntu-20.04, main] + timeout-minutes: 900 + name: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + strategy: + matrix: + job_name: ["beam_Wordcount_Python_Cost_Benchmarks_Dataflow"] + job_phrase: ["Run Wordcount Cost Benchmark"] + steps: + - uses: actions/checkout@v4 + - name: Setup repository + uses: ./.github/actions/setup-action + with: + comment_phrase: ${{ matrix.job_phrase }} + github_token: ${{ secrets.GITHUB_TOKEN }} + github_job: ${{ matrix.job_name }} (${{ matrix.job_phrase }}) + - name: Setup Python environment + uses: ./.github/actions/setup-environment-action + with: + python-version: '3.10' + - name: Prepare test arguments + uses: ./.github/actions/test-arguments-action + with: + test-type: load + test-language: python + argument-file-paths: | + ${{ github.workspace }}/.github/workflows/cost-benchmarks-pipeline-options/python_wordcount.txt + # The env variables are created and populated in the test-arguments-action as "_test_arguments_" + - name: get current time + run: echo "NOW_UTC=$(date '+%m%d%H%M%S' --utc)" >> $GITHUB_ENV + - name: run wordcount on Dataflow Python + uses: ./.github/actions/gradle-command-self-hosted-action + timeout-minutes: 30 + with: + gradle-command: :sdks:python:apache_beam:testing:load_tests:run + arguments: | + -PloadTest.mainClass=apache_beam.testing.benchmarks.wordcount.wordcount \ + -Prunner=DataflowRunner \ + -PpythonVersion=3.10 \ + '-PloadTest.args=${{ env.beam_Inference_Python_Benchmarks_Dataflow_test_arguments_1 }} --job_name=benchmark-tests-wordcount-python-${{env.NOW_UTC}} --output=gs://temp-storage-for-end-to-end-tests/wordcount/result_wordcount-${{env.NOW_UTC}}.txt' \ \ No newline at end of file diff --git a/.github/workflows/cost-benchmarks-pipeline-options/python_wordcount.txt b/.github/workflows/cost-benchmarks-pipeline-options/python_wordcount.txt new file mode 100644 index 000000000000..424936ddad97 --- /dev/null +++ b/.github/workflows/cost-benchmarks-pipeline-options/python_wordcount.txt @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +--region=us-central1 +--machine_type=n1-standard-2 +--num_workers=1 +--disk_size_gb=50 +--autoscaling_algorithm=NONE +--input_options={} +--staging_location=gs://temp-storage-for-perf-tests/loadtests +--temp_location=gs://temp-storage-for-perf-tests/loadtests +--publish_to_big_query=true +--metrics_dataset=beam_run_inference +--metrics_table=python_wordcount +--runner=DataflowRunner \ No newline at end of file diff --git a/sdks/python/apache_beam/examples/wordcount.py b/sdks/python/apache_beam/examples/wordcount.py index 31407aec6c40..a9138647581c 100644 --- a/sdks/python/apache_beam/examples/wordcount.py +++ b/sdks/python/apache_beam/examples/wordcount.py @@ -45,6 +45,7 @@ from apache_beam.io import WriteToText from apache_beam.options.pipeline_options import PipelineOptions from apache_beam.options.pipeline_options import SetupOptions +from apache_beam.runners.runner import PipelineResult class WordExtractingDoFn(beam.DoFn): @@ -63,7 +64,7 @@ def process(self, element): return re.findall(r'[\w\']+', element, re.UNICODE) -def run(argv=None, save_main_session=True): +def run(argv=None, save_main_session=True) -> PipelineResult: """Main entry point; defines and runs the wordcount pipeline.""" parser = argparse.ArgumentParser() parser.add_argument( @@ -83,27 +84,31 @@ def run(argv=None, save_main_session=True): pipeline_options = PipelineOptions(pipeline_args) pipeline_options.view_as(SetupOptions).save_main_session = save_main_session - # The pipeline will be run on exiting the with block. - with beam.Pipeline(options=pipeline_options) as p: + pipeline = beam.Pipeline(options=pipeline_options) - # Read the text file[pattern] into a PCollection. - lines = p | 'Read' >> ReadFromText(known_args.input) + # Read the text file[pattern] into a PCollection. + lines = pipeline | 'Read' >> ReadFromText(known_args.input) - counts = ( - lines - | 'Split' >> (beam.ParDo(WordExtractingDoFn()).with_output_types(str)) - | 'PairWithOne' >> beam.Map(lambda x: (x, 1)) - | 'GroupAndSum' >> beam.CombinePerKey(sum)) + counts = ( + lines + | 'Split' >> (beam.ParDo(WordExtractingDoFn()).with_output_types(str)) + | 'PairWithOne' >> beam.Map(lambda x: (x, 1)) + | 'GroupAndSum' >> beam.CombinePerKey(sum)) - # Format the counts into a PCollection of strings. - def format_result(word, count): - return '%s: %d' % (word, count) + # Format the counts into a PCollection of strings. + def format_result(word, count): + return '%s: %d' % (word, count) - output = counts | 'Format' >> beam.MapTuple(format_result) + output = counts | 'Format' >> beam.MapTuple(format_result) - # Write the output using a "Write" transform that has side effects. - # pylint: disable=expression-not-assigned - output | 'Write' >> WriteToText(known_args.output) + # Write the output using a "Write" transform that has side effects. + # pylint: disable=expression-not-assigned + output | 'Write' >> WriteToText(known_args.output) + + # Execute the pipeline and return the result. + result = pipeline.run() + result.wait_until_finish() + return result if __name__ == '__main__': diff --git a/sdks/python/apache_beam/testing/benchmarks/wordcount/__init__.py b/sdks/python/apache_beam/testing/benchmarks/wordcount/__init__.py new file mode 100644 index 000000000000..cce3acad34a4 --- /dev/null +++ b/sdks/python/apache_beam/testing/benchmarks/wordcount/__init__.py @@ -0,0 +1,16 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# diff --git a/sdks/python/apache_beam/testing/benchmarks/wordcount/wordcount.py b/sdks/python/apache_beam/testing/benchmarks/wordcount/wordcount.py new file mode 100644 index 000000000000..513ede47e80a --- /dev/null +++ b/sdks/python/apache_beam/testing/benchmarks/wordcount/wordcount.py @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +# pytype: skip-file + +import logging + +from apache_beam.examples import wordcount +from apache_beam.testing.load_tests.dataflow_cost_benchmark import DataflowCostBenchmark + + +class WordcountCostBenchmark(DataflowCostBenchmark): + def __init__(self): + super().__init__() + + def test(self): + extra_opts = {} + extra_opts['output'] = self.pipeline.get_option('output_file') + self.result = wordcount.run( + self.pipeline.get_full_options_as_args(**extra_opts), + save_main_session=False) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + WordcountCostBenchmark().run() diff --git a/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py b/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py new file mode 100644 index 000000000000..b60af1249756 --- /dev/null +++ b/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py @@ -0,0 +1,113 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# pytype: skip-file + +import logging +import time +from typing import Any +from typing import Optional + +import apache_beam.testing.load_tests.dataflow_cost_consts as costs +from apache_beam.metrics.execution import MetricResult +from apache_beam.runners.dataflow.dataflow_runner import DataflowPipelineResult +from apache_beam.runners.runner import PipelineState +from apache_beam.testing.load_tests.load_test import LoadTest + + +class DataflowCostBenchmark(LoadTest): + """Base class for Dataflow performance tests which export metrics to + external databases: BigQuery or/and InfluxDB. Calculates the expected cost + for running the job on Dataflow in region us-central1. + + Refer to :class:`~apache_beam.testing.load_tests.LoadTestOptions` for more + information on the required pipeline options. + + If using InfluxDB with Basic HTTP authentication enabled, provide the + following environment options: `INFLUXDB_USER` and `INFLUXDB_USER_PASSWORD`. + + If the hardware configuration for the job includes use of a GPU, please + specify the version in use with the Accelerator enumeration. This is used to + calculate the cost of the job later, as different accelerators have different + billing rates per hour of use. + """ + def __init__( + self, + metrics_namespace: Optional[str] = None, + is_streaming: bool = False, + gpu: Optional[costs.Accelerator] = None): + self.is_streaming = is_streaming + self.gpu = gpu + super().__init__(metrics_namespace=metrics_namespace) + + def run(self): + try: + self.test() + if not hasattr(self, 'result'): + self.result = self.pipeline.run() + # Defaults to waiting forever unless timeout has been set + state = self.result.wait_until_finish(duration=self.timeout_ms) + assert state != PipelineState.FAILED + logging.info( + 'Pipeline complete, sleeping for 4 minutes to allow resource ' + 'metrics to populate.') + time.sleep(240) + self.extra_metrics = self._retrieve_cost_metrics(self.result) + self._metrics_monitor.publish_metrics(self.result, self.extra_metrics) + finally: + self.cleanup() + + def _retrieve_cost_metrics(self, + result: DataflowPipelineResult) -> dict[str, Any]: + job_id = result.job_id() + metrics = result.metrics().all_metrics(job_id) + metrics_dict = self._process_metrics_list(metrics) + logging.info(metrics_dict) + cost = 0.0 + if (self.is_streaming): + cost += metrics_dict.get( + "TotalVcpuTime", 0.0) / 3600 * costs.VCPU_PER_HR_STREAMING + cost += ( + metrics_dict.get("TotalMemoryUsage", 0.0) / + 1000) / 3600 * costs.MEM_PER_GB_HR_STREAMING + cost += metrics_dict.get( + "TotalStreamingDataProcessed", 0.0) * costs.SHUFFLE_PER_GB_STREAMING + else: + cost += metrics_dict.get( + "TotalVcpuTime", 0.0) / 3600 * costs.VCPU_PER_HR_BATCH + cost += ( + metrics_dict.get("TotalMemoryUsage", 0.0) / + 1000) / 3600 * costs.MEM_PER_GB_HR_BATCH + cost += metrics_dict.get( + "TotalStreamingDataProcessed", 0.0) * costs.SHUFFLE_PER_GB_BATCH + if (self.gpu): + rate = costs.ACCELERATOR_TO_COST[self.gpu] + cost += metrics_dict.get("TotalGpuTime", 0.0) / 3600 * rate + cost += metrics_dict.get("TotalPdUsage", 0.0) / 3600 * costs.PD_PER_GB_HR + cost += metrics_dict.get( + "TotalSsdUsage", 0.0) / 3600 * costs.PD_SSD_PER_GB_HR + metrics_dict["EstimatedCost"] = cost + return metrics_dict + + def _process_metrics_list(self, + metrics: list[MetricResult]) -> dict[str, Any]: + system_metrics = {} + for entry in metrics: + metric_key = entry.key + metric = metric_key.metric + if metric_key.step == '' and metric.namespace == 'dataflow/v1b3': + system_metrics[metric.name] = entry.committed + return system_metrics diff --git a/sdks/python/apache_beam/testing/load_tests/dataflow_cost_consts.py b/sdks/python/apache_beam/testing/load_tests/dataflow_cost_consts.py new file mode 100644 index 000000000000..f291991b48bb --- /dev/null +++ b/sdks/python/apache_beam/testing/load_tests/dataflow_cost_consts.py @@ -0,0 +1,59 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# These values are Dataflow costs for running jobs in us-central1. +# The cost values are found at https://cloud.google.com/dataflow/pricing + +from enum import Enum + +VCPU_PER_HR_BATCH = 0.056 +VCPU_PER_HR_STREAMING = 0.069 +MEM_PER_GB_HR_BATCH = 0.003557 +MEM_PER_GB_HR_STREAMING = 0.0035557 +PD_PER_GB_HR = 0.000054 +PD_SSD_PER_GB_HR = 0.000298 +SHUFFLE_PER_GB_BATCH = 0.011 +SHUFFLE_PER_GB_STREAMING = 0.018 + +# GPU Resource Pricing +P100_PER_GPU_PER_HOUR = 1.752 +V100_PER_GPU_PER_HOUR = 2.976 +T4_PER_GPU_PER_HOUR = 0.42 +P4_PER_GPU_PER_HOUR = 0.72 +L4_PER_GPU_PER_HOUR = 0.672 +A100_40GB_PER_GPU_PER_HOUR = 3.72 +A100_80GB_PER_GPU_PER_HOUR = 4.7137 + + +class Accelerator(Enum): + P100 = 1 + V100 = 2 + T4 = 3 + P4 = 4 + L4 = 5 + A100_40GB = 6 + A100_80GB = 7 + + +ACCELERATOR_TO_COST: dict[Accelerator, float] = { + Accelerator.P100: P100_PER_GPU_PER_HOUR, + Accelerator.V100: V100_PER_GPU_PER_HOUR, + Accelerator.T4: T4_PER_GPU_PER_HOUR, + Accelerator.P4: P4_PER_GPU_PER_HOUR, + Accelerator.L4: L4_PER_GPU_PER_HOUR, + Accelerator.A100_40GB: A100_40GB_PER_GPU_PER_HOUR, + Accelerator.A100_80GB: A100_80GB_PER_GPU_PER_HOUR, +} From 354e8e312e331d836309906dd5d8b6256499caf8 Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:58:30 -0500 Subject: [PATCH 090/135] Replace None metric values with 0.0 in Dataflow Cost Benchmark (#33336) --- .../apache_beam/testing/load_tests/dataflow_cost_benchmark.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py b/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py index b60af1249756..96a1cd31e298 100644 --- a/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py +++ b/sdks/python/apache_beam/testing/load_tests/dataflow_cost_benchmark.py @@ -109,5 +109,7 @@ def _process_metrics_list(self, metric_key = entry.key metric = metric_key.metric if metric_key.step == '' and metric.namespace == 'dataflow/v1b3': + if entry.committed is None: + entry.committed = 0.0 system_metrics[metric.name] = entry.committed return system_metrics From 6d8e02e262951f26e8a4e835df462a219155bc11 Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Tue, 10 Dec 2024 18:36:44 +0100 Subject: [PATCH 091/135] [java] BQ: add missing avro conversions to BQ TableRow (#33221) * [java] BQ: add missing avro conversions to BQ TableRow Avro float fields can be used to write BQ FLOAT columns. Add TableRow conversion for such field. Adding conversion for aveo 1.10+ logical types local-timestamp-millis and local-timestam-micros. * Rework tests * Add map and fixed types conversion * Fix checkstyle * Use valid parameters * Test record nullable field --- .../io/gcp/bigquery/BigQueryAvroUtils.java | 112 +- .../gcp/bigquery/BigQueryAvroUtilsTest.java | 1037 +++++++++++------ 2 files changed, 746 insertions(+), 403 deletions(-) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtils.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtils.java index cddde05b194c..1af44ba7a012 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtils.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryAvroUtils.java @@ -34,6 +34,8 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.Set; import org.apache.avro.Conversions; import org.apache.avro.LogicalType; @@ -50,14 +52,14 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; -/** - * A set of utilities for working with Avro files. - * - *