From e303fada1396923fe9a574f2b3e05cbafa0388d3 Mon Sep 17 00:00:00 2001 From: Kyle Weaver Date: Tue, 1 Feb 2022 16:53:38 -0800 Subject: [PATCH 01/61] [BEAM-13796] projection pushdown in BQ IO --- .../beam/sdk/io/gcp/bigquery/BigQueryIO.java | 28 +++++++++++++-- .../bigquery/BigQueryIOStorageQueryTest.java | 34 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) 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 f6d433498fde1..b64c99988669a 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 @@ -74,7 +74,6 @@ import org.apache.beam.sdk.io.gcp.bigquery.BigQueryHelpers.TableSchemaToJsonSchema; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryHelpers.TableSpecToTableRef; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryHelpers.TimePartitioningToJson; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.TypedRead.Method; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryResourceNaming.JobType; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryServices.DatasetService; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryServices.JobService; @@ -91,6 +90,8 @@ import org.apache.beam.sdk.options.ValueProvider; import org.apache.beam.sdk.options.ValueProvider.NestedValueProvider; import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider; +import org.apache.beam.sdk.schemas.FieldAccessDescriptor; +import org.apache.beam.sdk.schemas.ProjectionProducer; import org.apache.beam.sdk.schemas.Schema; import org.apache.beam.sdk.transforms.Create; import org.apache.beam.sdk.transforms.DoFn; @@ -117,6 +118,7 @@ import org.apache.beam.sdk.values.ValueInSingleWindow; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.annotations.VisibleForTesting; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.MoreObjects; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Predicates; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Strings; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; @@ -730,7 +732,8 @@ public Read withTemplateCompatibility() { /** Implementation of {@link BigQueryIO#read(SerializableFunction)}. */ @AutoValue - public abstract static class TypedRead extends PTransform> { + public abstract static class TypedRead extends PTransform> + implements ProjectionProducer>> { /** Determines the method used to read data from BigQuery. */ public enum Method { /** The default behavior if no method is explicitly set. Currently {@link #EXPORT}. */ @@ -1619,6 +1622,27 @@ public TypedRead withTestServices(BigQueryServices testServices) { public TypedRead useAvroLogicalTypes() { return toBuilder().setUseAvroLogicalTypes(true).build(); } + + @Override + public boolean supportsProjectionPushdown() { + return Method.DIRECT_READ.equals(getMethod()); + } + + @Override + public PTransform> actuateProjectionPushdown( + Map, FieldAccessDescriptor> outputFields) { + Preconditions.checkArgument(supportsProjectionPushdown()); + FieldAccessDescriptor fieldAccessDescriptor = outputFields.get(new TupleTag<>("output")); + org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull( + fieldAccessDescriptor, "Expected pushdown on the main output (tagged 'output')"); + Preconditions.checkArgument( + outputFields.size() == 1, + "Expected only to pushdown on the main output (tagged 'output'). Requested tags: %s", + outputFields.keySet()); + ImmutableList fields = + ImmutableList.copyOf(fieldAccessDescriptor.fieldNamesAccessed()); + return withSelectedFields(fields); + } } static String getExtractDestinationUri(String extractDestinationDir) { diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java index 86f538a4d0f3b..8dceff25dab1d 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java @@ -69,19 +69,28 @@ import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.TypedRead.QueryPriority; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryResourceNaming.JobType; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryServices.StorageClient; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryUtils.ConversionOptions; import org.apache.beam.sdk.io.gcp.testing.FakeBigQueryServices; import org.apache.beam.sdk.io.gcp.testing.FakeBigQueryServices.FakeBigQueryServerStream; import org.apache.beam.sdk.io.gcp.testing.FakeDatasetService; import org.apache.beam.sdk.io.gcp.testing.FakeJobService; import org.apache.beam.sdk.options.ValueProvider; +import org.apache.beam.sdk.schemas.FieldAccessDescriptor; +import org.apache.beam.sdk.schemas.SchemaCoder; import org.apache.beam.sdk.testing.PAssert; import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.PTransform; import org.apache.beam.sdk.transforms.SerializableFunction; import org.apache.beam.sdk.transforms.display.DisplayData; import org.apache.beam.sdk.values.KV; +import org.apache.beam.sdk.values.PBegin; import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.Row; +import org.apache.beam.sdk.values.TupleTag; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Lists; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -753,6 +762,31 @@ public void testQuerySourceCreateReader() throws Exception { querySource.createReader(options); } + @Test + public void testActuateProjectionPushdown() { + org.apache.beam.sdk.schemas.Schema schema = + org.apache.beam.sdk.schemas.Schema.builder() + .addStringField("foo") + .addStringField("bar") + .build(); + TypedRead read = + BigQueryIO.read( + record -> + BigQueryUtils.toBeamRow( + record.getRecord(), schema, ConversionOptions.builder().build())) + .withMethod(Method.DIRECT_READ) + .withCoder(SchemaCoder.of(schema)); + + assertTrue(read.supportsProjectionPushdown()); + PTransform> pushdownT = + read.actuateProjectionPushdown( + ImmutableMap.of(new TupleTag<>("output"), FieldAccessDescriptor.withFieldNames("foo"))); + + TypedRead pushdownRead = (TypedRead) pushdownT; + assertEquals(Method.DIRECT_READ, pushdownRead.getMethod()); + assertThat(pushdownRead.getSelectedFields().get(), Matchers.containsInAnyOrder("foo")); + } + @Test public void testReadFromBigQueryIO() throws Exception { doReadFromBigQueryIO(false); From 49d376c2c581e1cb2d244247f06eaf260ad77d26 Mon Sep 17 00:00:00 2001 From: egalpin Date: Fri, 4 Feb 2022 15:57:52 -0500 Subject: [PATCH 02/61] Use default context output rather than outputWithTimestamp for ElasticsearchIO --- .../apache/beam/sdk/io/elasticsearch/ElasticsearchIO.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdks/java/io/elasticsearch/src/main/java/org/apache/beam/sdk/io/elasticsearch/ElasticsearchIO.java b/sdks/java/io/elasticsearch/src/main/java/org/apache/beam/sdk/io/elasticsearch/ElasticsearchIO.java index 273443a4fdbd8..dc29ac6074b07 100644 --- a/sdks/java/io/elasticsearch/src/main/java/org/apache/beam/sdk/io/elasticsearch/ElasticsearchIO.java +++ b/sdks/java/io/elasticsearch/src/main/java/org/apache/beam/sdk/io/elasticsearch/ElasticsearchIO.java @@ -2359,9 +2359,10 @@ private ProcessContextAdapter(DoFn.ProcessContext context) { @Override public void output( - TupleTag tag, Document document, Instant timestamp, BoundedWindow ignored) { - // Note: window is intentionally unused, but required as a param to fit the interface - context.outputWithTimestamp(tag, document, timestamp); + TupleTag tag, Document document, Instant ignored1, BoundedWindow ignored2) { + // Note: window and timestamp are intentionally unused, but required as params to fit the + // interface + context.output(tag, document); } } From faf8c0fdc5ba283a28bc28d88118903929147398 Mon Sep 17 00:00:00 2001 From: Kyle Weaver Date: Thu, 17 Feb 2022 18:03:03 -0800 Subject: [PATCH 03/61] [BEAM-13796] Move test to ReadTest class and correct javadoc for QueryTest. --- .../bigquery/BigQueryIOStorageQueryTest.java | 39 ++----------------- .../bigquery/BigQueryIOStorageReadTest.java | 34 ++++++++++++++++ 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java index 8dceff25dab1d..b23de6b3fc334 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageQueryTest.java @@ -69,28 +69,19 @@ import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.TypedRead.QueryPriority; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryResourceNaming.JobType; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryServices.StorageClient; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryUtils.ConversionOptions; import org.apache.beam.sdk.io.gcp.testing.FakeBigQueryServices; import org.apache.beam.sdk.io.gcp.testing.FakeBigQueryServices.FakeBigQueryServerStream; import org.apache.beam.sdk.io.gcp.testing.FakeDatasetService; import org.apache.beam.sdk.io.gcp.testing.FakeJobService; import org.apache.beam.sdk.options.ValueProvider; -import org.apache.beam.sdk.schemas.FieldAccessDescriptor; -import org.apache.beam.sdk.schemas.SchemaCoder; import org.apache.beam.sdk.testing.PAssert; import org.apache.beam.sdk.testing.TestPipeline; -import org.apache.beam.sdk.transforms.PTransform; import org.apache.beam.sdk.transforms.SerializableFunction; import org.apache.beam.sdk.transforms.display.DisplayData; import org.apache.beam.sdk.values.KV; -import org.apache.beam.sdk.values.PBegin; import org.apache.beam.sdk.values.PCollection; -import org.apache.beam.sdk.values.Row; -import org.apache.beam.sdk.values.TupleTag; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; -import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Lists; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -102,7 +93,10 @@ import org.junit.runners.JUnit4; import org.junit.runners.model.Statement; -/** Tests for {@link BigQueryIO#readTableRows()} using {@link Method#DIRECT_READ}. */ +/** + * Tests for {@link BigQueryIO#read(SerializableFunction)} using {@link Method#DIRECT_READ} and + * {@link BigQueryIO.TypedRead#fromQuery(String)}. + */ @RunWith(JUnit4.class) public class BigQueryIOStorageQueryTest { @@ -762,31 +756,6 @@ public void testQuerySourceCreateReader() throws Exception { querySource.createReader(options); } - @Test - public void testActuateProjectionPushdown() { - org.apache.beam.sdk.schemas.Schema schema = - org.apache.beam.sdk.schemas.Schema.builder() - .addStringField("foo") - .addStringField("bar") - .build(); - TypedRead read = - BigQueryIO.read( - record -> - BigQueryUtils.toBeamRow( - record.getRecord(), schema, ConversionOptions.builder().build())) - .withMethod(Method.DIRECT_READ) - .withCoder(SchemaCoder.of(schema)); - - assertTrue(read.supportsProjectionPushdown()); - PTransform> pushdownT = - read.actuateProjectionPushdown( - ImmutableMap.of(new TupleTag<>("output"), FieldAccessDescriptor.withFieldNames("foo"))); - - TypedRead pushdownRead = (TypedRead) pushdownT; - assertEquals(Method.DIRECT_READ, pushdownRead.getMethod()); - assertThat(pushdownRead.getSelectedFields().get(), Matchers.containsInAnyOrder("foo")); - } - @Test public void testReadFromBigQueryIO() throws Exception { doReadFromBigQueryIO(false); diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java index fcf335c01b309..0feb586698b4d 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java @@ -92,20 +92,29 @@ import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.TypedRead; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.TypedRead.Method; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryServices.StorageClient; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryUtils.ConversionOptions; import org.apache.beam.sdk.io.gcp.testing.FakeBigQueryServices; import org.apache.beam.sdk.io.gcp.testing.FakeBigQueryServices.FakeBigQueryServerStream; import org.apache.beam.sdk.io.gcp.testing.FakeDatasetService; import org.apache.beam.sdk.options.PipelineOptions; import org.apache.beam.sdk.options.ValueProvider; import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider; +import org.apache.beam.sdk.schemas.FieldAccessDescriptor; +import org.apache.beam.sdk.schemas.SchemaCoder; import org.apache.beam.sdk.testing.PAssert; import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.PTransform; import org.apache.beam.sdk.transforms.SerializableFunction; import org.apache.beam.sdk.transforms.display.DisplayData; import org.apache.beam.sdk.values.KV; +import org.apache.beam.sdk.values.PBegin; import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.Row; +import org.apache.beam.sdk.values.TupleTag; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Lists; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -2064,6 +2073,31 @@ public void testStreamSourceSplitAtFractionFailsWhenParentIsPastSplitPointArrow( assertFalse(parent.advance()); } + @Test + public void testActuateProjectionPushdown() { + org.apache.beam.sdk.schemas.Schema schema = + org.apache.beam.sdk.schemas.Schema.builder() + .addStringField("foo") + .addStringField("bar") + .build(); + TypedRead read = + BigQueryIO.read( + record -> + BigQueryUtils.toBeamRow( + record.getRecord(), schema, ConversionOptions.builder().build())) + .withMethod(Method.DIRECT_READ) + .withCoder(SchemaCoder.of(schema)); + + assertTrue(read.supportsProjectionPushdown()); + PTransform> pushdownT = + read.actuateProjectionPushdown( + ImmutableMap.of(new TupleTag<>("output"), FieldAccessDescriptor.withFieldNames("foo"))); + + TypedRead pushdownRead = (TypedRead) pushdownT; + assertEquals(Method.DIRECT_READ, pushdownRead.getMethod()); + assertThat(pushdownRead.getSelectedFields().get(), Matchers.containsInAnyOrder("foo")); + } + private static org.apache.arrow.vector.types.pojo.Field field( String name, boolean nullable, From 185c2e3814d353621c5c673201afd4581d52f7c7 Mon Sep 17 00:00:00 2001 From: Kyle Weaver Date: Fri, 18 Feb 2022 14:39:14 -0800 Subject: [PATCH 04/61] [BEAM-13796] Pushdown is not supported on TypedRead#fromQuery. --- .../beam/sdk/io/gcp/bigquery/BigQueryIO.java | 4 ++- .../bigquery/BigQueryIOStorageReadTest.java | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 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 b64c99988669a..97a9bfa1595c7 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 @@ -1625,7 +1625,9 @@ public TypedRead useAvroLogicalTypes() { @Override public boolean supportsProjectionPushdown() { - return Method.DIRECT_READ.equals(getMethod()); + // We can't do projection pushdown when a query is set. The query may project certain fields + // itself, and we can't know without parsing the query. + return Method.DIRECT_READ.equals(getMethod()) && getQuery() == null; } @Override diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java index 0feb586698b4d..8f8ab867a9101 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryIOStorageReadTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -2098,6 +2099,31 @@ record -> assertThat(pushdownRead.getSelectedFields().get(), Matchers.containsInAnyOrder("foo")); } + @Test + public void testReadFromQueryDoesNotSupportProjectionPushdown() { + org.apache.beam.sdk.schemas.Schema schema = + org.apache.beam.sdk.schemas.Schema.builder() + .addStringField("foo") + .addStringField("bar") + .build(); + TypedRead read = + BigQueryIO.read( + record -> + BigQueryUtils.toBeamRow( + record.getRecord(), schema, ConversionOptions.builder().build())) + .fromQuery("SELECT bar FROM `dataset.table`") + .withMethod(Method.DIRECT_READ) + .withCoder(SchemaCoder.of(schema)); + + assertFalse(read.supportsProjectionPushdown()); + assertThrows( + IllegalArgumentException.class, + () -> + read.actuateProjectionPushdown( + ImmutableMap.of( + new TupleTag<>("output"), FieldAccessDescriptor.withFieldNames("foo")))); + } + private static org.apache.arrow.vector.types.pojo.Field field( String name, boolean nullable, From c975031b1babef3b54156458b34a7294ddfa261c Mon Sep 17 00:00:00 2001 From: Benjamin Gonzalez <74670721+benWize@users.noreply.github.com> Date: Fri, 18 Feb 2022 16:42:06 -0600 Subject: [PATCH 05/61] [BEAM-12572] Fix failing python examples tests in Dataflow runner (#16853) --- .../examples/complete/autocomplete_it_test.py | 116 +++++++++++++++++ .../examples/complete/autocomplete_test.py | 58 --------- .../examples/complete/estimate_pi_it_test.py | 68 ++++++++++ .../examples/complete/estimate_pi_test.py | 21 ---- .../top_wikipedia_sessions_it_test.py | 118 ++++++++++++++++++ .../complete/top_wikipedia_sessions_test.py | 33 ----- .../examples/cookbook/coders_it_test.py | 104 +++++++++++++++ .../examples/cookbook/coders_test.py | 47 ------- sdks/python/pytest.ini | 1 + .../python/test-suites/dataflow/common.gradle | 4 +- 10 files changed, 409 insertions(+), 161 deletions(-) create mode 100644 sdks/python/apache_beam/examples/complete/autocomplete_it_test.py create mode 100644 sdks/python/apache_beam/examples/complete/estimate_pi_it_test.py create mode 100644 sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_it_test.py create mode 100644 sdks/python/apache_beam/examples/cookbook/coders_it_test.py diff --git a/sdks/python/apache_beam/examples/complete/autocomplete_it_test.py b/sdks/python/apache_beam/examples/complete/autocomplete_it_test.py new file mode 100644 index 0000000000000..28312b7303b2a --- /dev/null +++ b/sdks/python/apache_beam/examples/complete/autocomplete_it_test.py @@ -0,0 +1,116 @@ +# +# 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. +# + +"""End-to-end test for Autocomplete example.""" +# pytype: skip-file + +import logging +import re +import unittest +import uuid + +import pytest + +from apache_beam.examples.complete import autocomplete +from apache_beam.testing.test_pipeline import TestPipeline + +# Protect against environments where gcsio library is not available. +try: + from apache_beam.io.gcp import gcsio +except ImportError: + gcsio = None + + +def read_gcs_output_file(file_pattern): + gcs = gcsio.GcsIO() + file_names = gcs.list_prefix(file_pattern).keys() + output = [] + for file_name in file_names: + output.append(gcs.open(file_name).read().decode('utf-8').strip()) + return '\n'.join(output) + + +def create_content_input_file(path, contents): + logging.info('Creating file: %s', path) + gcs = gcsio.GcsIO() + with gcs.open(path, 'w') as f: + f.write(str.encode(contents, 'utf-8')) + + +def format_output_file(output_string): + def extract_prefix_topk_words_tuples(line): + match = re.match(r'(.*): \[(.*)\]', line) + prefix = match.group(1) + topK_words_string = extract_top_k_words_tuples(match.group(2)) + return prefix, topK_words_string + + def extract_top_k_words_tuples(top_k_words_string): + top_k_list = top_k_words_string.split("), (") + return tuple( + map( + lambda top_k_string: tuple(format_top_k_tuples(top_k_string)), + top_k_list)) + + def format_top_k_tuples(top_k_string): + (frequency, words) = top_k_string.replace('(', '').replace(')', '').replace( + '\"', '').replace('\'', '').replace(' ', '').split(',') + return int(frequency), words + + return list( + map( + lambda line: extract_prefix_topk_words_tuples(line), + output_string.split('\n'))) + + +class AutocompleteIT(unittest.TestCase): + WORDS = ['this', 'this', 'that', 'to', 'to', 'to'] + EXPECTED_PREFIXES = [ + ('t', ((3, 'to'), (2, 'this'), (1, 'that'))), + ('to', ((3, 'to'), )), + ('th', ((2, 'this'), (1, 'that'))), + ('thi', ((2, 'this'), )), + ('this', ((2, 'this'), )), + ('tha', ((1, 'that'), )), + ('that', ((1, 'that'), )), + ] + + @pytest.mark.no_xdist + @pytest.mark.examples_postcommit + def test_autocomplete_output_files_on_small_input(self): + test_pipeline = TestPipeline(is_integration_test=True) + # Setup the files with expected content. + OUTPUT_FILE_DIR = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/output' + output = '/'.join([OUTPUT_FILE_DIR, str(uuid.uuid4()), 'result']) + INPUT_FILE_DIR = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/input' + input = '/'.join([INPUT_FILE_DIR, str(uuid.uuid4()), 'input.txt']) + create_content_input_file(input, ' '.join(self.WORDS)) + extra_opts = {'input': input, 'output': output} + + autocomplete.run(test_pipeline.get_full_options_as_args(**extra_opts)) + + # Load result file and compare. + result = read_gcs_output_file(output).strip() + + self.assertEqual( + sorted(self.EXPECTED_PREFIXES), sorted(format_output_file(result))) + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.INFO) + unittest.main() diff --git a/sdks/python/apache_beam/examples/complete/autocomplete_test.py b/sdks/python/apache_beam/examples/complete/autocomplete_test.py index 4a0a2fc180239..8b941f38a6490 100644 --- a/sdks/python/apache_beam/examples/complete/autocomplete_test.py +++ b/sdks/python/apache_beam/examples/complete/autocomplete_test.py @@ -19,10 +19,6 @@ # pytype: skip-file -import logging -import os -import re -import tempfile import unittest import pytest @@ -33,38 +29,6 @@ from apache_beam.testing.test_utils import compute_hash from apache_beam.testing.util import assert_that from apache_beam.testing.util import equal_to -from apache_beam.testing.util import open_shards - - -def format_output_file(output_string): - def extract_prefix_topk_words_tuples(line): - match = re.match(r'(.*): \[(.*)\]', line) - prefix = match.group(1) - topK_words_string = extract_top_k_words_tuples(match.group(2)) - return prefix, topK_words_string - - def extract_top_k_words_tuples(top_k_words_string): - top_k_list = top_k_words_string.split("), (") - return tuple( - map( - lambda top_k_string: tuple(format_top_k_tuples(top_k_string)), - top_k_list)) - - def format_top_k_tuples(top_k_string): - (frequency, words) = top_k_string.replace('(', '').replace(')', '').replace( - '\"', '').replace('\'', '').replace(' ', '').split(',') - return int(frequency), words - - return list( - map( - lambda line: extract_prefix_topk_words_tuples(line), - output_string.split('\n'))) - - -def create_content_input_file(path, contents): - logging.info('Creating temp file: %s', path) - with open(path, 'w') as f: - f.write(contents) class AutocompleteTest(unittest.TestCase): @@ -105,28 +69,6 @@ def test_autocomplete_it(self): assert_that(checksum, equal_to([self.KINGLEAR_HASH_SUM])) - @pytest.mark.no_xdist - @pytest.mark.examples_postcommit - def test_autocomplete_output_files_on_small_input(self): - test_pipeline = TestPipeline(is_integration_test=True) - # Setup the files with expected content. - temp_folder = tempfile.mkdtemp() - create_content_input_file( - os.path.join(temp_folder, 'input.txt'), ' '.join(self.WORDS)) - extra_opts = { - 'input': '%s/input.txt' % temp_folder, - 'output': os.path.join(temp_folder, 'result') - } - - autocomplete.run(test_pipeline.get_full_options_as_args(**extra_opts)) - - # Load result file and compare. - with open_shards(os.path.join(temp_folder, 'result-*-of-*')) as result_file: - result = result_file.read().strip() - - self.assertEqual( - sorted(self.EXPECTED_PREFIXES), sorted(format_output_file(result))) - if __name__ == '__main__': unittest.main() diff --git a/sdks/python/apache_beam/examples/complete/estimate_pi_it_test.py b/sdks/python/apache_beam/examples/complete/estimate_pi_it_test.py new file mode 100644 index 0000000000000..cda92e5da4cb2 --- /dev/null +++ b/sdks/python/apache_beam/examples/complete/estimate_pi_it_test.py @@ -0,0 +1,68 @@ +# +# 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. +# + +"""End-to-end test for Estimate Pi example.""" +# pytype: skip-file + +import json +import logging +import unittest +import uuid + +import pytest + +from apache_beam.examples.complete import estimate_pi +from apache_beam.testing.test_pipeline import TestPipeline + +# Protect against environments where gcsio library is not available. +try: + from apache_beam.io.gcp import gcsio +except ImportError: + gcsio = None + + +def read_gcs_output_file(file_pattern): + gcs = gcsio.GcsIO() + file_names = gcs.list_prefix(file_pattern).keys() + output = [] + for file_name in file_names: + output.append(gcs.open(file_name).read().decode('utf-8')) + return '\n'.join(output) + + +class EstimatePiIT(unittest.TestCase): + @pytest.mark.no_xdist + @pytest.mark.examples_postcommit + def test_estimate_pi_output_file(self): + test_pipeline = TestPipeline(is_integration_test=True) + OUTPUT_FILE = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/output' + output = '/'.join([OUTPUT_FILE, str(uuid.uuid4()), 'result']) + extra_opts = {'output': output} + estimate_pi.run(test_pipeline.get_full_options_as_args(**extra_opts)) + # Load result file and compare. + result = read_gcs_output_file(output) + [_, _, estimated_pi] = json.loads(result.strip()) + # Note: Probabilistically speaking this test can fail with a probability + # that is very small (VERY) given that we run at least 100 thousand + # trials. + self.assertTrue(3.125 <= estimated_pi <= 3.155) + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.INFO) + unittest.main() diff --git a/sdks/python/apache_beam/examples/complete/estimate_pi_test.py b/sdks/python/apache_beam/examples/complete/estimate_pi_test.py index 08ac93f47bf56..ff224f4a9cab0 100644 --- a/sdks/python/apache_beam/examples/complete/estimate_pi_test.py +++ b/sdks/python/apache_beam/examples/complete/estimate_pi_test.py @@ -19,19 +19,13 @@ # pytype: skip-file -import json import logging -import os -import tempfile import unittest -import pytest - from apache_beam.examples.complete import estimate_pi from apache_beam.testing.test_pipeline import TestPipeline from apache_beam.testing.util import BeamAssertException from apache_beam.testing.util import assert_that -from apache_beam.testing.util import open_shards def in_between(lower, upper): @@ -54,21 +48,6 @@ def test_basics(self): # trials. assert_that(result, in_between(3.125, 3.155)) - @pytest.mark.no_xdist - @pytest.mark.examples_postcommit - def test_estimate_pi_output_file(self): - test_pipeline = TestPipeline(is_integration_test=True) - temp_folder = tempfile.mkdtemp() - extra_opts = {'output': os.path.join(temp_folder, 'result')} - estimate_pi.run(test_pipeline.get_full_options_as_args(**extra_opts)) - # Load result file and compare. - with open_shards(os.path.join(temp_folder, 'result-*-of-*')) as result_file: - [_, _, estimated_pi] = json.loads(result_file.read().strip()) - # Note: Probabilistically speaking this test can fail with a probability - # that is very small (VERY) given that we run at least 100 thousand - # trials. - self.assertTrue(3.125 <= estimated_pi <= 3.155) - if __name__ == '__main__': logging.getLogger().setLevel(logging.INFO) diff --git a/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_it_test.py b/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_it_test.py new file mode 100644 index 0000000000000..64dcd06c8ecb7 --- /dev/null +++ b/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_it_test.py @@ -0,0 +1,118 @@ +# +# 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. +# + +"""End-to-end test for Top Wikipedia Sessions example.""" +# pytype: skip-file + +import json +import logging +import unittest +import uuid + +import pytest + +from apache_beam.examples.complete import top_wikipedia_sessions +from apache_beam.testing.test_pipeline import TestPipeline + +# Protect against environments where gcsio library is not available. +try: + from apache_beam.io.gcp import gcsio +except ImportError: + gcsio = None + + +def read_gcs_output_file(file_pattern): + gcs = gcsio.GcsIO() + file_names = gcs.list_prefix(file_pattern).keys() + output = [] + for file_name in file_names: + output.append(gcs.open(file_name).read().decode('utf-8')) + return '\n'.join(output) + + +def create_content_input_file(path, contents): + logging.info('Creating file: %s', path) + gcs = gcsio.GcsIO() + with gcs.open(path, 'w') as f: + f.write(str.encode(contents, 'utf-8')) + + +class ComputeTopSessionsIT(unittest.TestCase): + EDITS = [ + json.dumps({ + 'timestamp': 0.0, 'contributor_username': 'user1' + }), + json.dumps({ + 'timestamp': 0.001, 'contributor_username': 'user1' + }), + json.dumps({ + 'timestamp': 0.002, 'contributor_username': 'user1' + }), + json.dumps({ + 'timestamp': 0.0, 'contributor_username': 'user2' + }), + json.dumps({ + 'timestamp': 0.001, 'contributor_username': 'user2' + }), + json.dumps({ + 'timestamp': 3.601, 'contributor_username': 'user2' + }), + json.dumps({ + 'timestamp': 3.602, 'contributor_username': 'user2' + }), + json.dumps({ + 'timestamp': 2 * 3600.0, 'contributor_username': 'user2' + }), + json.dumps({ + 'timestamp': 35 * 24 * 3.600, 'contributor_username': 'user3' + }) + ] + + EXPECTED = [ + 'user1 : [0.0, 3600.002) : 3 : [0.0, 2592000.0)', + 'user2 : [0.0, 3603.602) : 4 : [0.0, 2592000.0)', + 'user2 : [7200.0, 10800.0) : 1 : [0.0, 2592000.0)', + 'user3 : [3024.0, 6624.0) : 1 : [0.0, 2592000.0)', + ] + + # TODO Enable when fixed this tests for Dataflow runner + @pytest.mark.sickbay_dataflow + @pytest.mark.no_xdist + @pytest.mark.examples_postcommit + def test_top_wikipedia_sessions_output_files_on_small_input(self): + test_pipeline = TestPipeline(is_integration_test=True) + # Setup the files with expected content. + OUTPUT_FILE_DIR = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/output' + output = '/'.join([OUTPUT_FILE_DIR, str(uuid.uuid4()), 'result']) + INPUT_FILE_DIR = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/input' + input = '/'.join([INPUT_FILE_DIR, str(uuid.uuid4()), 'input.txt']) + create_content_input_file(input, '\n'.join(self.EDITS)) + extra_opts = {'input': input, 'output': output, 'sampling_threshold': '1.0'} + top_wikipedia_sessions.run( + test_pipeline.get_full_options_as_args(**extra_opts)) + + # Load result file and compare. + result = read_gcs_output_file(output).strip().splitlines() + + self.assertEqual(self.EXPECTED, sorted(result, key=lambda x: x.split()[0])) + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.INFO) + unittest.main() diff --git a/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_test.py b/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_test.py index a384ed513055d..3c171664e45d2 100644 --- a/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_test.py +++ b/sdks/python/apache_beam/examples/complete/top_wikipedia_sessions_test.py @@ -20,19 +20,13 @@ # pytype: skip-file import json -import logging -import os -import tempfile import unittest -import pytest - import apache_beam as beam from apache_beam.examples.complete import top_wikipedia_sessions from apache_beam.testing.test_pipeline import TestPipeline from apache_beam.testing.util import assert_that from apache_beam.testing.util import equal_to -from apache_beam.testing.util import open_shards class ComputeTopSessionsTest(unittest.TestCase): @@ -74,11 +68,6 @@ class ComputeTopSessionsTest(unittest.TestCase): 'user3 : [3024.0, 6624.0) : 1 : [0.0, 2592000.0)', ] - def create_content_input_file(self, path, contents): - logging.info('Creating temp file: %s', path) - with open(path, 'w') as f: - f.write(contents) - def test_compute_top_sessions(self): with TestPipeline() as p: edits = p | beam.Create(self.EDITS) @@ -86,28 +75,6 @@ def test_compute_top_sessions(self): assert_that(result, equal_to(self.EXPECTED)) - @pytest.mark.no_xdist - @pytest.mark.examples_postcommit - def test_top_wikipedia_sessions_output_files_on_small_input(self): - test_pipeline = TestPipeline(is_integration_test=True) - # Setup the files with expected content. - temp_folder = tempfile.mkdtemp() - self.create_content_input_file( - os.path.join(temp_folder, 'input.txt'), '\n'.join(self.EDITS)) - extra_opts = { - 'input': '%s/input.txt' % temp_folder, - 'output': os.path.join(temp_folder, 'result'), - 'sampling_threshold': '1.0' - } - top_wikipedia_sessions.run( - test_pipeline.get_full_options_as_args(**extra_opts)) - - # Load result file and compare. - with open_shards(os.path.join(temp_folder, 'result-*-of-*')) as result_file: - result = result_file.read().strip().splitlines() - - self.assertEqual(self.EXPECTED, sorted(result, key=lambda x: x.split()[0])) - if __name__ == '__main__': unittest.main() diff --git a/sdks/python/apache_beam/examples/cookbook/coders_it_test.py b/sdks/python/apache_beam/examples/cookbook/coders_it_test.py new file mode 100644 index 0000000000000..c402003485077 --- /dev/null +++ b/sdks/python/apache_beam/examples/cookbook/coders_it_test.py @@ -0,0 +1,104 @@ +# +# 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. +# + +"""End-to-end test for Coders example.""" +# pytype: skip-file + +import json +import logging +import unittest +import uuid + +import pytest + +from apache_beam.examples.cookbook import coders +from apache_beam.testing.test_pipeline import TestPipeline + +# Protect against environments where gcsio library is not available. +try: + from apache_beam.io.gcp import gcsio +except ImportError: + gcsio = None + + +def read_gcs_output_file(file_pattern): + gcs = gcsio.GcsIO() + file_names = gcs.list_prefix(file_pattern).keys() + output = [] + for file_name in file_names: + output.append(gcs.open(file_name).read().decode('utf-8').strip()) + return '\n'.join(output) + + +def create_content_input_file(path, contents): + logging.info('Creating file: %s', path) + gcs = gcsio.GcsIO() + with gcs.open(path, 'w') as f: + f.write(str.encode(contents, 'utf-8')) + + +def format_result(result_string): + def format_tuple(result_elem_list): + [country, counter] = result_elem_list + return country, int(counter.strip()) + + result_list = list( + map( + lambda result_elem: format_tuple(result_elem.split(',')), + result_string.replace('\'', '').replace('[', + '').replace(']', '').replace( + '\"', '').split('\n'))) + return result_list + + +class CodersIT(unittest.TestCase): + SAMPLE_RECORDS = [{ + 'host': ['Germany', 1], 'guest': ['Italy', 0] + }, { + 'host': ['Germany', 1], 'guest': ['Brasil', 3] + }, { + 'host': ['Brasil', 1], 'guest': ['Italy', 0] + }] + + EXPECTED_RESULT = [('Italy', 0), ('Brasil', 6), ('Germany', 3)] + + @pytest.mark.no_xdist + @pytest.mark.examples_postcommit + def test_coders_output_files_on_small_input(self): + test_pipeline = TestPipeline(is_integration_test=True) + # Setup the files with expected content. + OUTPUT_FILE_DIR = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/output' + output = '/'.join([OUTPUT_FILE_DIR, str(uuid.uuid4()), 'result']) + INPUT_FILE_DIR = \ + 'gs://temp-storage-for-end-to-end-tests/py-it-cloud/input' + input = '/'.join([INPUT_FILE_DIR, str(uuid.uuid4()), 'input.txt']) + create_content_input_file( + input, '\n'.join(map(json.dumps, self.SAMPLE_RECORDS))) + extra_opts = {'input': input, 'output': output} + coders.run(test_pipeline.get_full_options_as_args(**extra_opts)) + + # Load result file and compare. + result = read_gcs_output_file(output).strip() + + self.assertEqual( + sorted(self.EXPECTED_RESULT), sorted(format_result(result))) + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.INFO) + unittest.main() diff --git a/sdks/python/apache_beam/examples/cookbook/coders_test.py b/sdks/python/apache_beam/examples/cookbook/coders_test.py index 540c7653476e1..ade5a84a19824 100644 --- a/sdks/python/apache_beam/examples/cookbook/coders_test.py +++ b/sdks/python/apache_beam/examples/cookbook/coders_test.py @@ -19,20 +19,14 @@ # pytype: skip-file -import json import logging -import os -import tempfile import unittest -import pytest - import apache_beam as beam from apache_beam.examples.cookbook import coders from apache_beam.testing.test_pipeline import TestPipeline from apache_beam.testing.util import assert_that from apache_beam.testing.util import equal_to -from apache_beam.testing.util import open_shards class CodersTest(unittest.TestCase): @@ -47,24 +41,6 @@ class CodersTest(unittest.TestCase): EXPECTED_RESULT = [('Italy', 0), ('Brasil', 6), ('Germany', 3)] - def create_content_input_file(self, path, contents): - logging.info('Creating temp file: %s', path) - with open(path, 'w') as f: - f.write(contents) - - def format_result(self, result_string): - def format_tuple(result_elem_list): - [country, counter] = result_elem_list - return country, int(counter.strip()) - - result_list = list( - map( - lambda result_elem: format_tuple(result_elem.split(',')), - result_string.replace('\'', - '').replace('[', '').replace(']', '').replace( - '\"', '').split('\n'))) - return result_list - def test_compute_points(self): with TestPipeline() as p: records = p | 'create' >> beam.Create(self.SAMPLE_RECORDS) @@ -74,29 +50,6 @@ def test_compute_points(self): | beam.CombinePerKey(sum)) assert_that(result, equal_to(self.EXPECTED_RESULT)) - @pytest.mark.no_xdist - @pytest.mark.examples_postcommit - def test_coders_output_files_on_small_input(self): - test_pipeline = TestPipeline(is_integration_test=True) - - # Setup the files with expected content. - temp_folder = tempfile.mkdtemp() - self.create_content_input_file( - os.path.join(temp_folder, 'input.txt'), - '\n'.join(map(json.dumps, self.SAMPLE_RECORDS))) - extra_opts = { - 'input': '%s/input.txt' % temp_folder, - 'output': os.path.join(temp_folder, 'result') - } - coders.run(test_pipeline.get_full_options_as_args(**extra_opts)) - - # Load result file and compare. - with open_shards(os.path.join(temp_folder, 'result-*-of-*')) as result_file: - result = result_file.read().strip() - - self.assertEqual( - sorted(self.EXPECTED_RESULT), sorted(self.format_result(result))) - if __name__ == '__main__': logging.getLogger().setLevel(logging.INFO) diff --git a/sdks/python/pytest.ini b/sdks/python/pytest.ini index 36e09d51cf19d..4a52bce20900c 100644 --- a/sdks/python/pytest.ini +++ b/sdks/python/pytest.ini @@ -39,6 +39,7 @@ markers = sickbay_direct: run without sickbay-direct sickbay_spark: run without sickbay-spark sickbay_flink: run without sickbay-flink + sickbay_dataflow: run without sickbay-dataflow # Tests using this marker conflict with the xdist plugin in some way, such # as enabling save_main_session. no_xdist: run without pytest-xdist plugin diff --git a/sdks/python/test-suites/dataflow/common.gradle b/sdks/python/test-suites/dataflow/common.gradle index a91165277e802..f263bc6297e3e 100644 --- a/sdks/python/test-suites/dataflow/common.gradle +++ b/sdks/python/test-suites/dataflow/common.gradle @@ -160,7 +160,7 @@ task examples { "sdk_location": files(configurations.distTarBall.files).singleFile, "runner_v2": "true", "suite": "postCommitIT-df${pythonVersionSuffix}-xdist", - "collect": "examples_postcommit and not no_xdist" + "collect": "examples_postcommit and not no_xdist and not sickbay_dataflow" ] def cmdArgs = mapToArgString(argMap) exec { @@ -176,7 +176,7 @@ task examples { "sdk_location": files(configurations.distTarBall.files).singleFile, "runner_v2": "true", "suite": "postCommitIT-df${pythonVersionSuffix}-no-xdist", - "collect": "examples_postcommit and no_xdist" + "collect": "examples_postcommit and no_xdist and not sickbay_dataflow" ] def cmdArgs = mapToArgString(argMap) exec { From 9c0282c66e776e910f5aa9e889cc9dc17dfce631 Mon Sep 17 00:00:00 2001 From: Ankur Goenka Date: Fri, 18 Feb 2022 15:09:32 -0800 Subject: [PATCH 06/61] [BEAM-13952] Sickbaying testAfterProcessingTimeContinuationTriggerUsingState --- runners/google-cloud-dataflow-java/build.gradle | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/runners/google-cloud-dataflow-java/build.gradle b/runners/google-cloud-dataflow-java/build.gradle index 89d185bf53512..355fc4547dfdc 100644 --- a/runners/google-cloud-dataflow-java/build.gradle +++ b/runners/google-cloud-dataflow-java/build.gradle @@ -367,7 +367,13 @@ task printRunnerV2PipelineOptions { task validatesRunner { group = "Verification" description "Validates Dataflow runner" - dependsOn(createLegacyWorkerValidatesRunnerTest(name: 'validatesRunnerLegacyWorkerTest')) + dependsOn(createLegacyWorkerValidatesRunnerTest( + name: 'validatesRunnerLegacyWorkerTest', + excludedTests: [ + // TODO(BEAM-13952) + 'org.apache.beam.sdk.transforms.GroupByKeyTest$BasicTests.testAfterProcessingTimeContinuationTriggerUsingState', + ] + )) } task validatesRunnerStreaming { @@ -382,6 +388,10 @@ task validatesRunnerStreaming { 'org.apache.beam.sdk.testing.UsesRequiresTimeSortedInput', 'org.apache.beam.sdk.testing.UsesSetState', ], + excludedTests: [ + // TODO(BEAM-13952) + 'org.apache.beam.sdk.transforms.GroupByKeyTest$BasicTests.testAfterProcessingTimeContinuationTriggerUsingState' + ] )) } @@ -473,6 +483,9 @@ task validatesRunnerV2 { 'org.apache.beam.sdk.transforms.GroupByKeyTest$WindowTests.testRewindowWithTimestampCombiner', 'org.apache.beam.sdk.transforms.FlattenTest.testFlattenWithDifferentInputAndOutputCoders2', + + // TODO(BEAM-13952) + 'org.apache.beam.sdk.transforms.GroupByKeyTest$BasicTests.testAfterProcessingTimeContinuationTriggerUsingState', ] )) } From 1c579f9524a35bcf9a3a7b758359c8f7aeabf5fe Mon Sep 17 00:00:00 2001 From: Ankur Date: Fri, 18 Feb 2022 16:08:06 -0800 Subject: [PATCH 07/61] Remove build status from PR (#16902) * Remove build status from PR * Add licence to build_status.md --- .github/PULL_REQUEST_TEMPLATE.md | 363 +--------------------------- .test-infra/BUILD_STATUS.md | 390 +++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+), 362 deletions(-) create mode 100644 .test-infra/BUILD_STATUS.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 90f8a4c382bcb..56bdd61b629e2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,368 +11,7 @@ Thank you for your contribution! Follow this checklist to help us incorporate yo See the [Contributor Guide](https://beam.apache.org/contribute) for more tips on [how to make review process smoother](https://beam.apache.org/contribute/#make-reviewers-job-easier). -`ValidatesRunner` compliance status (on master branch) --------------------------------------------------------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LangULRDataflowFlinkSamzaSparkTwister2
Go--- - - Build Status - - - - Build Status - - - - Build Status - - - - Build Status - - ---
Java - - Build Status - - - - Build Status -
- - Build Status -
- - Build Status -
- - Build Status -
- - Build Status -
-
- - Build Status -
- - Build Status -
- - Build Status -
- - Build Status - -
- - Build Status -
- - Build Status - -
- - Build Status -
- - Build Status -
- - Build Status - -
- - Build Status - -
Python--- - - Build Status -
- - Build Status -
- - Build Status - -
- - Build Status -
- - Build Status - -
- - Build Status - - - - Build Status - - ---
XLang - - Build Status - - - - Build Status -
- - Build Status -
- - Build Status -
-
- - Build Status - - - - Build Status - - - - Build Status - - ---
- -Examples testing status on various runners --------------------------------------------------------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LangULRDataflowFlinkSamzaSparkTwister2
Go---------------------
Java--- - - Build Status -
- - Build Status -
- - Build Status -
-
---------------
Python---------------------
XLang---------------------
- -Post-Commit SDK/Transform Integration Tests Status (on master branch) ------------------------------------------------------------------------------------------------- - - - - - - - - - - - - - - - - -
GoJavaPython
- - Build Status - - - - Build Status - - - - Build Status -
- - Build Status -
- - Build Status - -
- -Pre-Commit Tests Status (on master branch) ------------------------------------------------------------------------------------------------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
---JavaPythonGoWebsiteWhitespaceTypescript
Non-portable - - Build Status -
-
- - Build Status -
- - Build Status -
- - Build Status -
- - Build Status - -
- - Build Status - - - - Build Status - - - - Build Status - - - - Build Status - -
Portable--- - - Build Status - - - - Build Status - - ---------
- -See [.test-infra/jenkins/README](https://github.com/apache/beam/blob/master/.test-infra/jenkins/README.md) for trigger phrase, status and link of all Jenkins jobs. - +To check the build health, please visit [https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md](https://github.com/apache/beam/blob/master/.test-infra/validate-runner/README.md) GitHub Actions Tests Status (on master branch) ------------------------------------------------------------------------------------------------ diff --git a/.test-infra/BUILD_STATUS.md b/.test-infra/BUILD_STATUS.md new file mode 100644 index 0000000000000..1972076df1e2b --- /dev/null +++ b/.test-infra/BUILD_STATUS.md @@ -0,0 +1,390 @@ + + + +`ValidatesRunner` compliance status (on master branch) +-------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LangULRDataflowFlinkSamzaSparkTwister2
Go--- + + Build Status + + + + Build Status + + + + Build Status + + + + Build Status + + ---
Java + + Build Status + + + + Build Status +
+ + Build Status +
+ + Build Status +
+ + Build Status +
+ + Build Status +
+
+ + Build Status +
+ + Build Status +
+ + Build Status +
+ + Build Status + +
+ + Build Status +
+ + Build Status + +
+ + Build Status +
+ + Build Status +
+ + Build Status + +
+ + Build Status + +
Python--- + + Build Status +
+ + Build Status +
+ + Build Status + +
+ + Build Status +
+ + Build Status + +
+ + Build Status + + + + Build Status + + ---
XLang + + Build Status + + + + Build Status +
+ + Build Status +
+ + Build Status +
+
+ + Build Status + + + + Build Status + + + + Build Status + + ---
+ +Examples testing status on various runners +-------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LangULRDataflowFlinkSamzaSparkTwister2
Go---------------------
Java--- + + Build Status +
+ + Build Status +
+ + Build Status +
+
---------------
Python---------------------
XLang---------------------
+ +Post-Commit SDK/Transform Integration Tests Status (on master branch) +------------------------------------------------------------------------------------------------ + + + + + + + + + + + + + + + + +
GoJavaPython
+ + Build Status + + + + Build Status + + + + Build Status +
+ + Build Status +
+ + Build Status + +
+ +Pre-Commit Tests Status (on master branch) +------------------------------------------------------------------------------------------------ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
---JavaPythonGoWebsiteWhitespaceTypescript
Non-portable + + Build Status +
+
+ + Build Status +
+ + Build Status +
+ + Build Status +
+ + Build Status + +
+ + Build Status + + + + Build Status + + + + Build Status + + + + Build Status + +
Portable--- + + Build Status + + + + Build Status + + ---------
+ +See [.test-infra/jenkins/README](https://github.com/apache/beam/blob/master/.test-infra/jenkins/README.md) for trigger phrase, status and link of all Jenkins jobs. + + +GitHub Actions Tests Status (on master branch) +------------------------------------------------------------------------------------------------ +[![Build python source distribution and wheels](https://github.com/apache/beam/workflows/Build%20python%20source%20distribution%20and%20wheels/badge.svg?branch=master&event=schedule)](https://github.com/apache/beam/actions?query=workflow%3A%22Build+python+source+distribution+and+wheels%22+branch%3Amaster+event%3Aschedule) +[![Python tests](https://github.com/apache/beam/workflows/Python%20tests/badge.svg?branch=master&event=schedule)](https://github.com/apache/beam/actions?query=workflow%3A%22Python+Tests%22+branch%3Amaster+event%3Aschedule) +[![Java tests](https://github.com/apache/beam/workflows/Java%20Tests/badge.svg?branch=master&event=schedule)](https://github.com/apache/beam/actions?query=workflow%3A%22Java+Tests%22+branch%3Amaster+event%3Aschedule) + +See [CI.md](https://github.com/apache/beam/blob/master/CI.md) for more information about GitHub Actions CI. From 6d60ac3da5ca2893e5100be2d11407044cf7df28 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 22 Feb 2022 17:50:51 +0100 Subject: [PATCH 08/61] [BEAM-13738] Reenable ignored SQS test after bumping elasticmq for fixed version (#16914) --- .../java/io/amazon-web-services2/build.gradle | 2 +- .../io/aws2/sqs/SqsUnboundedReaderTest.java | 2 -- .../src/test/resources/application.conf | 21 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 sdks/java/io/amazon-web-services2/src/test/resources/application.conf diff --git a/sdks/java/io/amazon-web-services2/build.gradle b/sdks/java/io/amazon-web-services2/build.gradle index 1af0532fd13c8..dceb4c41bed91 100644 --- a/sdks/java/io/amazon-web-services2/build.gradle +++ b/sdks/java/io/amazon-web-services2/build.gradle @@ -59,10 +59,10 @@ dependencies { testImplementation project(path: ":sdks:java:io:common", configuration: "testRuntimeMigration") testImplementation project(path: ":sdks:java:io:kinesis", configuration: "testRuntimeMigration") testImplementation "io.findify:s3mock_2.12:0.2.6" + testImplementation 'org.elasticmq:elasticmq-rest-sqs_2.12:1.3.5' testImplementation library.java.mockito_core testImplementation library.java.guava_testlib testImplementation library.java.junit - testImplementation 'org.elasticmq:elasticmq-rest-sqs_2.12:0.15.6' // later versions conflict with s3mock testImplementation library.java.hamcrest testImplementation "org.assertj:assertj-core:3.11.1" testRuntimeOnly library.java.slf4j_jdk14 diff --git a/sdks/java/io/amazon-web-services2/src/test/java/org/apache/beam/sdk/io/aws2/sqs/SqsUnboundedReaderTest.java b/sdks/java/io/amazon-web-services2/src/test/java/org/apache/beam/sdk/io/aws2/sqs/SqsUnboundedReaderTest.java index 2c2329974df2b..5b1ac0cbb28b2 100644 --- a/sdks/java/io/amazon-web-services2/src/test/java/org/apache/beam/sdk/io/aws2/sqs/SqsUnboundedReaderTest.java +++ b/sdks/java/io/amazon-web-services2/src/test/java/org/apache/beam/sdk/io/aws2/sqs/SqsUnboundedReaderTest.java @@ -37,7 +37,6 @@ import org.apache.beam.sdk.io.aws2.sqs.EmbeddedSqsServer.TestCaseEnv; import org.apache.beam.sdk.util.CoderUtils; import org.junit.ClassRule; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -110,7 +109,6 @@ public void testAckDeletedMessage() throws IOException { } @Test - @Ignore("Behavior of SQSRestServer is broken: https://issues.apache.org/jira/browse/BEAM-13738") public void testExtendDeletedMessage() throws IOException { setupMessages(DATA); Clock clock = mock(Clock.class); diff --git a/sdks/java/io/amazon-web-services2/src/test/resources/application.conf b/sdks/java/io/amazon-web-services2/src/test/resources/application.conf new file mode 100644 index 0000000000000..f1fcb1e9f3da4 --- /dev/null +++ b/sdks/java/io/amazon-web-services2/src/test/resources/application.conf @@ -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. +############################################################################### + +# Enable implicit HEAD requests using GET routes for S3Mock +# https://doc.akka.io/docs/akka-http/current/migration-guide/migration-guide-10.2.x.html#transparent-head-requests-now-disabled-by-default +akka.http.server.transparent-head-requests = on From 837e50552189c5d46c8bcc643391531d4c628b61 Mon Sep 17 00:00:00 2001 From: Ankur Date: Tue, 22 Feb 2022 09:47:14 -0800 Subject: [PATCH 09/61] fix build status link (#16907) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 56bdd61b629e2..be1a00e2f56ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,7 +11,7 @@ Thank you for your contribution! Follow this checklist to help us incorporate yo See the [Contributor Guide](https://beam.apache.org/contribute) for more tips on [how to make review process smoother](https://beam.apache.org/contribute/#make-reviewers-job-easier). -To check the build health, please visit [https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md](https://github.com/apache/beam/blob/master/.test-infra/validate-runner/README.md) +To check the build health, please visit [https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md](https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md) GitHub Actions Tests Status (on master branch) ------------------------------------------------------------------------------------------------ From 675c0bc10f813ea593702f5e6a0fd2ce38caf720 Mon Sep 17 00:00:00 2001 From: andreukus Date: Tue, 22 Feb 2022 21:04:20 +0300 Subject: [PATCH 10/61] Merge pull request #16549 from [BEAM-13681][Playground] Refactoring terraform scripts * [BEAM-13681][Playground] Description infrastructure * [BEAM-13681][Playground] Description infrastructure * [BEAM-13681][Playground] Description infrastructure * [BEAM-13681][Playground] Description infrastructure [BEAM-13681][Playground] Description infrastructure [BEAM-13681][Playground] Description infrastructure * [BEAM-13681][Playground] Description infrastructure * [BEAM-13681]fix name account resource * formatting + extracting variables to variables.tf * refactor memorystore, reduce size * display name to variables.tf * rename bucket name for state variable * fix comments * refactoring for terraform scripts + readme * fix image name for java backend * changed README.md * remove whitespaces * fix README.md * Infra READMEs update * fix comments * Terraform refactoring (#148) * Add *.tfvars to .gitignore * Refactor solution with setup module * Remove google-beta from main * Configure provider in separate file * Add network property to gke and memorystore * Fix network provisioning * Patch memorystory Co-authored-by: Damon Douglas * fix checks Co-authored-by: akustov Co-authored-by: Ilya Kozyrev Co-authored-by: Artur Khanin Co-authored-by: Damon Douglas --- .gitignore | 1 + playground/README.md | 13 +- playground/terraform/README.md | 90 ++-------- playground/terraform/applications/README.md | 59 +++++++ .../terraform/applications/backend-go/main.tf | 20 +-- .../applications/backend-go/variables.tf | 2 +- .../applications/backend-java/main.tf | 26 +-- .../applications/backend-java/variables.tf | 10 +- .../applications/backend-python/main.tf | 24 +-- .../applications/backend-python/variables.tf | 4 +- .../applications/backend-router/main.tf | 26 ++- .../applications/backend-router/variables.tf | 2 +- .../terraform/applications/frontend/main.tf | 8 +- .../applications/frontend/variables.tf | 2 +- playground/terraform/infrastructure/README.md | 70 ++++++++ .../artifact_registry/main.tf | 12 +- .../artifact_registry/output.tf | 9 +- .../artifact_registry/variables.tf | 1 - .../buckets/main.tf | 20 +-- .../buckets/output.tf | 16 +- .../buckets/variables.tf | 1 - .../{modules => infrastructure}/gke/main.tf | 27 ++- .../{modules => infrastructure}/gke/output.tf | 3 +- .../terraform/infrastructure/gke/variables.tf | 54 ++++++ playground/terraform/infrastructure/main.tf | 81 +++++++++ .../infrastructure/memorystore/main.tf | 48 +++++ .../infrastructure/memorystore/variables.tf | 77 ++++++++ .../terraform/infrastructure/network/main.tf | 33 ++++ .../vpc => infrastructure/network}/output.tf | 9 +- .../network}/variables.tf | 22 +-- .../terraform/infrastructure/provider.tf | 33 ++++ .../terraform/infrastructure/setup/iam.tf | 62 +++++++ .../terraform/infrastructure/setup/output.tf | 22 +++ .../infrastructure/setup/provider.tf | 23 +++ .../setup/services.tf} | 16 +- .../gke => infrastructure/setup}/variables.tf | 15 +- .../terraform/infrastructure/variables.tf | 165 ++++++++++++++++++ playground/terraform/provider.tf | 6 +- 38 files changed, 877 insertions(+), 235 deletions(-) create mode 100644 playground/terraform/applications/README.md create mode 100644 playground/terraform/infrastructure/README.md rename playground/terraform/{modules => infrastructure}/artifact_registry/main.tf (79%) rename playground/terraform/{modules => infrastructure}/artifact_registry/output.tf (77%) rename playground/terraform/{modules => infrastructure}/artifact_registry/variables.tf (99%) rename playground/terraform/{modules => infrastructure}/buckets/main.tf (70%) rename playground/terraform/{modules => infrastructure}/buckets/output.tf (69%) rename playground/terraform/{modules => infrastructure}/buckets/variables.tf (99%) rename playground/terraform/{modules => infrastructure}/gke/main.tf (68%) rename playground/terraform/{modules => infrastructure}/gke/output.tf (93%) create mode 100644 playground/terraform/infrastructure/gke/variables.tf create mode 100644 playground/terraform/infrastructure/main.tf create mode 100644 playground/terraform/infrastructure/memorystore/main.tf create mode 100644 playground/terraform/infrastructure/memorystore/variables.tf create mode 100644 playground/terraform/infrastructure/network/main.tf rename playground/terraform/{modules/vpc => infrastructure/network}/output.tf (83%) rename playground/terraform/{modules/vpc => infrastructure/network}/variables.tf (68%) create mode 100644 playground/terraform/infrastructure/provider.tf create mode 100644 playground/terraform/infrastructure/setup/iam.tf create mode 100644 playground/terraform/infrastructure/setup/output.tf create mode 100644 playground/terraform/infrastructure/setup/provider.tf rename playground/terraform/{modules/vpc/main.tf => infrastructure/setup/services.tf} (76%) rename playground/terraform/{modules/gke => infrastructure/setup}/variables.tf (79%) create mode 100644 playground/terraform/infrastructure/variables.tf diff --git a/.gitignore b/.gitignore index a749d90d585b4..84720d319b163 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ website/www/yarn-error.log **/*.tfstate **/*.tfstate.* **/*.hcl +**/*.tfvars diff --git a/playground/README.md b/playground/README.md index a119a6eb08b32..875755df9a14e 100644 --- a/playground/README.md +++ b/playground/README.md @@ -22,7 +22,7 @@ The Beam Playground is a web application to run Beam code snippets in a modern browser. This directory holds code to build, test, and deploy the frontend and backend services. -# Requirements +# Development Requirements The following requirements are needed for development, testing, and deploying. @@ -55,12 +55,7 @@ cd beam ./gradlew playground:generateProto ``` -# Backend application +# Deployment -All backend's files are placed into the [backend](./backend) folder. For more details about the backend there are -also [Contribution](./backend/CONTRIBUTE.md) guide and [README](./backend/README.md) file. - -# Frontend application - -All frontend's files are placed into the [frontend](./frontend) folder. For more details about the frontend there are -also [Contribution](./frontend/CONTRIBUTE.md) guide and [README](./frontend/README.md) file. \ No newline at end of file +See [terraform](./terraform/README.md) for details on how to build and deploy +the application and its dependent infrastructure. diff --git a/playground/terraform/README.md b/playground/terraform/README.md index b9afe92fcd2a1..fc24b151b0f54 100644 --- a/playground/terraform/README.md +++ b/playground/terraform/README.md @@ -17,91 +17,25 @@ under the License. --> -## Requirements and setup +# Requirements -In order install a Playground cluster on GCP using Terraform, you'll need: +The following items need to be setup for the Playground cluster deployment on GCP: -* An [GCP account](https://cloud.google.com/) and the [`gcloud`](https://cloud.google.com/sdk/gcloud) command-line tool +* [GCP account](https://cloud.google.com/) +* [`gcloud` command-line tool](https://cloud.google.com/sdk/gcloud) and required setup i.e. login * [Terraform](https://www.terraform.io/downloads.html) tool +* [Docker](https://www.docker.com/get-started) -You'll also need to make sure that you're currently logged into your GCP account via the `gcloud` tool: +# Deployment steps -```bash -$ gcloud auth login -``` -In other case you'll need an environment variable `GOOGLE_APPLICATION_CREDENTIALS` set to JSON key for service account that will be used to deploy resources +## 1. Provision infrastructure -## Installation +To deploy Playground infrastructure follow [README.md](./infrastructure/README.md) for infrastructure module. -You can install Terraform using the instructions [here](https://www.terraform.io/intro/getting-started/install.html). +## 2. Build containers +TBD +## 3. Deploy application -## Creating GCP resources using Terraform - -To get started building GCP resources with Terraform, you'll need to install all Terraform dependencies: - -```bash -$ terraform init -# This will create a .terraform folder -``` - -Once you've done that, you can apply the default Terraform configuration: - -```bash -$ terraform apply -``` - -You should then see this prompt: - -```bash -Do you want to perform these actions? - Terraform will perform the actions described above. - Only 'yes' will be accepted to approve. - - Enter a value: -``` - -Type `yes` and hit **Enter**. Applying the configuration could take several minutes. When it's finished, you should see `Apply complete!` along with some other information, including the number of resources created. - -### Applying a non-default configuration - -You can apply a non-default Terraform configuration by changing the values in the `terraform.tfvars` file. The following variables are available: - -Variable name | Description | Default -:-------------|:------------|:------- -`project_id` | GCP Project ID that will be used to create resources | None -`docker_registry_address` | Address of docker registry | None -`docker_image_name` | Docker Image Name To Be Deployed | `beam_playground-backend`/`beam_playground-frontend` -`docker_image_tag` | Docker Image Tag To Be Deployed | `latest` -`repository_location` | Location of Artifact Registry | `us-central1` -`repository_id` | ID of Artifact Registry | `playground_repository` -`examples_bucket_name` | Name of Bucket to Store Playground Examples | `playground-examples` -`examples_bucket_location` | Location of Playground Examples Bucket | `US` -`examples_storage_class` | Examples Bucket Storage Class | `STANDARD` -`terraform_bucket_name` | Name of Bucket to Store Terraform States | `playground-terraform` -`terraform_bucket_location` | Location of Playground Terraform Bucket | `US` -`terraform_storage_class` | Terraform Bucket Storage Class | `STANDARD` -`vpc_name` | Name of VPC to be created | `playground-vpc` -`create_subnets` | Auto Create Subnets Inside VPC | `true` -`mtu` | MTU Inside VPC | `1460` - -### What is installed - -After applying terraform following resources will be created: - -* GCP [Artifact Registry](https://cloud.google.com/artifact-registry) used to store application docker files: -* VPC to run GCP App Engine VM's ([VPC](https://cloud.google.com/vpc)) -* 2 buckets to store Examples for Playground Application and Terraform states [buckets](https://cloud.google.com/storage/docs/key-terms#buckets) -* 2 GCP [App Engine](https://cloud.google.com/appengine) services for backend and frontend applications - - -### Destroying your cluster - -At any point, you can destroy all GCP resources associated with your cluster using Terraform's `destroy` command: - -```bash -$ terraform destroy -``` - - +TBD diff --git a/playground/terraform/applications/README.md b/playground/terraform/applications/README.md new file mode 100644 index 0000000000000..84c1f3fa43c8b --- /dev/null +++ b/playground/terraform/applications/README.md @@ -0,0 +1,59 @@ + + +## Deployment to AppEngine + +*Note: All requirements are listed in the [README.md](../README.md) of the Terraform module.* + +Installation of all Terraform dependencies is required to get started building GCP resources with Terraform: + +```bash +$ terraform init +# This will create a .terraform folder +``` + +Once it has been done, default Terraform configuration can be applied: + +```bash +$ terraform apply +``` + +Then the following dialog will be displayed in the console: + +```bash +Do you want to perform these actions? + Terraform will perform the actions described above. + Only 'yes' will be accepted to approve. + + Enter a value: +``` + +Type `yes` and hit **Enter**. Applying of the configuration could take several minutes. `Apply complete!` will be displayed +when it is finished, along with the number of created resources. + +### Applying a non-default configuration + +To apply non-default Terraform configuration, pass the corresponding values as a variables with `terraform apply` command. +All variables are listed in the [variables.tf (TBD)](variables.tf) file. + +### What is installed + +TBD + + diff --git a/playground/terraform/applications/backend-go/main.tf b/playground/terraform/applications/backend-go/main.tf index 47d68476954a5..48b2759fe1c00 100644 --- a/playground/terraform/applications/backend-go/main.tf +++ b/playground/terraform/applications/backend-go/main.tf @@ -18,10 +18,10 @@ # resource "google_app_engine_flexible_app_version" "backend_app_go" { - version_id = "v1" - project = "${var.project_id}" - service = "${var.service_name}" - runtime = "custom" + version_id = "v1" + project = var.project_id + service = var.service_name + runtime = "custom" delete_service_on_destroy = true liveness_check { @@ -35,7 +35,7 @@ resource "google_app_engine_flexible_app_version" "backend_app_go" { automatic_scaling { max_total_instances = 7 min_total_instances = 2 - cool_down_period = "120s" + cool_down_period = "120s" cpu_utilization { target_utilization = 0.7 } @@ -43,14 +43,14 @@ resource "google_app_engine_flexible_app_version" "backend_app_go" { resources { memory_gb = 16 - cpu = 8 + cpu = 8 } env_variables = { - CACHE_TYPE="${var.cache_type}" - CACHE_ADDRESS="${var.cache_address}:6379" - NUM_PARALLEL_JOBS=30 - LAUNCH_SITE = "app_engine" + CACHE_TYPE = var.cache_type + CACHE_ADDRESS = "${var.cache_address}:6379" + NUM_PARALLEL_JOBS = 30 + LAUNCH_SITE = "app_engine" } deployment { diff --git a/playground/terraform/applications/backend-go/variables.tf b/playground/terraform/applications/backend-go/variables.tf index 52df044a879a4..6a69ef630bed3 100644 --- a/playground/terraform/applications/backend-go/variables.tf +++ b/playground/terraform/applications/backend-go/variables.tf @@ -32,7 +32,7 @@ variable "docker_image_name" { variable "docker_image_tag" { description = "Docker Image Tag To Be Deployed" - default = "latest" + default = "latest" } variable "service_name" { diff --git a/playground/terraform/applications/backend-java/main.tf b/playground/terraform/applications/backend-java/main.tf index f2c47011cb255..e7dc97fec4d77 100644 --- a/playground/terraform/applications/backend-java/main.tf +++ b/playground/terraform/applications/backend-java/main.tf @@ -18,16 +18,16 @@ # resource "google_app_engine_flexible_app_version" "backend_app" { - version_id = "v1" - project = "${var.project_id}" - service = "${var.service_name}" - runtime = "custom" + version_id = "v1" + project = var.project_id + service = var.service_name + runtime = "custom" delete_service_on_destroy = true - - + + liveness_check { - path = "/liveness" - initial_delay = "40s" + path = "/liveness" + initial_delay = "40s" } readiness_check { @@ -44,15 +44,15 @@ resource "google_app_engine_flexible_app_version" "backend_app" { } env_variables = { - CACHE_TYPE="${var.cache_type}" - CACHE_ADDRESS="${var.cache_address}:6379" - NUM_PARALLEL_JOBS=10 - LAUNCH_SITE = "app_engine" + CACHE_TYPE = var.cache_type + CACHE_ADDRESS = "${var.cache_address}:6379" + NUM_PARALLEL_JOBS = 10 + LAUNCH_SITE = "app_engine" } resources { memory_gb = 16 - cpu = 8 + cpu = 8 volumes { name = "inmemory" size_gb = var.volume_size diff --git a/playground/terraform/applications/backend-java/variables.tf b/playground/terraform/applications/backend-java/variables.tf index 62bc438b65b8b..0330aa0af6a0e 100644 --- a/playground/terraform/applications/backend-java/variables.tf +++ b/playground/terraform/applications/backend-java/variables.tf @@ -32,19 +32,19 @@ variable "docker_image_name" { variable "docker_image_tag" { description = "Docker Image Tag To Be Deployed" - default = "latest" + default = "latest" } variable "memory_size" { description = "RAM in GB. The requested memory for the application" - type = number - default = 2 + type = number + default = 2 } variable "volume_size" { description = "Size of the in memory file system to be used by the application, in GB" - type = number - default = 1 + type = number + default = 1 } variable "service_name" { diff --git a/playground/terraform/applications/backend-python/main.tf b/playground/terraform/applications/backend-python/main.tf index 73ed1701a1c3c..e4d71db4047ac 100644 --- a/playground/terraform/applications/backend-python/main.tf +++ b/playground/terraform/applications/backend-python/main.tf @@ -18,24 +18,26 @@ # resource "google_app_engine_flexible_app_version" "backend_app_python" { - version_id = "v1" - project = "${var.project_id}" - service = "${var.service_name}" - runtime = "custom" + version_id = "v1" + project = var.project_id + service = var.service_name + runtime = "custom" delete_service_on_destroy = true liveness_check { - path = "" + path = "/liveness" + initial_delay = "40s" } readiness_check { path = "/readiness" } + automatic_scaling { max_total_instances = 7 min_total_instances = 2 - cool_down_period = "120s" + cool_down_period = "120s" cpu_utilization { target_utilization = 0.7 } @@ -43,14 +45,14 @@ resource "google_app_engine_flexible_app_version" "backend_app_python" { resources { memory_gb = 16 - cpu = 8 + cpu = 8 } env_variables = { - CACHE_TYPE="${var.cache_type}" - CACHE_ADDRESS="${var.cache_address}:6379" - NUM_PARALLEL_JOBS=70 - LAUNCH_SITE = "app_engine" + CACHE_TYPE = var.cache_type + CACHE_ADDRESS = "${var.cache_address}:6379" + NUM_PARALLEL_JOBS = 70 + LAUNCH_SITE = "app_engine" } deployment { diff --git a/playground/terraform/applications/backend-python/variables.tf b/playground/terraform/applications/backend-python/variables.tf index 4a309e47c9a58..518f20b14b785 100644 --- a/playground/terraform/applications/backend-python/variables.tf +++ b/playground/terraform/applications/backend-python/variables.tf @@ -27,12 +27,12 @@ variable "docker_registry_address" { variable "docker_image_name" { description = "Docker Image Name To Be Deployed" - default = "beam_playground-backend-python" + default = "beam_playground-backend-python" } variable "docker_image_tag" { description = "Docker Image Tag To Be Deployed" - default = "latest" + default = "latest" } variable "service_name" { diff --git a/playground/terraform/applications/backend-router/main.tf b/playground/terraform/applications/backend-router/main.tf index 92cc652bfd9d9..7227b4b4367b2 100644 --- a/playground/terraform/applications/backend-router/main.tf +++ b/playground/terraform/applications/backend-router/main.tf @@ -18,14 +18,15 @@ # resource "google_app_engine_flexible_app_version" "backend_app_router" { - version_id = "v1" - project = "${var.project_id}" - service = "${var.service_name}" - runtime = "custom" + version_id = "v1" + project = var.project_id + service = var.service_name + runtime = "custom" delete_service_on_destroy = true liveness_check { - path = "" + path = "/liveness" + initial_delay = "40s" } readiness_check { @@ -34,7 +35,7 @@ resource "google_app_engine_flexible_app_version" "backend_app_router" { automatic_scaling { max_total_instances = 3 - min_total_instances = 2 + min_total_instances = 1 cool_down_period = "120s" cpu_utilization { target_utilization = 0.7 @@ -42,17 +43,14 @@ resource "google_app_engine_flexible_app_version" "backend_app_router" { } resources { - memory_gb = 16 - cpu = 8 + memory_gb = 4 + cpu = 2 } env_variables = { - CACHE_TYPE="${var.cache_type}" - CACHE_ADDRESS="${var.cache_address}:6379" - NUM_PARALLEL_JOBS=30 - LAUNCH_SITE = "app_engine" - PIPELINE_EXPIRATION_TIMEOUT = "5m" - KEY_EXPIRATION_TIME = "7m" + CACHE_TYPE = var.cache_type + CACHE_ADDRESS = "${var.cache_address}:6379" + LAUNCH_SITE = "app_engine" } deployment { diff --git a/playground/terraform/applications/backend-router/variables.tf b/playground/terraform/applications/backend-router/variables.tf index 8211843dd7c82..37f693569cd89 100644 --- a/playground/terraform/applications/backend-router/variables.tf +++ b/playground/terraform/applications/backend-router/variables.tf @@ -32,7 +32,7 @@ variable "docker_image_name" { variable "docker_image_tag" { description = "Docker Image Tag To Be Deployed" - default = "latest" + default = "latest" } variable "service_name" { diff --git a/playground/terraform/applications/frontend/main.tf b/playground/terraform/applications/frontend/main.tf index d8549d62f69b4..91f75b6dac44d 100644 --- a/playground/terraform/applications/frontend/main.tf +++ b/playground/terraform/applications/frontend/main.tf @@ -18,10 +18,10 @@ # resource "google_app_engine_flexible_app_version" "frontend_app" { - version_id = "v1" - project = "${var.project_id}" - service = "${var.service_name}" - runtime = "custom" + version_id = "v1" + project = var.project_id + service = var.service_name + runtime = "custom" delete_service_on_destroy = true liveness_check { diff --git a/playground/terraform/applications/frontend/variables.tf b/playground/terraform/applications/frontend/variables.tf index 1e5b2a27b175c..20317903651ef 100644 --- a/playground/terraform/applications/frontend/variables.tf +++ b/playground/terraform/applications/frontend/variables.tf @@ -32,7 +32,7 @@ variable "docker_image_name" { variable "docker_image_tag" { description = "Docker Image Tag To Be Deployed" - default = "latest" + default = "latest" } variable "service_name" { diff --git a/playground/terraform/infrastructure/README.md b/playground/terraform/infrastructure/README.md new file mode 100644 index 0000000000000..90a522316b445 --- /dev/null +++ b/playground/terraform/infrastructure/README.md @@ -0,0 +1,70 @@ + + +# Overview + +This directory provisions required infrastructure for the application. + +# Requirements + +See [playground/README.md](../README.md) for a list of the requirements +prior to following these instructions. + +# Usage + +## Terraform init + +Follow conventional terraform workflow to build this solution. +You will be prompted for required variables. +Alternatively, you may create a `vars.tfvars` file and +apply the `-var-file=vars.tfvars` flag. + +Initialize the terraform environment. + +``` +terraform init +``` + +## Terraform plan + +Plan the terraform solution. + +``` +terraform plan +``` + +or + +``` +terraform plan -var-file=vars.tfvars +``` + +## Terraform apply + +Apply the terraform solution. + +``` +terraform apply +``` + +or + +``` +terraform apply -var-file=vars.tfvars +``` \ No newline at end of file diff --git a/playground/terraform/modules/artifact_registry/main.tf b/playground/terraform/infrastructure/artifact_registry/main.tf similarity index 79% rename from playground/terraform/modules/artifact_registry/main.tf rename to playground/terraform/infrastructure/artifact_registry/main.tf index 6bbdf53462d41..d790f9d5a8282 100644 --- a/playground/terraform/modules/artifact_registry/main.tf +++ b/playground/terraform/infrastructure/artifact_registry/main.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -19,11 +18,12 @@ # resource "google_artifact_registry_repository" "playground_repo" { + // TODO: remove when generally available provider = google-beta - project = "${var.project_id}" - location = "${var.repository_location}" - repository_id = "${var.repository_id}" - description = "Playground docker repository" - format = "DOCKER" + project = var.project_id + location = var.repository_location + repository_id = var.repository_id + description = "Playground docker repository" + format = "DOCKER" } diff --git a/playground/terraform/modules/artifact_registry/output.tf b/playground/terraform/infrastructure/artifact_registry/output.tf similarity index 77% rename from playground/terraform/modules/artifact_registry/output.tf rename to playground/terraform/infrastructure/artifact_registry/output.tf index 89a8af415de07..822f589b9a0e1 100644 --- a/playground/terraform/modules/artifact_registry/output.tf +++ b/playground/terraform/infrastructure/artifact_registry/output.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -19,13 +18,13 @@ # output "registry_name" { - value = "${google_artifact_registry_repository.playground_repo.name}" + value = google_artifact_registry_repository.playground_repo.name } -output "regirsty_id" { - value = "${google_artifact_registry_repository.playground_repo.id}" +output "registry_id" { + value = google_artifact_registry_repository.playground_repo.id } output "location" { - value = "${google_artifact_registry_repository.playground_repo.location}" + value = google_artifact_registry_repository.playground_repo.location } diff --git a/playground/terraform/modules/artifact_registry/variables.tf b/playground/terraform/infrastructure/artifact_registry/variables.tf similarity index 99% rename from playground/terraform/modules/artifact_registry/variables.tf rename to playground/terraform/infrastructure/artifact_registry/variables.tf index 7dcacd864d00c..f3c450fecc45a 100644 --- a/playground/terraform/modules/artifact_registry/variables.tf +++ b/playground/terraform/infrastructure/artifact_registry/variables.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file diff --git a/playground/terraform/modules/buckets/main.tf b/playground/terraform/infrastructure/buckets/main.tf similarity index 70% rename from playground/terraform/modules/buckets/main.tf rename to playground/terraform/infrastructure/buckets/main.tf index 4d85cf983e89b..b0f51d9f6681f 100644 --- a/playground/terraform/modules/buckets/main.tf +++ b/playground/terraform/infrastructure/buckets/main.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -19,22 +18,21 @@ # resource "google_storage_bucket" "examples_bucket" { - name = "${var.examples_bucket_name}" - location = "${var.examples_bucket_location}" - project = "${var.project_id}" - storage_class = "${var.examples_storage_class}" - uniform_bucket_level_access = true + name = var.examples_bucket_name + location = var.examples_bucket_location + project = var.project_id + storage_class = var.examples_storage_class } resource "google_storage_bucket_access_control" "public_rule" { bucket = google_storage_bucket.examples_bucket.name - role = "VIEWER" + role = "READER" entity = "allUsers" } resource "google_storage_bucket" "terraform_bucket" { - name = "${var.terraform_bucket_name}" - location = "${var.terraform_bucket_location}" - project = "${var.project_id}" - storage_class = "${var.terraform_storage_class}" + name = var.terraform_bucket_name + location = var.terraform_bucket_location + project = var.project_id + storage_class = var.terraform_storage_class } diff --git a/playground/terraform/modules/buckets/output.tf b/playground/terraform/infrastructure/buckets/output.tf similarity index 69% rename from playground/terraform/modules/buckets/output.tf rename to playground/terraform/infrastructure/buckets/output.tf index 15a0ca88a09dc..9118f527fe667 100644 --- a/playground/terraform/modules/buckets/output.tf +++ b/playground/terraform/infrastructure/buckets/output.tf @@ -18,33 +18,33 @@ # output "examples-bucket-id" { - value = "${google_storage_bucket.examples_bucket.id}" + value = google_storage_bucket.examples_bucket.id } output "examples-bucket-name" { - value = "${google_storage_bucket.examples_bucket.name}" + value = google_storage_bucket.examples_bucket.name } output "examples-bucket-project" { - value = "${google_storage_bucket.examples_bucket.project}" + value = google_storage_bucket.examples_bucket.project } output "examples-bucket-location" { - value = "${google_storage_bucket.examples_bucket.location}" + value = google_storage_bucket.examples_bucket.location } output "terraform-bucket-id" { - value = "${google_storage_bucket.terraform_bucket.id}" + value = google_storage_bucket.terraform_bucket.id } output "terraform-bucket-name" { - value = "${google_storage_bucket.terraform_bucket.name}" + value = google_storage_bucket.terraform_bucket.name } output "terraform-bucket-project" { - value = "${google_storage_bucket.terraform_bucket.project}" + value = google_storage_bucket.terraform_bucket.project } output "terraform-bucket-location" { - value = "${google_storage_bucket.terraform_bucket.location}" + value = google_storage_bucket.terraform_bucket.location } diff --git a/playground/terraform/modules/buckets/variables.tf b/playground/terraform/infrastructure/buckets/variables.tf similarity index 99% rename from playground/terraform/modules/buckets/variables.tf rename to playground/terraform/infrastructure/buckets/variables.tf index a0aba8b76d2f8..4e256fd0dbd3b 100644 --- a/playground/terraform/modules/buckets/variables.tf +++ b/playground/terraform/infrastructure/buckets/variables.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file diff --git a/playground/terraform/modules/gke/main.tf b/playground/terraform/infrastructure/gke/main.tf similarity index 68% rename from playground/terraform/modules/gke/main.tf rename to playground/terraform/infrastructure/gke/main.tf index bbe1862fbfac3..bdc11d1b6b29d 100644 --- a/playground/terraform/modules/gke/main.tf +++ b/playground/terraform/infrastructure/gke/main.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -18,28 +17,24 @@ # under the License. # -terraform { - backend "gcs" { - bucket = "playground_terraform" - } -} - resource "google_container_cluster" "playground-gke" { - name = "playground-examples" - project = "${var.project_id}" - location = "us-central1-a" - initial_node_count = "${var.node_count}" + name = var.name + project = var.project_id + location = var.location + initial_node_count = var.node_count + network = var.network + subnetwork = var.subnetwork node_config { - machine_type = "${var.machine_type}" - service_account = "${var.service_account}" + machine_type = var.machine_type + service_account = var.service_account_email - oauth_scopes = [ + oauth_scopes = [ "https://www.googleapis.com/auth/cloud-platform" ] labels = { - component = "beam-playground" + component = "beam-playground" } - tags = ["beam-playground"] + tags = ["beam-playground"] } } diff --git a/playground/terraform/modules/gke/output.tf b/playground/terraform/infrastructure/gke/output.tf similarity index 93% rename from playground/terraform/modules/gke/output.tf rename to playground/terraform/infrastructure/gke/output.tf index b380083b7609e..96c5796cc1eef 100644 --- a/playground/terraform/modules/gke/output.tf +++ b/playground/terraform/infrastructure/gke/output.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -19,5 +18,5 @@ # output "gke_name" { - value = "${google_container_cluster.playground-gke.name}" + value = google_container_cluster.playground-gke.name } diff --git a/playground/terraform/infrastructure/gke/variables.tf b/playground/terraform/infrastructure/gke/variables.tf new file mode 100644 index 0000000000000..3d7cb491e11e1 --- /dev/null +++ b/playground/terraform/infrastructure/gke/variables.tf @@ -0,0 +1,54 @@ +# +# 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. +# + +variable "project_id" { + description = "The GCP Project ID where Playground Applications will be created" +} + +variable "machine_type" { + description = "Node pool machine types" + default = "e2-standard-4" +} + +variable "node_count" { + description = "Node pool size" + default = 1 +} + +variable "service_account_email" { + description = "Service account email" +} + +variable "name" { + description = "Name of GKE cluster" + default = "playground-examples" +} + +variable "location" { + description = "Location of GKE cluster" + default = "us-central1-a" +} + +variable "network" { + description = "GCP network within which resources are provisioned" +} + +variable "subnetwork" { + description = "GCP subnetwork within which resources are provisioned" +} diff --git a/playground/terraform/infrastructure/main.tf b/playground/terraform/infrastructure/main.tf new file mode 100644 index 0000000000000..702bae61c1bdf --- /dev/null +++ b/playground/terraform/infrastructure/main.tf @@ -0,0 +1,81 @@ +# +# 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. +# + +module "setup" { + source = "./setup" + project_id = var.project_id + region = var.region + service_account_id = var.service_account_id +} + +module "network" { + depends_on = [module.setup] + source = "./network" + project_id = var.project_id + region = var.region +} + +module "buckets" { + depends_on = [module.setup] + source = "./buckets" + project_id = var.project_id + terraform_bucket_name = var.terraform_bucket_name + terraform_storage_class = var.examples_storage_class + terraform_bucket_location = var.terraform_bucket_location + examples_bucket_name = var.examples_bucket_name + examples_storage_class = var.examples_storage_class + examples_bucket_location = var.examples_bucket_location +} + +module "artifact_registry" { + depends_on = [module.setup, module.buckets] + source = "./artifact_registry" + project_id = var.project_id + repository_id = var.repository_id + repository_location = var.repository_location +} + +module "memorystore" { + depends_on = [module.setup, module.artifact_registry] + source = "./memorystore" + project_id = var.project_id + terraform_state_bucket_name = var.terraform_bucket_name + redis_version = var.redis_version + redis_region = var.redis_region + redis_name = var.redis_name + redis_tier = var.redis_tier + redis_replica_count = var.redis_replica_count + redis_memory_size_gb = var.redis_memory_size_gb + read_replicas_mode = var.read_replicas_mode + network = module.network.network + subnetwork = module.network.subnetwork +} + +module "gke" { + depends_on = [module.setup, module.artifact_registry, module.memorystore] + source = "./gke" + project_id = var.project_id + machine_type = var.gke_machine_type + node_count = var.gke_node_count + name = var.gke_name + location = var.gke_location + service_account_email = module.setup.service_account_email + network = module.network.network + subnetwork = module.network.subnetwork +} diff --git a/playground/terraform/infrastructure/memorystore/main.tf b/playground/terraform/infrastructure/memorystore/main.tf new file mode 100644 index 0000000000000..9248fa7e84c25 --- /dev/null +++ b/playground/terraform/infrastructure/memorystore/main.tf @@ -0,0 +1,48 @@ +# +# 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. +# + + +data "terraform_remote_state" "remote_state_vpc" { + backend = "gcs" + config = { + bucket = var.terraform_state_bucket_name + } +} + +data "google_compute_network" "default" { + project = var.project_id + name = var.network +} + +# Redis for storing state of Playground application. +# In this cache Playground instances stores pipeline's statuses, outputs and pipeline's graph +resource "google_redis_instance" "cache" { + // TODO: remove when replica_count, etc is generally available + provider = google-beta + project = var.project_id + region = var.redis_region + name = var.redis_name + tier = var.redis_tier + memory_size_gb = var.redis_memory_size_gb + replica_count = var.redis_replica_count + redis_version = var.redis_version + display_name = var.display_name + read_replicas_mode = var.read_replicas_mode + authorized_network = data.google_compute_network.default.id +} diff --git a/playground/terraform/infrastructure/memorystore/variables.tf b/playground/terraform/infrastructure/memorystore/variables.tf new file mode 100644 index 0000000000000..31c79bde11fce --- /dev/null +++ b/playground/terraform/infrastructure/memorystore/variables.tf @@ -0,0 +1,77 @@ +# +# 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. +# + +variable "project_id" { + description = "The GCP Project ID where Playground Applications will be created" +} + +variable "redis_version" { + description = "The GCP Project ID where Playground Applications will be created" + default = "REDIS_6_X" +} + +variable "terraform_state_bucket_name" { + description = "Bucket name for terraform state" + default = "beam_playground_terraform" +} + +variable "redis_region" { + description = "Region of Redis" + default = "us-central1" +} + +variable "redis_name" { + description = "Name of Redis" + default = "playground-backend-cache" +} + +variable "redis_tier" { + description = "Tier of Redis" + default = "STANDARD_HA" +} + +variable "redis_replica_count" { + type = number + description = "Redis's replica count" + default = 1 +} + +variable "redis_memory_size_gb" { + type = number + description = "Size of Redis memory" + default = 5 +} + +variable "display_name" { + default = "Playground Cache" + description = "Display name for Redis service" +} + +variable "read_replicas_mode" { + description = "Read replica mode. Can only be specified when trying to create the instance." + default = "READ_REPLICAS_ENABLED" +} + +variable "network" { + description = "GCP network within which resources are provisioned" +} + +variable "subnetwork" { + description = "GCP subnetwork within which resources are provisioned" +} diff --git a/playground/terraform/infrastructure/network/main.tf b/playground/terraform/infrastructure/network/main.tf new file mode 100644 index 0000000000000..f9c346b2cd077 --- /dev/null +++ b/playground/terraform/infrastructure/network/main.tf @@ -0,0 +1,33 @@ +# +# 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. +# + +resource "google_compute_network" "playground" { + project = var.project_id + name = var.network_name + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "playground" { + ip_cidr_range = var.subnetwork_cidr_range + name = var.subnetwork_name + network = google_compute_network.playground.id + region = var.region + project = var.project_id + private_ip_google_access = true +} diff --git a/playground/terraform/modules/vpc/output.tf b/playground/terraform/infrastructure/network/output.tf similarity index 83% rename from playground/terraform/modules/vpc/output.tf rename to playground/terraform/infrastructure/network/output.tf index f96d6ba499617..abaa9f9cbb6c6 100644 --- a/playground/terraform/modules/vpc/output.tf +++ b/playground/terraform/infrastructure/network/output.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -18,10 +17,10 @@ # under the License. # -output "vpc_name" { - value = "${google_compute_network.playground_vpc.name}" +output "network" { + value = google_compute_network.playground.name } -output "vpc_id" { - value = "${google_compute_network.playground_vpc.id}" +output "subnetwork" { + value = google_compute_subnetwork.playground.name } diff --git a/playground/terraform/modules/vpc/variables.tf b/playground/terraform/infrastructure/network/variables.tf similarity index 68% rename from playground/terraform/modules/vpc/variables.tf rename to playground/terraform/infrastructure/network/variables.tf index 61fcbbd43b861..1d8e1b8063668 100644 --- a/playground/terraform/modules/vpc/variables.tf +++ b/playground/terraform/infrastructure/network/variables.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -22,20 +21,21 @@ variable "project_id" { description = "The GCP Project ID where Playground Applications will be created" } -variable "vpc_name" { +variable "region" { + description = "The Google Cloud Platform (GCP) region in which to provision resources" +} + +variable "network_name" { description = "Name of VPC to be created" default = "playground-vpc" } -variable "create_subnets" { - description = "Auto Create Subnets Inside VPC" - default = true +variable "subnetwork_name" { + description = "Name of VPC to be created" + default = "playground-vpc" } -variable "mtu" { - description = "MTU Inside VPC" - default = 1460 +variable "subnetwork_cidr_range" { + description = "The address range for this subnet, in CIDR notation. Use a standard private VPC network address range: for example, 10.0.0.0/9." + default = "10.128.0.0/20" } - - - diff --git a/playground/terraform/infrastructure/provider.tf b/playground/terraform/infrastructure/provider.tf new file mode 100644 index 0000000000000..ae8c6f0af5799 --- /dev/null +++ b/playground/terraform/infrastructure/provider.tf @@ -0,0 +1,33 @@ +# +# 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. +# + +provider "google" { + region = var.region + // TODO may need to run module.setup first independent of this solution and add the terraform service account as a variable + // This allows us to use a service account to provision resources without downloading or storing service account keys + # impersonate_service_account = module.setup.terraform_service_account_email +} + +// TODO: required by artifact registry and memorystore; remove when generally available +provider "google-beta" { + region = var.region + // TODO may need to run module.setup first independent of this solution and add the terraform service account as a variable + // This allows us to use a service account to provision resources without downloading or storing service account keys + # impersonate_service_account = module.setup.terraform_service_account_email +} diff --git a/playground/terraform/infrastructure/setup/iam.tf b/playground/terraform/infrastructure/setup/iam.tf new file mode 100644 index 0000000000000..789940d0a199a --- /dev/null +++ b/playground/terraform/infrastructure/setup/iam.tf @@ -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. +# + +#resource "google_service_account" "terraform_service_account" { +# account_id = "terraform" +# display_name = "terraform" +#} +# +#resource "google_project_iam_member" "terraform_service_account_roles" { +# for_each = toset([ +# // TODO: add the required roles to provision resources (not OWNER :-)!) +# ]) +# role = each.key +# member = "serviceAccount:${google_service_account.terraform_service_account.email}" +# project = var.project_id +#} +# +#resource "google_service_account_iam_binding" "terraform_service_account_token_permissions" { +# service_account_id = google_service_account.terraform_service_account.id +# members = [ +# "user:${var.developer_account_email}" // TODO: add variable +# ] +# role = "roles/iam.serviceAccountTokenCreator" +#} + +#resource "google_service_account_iam_binding" "application_service_account_binding" { +# members = [ +# "serviceAccount:${google_service_account.terraform_service_account.email}" +# ] +# role = "roles/iam.serviceAccountUser" +# service_account_id = google_service_account.playground_service_account.id +#} + +resource "google_service_account" "playground_service_account" { + account_id = var.service_account_id + display_name = var.service_account_id +} + +resource "google_project_iam_member" "terraform_service_account_roles" { + for_each = toset([ + "roles/container.serviceAgent", + ]) + role = each.key + member = "serviceAccount:${google_service_account.playground_service_account.email}" + project = var.project_id +} \ No newline at end of file diff --git a/playground/terraform/infrastructure/setup/output.tf b/playground/terraform/infrastructure/setup/output.tf new file mode 100644 index 0000000000000..547bc4fe97706 --- /dev/null +++ b/playground/terraform/infrastructure/setup/output.tf @@ -0,0 +1,22 @@ +# +# 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. +# + +output "service_account_email" { + value = google_service_account.playground_service_account.email +} \ No newline at end of file diff --git a/playground/terraform/infrastructure/setup/provider.tf b/playground/terraform/infrastructure/setup/provider.tf new file mode 100644 index 0000000000000..8576aa7e94f73 --- /dev/null +++ b/playground/terraform/infrastructure/setup/provider.tf @@ -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. +# + +provider "google" { + region = var.region + project = var.project_id +} diff --git a/playground/terraform/modules/vpc/main.tf b/playground/terraform/infrastructure/setup/services.tf similarity index 76% rename from playground/terraform/modules/vpc/main.tf rename to playground/terraform/infrastructure/setup/services.tf index 8865276bba17a..52a3306d81db8 100644 --- a/playground/terraform/modules/vpc/main.tf +++ b/playground/terraform/infrastructure/setup/services.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -18,9 +17,14 @@ # under the License. # -resource "google_compute_network" "playground_vpc" { - project = "${var.project_id}" - name = "${var.vpc_name}" - auto_create_subnetworks = "${var.create_subnets}" - mtu = "${var.mtu}" +resource "google_project_service" "required_services" { + for_each = toset([ + "artifactregistry", + "compute", + "container", + "redis", + ]) + service = "${each.key}.googleapis.com" + disable_on_destroy = false } + diff --git a/playground/terraform/modules/gke/variables.tf b/playground/terraform/infrastructure/setup/variables.tf similarity index 79% rename from playground/terraform/modules/gke/variables.tf rename to playground/terraform/infrastructure/setup/variables.tf index 379f703e8fea8..8271fde897f9f 100644 --- a/playground/terraform/modules/gke/variables.tf +++ b/playground/terraform/infrastructure/setup/variables.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -22,16 +21,10 @@ variable "project_id" { description = "The GCP Project ID where Playground Applications will be created" } -variable "machine_type" { - description = "Node pool machine types" - default = "e2-standard-4" -} - -variable "node_count" { - description = "Node pool size" - default = 1 +variable "region" { + description = "The GCP region within which we provision resources" } -variable "service_account" { - description = "Service account email" +variable "service_account_id" { + description = "Service account ID" } diff --git a/playground/terraform/infrastructure/variables.tf b/playground/terraform/infrastructure/variables.tf new file mode 100644 index 0000000000000..217e778c06e2a --- /dev/null +++ b/playground/terraform/infrastructure/variables.tf @@ -0,0 +1,165 @@ +# +# 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. +# + +variable "project_id" { + description = "The GCP Project ID where Playground Applications will be created" +} + +variable "region" { + description = "The GCP region within which we provision resources" + default = "us-central1" +} + +#IAM + +variable "service_account_id" { + description = "Service account ID" + default = "beam-playground" +} + +#GCS + +variable "examples_bucket_name" { + description = "Name of Bucket to Store Playground Examples" + default = "playground-examples" +} + +variable "examples_bucket_location" { + description = "Location of Playground Examples Bucket" + default = "US" +} + +variable "examples_storage_class" { + description = "Examples Bucket Storage Class" + default = "STANDARD" +} + +variable "terraform_bucket_name" { + description = "Name of Bucket to Store Terraform States" + default = "playground_terraform" +} + +variable "terraform_bucket_location" { + description = "Location of Playground Examples Bucket" + default = "US" +} + +variable "terraform_storage_class" { + description = "Terraform Bucket Storage Class" + default = "STANDARD" +} + +# Artifact Registry + +variable "repository_id" { + description = "ID of Artifact Registry" + default = "playground-repository" +} + +variable "repository_location" { + description = "Location of Artifact Registry" + default = "us-central1" +} + +variable "service_account" { + description = "Service account id" + default = "service-account-playground" +} + +#Redis + +variable "redis_version" { + description = "The GCP Project ID where Playground Applications will be created" + default = "REDIS_6_X" +} + +variable "terraform_state_bucket_name" { + description = "Bucket name for terraform state" + default = "beam_playground_terraform" +} + +variable "redis_region" { + description = "Region of Redis" + default = "us-central1" +} + +variable "redis_name" { + description = "Name of Redis" + default = "playground-backend-cache" +} + +variable "redis_tier" { + description = "Tier of Redis" + default = "STANDARD_HA" +} + +variable "redis_replica_count" { + description = "Redis's replica count" + default = 1 +} + +variable "redis_memory_size_gb" { + description = "Size of Redis memory" + default = 5 +} + +variable "read_replicas_mode" { + description = "Read replica mode. Can only be specified when trying to create the instance." + default = "READ_REPLICAS_ENABLED" +} + +#VPC + +variable "vpc_name" { + description = "Name of VPC to be created" + default = "playground-vpc" +} + +variable "create_subnets" { + description = "Auto Create Subnets Inside VPC" + default = true +} + +variable "mtu" { + description = "MTU Inside VPC" + default = 1460 +} + +# GKE + + +variable "gke_machine_type" { + description = "Node pool machine types" + default = "e2-standard-4" +} + +variable "gke_node_count" { + description = "Node pool size" + default = 1 +} + +variable "gke_name" { + description = "Name of GKE cluster" + default = "playground-examples" +} + +variable "gke_location" { + description = "Location of GKE cluster" + default = "us-central1-a" +} diff --git a/playground/terraform/provider.tf b/playground/terraform/provider.tf index 0d212f5f00791..3227ce52fed54 100644 --- a/playground/terraform/provider.tf +++ b/playground/terraform/provider.tf @@ -1,4 +1,3 @@ - # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -19,8 +18,9 @@ # provider "google" { - region = "us-central" + region = "us-central" } +# TODO Please remove it when all resources are available in the stable version provider "google-beta" { - region = "us-central" + region = "us-central" } From 615b4eb129000e5ce84b6c6a5ae5da3d25ef4db0 Mon Sep 17 00:00:00 2001 From: Aydar Farrakhov Date: Tue, 22 Feb 2022 21:05:53 +0300 Subject: [PATCH 11/61] Merge pull request #16732 from [BEAM-13825] [Playground] updated click_run and run_time events with correct label * [BEAM-13825] updated click_run and run_time events with correct label * [BEAM-13825] fix showing output header --- playground/frontend/lib/l10n/app_en.arb | 4 --- .../modules/analytics/analytics_service.dart | 4 +-- .../lib/modules/output/components/output.dart | 2 +- .../components/embedded_appbar_title.dart | 14 ++++++---- .../components/editor_textarea_wrapper.dart | 11 ++++++-- .../playground/states/playground_state.dart | 16 ++++++----- .../frontend/lib/utils/analytics_utils.dart | 28 +++++++++++++++++++ 7 files changed, 56 insertions(+), 23 deletions(-) create mode 100644 playground/frontend/lib/utils/analytics_utils.dart diff --git a/playground/frontend/lib/l10n/app_en.arb b/playground/frontend/lib/l10n/app_en.arb index df18234b71dd1..8b85370a5e807 100644 --- a/playground/frontend/lib/l10n/app_en.arb +++ b/playground/frontend/lib/l10n/app_en.arb @@ -27,10 +27,6 @@ "@cancelExecution": { "description": "Title for the cancel execution notification" }, - "unknownExample": "Unknown Example", - "@unknownExample": { - "description": "Unknown example text part" - }, "log": "Log", "@log": { "description": "Title for the log section" diff --git a/playground/frontend/lib/modules/analytics/analytics_service.dart b/playground/frontend/lib/modules/analytics/analytics_service.dart index 4d6d0c7f6ed11..7c4911e52a466 100644 --- a/playground/frontend/lib/modules/analytics/analytics_service.dart +++ b/playground/frontend/lib/modules/analytics/analytics_service.dart @@ -91,11 +91,11 @@ class AnalyticsService { safeSendEvent(kFeedbackCategory, kClickReportIssueEvent); } - void trackClickRunEvent(ExampleModel? example) { + void trackClickRunEvent(String exampleName) { safeSendEvent( kRunCodeCategory, kClickRunEvent, - label: example?.path ?? '', + label: exampleName, ); } diff --git a/playground/frontend/lib/modules/output/components/output.dart b/playground/frontend/lib/modules/output/components/output.dart index e3d6a5c98993c..0cbf7e311d524 100644 --- a/playground/frontend/lib/modules/output/components/output.dart +++ b/playground/frontend/lib/modules/output/components/output.dart @@ -64,7 +64,7 @@ class _OutputState extends State with SingleTickerProviderStateMixin { children: [ OutputHeader( tabController: tabController, - showOutputPlacements: widget.isEmbedded, + showOutputPlacements: !widget.isEmbedded, showGraph: widget.showGraph, ), Expanded( diff --git a/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart b/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart index 07ad337f1574b..703f765c62091 100644 --- a/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart +++ b/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart @@ -26,8 +26,8 @@ import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/modules/editor/components/run_button.dart'; import 'package:playground/modules/notifications/components/notification.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground/utils/analytics_utils.dart'; import 'package:provider/provider.dart'; class EmbeddedAppBarTitle extends StatelessWidget { @@ -53,18 +53,20 @@ class EmbeddedAppBarTitle extends StatelessWidget { }, runCode: () { final stopwatch = Stopwatch()..start(); + final exampleName = getAnalyticsExampleName( + state.selectedExample, + state.isExampleChanged, + state.sdk, + ); state.runCode( onFinish: () { AnalyticsService.get(context).trackRunTimeEvent( - state.selectedExample?.path ?? - '${AppLocalizations.of(context)!.unknownExample}, sdk ${state.sdk.displayName}', + exampleName, stopwatch.elapsedMilliseconds, ); }, ); - AnalyticsService.get(context).trackClickRunEvent( - state.selectedExample, - ); + AnalyticsService.get(context).trackClickRunEvent(exampleName); }, ), const ToggleThemeIconButton(), diff --git a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart index c7182ddcefc43..52ab98fff3672 100644 --- a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart +++ b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart @@ -27,6 +27,7 @@ import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/modules/notifications/components/notification.dart'; import 'package:playground/modules/sdk/models/sdk.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground/utils/analytics_utils.dart'; import 'package:provider/provider.dart'; class CodeTextAreaWrapper extends StatelessWidget { @@ -86,17 +87,21 @@ class CodeTextAreaWrapper extends StatelessWidget { AnalyticsService analyticsService = AnalyticsService.get(context); final stopwatch = Stopwatch()..start(); + final exampleName = getAnalyticsExampleName( + state.selectedExample, + state.isExampleChanged, + state.sdk, + ); state.runCode( onFinish: () { analyticsService.trackRunTimeEvent( - state.selectedExample?.path ?? - '${AppLocalizations.of(context)!.unknownExample}, sdk ${state.sdk.displayName}', + exampleName, stopwatch.elapsedMilliseconds, ); }, ); AnalyticsService.get(context) - .trackClickRunEvent(state.selectedExample); + .trackClickRunEvent(exampleName); }, ), ], diff --git a/playground/frontend/lib/pages/playground/states/playground_state.dart b/playground/frontend/lib/pages/playground/states/playground_state.dart index 8d08e2fc91122..22f94849afe2a 100644 --- a/playground/frontend/lib/pages/playground/states/playground_state.dart +++ b/playground/frontend/lib/pages/playground/states/playground_state.dart @@ -79,6 +79,14 @@ class PlaygroundState with ChangeNotifier { Stream? get executionTime => _executionTime?.stream; + bool get isExampleChanged { + return selectedExample?.source != source || _arePipelineOptionsChanges; + } + + bool get _arePipelineOptionsChanges { + return pipelineOptions != (_selectedExample?.pipelineOptions ?? ''); + } + bool get graphAvailable => selectedExample?.type != ExampleType.test && [SDK.java, SDK.python].contains(sdk); @@ -139,9 +147,7 @@ class PlaygroundState with ChangeNotifier { } _executionTime?.close(); _executionTime = _createExecutionTimeStream(); - if (_selectedExample?.source == source && - _selectedExample?.outputs != null && - !_arePipelineOptionsChanges) { + if (!isExampleChanged && _selectedExample?.outputs != null) { _showPrecompiledResult(); } else { final request = RunCodeRequestWrapper( @@ -177,10 +183,6 @@ class PlaygroundState with ChangeNotifier { notifyListeners(); } - bool get _arePipelineOptionsChanges { - return pipelineOptions != (_selectedExample?.pipelineOptions ?? ''); - } - _showPrecompiledResult() async { _result = RunCodeResult( status: RunCodeStatus.preparation, diff --git a/playground/frontend/lib/utils/analytics_utils.dart b/playground/frontend/lib/utils/analytics_utils.dart new file mode 100644 index 0000000000000..a9c2081cbea9c --- /dev/null +++ b/playground/frontend/lib/utils/analytics_utils.dart @@ -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. + */ + +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground/modules/sdk/models/sdk.dart'; + +String getAnalyticsExampleName(ExampleModel? example, bool isExampleChanged, SDK sdk) { + final customCodeName = 'Custom code, sdk ${sdk.displayName}'; + if (isExampleChanged) { + return customCodeName; + } + return example?.path ?? customCodeName; +} From c68ade15a76a10884a30bda29243324352a1d0f0 Mon Sep 17 00:00:00 2001 From: "daria.malkova" Date: Tue, 22 Feb 2022 21:09:47 +0300 Subject: [PATCH 12/61] Merge pull request #16683 from [BEAM-13713][Playground] Java graph extraction * Add preparer for graph saving * add goroutine to read graph file * Update playground/backend/internal/code_processing/code_processing.go Co-authored-by: Artur Khanin * Update playground/backend/internal/preparers/python_preparers.go Co-authored-by: Artur Khanin * fix comments * fix comments * Add graph extraction for java pipelint * fix comments * make Wrap method not public * graph * move method GetPreparers to avoid cycle dependencies * move method GetValidators to avoid cycle dependencies * fix merge * fix bug with pipeline.run place Co-authored-by: Artur Khanin Co-authored-by: Ilya Kozyrev --- .../code_processing/code_processing.go | 4 +- playground/backend/internal/fs_tool/fs.go | 1 + .../backend/internal/fs_tool/fs_test.go | 4 + .../backend/internal/fs_tool/go_fs_test.go | 2 + .../backend/internal/fs_tool/java_fs_test.go | 2 + .../internal/fs_tool/lc_constructor.go | 5 ++ .../internal/fs_tool/python_fs_test.go | 2 + .../internal/preparers/java_preparers.go | 66 +++++++++++++++- .../internal/preparers/java_preparers_test.go | 51 +++++++++++- .../internal/preparers/python_preparers.go | 1 - .../setup_tools/builder/setup_builder.go | 3 +- .../setup_tools/builder/setup_builder_test.go | 3 +- .../life_cycle/life_cycle_setuper.go | 3 + .../life_cycle/life_cycle_setuper_test.go | 2 + .../internal/utils/preparares_utils.go | 6 +- .../internal/utils/validators_utils.go | 40 ---------- .../internal/utils/validators_utils_test.go | 79 ------------------- .../backend/internal/validators/validator.go | 23 ++++++ .../internal/validators/validator_test.go | 58 ++++++++++++++ 19 files changed, 222 insertions(+), 133 deletions(-) delete mode 100644 playground/backend/internal/utils/validators_utils.go delete mode 100644 playground/backend/internal/utils/validators_utils_test.go diff --git a/playground/backend/internal/code_processing/code_processing.go b/playground/backend/internal/code_processing/code_processing.go index 88baac8a59294..e39e1a39b553c 100644 --- a/playground/backend/internal/code_processing/code_processing.go +++ b/playground/backend/internal/code_processing/code_processing.go @@ -23,7 +23,6 @@ import ( "beam.apache.org/playground/backend/internal/executors" "beam.apache.org/playground/backend/internal/fs_tool" "beam.apache.org/playground/backend/internal/logger" - "beam.apache.org/playground/backend/internal/preparers" "beam.apache.org/playground/backend/internal/setup_tools/builder" "beam.apache.org/playground/backend/internal/streaming" "beam.apache.org/playground/backend/internal/utils" @@ -35,7 +34,6 @@ import ( "io" "os" "os/exec" - "path/filepath" "reflect" "sync" "time" @@ -114,7 +112,7 @@ func runStep(ctx context.Context, cacheService cache.Cache, paths *fs_tool.LifeC var runError bytes.Buffer runOutput := streaming.RunOutputWriter{Ctx: pipelineLifeCycleCtx, CacheService: cacheService, PipelineId: pipelineId} go readLogFile(pipelineLifeCycleCtx, ctx, cacheService, paths.AbsoluteLogFilePath, pipelineId, stopReadLogsChannel, finishReadLogsChannel) - go readGraphFile(pipelineLifeCycleCtx, ctx, cacheService, filepath.Join(paths.AbsoluteBaseFolderPath, preparers.GraphFileName), pipelineId) + go readGraphFile(pipelineLifeCycleCtx, ctx, cacheService, paths.AbsoluteGraphFilePath, pipelineId) if sdkEnv.ApacheBeamSdk == pb.Sdk_SDK_GO { // For go SDK all logs are placed to stdErr. diff --git a/playground/backend/internal/fs_tool/fs.go b/playground/backend/internal/fs_tool/fs.go index 0cea7c57eada3..b0984279d7f87 100644 --- a/playground/backend/internal/fs_tool/fs.go +++ b/playground/backend/internal/fs_tool/fs.go @@ -40,6 +40,7 @@ type LifeCyclePaths struct { AbsoluteExecutableFilePath string // /path/to/workingDir/pipelinesFolder/{pipelineId}/bin/{pipelineId}.{executableFileExtension} AbsoluteBaseFolderPath string // /path/to/workingDir/pipelinesFolder/{pipelineId} AbsoluteLogFilePath string // /path/to/workingDir/pipelinesFolder/{pipelineId}/logs.log + AbsoluteGraphFilePath string // /path/to/workingDir/pipelinesFolder/{pipelineId}/graph.dot ProjectDir string // /path/to/workingDir/ ExecutableName func(string) (string, error) } diff --git a/playground/backend/internal/fs_tool/fs_test.go b/playground/backend/internal/fs_tool/fs_test.go index 0379466e222e8..8ff4b0d456e2b 100644 --- a/playground/backend/internal/fs_tool/fs_test.go +++ b/playground/backend/internal/fs_tool/fs_test.go @@ -17,6 +17,7 @@ package fs_tool import ( pb "beam.apache.org/playground/backend/internal/api/v1" + "beam.apache.org/playground/backend/internal/utils" "fmt" "github.com/google/uuid" "os" @@ -294,6 +295,7 @@ func TestNewLifeCycle(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(execFileFolder, fmt.Sprintf("%s%s", pipelineId.String(), javaCompiledFileExtension)), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), }, }, }, @@ -315,6 +317,7 @@ func TestNewLifeCycle(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(execFileFolder, fmt.Sprintf("%s%s", pipelineId.String(), goExecutableFileExtension)), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), }, }, }, @@ -336,6 +339,7 @@ func TestNewLifeCycle(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(baseFileFolder, fmt.Sprintf("%s%s", pipelineId.String(), pythonExecutableFileExtension)), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), }, }, }, diff --git a/playground/backend/internal/fs_tool/go_fs_test.go b/playground/backend/internal/fs_tool/go_fs_test.go index 55dbf968b3e9f..a0211751d16ca 100644 --- a/playground/backend/internal/fs_tool/go_fs_test.go +++ b/playground/backend/internal/fs_tool/go_fs_test.go @@ -16,6 +16,7 @@ package fs_tool import ( + "beam.apache.org/playground/backend/internal/utils" "github.com/google/uuid" "path/filepath" "reflect" @@ -57,6 +58,7 @@ func Test_newGoLifeCycle(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(binFileFolder, pipelineId.String()+goExecutableFileExtension), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), }, }, }, diff --git a/playground/backend/internal/fs_tool/java_fs_test.go b/playground/backend/internal/fs_tool/java_fs_test.go index d0d9aa72b369f..d51e7c0318465 100644 --- a/playground/backend/internal/fs_tool/java_fs_test.go +++ b/playground/backend/internal/fs_tool/java_fs_test.go @@ -16,6 +16,7 @@ package fs_tool import ( + "beam.apache.org/playground/backend/internal/utils" "github.com/google/uuid" "os" "path/filepath" @@ -58,6 +59,7 @@ func Test_newJavaLifeCycle(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(binFileFolder, pipelineId.String()+javaCompiledFileExtension), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), ExecutableName: executableName, }, }, diff --git a/playground/backend/internal/fs_tool/lc_constructor.go b/playground/backend/internal/fs_tool/lc_constructor.go index 097c554147c4d..a33f07b5633f5 100644 --- a/playground/backend/internal/fs_tool/lc_constructor.go +++ b/playground/backend/internal/fs_tool/lc_constructor.go @@ -16,6 +16,7 @@ package fs_tool import ( + "beam.apache.org/playground/backend/internal/utils" "github.com/google/uuid" "path/filepath" ) @@ -39,6 +40,7 @@ func newCompilingLifeCycle(pipelineId uuid.UUID, pipelinesFolder, sourceFileExte absExecFilePath, _ := filepath.Abs(filepath.Join(absExecFileFolderPath, execFileName)) absBaseFolderPath, _ := filepath.Abs(baseFileFolder) absLogFilePath, _ := filepath.Abs(filepath.Join(absBaseFolderPath, logFileName)) + absGraphFilePath, _ := filepath.Abs(filepath.Join(absBaseFolderPath, utils.GraphFileName)) return &LifeCycle{ folderGlobs: []string{baseFileFolder, srcFileFolder, binFileFolder}, @@ -51,6 +53,7 @@ func newCompilingLifeCycle(pipelineId uuid.UUID, pipelinesFolder, sourceFileExte AbsoluteExecutableFilePath: absExecFilePath, AbsoluteBaseFolderPath: absBaseFolderPath, AbsoluteLogFilePath: absLogFilePath, + AbsoluteGraphFilePath: absGraphFilePath, }, } } @@ -63,6 +66,7 @@ func newInterpretedLifeCycle(pipelineId uuid.UUID, pipelinesFolder, sourceFileEx absFileFolderPath, _ := filepath.Abs(sourceFileFolder) absFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, fileName)) absLogFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, logFileName)) + absGraphFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, utils.GraphFileName)) return &LifeCycle{ folderGlobs: []string{sourceFileFolder}, @@ -75,6 +79,7 @@ func newInterpretedLifeCycle(pipelineId uuid.UUID, pipelinesFolder, sourceFileEx AbsoluteExecutableFilePath: absFilePath, AbsoluteBaseFolderPath: absFileFolderPath, AbsoluteLogFilePath: absLogFilePath, + AbsoluteGraphFilePath: absGraphFilePath, }, } } diff --git a/playground/backend/internal/fs_tool/python_fs_test.go b/playground/backend/internal/fs_tool/python_fs_test.go index 628722cb8470a..98ee39e9805d6 100644 --- a/playground/backend/internal/fs_tool/python_fs_test.go +++ b/playground/backend/internal/fs_tool/python_fs_test.go @@ -16,6 +16,7 @@ package fs_tool import ( + "beam.apache.org/playground/backend/internal/utils" "github.com/google/uuid" "path/filepath" "reflect" @@ -55,6 +56,7 @@ func Test_newPythonLifeCycle(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(baseFileFolder, pipelineId.String()+pythonExecutableFileExtension), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), }, }, }, diff --git a/playground/backend/internal/preparers/java_preparers.go b/playground/backend/internal/preparers/java_preparers.go index 881afe039f573..e42d53f8b43b0 100644 --- a/playground/backend/internal/preparers/java_preparers.go +++ b/playground/backend/internal/preparers/java_preparers.go @@ -21,6 +21,7 @@ import ( "bufio" "fmt" "io" + "io/ioutil" "os" "regexp" "strings" @@ -35,6 +36,14 @@ const ( pathSeparatorPattern = os.PathSeparator tmpFileSuffix = "tmp" javaPublicClassNamePattern = "public class (.*?) [{|implements(.*)]" + pipelineNamePattern = `Pipeline\s([A-z|0-9_]*)\s=\sPipeline\.create` + graphSavePattern = "String dotString = org.apache.beam.runners.core.construction.renderer.PipelineDotRenderer.toDotString(%s);\n" + + " try (java.io.PrintWriter out = new java.io.PrintWriter(\"%s\")) {\n " + + " out.println(dotString);\n " + + " } catch (java.io.FileNotFoundException e) {\n" + + " e.printStackTrace();\n " + + "\n}\n" + graphRunPattern = "(.*%s.run.*;)" ) //JavaPreparersBuilder facet of PreparersBuilder @@ -87,12 +96,48 @@ func (builder *JavaPreparersBuilder) WithFileNameChanger() *JavaPreparersBuilder return builder } +//WithGraphHandler adds code to save the graph +func (builder *JavaPreparersBuilder) WithGraphHandler() *JavaPreparersBuilder { + graphCodeAdder := Preparer{ + Prepare: addCodeToSaveGraph, + Args: []interface{}{builder.filePath}, + } + builder.AddPreparer(graphCodeAdder) + return builder +} + +func addCodeToSaveGraph(args ...interface{}) error { + filePath := args[0].(string) + pipelineObjectName, _ := findPipelineObjectName(filePath) + graphSaveCode := fmt.Sprintf(graphSavePattern, pipelineObjectName, utils.GraphFileName) + + if pipelineObjectName != utils.EmptyLine { + reg := regexp.MustCompile(fmt.Sprintf(graphRunPattern, pipelineObjectName)) + code, err := ioutil.ReadFile(filePath) + if err != nil { + logger.Error("Can't read file") + return err + } + result := reg.ReplaceAllString(string(code), fmt.Sprintf(`%s$1`, graphSaveCode)) + if err != nil { + logger.Error("Can't add graph extraction code") + return err + } + if err = ioutil.WriteFile(filePath, []byte(result), 0666); err != nil { + logger.Error("Can't rewrite file %s", filePath) + return err + } + } + return nil +} + // GetJavaPreparers returns preparation methods that should be applied to Java code func GetJavaPreparers(builder *PreparersBuilder, isUnitTest bool, isKata bool) { if !isUnitTest && !isKata { builder.JavaPreparers(). WithPublicClassRemover(). - WithPackageChanger() + WithPackageChanger(). + WithGraphHandler() } if isUnitTest { builder.JavaPreparers(). @@ -102,10 +147,27 @@ func GetJavaPreparers(builder *PreparersBuilder, isUnitTest bool, isKata bool) { if isKata { builder.JavaPreparers(). WithPublicClassRemover(). - WithPackageRemover() + WithPackageRemover(). + WithGraphHandler() } } +// findPipelineObjectName finds name of pipeline in JAVA code when pipeline is created +func findPipelineObjectName(filepath string) (string, error) { + reg := regexp.MustCompile(pipelineNamePattern) + b, err := ioutil.ReadFile(filepath) + if err != nil { + return "", err + } + matches := reg.FindStringSubmatch(string(b)) + if len(matches) > 0 { + return matches[1], nil + } else { + return "", nil + } + +} + // replace processes file by filePath and replaces all patterns to newPattern func replace(args ...interface{}) error { filePath := args[0].(string) diff --git a/playground/backend/internal/preparers/java_preparers_test.go b/playground/backend/internal/preparers/java_preparers_test.go index 23720b4444957..913cd1ce6dcba 100644 --- a/playground/backend/internal/preparers/java_preparers_test.go +++ b/playground/backend/internal/preparers/java_preparers_test.go @@ -102,7 +102,7 @@ func TestGetJavaPreparers(t *testing.T) { { name: "Test number of preparers for code", args: args{"MOCK_FILEPATH", false, false}, - want: 2, + want: 3, }, { name: "Test number of preparers for unit test", @@ -112,7 +112,7 @@ func TestGetJavaPreparers(t *testing.T) { { name: "Test number of preparers for kata", args: args{"MOCK_FILEPATH", false, true}, - want: 2, + want: 3, }, } for _, tt := range tests { @@ -171,3 +171,50 @@ func Test_changeJavaTestFileName(t *testing.T) { }) } } + +func Test_findPipelineObjectName(t *testing.T) { + code := "package org.apache.beam.examples;\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// beam-playground:\n// name: Branching\n// description: Task from katas to branch out the numbers to two different transforms, one transform\n// is multiplying each number by 5 and the other transform is multiplying each number by 10.\n// multifile: false\n// categories:\n// - Branching\n// - Core Transforms\n\nimport static org.apache.beam.sdk.values.TypeDescriptors.integers;\n\nimport org.apache.beam.sdk.Pipeline;\nimport org.apache.beam.sdk.options.PipelineOptions;\nimport org.apache.beam.sdk.options.PipelineOptionsFactory;\nimport org.apache.beam.sdk.transforms.Create;\nimport org.apache.beam.sdk.transforms.MapElements;\nimport org.apache.beam.sdk.values.PCollection;\nimport org.apache.beam.runners.core.construction.renderer.PipelineDotRenderer;\n\npublic class Task {\n\n\n\n public static void main(String[] args) {\n PipelineOptions options = PipelineOptionsFactory.fromArgs(args).create();\n Pipeline pipeline = Pipeline.create(options);\n\n PCollection numbers =\n pipeline.apply(Create.of(1, 2, 3, 4, 5));\n\n PCollection mult5Results = applyMultiply5Transform(numbers);\n PCollection mult10Results = applyMultiply10Transform(numbers);\n\n mult5Results.apply(\"Log multiply 5\", Log.ofElements(\"Multiplied by 5: \"));\n mult10Results.apply(\"Log multiply 10\", Log.ofElements(\"Multiplied by 10: \"));\n\n String dotString = PipelineDotRenderer.toDotString(pipeline);\n System.out.println(dotString);\n pipeline.run();\n\n }\n\n static PCollection applyMultiply5Transform(PCollection input) {\n return input.apply(\"Multiply by 5\", MapElements.into(integers()).via(num -> num * 5));\n }\n\n static PCollection applyMultiply10Transform(PCollection input) {\n return input.apply(\"Multiply by 10\", MapElements.into(integers()).via(num -> num * 10));\n }\n\n}\n" + lc := createTempFileWithCode(code) + codeWithoutPipeline := "package org.apache.beam.examples;\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// beam-playground:\n// name: Branching\n// description: Task from katas to branch out the numbers to two different transforms, one transform\n// is multiplying each number by 5 and the other transform is multiplying each number by 10.\n// multifile: false\n// categories:\n// - Branching\n// - Core Transforms\n\nimport static org.apache.beam.sdk.values.TypeDescriptors.integers;\n\nimport org.apache.beam.sdk.Pipeline;\nimport org.apache.beam.sdk.options.PipelineOptions;\nimport org.apache.beam.sdk.options.PipelineOptionsFactory;\nimport org.apache.beam.sdk.transforms.Create;\nimport org.apache.beam.sdk.transforms.MapElements;\nimport org.apache.beam.sdk.values.PCollection;\nimport org.apache.beam.runners.core.construction.renderer.PipelineDotRenderer;\n\npublic class Task {\n\n\n\n public static void main(String[] args) {\n PipelineOptions options = PipelineOptionsFactory.fromArgs(args).create();\n PCollection numbers =\n pipeline.apply(Create.of(1, 2, 3, 4, 5));\n\n PCollection mult5Results = applyMultiply5Transform(numbers);\n PCollection mult10Results = applyMultiply10Transform(numbers);\n\n mult5Results.apply(\"Log multiply 5\", Log.ofElements(\"Multiplied by 5: \"));\n mult10Results.apply(\"Log multiply 10\", Log.ofElements(\"Multiplied by 10: \"));\n\n String dotString = PipelineDotRenderer.toDotString(pipeline);\n System.out.println(dotString);\n pipeline.run();\n\n }\n\n static PCollection applyMultiply5Transform(PCollection input) {\n return input.apply(\"Multiply by 5\", MapElements.into(integers()).via(num -> num * 5));\n }\n\n static PCollection applyMultiply10Transform(PCollection input) {\n return input.apply(\"Multiply by 10\", MapElements.into(integers()).via(num -> num * 10));\n }\n\n}\n" + lcWithoutPipeline := createTempFileWithCode(codeWithoutPipeline) + path, _ := os.Getwd() + defer os.RemoveAll(filepath.Join(path, "temp")) + + type args struct { + filepath string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {name: "pipeline name found", args: args{filepath: lc.AbsoluteSourceFilePath}, wantErr: false, want: "pipeline"}, + {name: "pipeline name not found", args: args{filepath: lcWithoutPipeline.AbsoluteSourceFilePath}, wantErr: false, want: ""}, + {name: "file not found", args: args{filepath: "someFile"}, wantErr: true, want: ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := findPipelineObjectName(tt.args.filepath) + if (err != nil) != tt.wantErr { + t.Errorf("findPipelineObjectName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("findPipelineObjectName() got = %v, want %v", got, tt.want) + } + }) + } +} + +func createTempFileWithCode(code string) fs_tool.LifeCyclePaths { + path, err := os.Getwd() + if err != nil { + panic(err) + } + lc, _ := fs_tool.NewLifeCycle(pb.Sdk_SDK_JAVA, uuid.New(), filepath.Join(path, "temp")) + _ = lc.CreateFolders() + + _ = lc.CreateSourceCodeFile(code) + return lc.Paths +} diff --git a/playground/backend/internal/preparers/python_preparers.go b/playground/backend/internal/preparers/python_preparers.go index 91ca4a72d9bb5..38aa4216cabf7 100644 --- a/playground/backend/internal/preparers/python_preparers.go +++ b/playground/backend/internal/preparers/python_preparers.go @@ -32,7 +32,6 @@ const ( indentationPattern = `^(%s){0,1}\w+` findPipelinePattern = `^(\s*)(.+) = beam.Pipeline` runPipelinePattern = `^(\s*).*%s.run\(\)` - GraphFileName = "graph.dot" ) // GetPythonPreparers returns preparation methods that should be applied to Python code diff --git a/playground/backend/internal/setup_tools/builder/setup_builder.go b/playground/backend/internal/setup_tools/builder/setup_builder.go index c4d05b1bc410d..51031fd983057 100644 --- a/playground/backend/internal/setup_tools/builder/setup_builder.go +++ b/playground/backend/internal/setup_tools/builder/setup_builder.go @@ -22,6 +22,7 @@ import ( "beam.apache.org/playground/backend/internal/fs_tool" "beam.apache.org/playground/backend/internal/preparers" "beam.apache.org/playground/backend/internal/utils" + "beam.apache.org/playground/backend/internal/validators" "fmt" "path/filepath" "strings" @@ -36,7 +37,7 @@ const ( // Validator return executor with set args for validator func Validator(paths *fs_tool.LifeCyclePaths, sdkEnv *environment.BeamEnvs) (*executors.ExecutorBuilder, error) { sdk := sdkEnv.ApacheBeamSdk - val, err := utils.GetValidators(sdk, paths.AbsoluteSourceFilePath) + val, err := validators.GetValidators(sdk, paths.AbsoluteSourceFilePath) if err != nil { return nil, err } diff --git a/playground/backend/internal/setup_tools/builder/setup_builder_test.go b/playground/backend/internal/setup_tools/builder/setup_builder_test.go index 5613b71ef49e7..6df72caa46791 100644 --- a/playground/backend/internal/setup_tools/builder/setup_builder_test.go +++ b/playground/backend/internal/setup_tools/builder/setup_builder_test.go @@ -21,7 +21,6 @@ import ( "beam.apache.org/playground/backend/internal/executors" "beam.apache.org/playground/backend/internal/fs_tool" "beam.apache.org/playground/backend/internal/preparers" - "beam.apache.org/playground/backend/internal/utils" "beam.apache.org/playground/backend/internal/validators" "fmt" "github.com/google/uuid" @@ -52,7 +51,7 @@ func setup() { } func TestValidator(t *testing.T) { - vals, err := utils.GetValidators(sdkEnv.ApacheBeamSdk, paths.AbsoluteSourceFilePath) + vals, err := validators.GetValidators(sdkEnv.ApacheBeamSdk, paths.AbsoluteSourceFilePath) if err != nil { panic(err) } diff --git a/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go b/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go index 025b688e32012..e17a31f8f4154 100644 --- a/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go +++ b/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper.go @@ -19,6 +19,7 @@ import ( pb "beam.apache.org/playground/backend/internal/api/v1" "beam.apache.org/playground/backend/internal/fs_tool" "beam.apache.org/playground/backend/internal/logger" + "beam.apache.org/playground/backend/internal/utils" "bufio" "errors" "github.com/google/uuid" @@ -172,6 +173,7 @@ func prepareSbtFiles(lc *fs_tool.LifeCycle, pipelineFolder string, workingDir st absFileFolderPath, _ := filepath.Abs(sourceFileFolder) absFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, fileName)) absLogFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, logFileName)) + absGraphFilePath, _ := filepath.Abs(filepath.Join(absFileFolderPath, utils.GraphFileName)) projectFolder, _ := filepath.Abs(filepath.Join(pipelineFolder, scioProjectName)) executableName := lc.Paths.ExecutableName @@ -195,6 +197,7 @@ func prepareSbtFiles(lc *fs_tool.LifeCycle, pipelineFolder string, workingDir st AbsoluteExecutableFilePath: absFilePath, AbsoluteBaseFolderPath: absFileFolderPath, AbsoluteLogFilePath: absLogFilePath, + AbsoluteGraphFilePath: absGraphFilePath, ProjectDir: projectFolder, }, } diff --git a/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper_test.go b/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper_test.go index 213816840a90f..9b5aa5ea984fd 100644 --- a/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper_test.go +++ b/playground/backend/internal/setup_tools/life_cycle/life_cycle_setuper_test.go @@ -18,6 +18,7 @@ package life_cycle import ( playground "beam.apache.org/playground/backend/internal/api/v1" "beam.apache.org/playground/backend/internal/fs_tool" + "beam.apache.org/playground/backend/internal/utils" "fmt" "github.com/google/uuid" "io/fs" @@ -123,6 +124,7 @@ func TestSetup(t *testing.T) { AbsoluteExecutableFilePath: filepath.Join(execFileFolder, fmt.Sprintf("%s%s", successPipelineId.String(), javaCompiledFileExtension)), AbsoluteBaseFolderPath: baseFileFolder, AbsoluteLogFilePath: filepath.Join(baseFileFolder, logFileName), + AbsoluteGraphFilePath: filepath.Join(baseFileFolder, utils.GraphFileName), }, }, wantErr: false, diff --git a/playground/backend/internal/utils/preparares_utils.go b/playground/backend/internal/utils/preparares_utils.go index de90eb7f2c6bd..c66d3b7306a01 100644 --- a/playground/backend/internal/utils/preparares_utils.go +++ b/playground/backend/internal/utils/preparares_utils.go @@ -29,7 +29,7 @@ import ( const ( indentationReplacement = "$0" - emptyLine = "" + EmptyLine = "" GraphFileName = "graph.dot" pythonGraphCodePattern = "$0# Write graph to file\n$0from apache_beam.runners.interactive.display import pipeline_graph\n$0dot = pipeline_graph.PipelineGraph(%s).get_dot()\n$0with open('%s', 'w') as file:\n$0 file.write(dot)\n" newLinePattern = "\n" @@ -50,12 +50,12 @@ func ReplaceSpacesWithEquals(pipelineOptions string) string { // InitVars creates empty variables func InitVars() (string, string, error, bool, PipelineDefinitionType) { - return emptyLine, emptyLine, errors.New(emptyLine), false, RegularDefinition + return EmptyLine, EmptyLine, errors.New(EmptyLine), false, RegularDefinition } // AddGraphToEndOfFile if no place for graph was found adds graph code to the end of the file func AddGraphToEndOfFile(spaces string, err error, tempFile *os.File, pipelineName string) { - line := emptyLine + line := EmptyLine regs := []*regexp.Regexp{regexp.MustCompile("^")} _, err = wrap(addGraphCode)(tempFile, &line, &spaces, &pipelineName, ®s) } diff --git a/playground/backend/internal/utils/validators_utils.go b/playground/backend/internal/utils/validators_utils.go deleted file mode 100644 index 5d9406e912df7..0000000000000 --- a/playground/backend/internal/utils/validators_utils.go +++ /dev/null @@ -1,40 +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. - -package utils - -import ( - pb "beam.apache.org/playground/backend/internal/api/v1" - "beam.apache.org/playground/backend/internal/validators" - "fmt" -) - -// GetValidators returns slice of validators.Validator according to sdk -func GetValidators(sdk pb.Sdk, filepath string) (*[]validators.Validator, error) { - var val *[]validators.Validator - switch sdk { - case pb.Sdk_SDK_JAVA: - val = validators.GetJavaValidators(filepath) - case pb.Sdk_SDK_GO: - val = validators.GetGoValidators(filepath) - case pb.Sdk_SDK_PYTHON: - val = validators.GetPyValidators(filepath) - case pb.Sdk_SDK_SCIO: - val = validators.GetScioValidators(filepath) - default: - return nil, fmt.Errorf("incorrect sdk: %s", sdk) - } - return val, nil -} diff --git a/playground/backend/internal/utils/validators_utils_test.go b/playground/backend/internal/utils/validators_utils_test.go deleted file mode 100644 index 8ec7da9bc7bd4..0000000000000 --- a/playground/backend/internal/utils/validators_utils_test.go +++ /dev/null @@ -1,79 +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. - -package utils - -import ( - playground "beam.apache.org/playground/backend/internal/api/v1" - "beam.apache.org/playground/backend/internal/validators" - "reflect" - "testing" -) - -func TestGetValidators(t *testing.T) { - type args struct { - sdk playground.Sdk - filepath string - } - tests := []struct { - name string - args args - want *[]validators.Validator - wantErr bool - }{ - { - // Test case with calling GetValidators method with incorrect SDK. - // As a result, want to receive an error. - name: "incorrect sdk", - args: args{ - sdk: playground.Sdk_SDK_UNSPECIFIED, - filepath: "", - }, - want: nil, - wantErr: true, - }, - { - // Test case with calling GetValidators method with correct SDK. - // As a result, want to receive an expected slice of validators. - name: "correct sdk", - args: args{ - sdk: playground.Sdk_SDK_JAVA, - filepath: "", - }, - want: validators.GetJavaValidators(""), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetValidators(tt.args.sdk, tt.args.filepath) - if (err != nil) != tt.wantErr { - t.Errorf("GetValidators() err = %v, wantErr %v", err, tt.wantErr) - } - if got != nil { - if !reflect.DeepEqual(len(*got), len(*tt.want)) { - t.Errorf("GetValidators() len = %v, want %v", len(*got), len(*tt.want)) - } - for i := range *got { - gotVal := (*got)[i] - wantVal := (*tt.want)[i] - if !reflect.DeepEqual(gotVal.Args, wantVal.Args) { - t.Errorf("GetValidators() %d = %v, want %v", i, gotVal.Args, wantVal.Args) - } - } - } - }) - } -} diff --git a/playground/backend/internal/validators/validator.go b/playground/backend/internal/validators/validator.go index cf504f7ade62e..c25455a4b7a2c 100644 --- a/playground/backend/internal/validators/validator.go +++ b/playground/backend/internal/validators/validator.go @@ -15,6 +15,11 @@ package validators +import ( + pb "beam.apache.org/playground/backend/internal/api/v1" + "fmt" +) + const ( UnitTestValidatorName = "UnitTest" KatasValidatorName = "Katas" @@ -26,3 +31,21 @@ type Validator struct { Args []interface{} Name string } + +// GetValidators returns slice of validators.Validator according to sdk +func GetValidators(sdk pb.Sdk, filepath string) (*[]Validator, error) { + var val *[]Validator + switch sdk { + case pb.Sdk_SDK_JAVA: + val = GetJavaValidators(filepath) + case pb.Sdk_SDK_GO: + val = GetGoValidators(filepath) + case pb.Sdk_SDK_PYTHON: + val = GetPyValidators(filepath) + case pb.Sdk_SDK_SCIO: + val = GetScioValidators(filepath) + default: + return nil, fmt.Errorf("incorrect sdk: %s", sdk) + } + return val, nil +} diff --git a/playground/backend/internal/validators/validator_test.go b/playground/backend/internal/validators/validator_test.go index 59b0f519a751e..52a8ef3b80811 100644 --- a/playground/backend/internal/validators/validator_test.go +++ b/playground/backend/internal/validators/validator_test.go @@ -16,8 +16,10 @@ package validators import ( + playground "beam.apache.org/playground/backend/internal/api/v1" "fmt" "os" + "reflect" "testing" ) @@ -59,3 +61,59 @@ func writeFile(path string, code string) { panic(fmt.Errorf("error during test setup: %s", err.Error())) } } + +func TestGetValidators(t *testing.T) { + type args struct { + sdk playground.Sdk + filepath string + } + tests := []struct { + name string + args args + want *[]Validator + wantErr bool + }{ + { + // Test case with calling GetValidators method with incorrect SDK. + // As a result, want to receive an error. + name: "incorrect sdk", + args: args{ + sdk: playground.Sdk_SDK_UNSPECIFIED, + filepath: "", + }, + want: nil, + wantErr: true, + }, + { + // Test case with calling GetValidators method with correct SDK. + // As a result, want to receive an expected slice of validators. + name: "correct sdk", + args: args{ + sdk: playground.Sdk_SDK_JAVA, + filepath: "", + }, + want: GetJavaValidators(""), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetValidators(tt.args.sdk, tt.args.filepath) + if (err != nil) != tt.wantErr { + t.Errorf("GetValidators() err = %v, wantErr %v", err, tt.wantErr) + } + if got != nil { + if !reflect.DeepEqual(len(*got), len(*tt.want)) { + t.Errorf("GetValidators() len = %v, want %v", len(*got), len(*tt.want)) + } + for i := range *got { + gotVal := (*got)[i] + wantVal := (*tt.want)[i] + if !reflect.DeepEqual(gotVal.Args, wantVal.Args) { + t.Errorf("GetValidators() %d = %v, want %v", i, gotVal.Args, wantVal.Args) + } + } + } + }) + } +} From 5345834a86c422347556e0bce7e5dd20e4854e44 Mon Sep 17 00:00:00 2001 From: Aydar Farrakhov Date: Tue, 22 Feb 2022 21:31:17 +0300 Subject: [PATCH 13/61] case study pages - improvements and fixes (#16896) * case study - small fixes: - Added dot to the title on the case studies page; - Added rights note to the case study community page; - Added file name example to the ADD_LOGO.md; * case study - small fixes: - Increase logo image height on cards - Add max width for the content * update hop logo * fix licence --- website/ADD_LOGO.md | 1 + website/www/site/assets/scss/_case_study.scss | 4 +- .../www/site/content/en/case-studies/hop.md | 4 +- .../site/content/en/community/case-study.md | 3 + .../www/site/layouts/case-studies/list.html | 2 +- .../static/images/logos/powered-by/hop.png | Bin 82744 -> 0 bytes .../static/images/logos/powered-by/hop.svg | 71 ++++++++++++++++++ 7 files changed, 81 insertions(+), 4 deletions(-) delete mode 100644 website/www/site/static/images/logos/powered-by/hop.png create mode 100644 website/www/site/static/images/logos/powered-by/hop.svg diff --git a/website/ADD_LOGO.md b/website/ADD_LOGO.md index 337890429a153..a478ab8676412 100644 --- a/website/ADD_LOGO.md +++ b/website/ADD_LOGO.md @@ -22,6 +22,7 @@ 1. Fork [Apache Beam](https://github.com/apache/beam) repository 2. Add file with company or project name to the [case-studies](https://github.com/apache/beam/tree/master/website/www/site/content/en/case-studies) folder + e.g., `company.md` 3. Add project/company logo to the [images/logos/powered-by](https://github.com/apache/beam/tree/master/website/www/site/static/images/logos/powered-by) folder. Please use your company/project name e.g. `ricardo.png` diff --git a/website/www/site/assets/scss/_case_study.scss b/website/www/site/assets/scss/_case_study.scss index 4de3561edff26..c8eb712ee334f 100644 --- a/website/www/site/assets/scss/_case_study.scss +++ b/website/www/site/assets/scss/_case_study.scss @@ -95,7 +95,7 @@ } .case-study-card-img img { - height: 35px; + height: 50px; } .case-study-card-title { @@ -296,11 +296,13 @@ h2.case-study-h2 { img { width: 100%; + max-height: 100px; } } } .case-study-post { + max-width: 1200px; flex: 1; } diff --git a/website/www/site/content/en/case-studies/hop.md b/website/www/site/content/en/case-studies/hop.md index c38f334ec726b..ae69c8f05f9a9 100644 --- a/website/www/site/content/en/case-studies/hop.md +++ b/website/www/site/content/en/case-studies/hop.md @@ -1,7 +1,7 @@ --- title: "Beam visual pipeline development with Hop" name: "Neo4j" -icon: /images/logos/powered-by/hop.png +icon: /images/logos/powered-by/hop.svg category: study cardTitle: "Visual Apache Beam Pipeline Design and Orchestration with Apache Hop" cardDescription: "Apache Hop is an open source data orchestration and engineering platform that extends Apache Beam with visual pipeline lifecycle management. Neo4j’s Chief Solution Architect and Apache Hop’s co-founder, Matt Casters, sees Apache Beam as a driving force behind Hop." @@ -25,7 +25,7 @@ limitations under the License.
- +

diff --git a/website/www/site/content/en/community/case-study.md b/website/www/site/content/en/community/case-study.md index 4406338c5720d..73556a70ac89e 100644 --- a/website/www/site/content/en/community/case-study.md +++ b/website/www/site/content/en/community/case-study.md @@ -34,3 +34,6 @@ started! Want to tell the world you are using Apache Beam? Just walk through [this instruction](https://github.com/apache/beam/tree/master/website/ADD_LOGO.md) and make it happen! + +The Apache Beam PMC reserves the right to remove logos of companies that are not demeed to be in good standing in the +community. diff --git a/website/www/site/layouts/case-studies/list.html b/website/www/site/layouts/case-studies/list.html index de262d3d9a1c6..ee1f19bb04d0d 100644 --- a/website/www/site/layouts/case-studies/list.html +++ b/website/www/site/layouts/case-studies/list.html @@ -14,7 +14,7 @@

Case Studies

Apache Beam powers many of today’s leading projects, industry-specific use cases, and - startups

+ startups.

{{ $pages := .Pages.ByPublishDate.Reverse }}
{{ range where $pages "Params.category" "study" }} diff --git a/website/www/site/static/images/logos/powered-by/hop.png b/website/www/site/static/images/logos/powered-by/hop.png deleted file mode 100644 index 58c25b633fa5396724d236c847ef732387ea4d40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82744 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

    HK}4I*s=jJ!x=mF{w6OEj3Tw>`LiX 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 EAIa6IDgXcg diff --git a/website/www/site/static/images/logos/powered-by/hop.svg b/website/www/site/static/images/logos/powered-by/hop.svg new file mode 100644 index 0000000000000..728eae3f4911b --- /dev/null +++ b/website/www/site/static/images/logos/powered-by/hop.svg @@ -0,0 +1,71 @@ + + +image/svg+xml + + From 43cd356f865fa52bcce217c613e747a9220136e0 Mon Sep 17 00:00:00 2001 From: Aydar Farrakhov Date: Wed, 23 Feb 2022 03:33:04 +0300 Subject: [PATCH 14/61] Palo Alto case study (#16915) * palo alto case study * palo alto case study image fixes * Update paloalto.md fixing a typo Co-authored-by: Alex Kosolapov --- website/www/site/assets/scss/_case_study.scss | 7 + .../site/content/en/case-studies/paloalto.md | 311 ++++++++++++++++++ .../case-study/paloalto/data_lake_scheme.png | Bin 0 -> 364516 bytes .../paloalto/direct_serialization.png | Bin 0 -> 73785 bytes .../paloalto/subscription_service_scheme.png | Bin 0 -> 103228 bytes .../case-study/paloalto/talat_uyarer.png | Bin 0 -> 256103 bytes .../images/logos/powered-by/paloalto.png | Bin 0 -> 20154 bytes 7 files changed, 318 insertions(+) create mode 100644 website/www/site/content/en/case-studies/paloalto.md create mode 100644 website/www/site/static/images/case-study/paloalto/data_lake_scheme.png create mode 100644 website/www/site/static/images/case-study/paloalto/direct_serialization.png create mode 100644 website/www/site/static/images/case-study/paloalto/subscription_service_scheme.png create mode 100644 website/www/site/static/images/case-study/paloalto/talat_uyarer.png create mode 100644 website/www/site/static/images/logos/powered-by/paloalto.png diff --git a/website/www/site/assets/scss/_case_study.scss b/website/www/site/assets/scss/_case_study.scss index c8eb712ee334f..3be4880a24447 100644 --- a/website/www/site/assets/scss/_case_study.scss +++ b/website/www/site/assets/scss/_case_study.scss @@ -312,6 +312,13 @@ h2.case-study-h2 { img { width: 100%; } + + &.vertical-scheme { + text-align: center; + img { + max-width: 480px; + } + } } @media screen and (max-width: $mobile) { diff --git a/website/www/site/content/en/case-studies/paloalto.md b/website/www/site/content/en/case-studies/paloalto.md new file mode 100644 index 0000000000000..0fdba878fbcd5 --- /dev/null +++ b/website/www/site/content/en/case-studies/paloalto.md @@ -0,0 +1,311 @@ +--- +title: "Real-time Event Stream Processing at Scale for Palo Alto Networks" +name: "Palo Alto" +icon: "/images/logos/powered-by/paloalto.png" +category: "study" +cardTitle: "Real-time Event Stream Processing at Scale for Palo Alto Networks" +cardDescription: "Palo Alto Networks is a global cybersecurity leader that deals with processing hundreds of billions of +security events per day in real-time, which is on the high end of the industry. Apache Beam provides a high-performing, +reliable, and resilient data processing framework to support this scale. With Apache Beam, Palo Alto Networks ultimately +achieved high performance and low latency, and reduced processing costs by 60%." +authorName: "Talat Uyarer" +authorPosition: "Sr Principal Software Engineer" +authorImg: /images/case-study/paloalto/talat_uyarer.png +--- + +

    +
    + +# Real-time Event Stream Processing at Scale for Palo Alto Networks + +## Background + +[Palo Alto Networks, Inc.](https://www.paloaltonetworks.com/) is a global cybersecurity leader with a comprehensive +portfolio of enterprise products. Palo Alto Networks protects and provides visibility, trusted intelligence, automation, +and flexibility to [over 85K customers](https://www.paloaltonetworks.com/about-us) across clouds, networks, and devices. + +Palo Alto Networks’ integrated security operations platform - [Cortex™](https://www.paloaltonetworks.com/cortex) - +applies AI and machine learning to enable security automation, advanced threat intelligence, and effective rapid +security responses for Palo Alto Networks’ customers. (Cortex™ Data +Lake)[https://www.paloaltonetworks.com/cortex/cortex-data-lake] infrastructure collects, integrates, and normalizes +enterprises’ security data combined with trillions of multi-source artifacts. + +Cortex™ data infrastructure processes ~10 millions of security log events per second currently, at ~3 PB per day, which +are on the high end of real-time streaming processing scale in the industry. Palo Alto Networks’ Sr Principal Software +Engineer, Talat Uyarer, shared insights on how Apache Beam provides a high-performing, reliable, and resilient data +processing framework to support this scale. + +## Large-scale Streaming Infrastructure + +When building the data infrastructure from the ground up, Palo Alto Networks’ Cortex Data Lake team faced a challenging +task. We needed to ensure that the Cortex platform could stream and process petabyte-sized data coming from customers’ +firewalls, networks, and all kinds of devices to customers and internal apps with low latency and perfect quality. + +
    + Cortex™ Data Lake +
    + +To meet the SLAs, the Cortex Data Lake team had to design a large-scale data infrastructure for real-time processing and +reduce time-to-value. One of their initial architectural decisions was to leverage Apache Beam, the industry standard +for unified distributed processing, due to its portability and abstraction. + +
    +

    + Beam is very flexible, its abstraction from implementation details of distributed data processing is wonderful for delivering proofs of concept really fast. +

    +
    +
    + +
    +
    +
    + Talat Uyarer +
    +
    + Sr Principal Software Engineer +
    +
    +
    +
    + +Apache Beam provides a variety of runners, offering freedom of choice between different data processing engines. Palo +Alto Networks’ data infrastructure is hosted entirely on [Google Cloud Platform](https://cloud.google.com/gcp/), +and [with Apache Beam Dataflow runner](https://beam.apache.org/documentation/runners/capability-matrix/), we could +easily benefit from [Google Cloud Dataflow](https://cloud.google.com/dataflow)’s managed service and +[autotuning](https://cloud.google.com/dataflow/docs/guides/deploying-a-pipeline#horizontal-autoscaling) capabilities. +Apache Kafka was selected as the message broker for the backend, and all events were stored as binary data with a common +schema on multiple Kafka clusters. + +The Cortex Data Lake team considered the option of having separate data processing infrastructures for each customer, +with multiple upstream applications creating their own streaming jobs, consuming and processing events from Kafka +directly. Therefore we are building a multi-tenants system. However, the team anticipated possible issues related to +Kafka migrations and partition creation, as well as a lack of visibility into the tenant use cases, which might arise +when having multiple infrastructures. + +Hence, the Cortex Data Lake team took a common streaming infrastructure approach. At the core of the common data +infrastructure, Apache Beam served as a unified programming model to implement business logic just once for all internal +and customer tenant applications. + +The first data workflows that the Cortex Data Lake team implemented were simple: reading from Kafka, creating a batch +job, and writing the results to sink. The release of +the [Apache Beam version with SQL support](https://beam.apache.org/get-started/downloads/#releases) opened up new +possibilities. [Beam Calcite SQL](https://beam.apache.org/documentation/dsls/sql/calcite/overview/) provides full +support for [complex Apache Calcite data types](https://beam.apache.org/documentation/dsls/sql/calcite/data-types/), +including nested rows, in SQL statements, so developers can use SQL queries in an Apache Beam pipeline for composite +transforms. The Cortex Data Lake team decided to take advantage of the +[Beam SQL](https://beam.apache.org/documentation/dsls/sql/overview/) to write Beam pipelines with standard SQL +statements. + +The main challenge of the common infrastructure was to support a variety of business logic customizations and +user-defined functions and transform them to a variety of sink formats. Tenant applications needed to consume data from +dynamically-changing Kafka clusters, and streaming pipeline [DAGs](https://en.wikipedia.org/wiki/Directed_acyclic_graph) +had to be regenerated if the jobs’ source had been updated. + +The Cortex Data Lake team developed their own “subscription” model that allows tenant applications to “subscribe” to the +streaming job when sending job deployment requests to the REST API service. The Subscription service abstracts tenant +applications from the changes in DAG by storing infrastructure-specific information in metadata service. This way, the +streaming jobs stay in sync with the dynamic Kafka infrastructure. + +
    + Cortex™ Data Lake Subscription Service +
    + +Apache Beam is flexible, it allows creating streaming jobs dynamically, on the fly. The Apache Beam constructs allow for +generic pipeline coding, enabling pipelines that process data even if schemas are not fully defined in advance. Cortex’s +Subscription Service generates Apache Beam pipeline DAG based on the tenant application’s REST payload and submits the +job to the runner. When the job is +running, [Apache Beam SDK’s Kafka I/O](https://beam.apache.org/releases/javadoc/2.4.0/org/apache/beam/sdk/io/kafka/KafkaIO.html) +returns an unbounded collection of Kafka records as +a [PCollection](https://beam.apache.org/releases/javadoc/2.1.0/org/apache/beam/sdk/values/PCollection.html) +. [Apache Avro](https://avro.apache.org/) turns the binary Kafka representation into generic records, which are further +converted to the [Apache Beam Row](https://beam.apache.org/releases/javadoc/2.4.0/org/apache/beam/sdk/values/Row.html) +format. The Row structure supports primitives, byte arrays, and containers, and allows organizing values in the same +order as the schema definition. + +Apache Beam’s cross-language transforms allow the Cortex Data Lake team to execute SQL with Java. The output of +an [SQL Transform](https://beam.apache.org/releases/javadoc/2.7.0/org/apache/beam/sdk/extensions/sql/SqlTransform.html) +performed inside the Apache Beam pipeline is sequentially converted from Beam Row format to a generic record, then to +the output format required by a subscriber application, such as Avro, JSON, CSV, etc. + +Once the base use cases had been implemented, the Cortex Data Lake team turned to more complex transformations, such as +filtering a subset of events directly inside Apache Beam pipelines, and kept looking into customization and +optimization. + +
    +

    + We have more than 10 use cases running across customers and apps. More are coming, like the machine learning use cases .... for these use cases, Beam provides a really good programming model. +

    +
    +
    + +
    +
    +
    + Talat Uyarer +
    +
    + Sr Principal Software Engineer +
    +
    +
    +
    + +Apache Beam provides a pluggable data processing model that seamlessly integrates with various tools and technologies, +which allowed the Cortex Data Lake team to customize their data processing to performance requirements and specific use +cases. + +## Customizing Serialization for Use Cases + +Palo Alto Networks’ streaming data infrastructure deals with hundreds of billions of real-time security events every +day, and even a sub-second difference in processing times is crucial. + +To enhance performance, the Cortex Data Lake team developed their own library for direct serialization and +deserialization. The library reads Avro binary records from Kafka and turns them into the Beam Row format, then converts +the Beam Row format pipeline output to the required sink format. + +This custom library replaced serializing data into generic records with steps optimized for Palo Alto Networks’ specific +use cases. Direct serialization eliminated shuffling and creating additional memory copies from processing steps. + +This customization increased serialization performance 10x times, allowing to process up to 3K events per second per +vCPU with reduced latency and infrastructure costs. + +
    + Direct Serialization from Avro to Beam Row +
    + +## In-flight Streaming Job Updates + +At a scale of thousands of jobs running concurrently, the Cortex Data Lake team faced cases when needed to improve the +pipeline code or fix bugs for an ongoing job. Google Cloud Dataflow provides a way +to [replace an “in-flight” streaming job](https://cloud.google.com/dataflow/docs/guides/updating-a-pipeline) with a new +job that runs an updated Apache Beam pipeline code. However, Palo Alto Networks needed to expand the supported +scenarios. + +To address updating jobs in the dynamically-changing Kafka infrastructure, the Cortex Data Lake team created an +additional workflow in their deployment service +which [drains the jobs](https://cloud.google.com/dataflow/docs/guides/stopping-a-pipeline#drain) if the change +is [not permitted](https://cloud.google.com/dataflow/docs/guides/updating-a-pipeline#UpdateSchemas) by the Dataflow +update and starts a new job with the exact same naming. This internal job replacement workflow allows the Cortex Data +Lake to update the jobs and payloads automatically for all use cases. + +## Handling Schema Changes In Beam SQL + +Another use case that Palo Alto Networks tackled is handling changes in data schemas for ongoing jobs. Apache Beam +allows PCollections to have [schemas](https://beam.apache.org/documentation/programming-guide/#schemas) with named +fields, that are validated at pipeline construction step. When a job is submitted, an execution plan in the form of a +Beam pipeline fragment is generated based on the latest schema. Beam SQL does not yet have built-in support for relaxed +schema compatibility for running jobs. For optimized performance, Beam SQL’s +Schema [RowCoder](https://beam.apache.org/releases/javadoc/2.4.0/org/apache/beam/sdk/coders/RowCoder.html) has a fixed +data format and doesn't handle schema evolution, so it is necessary to restart the jobs to regenerate their execution +plan. At a scale of 10K+ streaming jobs, Cortex Data Lake team wanted to avoid resubmitting the jobs as much as +possible. + +We created an internal workflow to identify the jobs with SQL queries relevant to the schema change. The schema update +workflow stores Reader schema of each job (Avro schema) and Writer schema of each Kafka message (metadata on Kafka +header) in the internal Schema Registry, compares them to the SQL queries of the running jobs, and restarts the affected +jobs only. This optimization allowed them to utilize resources more efficiently. + +## Fine-tuning Performance for Kafka Changes + +With multiple clusters and topics, and over 100K partitions in Kafka, Palo Alto Networks needed to make sure that +actively-running jobs are not being affected by the frequent Kafka infrastructure changes such as cluster migrations or +changes in partition count. + +The Cortex Data Lake team developed several internal Kafka lifecycle support tools, including a “Self Healing” service. +Depending on the amount of traffic per topic coming from a specific tenant, the internal service increases the number of +partitions or creates new topics with fewer partitions. The “Self Healing” service compares the Kafka states in the data +store and then finds and updates all related streaming Apache Beam jobs on Cloud Dataflow automatically. + +With the [release of Apache Beam 2.28.0](https://beam.apache.org/blog/beam-2.28.0/) in early +2021, [the pre-built Kafka I/O dynamic read feature](https://beam.apache.org/releases/javadoc/2.29.0/org/apache/beam/sdk/io/kafka/KafkaIO.html) +provides an out-of-the-box solution for detecting Kafka partition changes to enable cost savings and increased +performance. Kafka I/O uses WatchKafkaTopicPartitionDoFn to emit +new [TopicPartitions](https://kafka.apache.org/24/javadoc/index.html?org/apache/kafka/common/TopicPartition.html), and +allows reading from Kafka topics dynamically when certain partitions are added or stop reading from them once they are +deleted. This feature eliminated the need to create in-house Kafka monitoring tools. + +In addition to performance optimization, the Cortex Data Lake team has been exploring ways to optimize the Cloud +Dataflow costs. We looked into resource usage optimization in cases when streaming jobs consume very few incoming +events. For cost efficiency, Google Cloud Dataflow provides +the [streaming autoscaling](https://cloud.google.com/dataflow/docs/guides/deploying-a-pipeline#streaming-autoscaling) +feature that adaptively changes the number of workers in response to changes in the load and resource utilization. For +some of Cortex Data Lake team’s use cases, where input data streams may quiesce for prolonged periods of time, we +implemented an internal “Cold Starter” service that analyzes Kafka topics traffic and hibernates pipelines whose input +dries up and reactivates them once their input resumes. + +Talat Uyarer presented the Cortex Data Lake’s experience of building and customizing the large-scale streaming +infrastructure during [Beam Summit 2021](https://2021.beamsummit.org/sessions/large-scale-streaming-infrastructure/). + +
    +

    + I really enjoy working with Beam. If you understand its internals, the understanding empowers you to fine-tune the open source, customize it, so that it provides the best performance for your specific use case. +

    +
    +
    + +
    +
    +
    + Talat Uyarer +
    +
    + Sr Principal Software Engineer +
    +
    +
    +
    + +## Results + +The level of abstraction of Apache Beam empowered the Cortex Data Lake team to create a common infrastructure across +their internal apps and tens of thousands of customers. With Apache Beam, we implement business logic just once and +dynamically generate 10K+ streaming pipelines running in parallel for over 10 use cases. + +The Cortex Data Lake team took advantage of Apache Beam’s portability and pluggability to fine-tune and enhance their +data processing infrastructure with custom libraries and services. Palo Alto Networks ultimately achieved high +performance and low latency, processing 3K+ streaming events per second per vCPU. Combining the benefits of open source +Apache Beam and Cloud Dataflow managed service, we were able to implement use-case specific customizations and reduced +their costs by more than 60%. + +The Apache Beam open source community welcomes and encourages the contributions of its numerous members, such as Palo +Alto Networks, that leverage the powerful capabilities of Apache Beam, bring new optimizations, and empower future +innovation by sharing their expertise and actively participating in the community. + +{{< case_study_feedback "Palo Alto" >}} + +
    +
    diff --git a/website/www/site/static/images/case-study/paloalto/data_lake_scheme.png b/website/www/site/static/images/case-study/paloalto/data_lake_scheme.png new file mode 100644 index 0000000000000000000000000000000000000000..857e0836bfcd1acf24859dc8366338aef1994956 GIT binary patch literal 364516 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

    qIV4{OL*dHMfGHIGgvu?qw56vwVn2#CS3>_ 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

    Qv%}k9>IMoVmaH^{@8q-i@TDPoDYvAGu-4 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 NW(b6Wwc0MR{|Bg7L#6-# literal 0 HcmV?d00001 diff --git a/website/www/site/static/images/case-study/paloalto/direct_serialization.png b/website/www/site/static/images/case-study/paloalto/direct_serialization.png new file mode 100644 index 0000000000000000000000000000000000000000..78d4fa36fbeb643f4f3973c60cf3647923ac0b60 GIT binary patch literal 73785 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

    8jEWAllT|F&!ybV6@^s#wAe3^AFHb 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 literal 0 HcmV?d00001 diff --git a/website/www/site/static/images/case-study/paloalto/subscription_service_scheme.png b/website/www/site/static/images/case-study/paloalto/subscription_service_scheme.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d35e7cb63bd37de5f978a29d5c4f9bef6a8e01 GIT binary patch literal 103228 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

    aSo?Vx@>{wR5h# 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 RjHz2Okd;!9ER!${_&=vlo38)> literal 0 HcmV?d00001 diff --git a/website/www/site/static/images/case-study/paloalto/talat_uyarer.png b/website/www/site/static/images/case-study/paloalto/talat_uyarer.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e5e1897eddc85f0e8a178d07ccace235e56a45 GIT binary patch literal 256103 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 z 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 z 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 y>8uvHd+TF~`_V!e@l%bwPVqDSlC+5bc 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 Y2a-j6Mi{?UxBvhE07*qoM6N<$g8W5d0RR91 literal 0 HcmV?d00001 From 6acb3f917e05344bfb2147fec8d3578cf94b5bc0 Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Wed, 23 Feb 2022 09:14:03 -0800 Subject: [PATCH 15/61] [BEAM-12645] Fix code-cov flakes due to monorepo. (#16925) --- .github/codecov.yml | 9 +++++++++ .github/workflows/go_tests.yml | 7 +++++-- sdks/python/tox.ini | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index 295234e93cde8..b01fdc714d1d6 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -29,12 +29,16 @@ coverage: status: project: python: + flags: + - python target: auto threshold: 5% base: auto paths: - "sdks/python" go: + flags: + - go target: auto threshold: 5% base: auto @@ -59,3 +63,8 @@ ignore: - "**/*_test.py" - "**/*_test_py3*.py" - "**/*_microbenchmark.py" + +# See https://docs.codecov.com/docs/flags for options. +flag_management: + default_rules: # the rules that will be followed for any flag added, generally + carryforward: true # recommended for multi-lang mono-repos. \ No newline at end of file diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml index d909a27001c3a..f58bfcc8a8a31 100644 --- a/.github/workflows/go_tests.yml +++ b/.github/workflows/go_tests.yml @@ -44,7 +44,10 @@ jobs: run: "cd sdks/go/pkg && rm -rf .coverage || :" - name: Run coverage run: cd sdks/go/pkg && go test -coverprofile=coverage.txt -covermode=atomic ./... - - name: Upload to codecov - run: bash <(curl -s https://codecov.io/bash) + - uses: codecov/codecov-action@v2 + with: + flags: go + files: ./sdks/go/pkg/coverage.txt + name: go-unittests - name: Run vet run: cd sdks/go/pkg/beam && go vet --copylocks=false --unsafeptr=false ./... diff --git a/sdks/python/tox.ini b/sdks/python/tox.ini index 2c34f5b0482ca..e7eb3abf62ca3 100644 --- a/sdks/python/tox.ini +++ b/sdks/python/tox.ini @@ -102,7 +102,7 @@ extras = test,gcp,interactive,dataframe,aws commands = -rm .coverage {toxinidir}/scripts/run_pytest.sh {envname} "{posargs}" "--cov-report=xml --cov=. --cov-append" - codecov + codecov -F python [testenv:py37-lint] # Don't set TMPDIR to avoid "AF_UNIX path too long" errors in pylint. From 596b61737656d01b81aae4ecb0299ae591c69a0b Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:14:10 -0500 Subject: [PATCH 16/61] [BEAM-13969] Deprecate stringx package (#16884) --- sdks/go/pkg/beam/core/util/stringx/bytes.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdks/go/pkg/beam/core/util/stringx/bytes.go b/sdks/go/pkg/beam/core/util/stringx/bytes.go index b2110fa1072ce..bf4cd111dddec 100644 --- a/sdks/go/pkg/beam/core/util/stringx/bytes.go +++ b/sdks/go/pkg/beam/core/util/stringx/bytes.go @@ -15,6 +15,9 @@ // Package stringx contains utilities for working with strings. It // complements the standard "strings" package. +// +// Deprecated: the utilities in this package are unused within the code base +// and will be removed in a future Beam release. package stringx // ToBytes converts a string to a byte slice. From 53dbd3266027f9ca8f3d62622a896fbf9940066d Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Wed, 23 Feb 2022 13:35:39 -0500 Subject: [PATCH 17/61] Add Go badge to ReadMe (#16897) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 672893546a690..b67429bb04aff 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ [![Maven Version](https://maven-badges.herokuapp.com/maven-central/org.apache.beam/beam-sdks-java-core/badge.svg)](http://search.maven.org/#search|gav|1|g:"org.apache.beam") [![PyPI version](https://badge.fury.io/py/apache-beam.svg)](https://badge.fury.io/py/apache-beam) +[![Go version](https://pkg.go.dev/badge/github.com/apache/beam/sdks/v2/go.svg)](https://pkg.go.dev/github.com/apache/beam/sdks/v2/go) [![Python coverage](https://codecov.io/gh/apache/beam/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/beam) [![Build python source distribution and wheels](https://github.com/apache/beam/workflows/Build%20python%20source%20distribution%20and%20wheels/badge.svg?branch=master&event=schedule)](https://github.com/apache/beam/actions?query=workflow%3A%22Build+python+source+distribution+and+wheels%22+branch%3Amaster+event%3Aschedule) [![Python tests](https://github.com/apache/beam/workflows/Python%20tests/badge.svg?branch=master&event=schedule)](https://github.com/apache/beam/actions?query=workflow%3A%22Python+Tests%22+branch%3Amaster+event%3Aschedule) From 0c73091468ec94d849153c16503d4d8d64d2cf68 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 23 Feb 2022 19:47:29 +0100 Subject: [PATCH 18/61] [BEAM-13980] Re-add method gone missing in af2f8ee6 (#16918) --- .../io/aws/clients/s3/boto3_client.py | 25 +++++++++++++++++++ .../www/site/content/en/blog/beam-2.36.0.md | 1 + 2 files changed, 26 insertions(+) diff --git a/sdks/python/apache_beam/io/aws/clients/s3/boto3_client.py b/sdks/python/apache_beam/io/aws/clients/s3/boto3_client.py index 878ad070555b7..aee24ac2c052c 100644 --- a/sdks/python/apache_beam/io/aws/clients/s3/boto3_client.py +++ b/sdks/python/apache_beam/io/aws/clients/s3/boto3_client.py @@ -78,6 +78,31 @@ def __init__(self, options): self._download_stream = None self._download_pos = 0 + def get_object_metadata(self, request): + """Retrieves an object's metadata. + + Args: + request: (GetRequest) input message + + Returns: + (Object) The response message. + """ + kwargs = {'Bucket': request.bucket, 'Key': request.object} + + try: + boto_response = self.client.head_object(**kwargs) + except Exception as e: + raise messages.S3ClientError(str(e), get_http_error_code(e)) + + item = messages.Item( + boto_response['ETag'], + request.object, + boto_response['LastModified'], + boto_response['ContentLength'], + boto_response['ContentType']) + + return item + def get_stream(self, request, start): """Opens a stream object starting at the given position. diff --git a/website/www/site/content/en/blog/beam-2.36.0.md b/website/www/site/content/en/blog/beam-2.36.0.md index 606c35e6c9801..57edefee044e1 100644 --- a/website/www/site/content/en/blog/beam-2.36.0.md +++ b/website/www/site/content/en/blog/beam-2.36.0.md @@ -52,6 +52,7 @@ notes](https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12319527 * Users may encounter an unexpected java.lang.ArithmeticException when outputting a timestamp for an element further than allowedSkew from an allowed DoFN skew set to a value more than Integer.MAX_VALUE. +* S3 object metadata retrieval broken in Python SDK ([BEAM-13980](https://issues.apache.org/jira/browse/BEAM-13980)) * See a full list of open [issues that affect](https://issues.apache.org/jira/issues/?jql=project%20%3D%20BEAM%20AND%20affectedVersion%20%3D%202.36.0%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC) this version. From 9145b5b191cdae5fd7e52480a9732c0cdc470343 Mon Sep 17 00:00:00 2001 From: Benjamin Gonzalez Date: Wed, 23 Feb 2022 13:14:58 -0600 Subject: [PATCH 19/61] Revert PR#16253 due errors with plugin flaky-test-handler --- .test-infra/jenkins/job_PreCommit_Java.groovy | 7 +------ .test-infra/jenkins/job_PreCommit_Python.groovy | 9 +-------- buildSrc/build.gradle.kts | 1 - .../apache/beam/gradle/BeamModulePlugin.groovy | 17 ----------------- 4 files changed, 2 insertions(+), 32 deletions(-) diff --git a/.test-infra/jenkins/job_PreCommit_Java.groovy b/.test-infra/jenkins/job_PreCommit_Java.groovy index aced96ab9bce8..444d2400abac5 100644 --- a/.test-infra/jenkins/job_PreCommit_Java.groovy +++ b/.test-infra/jenkins/job_PreCommit_Java.groovy @@ -23,7 +23,6 @@ PrecommitJobBuilder builder = new PrecommitJobBuilder( nameBase: 'Java', gradleTask: ':javaPreCommit', gradleSwitches: [ - '-PretryFlakyTest=true', '-PdisableSpotlessCheck=true' ], // spotless checked in separate pre-commit triggerPathPatterns: [ @@ -40,11 +39,7 @@ PrecommitJobBuilder builder = new PrecommitJobBuilder( ) builder.build { publishers { - archiveJunit('**/build/test-results/**/*.xml') { - testDataPublishers { - publishFlakyTestsReport() - } - } + archiveJunit('**/build/test-results/**/*.xml') recordIssues { tools { errorProne() diff --git a/.test-infra/jenkins/job_PreCommit_Python.groovy b/.test-infra/jenkins/job_PreCommit_Python.groovy index 9a2249572b2ad..0b5270f96eef0 100644 --- a/.test-infra/jenkins/job_PreCommit_Python.groovy +++ b/.test-infra/jenkins/job_PreCommit_Python.groovy @@ -21,9 +21,6 @@ import PrecommitJobBuilder PrecommitJobBuilder builder = new PrecommitJobBuilder( scope: this, nameBase: 'Python', - gradleSwitches: [ - '-PretryFlakyTest=true', - ], gradleTask: ':pythonPreCommit', timeoutMins: 180, triggerPathPatterns: [ @@ -35,10 +32,6 @@ PrecommitJobBuilder builder = new PrecommitJobBuilder( builder.build { // Publish all test results to Jenkins. publishers { - archiveJunit('**/pytest*.xml'){ - testDataPublishers { - publishFlakyTestsReport() - } - } + archiveJunit('**/pytest*.xml') } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 212074ed93d2a..f7992ed6086de 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -54,7 +54,6 @@ dependencies { runtimeOnly("ca.cutterslade.gradle:gradle-dependency-analyze:1.8.3") // Enable dep analysis runtimeOnly("gradle.plugin.net.ossindex:ossindex-gradle-plugin:0.4.11") // Enable dep vulnerability analysis runtimeOnly("org.checkerframework:checkerframework-gradle-plugin:0.5.16") // Enable enhanced static checking plugin - runtimeOnly("org.gradle:test-retry-gradle-plugin:1.3.1") // Enable flaky tests mitigation } // Because buildSrc is built and tested automatically _before_ gradle 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 6c6b140c6cf56..f46130549f1c1 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -409,23 +409,6 @@ class BeamModulePlugin implements Plugin { project.tasks.withType(Test) { jacoco.enabled = enabled } } - // Add Retry Gradle Plugin to mitigate flaky tests - if (project.hasProperty("retryFlakyTest")) { - project.apply plugin: "org.gradle.test-retry" - project.tasks.withType(Test) { - reports{ - junitXml{ - mergeReruns = true - } - } - retry { - failOnPassedAfterRetry = false - maxFailures = 10 - maxRetries = 3 - } - } - } - // Apply a plugin which provides tasks for dependency / property / task reports. // See https://docs.gradle.org/current/userguide/project_reports_plugin.html // for further details. This is typically very useful to look at the "htmlDependencyReport" From 0e6e77b7849df5961882311f8d0b37c89670cf62 Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:37:08 -0500 Subject: [PATCH 20/61] [BEAM-13884] Improve mtime package (#16924) --- sdks/go/pkg/beam/core/graph/mtime/time.go | 22 +- .../go/pkg/beam/core/graph/mtime/time_test.go | 215 ++++++++++++++++++ 2 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 sdks/go/pkg/beam/core/graph/mtime/time_test.go diff --git a/sdks/go/pkg/beam/core/graph/mtime/time.go b/sdks/go/pkg/beam/core/graph/mtime/time.go index 25ea9ce567f72..bdbb0c441af80 100644 --- a/sdks/go/pkg/beam/core/graph/mtime/time.go +++ b/sdks/go/pkg/beam/core/graph/mtime/time.go @@ -37,7 +37,7 @@ const ( // EndOfGlobalWindowTime is the timestamp at the end of the global window. It // is a day before the max timestamp. - // TODO Use GLOBAL_WINDOW_MAX_TIMESTAMP_MILLIS from the Runner API constants + // TODO(BEAM-4179) Use GLOBAL_WINDOW_MAX_TIMESTAMP_MILLIS from the Runner API constants EndOfGlobalWindowTime = MaxTimestamp - 24*60*60*1000 // ZeroTimestamp is the default zero value time. It corresponds to the unix epoch. @@ -65,7 +65,8 @@ func FromDuration(d time.Duration) Time { // FromTime returns a milli-second precision timestamp from a time.Time. func FromTime(t time.Time) Time { - return Normalize(Time(n2m(t.UnixNano()))) + // TODO(BEAM-13988): Replace t.UnixNano() with t.UnixMilli() for Go 1.17 or higher. + return Normalize(Time(t.UnixNano() / 1e6)) } // Milliseconds returns the number of milli-seconds since the Unix epoch. @@ -73,14 +74,18 @@ func (t Time) Milliseconds() int64 { return int64(t) } -// Add returns the time plus the duration. +// Add returns the time plus the duration. Input Durations of less than one +// millisecond will not increment the time due to a loss of precision when +// converting to milliseconds. func (t Time) Add(d time.Duration) Time { - return Normalize(Time(int64(t) + n2m(d.Nanoseconds()))) + return Normalize(Time(int64(t) + d.Milliseconds())) } -// Subtract returns the time minus the duration. +// Subtract returns the time minus the duration. Input Durations of less than one +// millisecond will not increment the time due to a loss of precision when +// converting to milliseconds. func (t Time) Subtract(d time.Duration) Time { - return Normalize(Time(int64(t) - n2m(d.Nanoseconds()))) + return Normalize(Time(int64(t) - d.Milliseconds())) } func (t Time) String() string { @@ -116,8 +121,3 @@ func Max(a, b Time) Time { func Normalize(t Time) Time { return Min(Max(t, MinTimestamp), MaxTimestamp) } - -// n2m converts nanoseconds to milliseconds. -func n2m(v int64) int64 { - return v / 1e6 -} diff --git a/sdks/go/pkg/beam/core/graph/mtime/time_test.go b/sdks/go/pkg/beam/core/graph/mtime/time_test.go new file mode 100644 index 0000000000000..7012184c18ce7 --- /dev/null +++ b/sdks/go/pkg/beam/core/graph/mtime/time_test.go @@ -0,0 +1,215 @@ +// 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 mtime + +import ( + "math" + "testing" + "time" +) + +func TestAdd(t *testing.T) { + tests := []struct { + name string + baseTime Time + addition time.Duration + expOut Time + }{ + { + "insignificant addition small", + Time(1000), + 1 * time.Nanosecond, + Time(1000), + }, + { + "insignificant addition large", + Time(1000), + 999999 * time.Nanosecond, + Time(1000), + }, + { + "significant addition small", + Time(1000), + 1 * time.Millisecond, + Time(1001), + }, + { + "significant addition large", + Time(1000), + 10 * time.Second, + Time(11000), + }, + { + "add past max timestamp", + MaxTimestamp, + 1 * time.Minute, + MaxTimestamp, + }, + { + "add across max boundary", + Time(int64(MaxTimestamp) - 10000), + 10 * time.Minute, + MaxTimestamp, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got, want := test.baseTime.Add(test.addition), test.expOut; got != want { + t.Errorf("(%v).Add(%v), got time %v, want %v", test.baseTime, test.addition, got, want) + } + }) + } +} + +func TestSubtract(t *testing.T) { + tests := []struct { + name string + baseTime Time + subtraction time.Duration + expOut Time + }{ + { + "insignificant subtraction small", + Time(1000), + 1 * time.Nanosecond, + Time(1000), + }, + { + "insignificant subtraction large", + Time(1000), + 999999 * time.Nanosecond, + Time(1000), + }, + { + "significant subtraction small", + Time(1000), + 1 * time.Millisecond, + Time(999), + }, + { + "significant subtraction large", + Time(1000), + 10 * time.Second, + Time(-9000), + }, + { + "subtract past min timestamp", + MinTimestamp, + 1 * time.Minute, + MinTimestamp, + }, + { + "subtract across min boundary", + Time(int64(MinTimestamp) + 10000), + 10 * time.Minute, + MinTimestamp, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got, want := test.baseTime.Subtract(test.subtraction), test.expOut; got != want { + t.Errorf("(%v).Subtract(%v), got time %v, want %v", test.baseTime, test.subtraction, got, want) + } + }) + } +} + +func TestNormalize(t *testing.T) { + tests := []struct { + name string + in Time + expOut Time + }{ + { + "min timestamp", + MinTimestamp, + MinTimestamp, + }, + { + "max timestamp", + MaxTimestamp, + MaxTimestamp, + }, + { + "end of global window", + EndOfGlobalWindowTime, + EndOfGlobalWindowTime, + }, + { + "beyond max timestamp", + Time(math.MaxInt64), + MaxTimestamp, + }, + { + "below min timestamp", + Time(math.MinInt64), + MinTimestamp, + }, + { + "normal value", + Time(int64(20000)), + Time(int64(20000)), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got, want := Normalize(test.in), test.expOut; got != want { + t.Errorf("Normalize(%v), got Time %v, want %v", test.in, got, want) + } + }) + } +} + +func TestFromTime(t *testing.T) { + tests := []struct { + name string + input time.Time + expOut Time + }{ + { + "zero unix", + time.Unix(0, 0).UTC(), + Time(0), + }, + { + "behind unix", + time.Unix(-1, 0).UTC(), + Time(-1000), + }, + { + "ahead of unix", + time.Unix(1, 0).UTC(), + Time(1000), + }, + { + "insignificant time small", + time.Unix(0, 1), + Time(0), + }, + { + "insignificant time large", + time.Unix(0, 999999), + Time(0), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got, want := FromTime(test.input), test.expOut; got != want { + t.Errorf("FromTime(%v), got %v, want %v", test.input, got, want) + } + }) + } +} From 4115242c30cd677d193da4e1ae42a33692cd7ff4 Mon Sep 17 00:00:00 2001 From: Brian Hulette Date: Wed, 23 Feb 2022 14:47:51 -0800 Subject: [PATCH 21/61] Minor: Update Go API doc links (#16932) --- website/www/site/data/io_matrix.yaml | 16 ++++++++-------- .../layouts/partials/section-menu/en/sdks.html | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/website/www/site/data/io_matrix.yaml b/website/www/site/data/io_matrix.yaml index f06c95f36a8dd..6c3e01aee25ec 100644 --- a/website/www/site/data/io_matrix.yaml +++ b/website/www/site/data/io_matrix.yaml @@ -34,7 +34,7 @@ categories: url: https://beam.apache.org/releases/pydoc/current/apache_beam.io.avroio.html - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/avroio - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/avroio + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/avroio - transform: TextIO description: PTransforms for reading and writing text files. implementations: @@ -46,7 +46,7 @@ categories: url: https://beam.apache.org/releases/pydoc/current/apache_beam.io.textio.html - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/textio - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/textio + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/textio - transform: TFRecordIO description: PTransforms for reading and writing [TensorFlow TFRecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) files. implementations: @@ -119,7 +119,7 @@ categories: url: https://beam.apache.org/releases/pydoc/current/apache_beam.io.gcp.gcsfilesystem.html - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/filesystem/gcs - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/filesystem/gcs + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/gcs - transform: LocalFileSystem description: "`FileSystem` implementation for accessing files on disk." implementations: @@ -131,7 +131,7 @@ categories: url: https://beam.apache.org/releases/pydoc/current/apache_beam.io.localfilesystem.html - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/filesystem/local - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/filesystem/local + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/local - transform: S3FileSystem description: "`FileSystem` implementation for [Amazon S3](https://aws.amazon.com/s3/)." implementations: @@ -143,7 +143,7 @@ categories: implementations: - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/filesystem/memfs - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/filesystem/memfs + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/memfs - name: Messaging description: These I/O connectors typically involve working with unbounded sources that come from messaging sources. rows: @@ -182,7 +182,7 @@ categories: url: https://beam.apache.org/releases/pydoc/current/apache_beam.io.external.gcp.pubsub.html - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/pubsubio - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/pubsubio + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/pubsubio - transform: JmsIO description: An unbounded source for [JMS](https://www.oracle.com/java/technologies/java-message-service.html) destinations (queues or topics). implementations: @@ -278,7 +278,7 @@ categories: url: https://beam.apache.org/releases/pydoc/current/apache_beam.io.gcp.bigquery.html - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/bigqueryio - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/bigqueryio + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/bigqueryio - transform: BigTableIO description: Read from (only for Java SDK) and write to [Google Cloud Bigtable](https://cloud.google.com/bigtable/). implementations: @@ -360,7 +360,7 @@ categories: implementations: - language: go name: github.com/apache/beam/sdks/go/pkg/beam/io/databaseio - url: https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam/io/databaseio + url: https://pkg.go.dev/github.com/apache/beam/sdks/v2/go/pkg/beam/io/databaseio - name: Miscellaneous description: Miscellaneous I/O sources. rows: diff --git a/website/www/site/layouts/partials/section-menu/en/sdks.html b/website/www/site/layouts/partials/section-menu/en/sdks.html index ef0b2bbdaaf48..4b9f3f2c6055c 100644 --- a/website/www/site/layouts/partials/section-menu/en/sdks.html +++ b/website/www/site/layouts/partials/section-menu/en/sdks.html @@ -48,7 +48,7 @@ Go

    • Go SDK overview
    • -
    • Go SDK API reference Go SDK API reference External link.
    • From 42de462785b848bea9474efc6a35308c7b73db1f Mon Sep 17 00:00:00 2001 From: Andy Ye Date: Wed, 23 Feb 2022 17:06:35 -0600 Subject: [PATCH 22/61] [BEAM-13218] Re-enable PubSubIntegrationTest.test_streaming_with_attributes (#16497) --- sdks/python/apache_beam/io/gcp/pubsub_integration_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdks/python/apache_beam/io/gcp/pubsub_integration_test.py b/sdks/python/apache_beam/io/gcp/pubsub_integration_test.py index e52d22032ed7c..6754c70068aeb 100644 --- a/sdks/python/apache_beam/io/gcp/pubsub_integration_test.py +++ b/sdks/python/apache_beam/io/gcp/pubsub_integration_test.py @@ -217,8 +217,6 @@ def test_streaming_data_only(self): @pytest.mark.it_postcommit def test_streaming_with_attributes(self): - if self.runner_name == 'TestDataflowRunner': - pytest.skip("BEAM-13218") self._test_streaming(with_attributes=True) From e3e30a74c3254fbc37ba6a4fbbeb872415eaa122 Mon Sep 17 00:00:00 2001 From: Aydar Farrakhov Date: Thu, 24 Feb 2022 08:25:02 +0300 Subject: [PATCH 23/61] Merge pull request #16857 from [BEAM-13662] [Playground] Support mutlifile examples * [BEAM-13771][Playground] Add multifile value to the response to the frontend * [BEAM-13771][Playground] Regenerate files * [BEAM-13771][Playground] Regenerate files * [BEAM-13662] playground - multifile example support * [BEAM-13662] playground - support multifile * [BEAM-13662] playground - redesign multifile examples * [BEAM-13662] playground - support multifile examples * [BEAM-13662] add licence * [BEAM-13662] refactor popover list for better readability * [BEAM-13662] refactor popover list for better readability Co-authored-by: AydarZaynutdinov Co-authored-by: Ilya --- playground/frontend/assets/multifile.svg | 22 ++++ playground/frontend/lib/config.g.dart | 12 +- playground/frontend/lib/constants/assets.dart | 1 + playground/frontend/lib/constants/links.dart | 1 + playground/frontend/lib/constants/sizes.dart | 1 + playground/frontend/lib/l10n/app_en.arb | 18 ++- .../modules/editor/components/run_button.dart | 11 +- .../description_popover.dart | 39 ++++-- .../description_popover_button.dart | 14 +- .../example_list/example_item_actions.dart | 66 ++++++++++ .../example_list/expansion_panel_item.dart | 9 +- .../multifile_popover/multifile_popover.dart | 67 ++++++++++ .../multifile_popover_button.dart | 88 +++++++++++++ .../components/outside_click_handler.dart | 44 +++++++ .../modules/examples/example_selector.dart | 120 +++++++++++------- .../examples/models/example_model.dart | 4 + .../examples/models/popover_state.dart | 32 +++++ .../example_client/grpc_example_client.dart | 2 + .../components/editor_textarea_wrapper.dart | 13 +- 19 files changed, 491 insertions(+), 73 deletions(-) create mode 100644 playground/frontend/assets/multifile.svg create mode 100644 playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart create mode 100644 playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart create mode 100644 playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart create mode 100644 playground/frontend/lib/modules/examples/components/outside_click_handler.dart create mode 100644 playground/frontend/lib/modules/examples/models/popover_state.dart diff --git a/playground/frontend/assets/multifile.svg b/playground/frontend/assets/multifile.svg new file mode 100644 index 0000000000000..2a27ab26cea62 --- /dev/null +++ b/playground/frontend/assets/multifile.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/playground/frontend/lib/config.g.dart b/playground/frontend/lib/config.g.dart index e189b32dd4cc2..c81baa5554c35 100644 --- a/playground/frontend/lib/config.g.dart +++ b/playground/frontend/lib/config.g.dart @@ -16,14 +16,14 @@ * limitations under the License. */ -const String kApiClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com'; const String kAnalyticsUA = 'UA-73650088-1'; +const String kApiClientURL = + 'https://backend-router-beta-dot-apache-beam-testing.appspot.com'; const String kApiJavaClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/java/'; + 'https://backend-java-beta-dot-apache-beam-testing.appspot.com'; const String kApiGoClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/go/'; + 'https://backend-go-beta-dot-apache-beam-testing.appspot.com'; const String kApiPythonClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/python/'; + 'https://backend-python-beta-dot-apache-beam-testing.appspot.com'; const String kApiScioClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/scio/'; + 'https://backend-scio-beta-dot-apache-beam-testing.appspot.com'; diff --git a/playground/frontend/lib/constants/assets.dart b/playground/frontend/lib/constants/assets.dart index deaad152d9430..796a97daa87fb 100644 --- a/playground/frontend/lib/constants/assets.dart +++ b/playground/frontend/lib/constants/assets.dart @@ -33,6 +33,7 @@ const kCopyIconAsset = 'copy.svg'; const kLinkIconAsset = 'link.svg'; const kDragHorizontalIconAsset = 'drag_horizontal.svg'; const kDragVerticalIconAsset = 'drag_vertical.svg'; +const kMultifileIconAsset = 'multifile.svg'; // notifications icons const kErrorNotificationIconAsset = 'error_notification.svg'; diff --git a/playground/frontend/lib/constants/links.dart b/playground/frontend/lib/constants/links.dart index e3b34ebc81652..688a7ad9be7ef 100644 --- a/playground/frontend/lib/constants/links.dart +++ b/playground/frontend/lib/constants/links.dart @@ -24,3 +24,4 @@ const kApacheBeamGithubLink = 'https://github.com/apache/beam'; const kBeamWebsiteLink = 'https://beam.apache.org/'; const kScioGithubLink = 'https://github.com/spotify/scio'; const kAboutBeamLink = 'https://beam.apache.org/get-started/beam-overview'; +const kAddExampleLink = 'https://beam.apache.org/get-started/try-beam-playground/#how-to-add-new-examples'; diff --git a/playground/frontend/lib/constants/sizes.dart b/playground/frontend/lib/constants/sizes.dart index 3962a192bf717..74f61ae307a96 100644 --- a/playground/frontend/lib/constants/sizes.dart +++ b/playground/frontend/lib/constants/sizes.dart @@ -52,6 +52,7 @@ const double kCursorSize = 1.0; // container size const double kContainerHeight = 40.0; +const double kCaptionFontSize = 10.0; const double kCodeFontSize = 14.0; const double kLabelFontSize = 16.0; const double kHintFontSize = 16.0; diff --git a/playground/frontend/lib/l10n/app_en.arb b/playground/frontend/lib/l10n/app_en.arb index 8b85370a5e807..2b2b4707811ba 100644 --- a/playground/frontend/lib/l10n/app_en.arb +++ b/playground/frontend/lib/l10n/app_en.arb @@ -184,7 +184,23 @@ "description": "Text value label" }, "pipelineOptionsError": "Please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,',. symbols are allowed", - "@value": { + "@pipelineOptionsError": { "description": "Pipeline options parse error" + }, + "viewOnGithub": "View on GitHub", + "@viewOnGithub": { + "description": "View on Github button" + }, + "addExample": "Add your own example", + "@addExample": { + "description": "Add example link text" + }, + "multifile": "Multifile", + "@multifile": { + "description": "Multifile example" + }, + "multifileWarning": "Multifile not supported yet for running in playground. Open it on github.", + "@multifileWarning": { + "description": "Multifile not supported text" } } \ No newline at end of file diff --git a/playground/frontend/lib/modules/editor/components/run_button.dart b/playground/frontend/lib/modules/editor/components/run_button.dart index 6aecc1f74d80d..d31e36d213515 100644 --- a/playground/frontend/lib/modules/editor/components/run_button.dart +++ b/playground/frontend/lib/modules/editor/components/run_button.dart @@ -32,12 +32,14 @@ class RunButton extends StatelessWidget { final bool isRunning; final VoidCallback runCode; final VoidCallback cancelRun; + final bool disabled; const RunButton({ Key? key, required this.isRunning, required this.runCode, required this.cancelRun, + this.disabled = false, }) : super(key: key); @override @@ -71,9 +73,16 @@ class RunButton extends StatelessWidget { } return Text(buttonText); }), - onPressed: !isRunning ? runCode : cancelRun, + onPressed: onPressHandler(), ), ), ); } + + onPressHandler() { + if (disabled) { + return null; + } + return !isRunning ? runCode : cancelRun; + } } diff --git a/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart b/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart index 6a37c8ff4bb30..5de8144a5cf24 100644 --- a/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart +++ b/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart @@ -17,9 +17,13 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:playground/constants/assets.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:url_launcher/url_launcher.dart'; const kDescriptionWidth = 300.0; @@ -30,26 +34,43 @@ class DescriptionPopover extends StatelessWidget { @override Widget build(BuildContext context) { + final hasLink = example.link?.isNotEmpty ?? false; return SizedBox( width: kDescriptionWidth, child: Card( child: Padding( padding: const EdgeInsets.all(kLgSpacing), child: Wrap( - runSpacing: kSmSpacing, + runSpacing: kMdSpacing, children: [ - Text( - example.name, - style: const TextStyle( - fontSize: kTitleFontSize, - fontWeight: kBoldWeight, - ), - ), - Text(example.description), + title, + description, + if (hasLink) getViewOnGithub(context), ], ), ), ), ); } + + Widget get title => Text( + example.name, + style: const TextStyle( + fontSize: kTitleFontSize, + fontWeight: kBoldWeight, + ), + ); + + Widget get description => Text(example.description); + + Widget getViewOnGithub(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return TextButton.icon( + icon: SvgPicture.asset(kGithubIconAsset), + onPressed: () { + launch(example.link ?? ''); + }, + label: Text(appLocale.viewOnGithub), + ); + } } diff --git a/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart b/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart index d0eb6f7223e41..d2e60a9fd3b7a 100644 --- a/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart +++ b/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart @@ -28,6 +28,8 @@ class DescriptionPopoverButton extends StatelessWidget { final ExampleModel example; final Alignment followerAnchor; final Alignment targetAnchor; + final void Function()? onOpen; + final void Function()? onClose; const DescriptionPopoverButton({ Key? key, @@ -35,6 +37,8 @@ class DescriptionPopoverButton extends StatelessWidget { required this.example, required this.followerAnchor, required this.targetAnchor, + this.onOpen, + this.onClose, }) : super(key: key); @override @@ -62,12 +66,15 @@ class DescriptionPopoverButton extends StatelessWidget { ExampleModel example, Alignment followerAnchor, Alignment targetAnchor, - ) { + ) async { // close previous description dialog Navigator.of(context, rootNavigator: true).popUntil((route) { return route.isFirst; }); - showAlignedDialog( + if (onOpen != null) { + onOpen!(); + } + await showAlignedDialog( context: context, builder: (dialogContext) => DescriptionPopover( example: example, @@ -76,5 +83,8 @@ class DescriptionPopoverButton extends StatelessWidget { targetAnchor: targetAnchor, barrierColor: Colors.transparent, ); + if (onClose != null) { + onClose!(); + } } } diff --git a/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart new file mode 100644 index 0000000000000..9cae77f6073b4 --- /dev/null +++ b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart @@ -0,0 +1,66 @@ +/* + * 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 'package:flutter/material.dart'; +import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground/modules/examples/models/popover_state.dart'; +import 'package:provider/provider.dart'; + +import '../multifile_popover/multifile_popover_button.dart'; + +class ExampleItemActions extends StatelessWidget { + final ExampleModel example; + final BuildContext parentContext; + + const ExampleItemActions( + {Key? key, required this.parentContext, required this.example}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (example.isMultiFile) multifilePopover, + descriptionPopover, + ], + ); + } + + Widget get multifilePopover => MultifilePopoverButton( + parentContext: parentContext, + example: example, + followerAnchor: Alignment.topLeft, + targetAnchor: Alignment.topRight, + onOpen: () => _setPopoverOpen(parentContext, true), + onClose: () => _setPopoverOpen(parentContext, false), + ); + + Widget get descriptionPopover => DescriptionPopoverButton( + parentContext: parentContext, + example: example, + followerAnchor: Alignment.topLeft, + targetAnchor: Alignment.topRight, + onOpen: () => _setPopoverOpen(parentContext, true), + onClose: () => _setPopoverOpen(parentContext, false), + ); + + void _setPopoverOpen(BuildContext context, bool isOpen) { + Provider.of(context, listen: false).setOpen(isOpen); + } +} diff --git a/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart b/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart index 74a6e23fb53df..7f29c441cd140 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; +import 'package:playground/modules/examples/components/example_list/example_item_actions.dart'; import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/pages/playground/states/examples_state.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; @@ -72,12 +72,7 @@ class ExpansionPanelItem extends StatelessWidget { ? const TextStyle(fontWeight: FontWeight.bold) : const TextStyle(), ), - DescriptionPopoverButton( - parentContext: context, - example: example, - followerAnchor: Alignment.topLeft, - targetAnchor: Alignment.topRight, - ), + ExampleItemActions(parentContext: context, example: example), ], ), ), diff --git a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart new file mode 100644 index 0000000000000..00db2d71ecf47 --- /dev/null +++ b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart @@ -0,0 +1,67 @@ +/* + * 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 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:playground/constants/assets.dart'; +import 'package:playground/constants/font_weight.dart'; +import 'package:playground/constants/sizes.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const kMultifileWidth = 300.0; + +class MultifilePopover extends StatelessWidget { + final ExampleModel example; + + const MultifilePopover({Key? key, required this.example}) : super(key: key); + + @override + Widget build(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return SizedBox( + width: kMultifileWidth, + child: Card( + child: Padding( + padding: const EdgeInsets.all(kLgSpacing), + child: Wrap( + runSpacing: kMdSpacing, + children: [ + Text( + appLocale.multifile, + style: const TextStyle( + fontSize: kTitleFontSize, + fontWeight: kBoldWeight, + ), + ), + Text(appLocale.multifileWarning), + TextButton.icon( + icon: SvgPicture.asset(kGithubIconAsset), + onPressed: () { + launch(example.link ?? ''); + }, + label: Text(appLocale.viewOnGithub), + ), + ], + ), + ), + ), + ); + } +} diff --git a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart new file mode 100644 index 0000000000000..149194dfafd4f --- /dev/null +++ b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart @@ -0,0 +1,88 @@ +/* + * 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 'package:aligned_dialog/aligned_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:playground/constants/assets.dart'; +import 'package:playground/constants/sizes.dart'; +import 'package:playground/modules/examples/components/multifile_popover/multifile_popover.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; + +class MultifilePopoverButton extends StatelessWidget { + final BuildContext? parentContext; + final ExampleModel example; + final Alignment followerAnchor; + final Alignment targetAnchor; + final void Function()? onOpen; + final void Function()? onClose; + + const MultifilePopoverButton({ + Key? key, + this.parentContext, + required this.example, + required this.followerAnchor, + required this.targetAnchor, + this.onOpen, + this.onClose, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return IconButton( + iconSize: kIconSizeMd, + splashRadius: kIconButtonSplashRadius, + icon: SvgPicture.asset(kMultifileIconAsset), + onPressed: () { + _showMultifilePopover( + parentContext ?? context, + example, + followerAnchor, + targetAnchor, + ); + }, + ); + } + + void _showMultifilePopover( + BuildContext context, + ExampleModel example, + Alignment followerAnchor, + Alignment targetAnchor, + ) async { + // close previous dialogs + Navigator.of(context, rootNavigator: true).popUntil((route) { + return route.isFirst; + }); + if (onOpen != null) { + onOpen!(); + } + await showAlignedDialog( + context: context, + builder: (dialogContext) => MultifilePopover( + example: example, + ), + followerAnchor: followerAnchor, + targetAnchor: targetAnchor, + barrierColor: Colors.transparent, + ); + if (onClose != null) { + onClose!(); + } + } +} diff --git a/playground/frontend/lib/modules/examples/components/outside_click_handler.dart b/playground/frontend/lib/modules/examples/components/outside_click_handler.dart new file mode 100644 index 0000000000000..16db300254fa4 --- /dev/null +++ b/playground/frontend/lib/modules/examples/components/outside_click_handler.dart @@ -0,0 +1,44 @@ +/* + * 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 'package:flutter/material.dart'; +import 'package:playground/modules/examples/models/popover_state.dart'; +import 'package:provider/provider.dart'; + +class OutsideClickHandler extends StatelessWidget { + final void Function() onTap; + + const OutsideClickHandler({Key? key, required this.onTap}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, state, child) { + if (state.isOpen) { + return Container(); + } + return GestureDetector( + onTap: onTap, + child: Container( + color: Colors.transparent, + height: double.infinity, + width: double.infinity, + ), + ); + }); + } +} diff --git a/playground/frontend/lib/modules/examples/example_selector.dart b/playground/frontend/lib/modules/examples/example_selector.dart index 1994a65d612ca..8824d6e3af384 100644 --- a/playground/frontend/lib/modules/examples/example_selector.dart +++ b/playground/frontend/lib/modules/examples/example_selector.dart @@ -17,21 +17,26 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/components/loading_indicator/loading_indicator.dart'; import 'package:playground/config/theme.dart'; +import 'package:playground/constants/links.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; +import 'package:playground/modules/examples/components/outside_click_handler.dart'; +import 'package:playground/modules/examples/models/popover_state.dart'; import 'package:playground/modules/examples/models/selector_size_model.dart'; import 'package:playground/pages/playground/states/example_selector_state.dart'; import 'package:playground/pages/playground/states/examples_state.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; const int kAnimationDurationInMilliseconds = 80; const Offset kAnimationBeginOffset = Offset(0.0, -0.02); const Offset kAnimationEndOffset = Offset(0.0, 0.0); const double kAdditionalDyAlignment = 50.0; -const double kLgContainerHeight = 444.0; +const double kLgContainerHeight = 490.0; const double kLgContainerWidth = 400.0; class ExampleSelector extends StatefulWidget { @@ -121,60 +126,63 @@ class _ExampleSelectorState extends State return OverlayEntry( builder: (context) { - return Consumer2( - builder: (context, exampleState, playgroundState, child) => Stack( - children: [ - GestureDetector( - onTap: () { - closeDropdown(exampleState); - // handle description dialogs - Navigator.of(context, rootNavigator: true).popUntil((route) { - return route.isFirst; - }); - }, - child: Container( - color: Colors.transparent, - height: double.infinity, - width: double.infinity, - ), - ), - ChangeNotifierProvider( - create: (context) => ExampleSelectorState( - exampleState, - playgroundState, - exampleState.getCategories(playgroundState.sdk)!, - ), - builder: (context, _) => Positioned( - left: posModel.xAlignment, - top: posModel.yAlignment + kAdditionalDyAlignment, - child: SlideTransition( - position: offsetAnimation, - child: Material( - elevation: kElevation.toDouble(), - child: Container( - height: kLgContainerHeight, - width: kLgContainerWidth, - decoration: BoxDecoration( - color: Theme.of(context).backgroundColor, - borderRadius: BorderRadius.circular(kMdBorderRadius), + return ChangeNotifierProvider( + create: (context) => PopoverState(false), + builder: (context, state) { + return Consumer2( + builder: (context, exampleState, playgroundState, child) => Stack( + children: [ + OutsideClickHandler( + onTap: () { + closeDropdown(exampleState); + // handle description dialogs + Navigator.of(context, rootNavigator: true).popUntil((route) { + return route.isFirst; + }); + }, + ), + ChangeNotifierProvider( + create: (context) => ExampleSelectorState( + exampleState, + playgroundState, + exampleState.getCategories(playgroundState.sdk)!, + ), + builder: (context, _) => Positioned( + left: posModel.xAlignment, + top: posModel.yAlignment + kAdditionalDyAlignment, + child: SlideTransition( + position: offsetAnimation, + child: Material( + elevation: kElevation.toDouble(), + child: Container( + height: kLgContainerHeight, + width: kLgContainerWidth, + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(kMdBorderRadius), + ), + child: exampleState.sdkCategories == null || + playgroundState.selectedExample == null + ? const LoadingIndicator(size: kContainerHeight) + : _buildDropdownContent(context, playgroundState), + ), ), - child: exampleState.sdkCategories == null || - playgroundState.selectedExample == null - ? const LoadingIndicator(size: kContainerHeight) - : _buildDropdownContent(playgroundState), ), ), ), - ), + ], ), - ], - ), + ); + } ); }, ); } - Widget _buildDropdownContent(PlaygroundState playgroundState) { + Widget _buildDropdownContent( + BuildContext context, + PlaygroundState playgroundState, + ) { return Column( children: [ SearchField(controller: textController), @@ -185,6 +193,28 @@ class _ExampleSelectorState extends State animationController: animationController, dropdown: examplesDropdown, ), + Divider( + height: kDividerHeight, + color: ThemeColors.of(context).greyColor, + indent: kLgSpacing, + endIndent: kLgSpacing, + ), + SizedBox( + width: double.infinity, + child: TextButton( + child: Padding( + padding: const EdgeInsets.all(kXlSpacing), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.addExample, + style: TextStyle(color: ThemeColors.of(context).primary), + ), + ), + ), + onPressed: () => launch(kAddExampleLink), + ), + ) ], ); } diff --git a/playground/frontend/lib/modules/examples/models/example_model.dart b/playground/frontend/lib/modules/examples/models/example_model.dart index ff95440427d36..e6f850ae4815e 100644 --- a/playground/frontend/lib/modules/examples/models/example_model.dart +++ b/playground/frontend/lib/modules/examples/models/example_model.dart @@ -43,6 +43,8 @@ class ExampleModel with Comparable { final String name; final String path; final String description; + bool isMultiFile; + String? link; String? source; String? outputs; String? logs; @@ -54,6 +56,8 @@ class ExampleModel with Comparable { required this.path, required this.description, required this.type, + this.isMultiFile = false, + this.link, this.source, this.outputs, this.logs, diff --git a/playground/frontend/lib/modules/examples/models/popover_state.dart b/playground/frontend/lib/modules/examples/models/popover_state.dart new file mode 100644 index 0000000000000..af51052cb79f0 --- /dev/null +++ b/playground/frontend/lib/modules/examples/models/popover_state.dart @@ -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. + */ + +import 'package:flutter/cupertino.dart'; + +class PopoverState extends ChangeNotifier { + bool _isOpen; + + bool get isOpen => _isOpen; + + PopoverState(this._isOpen); + + setOpen(bool open) { + _isOpen = open; + notifyListeners(); + } +} diff --git a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart index 2b91d915fab1c..672b2e52cd6cb 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart @@ -224,6 +224,8 @@ class GrpcExampleClient implements ExampleClient { type: _exampleTypeFromString(example.type), path: example.cloudPath, pipelineOptions: example.pipelineOptions, + isMultiFile: example.multifile, + link: example.link, ); } } diff --git a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart index 52ab98fff3672..4f88f3d4204ce 100644 --- a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart +++ b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart @@ -23,6 +23,7 @@ import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/modules/editor/components/editor_textarea.dart'; import 'package:playground/modules/editor/components/run_button.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; +import 'package:playground/modules/examples/components/multifile_popover/multifile_popover_button.dart'; import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/modules/notifications/components/notification.dart'; import 'package:playground/modules/sdk/models/sdk.dart'; @@ -53,7 +54,7 @@ class CodeTextAreaWrapper extends StatelessWidget { children: [ Positioned.fill( child: EditorTextArea( - enabled: true, + enabled: !(state.selectedExample?.isMultiFile ?? false), example: state.selectedExample, sdk: state.sdk, onSourceChange: state.setSource, @@ -66,13 +67,21 @@ class CodeTextAreaWrapper extends StatelessWidget { height: kButtonHeight, child: Row( children: [ - if (state.selectedExample != null) + if (state.selectedExample != null) ...[ + if (state.selectedExample?.isMultiFile ?? false) + MultifilePopoverButton( + example: state.selectedExample!, + followerAnchor: Alignment.topRight, + targetAnchor: Alignment.bottomRight, + ), DescriptionPopoverButton( example: state.selectedExample!, followerAnchor: Alignment.topRight, targetAnchor: Alignment.bottomRight, ), + ], RunButton( + disabled: state.selectedExample?.isMultiFile ?? false, isRunning: state.isCodeRunning, cancelRun: () { state.cancelRun().catchError( From 2a45a5b6c34141dcc595c5cda3d28264e542bdff Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Thu, 24 Feb 2022 08:27:02 +0300 Subject: [PATCH 24/61] Merge pull request #16826 from [BEAM-13870] [Playground] Increase test coverage for the cache package * Increase test coverage for the cache package * Add commentaries for several tests. * Edit comments * Edit comments * Refactoring code --- .../internal/cache/local/local_cache_test.go | 115 ++++++++++++++++-- .../internal/cache/redis/redis_cache_test.go | 68 ++++++++--- 2 files changed, 157 insertions(+), 26 deletions(-) diff --git a/playground/backend/internal/cache/local/local_cache_test.go b/playground/backend/internal/cache/local/local_cache_test.go index c4d5a580d07a1..34b2654ad065c 100644 --- a/playground/backend/internal/cache/local/local_cache_test.go +++ b/playground/backend/internal/cache/local/local_cache_test.go @@ -58,6 +58,8 @@ func TestLocalCache_GetValue(t *testing.T) { preparedItemsMap[preparedId][preparedSubKey] = value preparedExpMap := make(map[uuid.UUID]time.Time) preparedExpMap[preparedId] = time.Now().Add(time.Millisecond) + endedExpMap := make(map[uuid.UUID]time.Time) + endedExpMap[preparedId] = time.Now().Add(-time.Millisecond) type fields struct { cleanupInterval time.Duration items map[uuid.UUID]map[cache.SubKey]interface{} @@ -76,7 +78,7 @@ func TestLocalCache_GetValue(t *testing.T) { wantErr bool }{ { - name: "Get exist value", + name: "Get existing value", fields: fields{ cleanupInterval: cleanupInterval, items: preparedItemsMap, @@ -91,7 +93,7 @@ func TestLocalCache_GetValue(t *testing.T) { wantErr: false, }, { - name: "Get not exist value", + name: "Get not existing value", fields: fields{ cleanupInterval: cleanupInterval, items: make(map[uuid.UUID]map[cache.SubKey]interface{}), @@ -103,6 +105,21 @@ func TestLocalCache_GetValue(t *testing.T) { want: nil, wantErr: true, }, + { + name: "Get an existing value that is expiring", + fields: fields{ + cleanupInterval: cleanupInterval, + items: preparedItemsMap, + pipelinesExpiration: endedExpMap, + }, + args: args{ + ctx: context.Background(), + pipelineId: preparedId, + subKey: preparedSubKey, + }, + want: nil, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -159,6 +176,21 @@ func TestLocalCache_SetValue(t *testing.T) { }, wantErr: false, }, + { + name: "Set value for RunOutputIndex subKey", + fields: fields{ + cleanupInterval: cleanupInterval, + items: make(map[uuid.UUID]map[cache.SubKey]interface{}), + pipelinesExpiration: preparedExpMap, + }, + args: args{ + ctx: context.Background(), + pipelineId: preparedId, + subKey: cache.RunOutputIndex, + value: 5, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -180,6 +212,9 @@ func TestLocalCache_SetValue(t *testing.T) { func TestLocalCache_SetExpTime(t *testing.T) { preparedId, _ := uuid.NewUUID() + preparedItems := make(map[uuid.UUID]map[cache.SubKey]interface{}) + preparedItems[preparedId] = make(map[cache.SubKey]interface{}) + preparedItems[preparedId][cache.Status] = 1 type fields struct { cleanupInterval time.Duration items map[uuid.UUID]map[cache.SubKey]interface{} @@ -191,12 +226,27 @@ func TestLocalCache_SetExpTime(t *testing.T) { expTime time.Duration } tests := []struct { - name string - fields fields - args args + name string + fields fields + args args + wantErr bool }{ { name: "Set expiration time", + fields: fields{ + cleanupInterval: cleanupInterval, + items: preparedItems, + pipelinesExpiration: make(map[uuid.UUID]time.Time), + }, + args: args{ + ctx: context.Background(), + pipelineId: preparedId, + expTime: time.Minute, + }, + wantErr: false, + }, + { + name: "Set expiration time for not existing value in cache items", fields: fields{ cleanupInterval: cleanupInterval, items: make(map[uuid.UUID]map[cache.SubKey]interface{}), @@ -207,6 +257,7 @@ func TestLocalCache_SetExpTime(t *testing.T) { pipelineId: preparedId, expTime: time.Minute, }, + wantErr: true, }, } for _, tt := range tests { @@ -216,14 +267,13 @@ func TestLocalCache_SetExpTime(t *testing.T) { items: tt.fields.items, pipelinesExpiration: tt.fields.pipelinesExpiration, } - _ = lc.SetValue(tt.args.ctx, preparedId, cache.Status, 1) err := lc.SetExpTime(tt.args.ctx, tt.args.pipelineId, tt.args.expTime) - if err != nil { - t.Error(err) - } - expTime, found := lc.pipelinesExpiration[tt.args.pipelineId] - if expTime.Round(time.Second) != time.Now().Add(tt.args.expTime).Round(time.Second) || !found { - t.Errorf("Expiration time of the pipeline: %s not set in cache.", tt.args.pipelineId) + if (err != nil) != tt.wantErr { + t.Errorf("SetExpTime() error = %v, wantErr %v", err, tt.wantErr) + expTime, found := lc.pipelinesExpiration[tt.args.pipelineId] + if expTime.Round(time.Second) != time.Now().Add(tt.args.expTime).Round(time.Second) || !found { + t.Errorf("Expiration time of the pipeline: %s not set in cache.", tt.args.pipelineId) + } } }) } @@ -429,6 +479,16 @@ func TestLocalCache_startGC(t *testing.T) { pipelinesExpiration: preparedExpMap, }, }, + { + // Test case with calling startGC method with nil cache items. + // As a result, items stay the same. + name: "Checking for deleting expired pipelines with nil cache items", + fields: fields{ + cleanupInterval: time.Microsecond, + items: nil, + pipelinesExpiration: preparedExpMap, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -540,3 +600,34 @@ func TestLocalCache_clearItems(t *testing.T) { }) } } + +func TestNew(t *testing.T) { + items := make(map[uuid.UUID]map[cache.SubKey]interface{}) + pipelinesExpiration := make(map[uuid.UUID]time.Time) + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want *Cache + }{ + { + name: "Initialize local cache", + args: args{ctx: context.Background()}, + want: &Cache{ + cleanupInterval: cleanupInterval, + items: items, + pipelinesExpiration: pipelinesExpiration, + catalog: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/playground/backend/internal/cache/redis/redis_cache_test.go b/playground/backend/internal/cache/redis/redis_cache_test.go index 02dec43b5d0ed..60a0ecfa2c252 100644 --- a/playground/backend/internal/cache/redis/redis_cache_test.go +++ b/playground/backend/internal/cache/redis/redis_cache_test.go @@ -54,7 +54,7 @@ func TestRedisCache_GetValue(t *testing.T) { wantErr bool }{ { - name: "error during HGet operation", + name: "Error during HGet operation", mocks: func() { mock.ExpectHGet(pipelineId.String(), string(marshSubKey)).SetErr(fmt.Errorf("MOCK_ERROR")) }, @@ -68,7 +68,7 @@ func TestRedisCache_GetValue(t *testing.T) { wantErr: true, }, { - name: "all success", + name: "Get existing value", mocks: func() { mock.ExpectHGet(pipelineId.String(), string(marshSubKey)).SetVal(string(marshValue)) }, @@ -122,7 +122,7 @@ func TestRedisCache_SetExpTime(t *testing.T) { wantErr bool }{ { - name: "error during Exists operation", + name: "Error during Exists operation", mocks: func() { mock.ExpectExists(pipelineId.String()).SetErr(fmt.Errorf("MOCK_ERROR")) }, @@ -148,7 +148,7 @@ func TestRedisCache_SetExpTime(t *testing.T) { wantErr: true, }, { - name: "error during Expire operation", + name: "Set expiration time with error during Expire operation", mocks: func() { mock.ExpectExists(pipelineId.String()).SetVal(1) mock.ExpectExpire(pipelineId.String(), expTime).SetErr(fmt.Errorf("MOCK_ERROR")) @@ -162,7 +162,7 @@ func TestRedisCache_SetExpTime(t *testing.T) { wantErr: true, }, { - name: "all success", + name: "Set expiration time", mocks: func() { mock.ExpectExists(pipelineId.String()).SetVal(1) mock.ExpectExpire(pipelineId.String(), expTime).SetVal(true) @@ -215,7 +215,7 @@ func TestRedisCache_SetValue(t *testing.T) { wantErr bool }{ { - name: "error during HSet operation", + name: "Error during HSet operation", mocks: func() { mock.ExpectHSet(pipelineId.String(), marshSubKey, marshValue).SetErr(fmt.Errorf("MOCK_ERROR")) }, @@ -229,7 +229,7 @@ func TestRedisCache_SetValue(t *testing.T) { wantErr: true, }, { - name: "all success", + name: "Set correct value", mocks: func() { mock.ExpectHSet(pipelineId.String(), marshSubKey, marshValue).SetVal(1) mock.ExpectExpire(pipelineId.String(), time.Minute*15).SetVal(true) @@ -243,6 +243,21 @@ func TestRedisCache_SetValue(t *testing.T) { }, wantErr: false, }, + { + name: "Set incorrect value", + mocks: func() { + mock.ExpectHSet(pipelineId.String(), marshSubKey, marshValue).SetVal(1) + mock.ExpectExpire(pipelineId.String(), time.Minute*15).SetVal(true) + }, + fields: fields{client}, + args: args{ + ctx: context.Background(), + pipelineId: pipelineId, + subKey: subKey, + value: make(chan int), + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -318,7 +333,7 @@ func TestCache_GetCatalog(t *testing.T) { wantErr bool }{ { - name: "Success", + name: "Get existing catalog", mocks: func() { mock.ExpectGet(cache.ExamplesCatalog).SetVal(string(catalogMarsh)) }, @@ -416,7 +431,7 @@ func TestCache_SetCatalog(t *testing.T) { wantErr bool }{ { - name: "Success", + name: "Set catalog", mocks: func() { mock.ExpectSet(cache.ExamplesCatalog, catalogMarsh, 0).SetVal("") }, @@ -465,7 +480,7 @@ func Test_newRedisCache(t *testing.T) { wantErr bool }{ { - name: "error during Ping operation", + name: "Error during Ping operation", args: args{ ctx: context.Background(), addr: address, @@ -487,6 +502,9 @@ func Test_unmarshalBySubKey(t *testing.T) { statusValue, _ := json.Marshal(status) output := "MOCK_OUTPUT" outputValue, _ := json.Marshal(output) + canceledValue, _ := json.Marshal(false) + runOutputIndex := 0 + runOutputIndexValue, _ := json.Marshal(runOutputIndex) type args struct { ctx context.Context subKey cache.SubKey @@ -499,7 +517,7 @@ func Test_unmarshalBySubKey(t *testing.T) { wantErr bool }{ { - name: "status subKey", + name: "Status subKey", args: args{ ctx: context.Background(), subKey: cache.Status, @@ -509,7 +527,7 @@ func Test_unmarshalBySubKey(t *testing.T) { wantErr: false, }, { - name: "runOutput subKey", + name: "RunOutput subKey", args: args{ subKey: cache.RunOutput, value: string(outputValue), @@ -518,7 +536,7 @@ func Test_unmarshalBySubKey(t *testing.T) { wantErr: false, }, { - name: "compileOutput subKey", + name: "CompileOutput subKey", args: args{ subKey: cache.CompileOutput, value: string(outputValue), @@ -527,7 +545,7 @@ func Test_unmarshalBySubKey(t *testing.T) { wantErr: false, }, { - name: "graph subKey", + name: "Graph subKey", args: args{ subKey: cache.Graph, value: string(outputValue), @@ -535,6 +553,28 @@ func Test_unmarshalBySubKey(t *testing.T) { want: output, wantErr: false, }, + { + // Test case with calling unmarshalBySubKey method with Canceled subKey. + // As a result, want to receive false. + name: "Canceled subKey", + args: args{ + subKey: cache.Canceled, + value: string(canceledValue), + }, + want: false, + wantErr: false, + }, + { + // Test case with calling unmarshalBySubKey method with RunOutputIndex subKey. + // As a result, want to receive expected runOutputIndex. + name: "RunOutputIndex subKey", + args: args{ + subKey: cache.RunOutputIndex, + value: string(runOutputIndexValue), + }, + want: float64(runOutputIndex), + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From f837506a0b7f1f06f9f00e5ccd3ec2ccc8ea813f Mon Sep 17 00:00:00 2001 From: Aydar Farrakhov Date: Thu, 24 Feb 2022 17:19:05 +0300 Subject: [PATCH 25/61] Palo Alto case study - fix link --- website/www/site/content/en/case-studies/paloalto.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/www/site/content/en/case-studies/paloalto.md b/website/www/site/content/en/case-studies/paloalto.md index 0fdba878fbcd5..e751bd833f780 100644 --- a/website/www/site/content/en/case-studies/paloalto.md +++ b/website/www/site/content/en/case-studies/paloalto.md @@ -60,9 +60,9 @@ and flexibility to [over 85K customers](https://www.paloaltonetworks.com/about-u Palo Alto Networks’ integrated security operations platform - [Cortex™](https://www.paloaltonetworks.com/cortex) - applies AI and machine learning to enable security automation, advanced threat intelligence, and effective rapid -security responses for Palo Alto Networks’ customers. (Cortex™ Data -Lake)[https://www.paloaltonetworks.com/cortex/cortex-data-lake] infrastructure collects, integrates, and normalizes -enterprises’ security data combined with trillions of multi-source artifacts. +security responses for Palo Alto Networks’ +customers. [Cortex™ Data Lake](https://www.paloaltonetworks.com/cortex/cortex-data-lake) infrastructure collects, +integrates, and normalizes enterprises’ security data combined with trillions of multi-source artifacts. Cortex™ data infrastructure processes ~10 millions of security log events per second currently, at ~3 PB per day, which are on the high end of real-time streaming processing scale in the industry. Palo Alto Networks’ Sr Principal Software From b0899a439d05961ccc019ec899cb83fcbc4eff46 Mon Sep 17 00:00:00 2001 From: dpcollins-google <40498610+dpcollins-google@users.noreply.github.com> Date: Thu, 24 Feb 2022 13:19:47 -0500 Subject: [PATCH 26/61] Fix BoundedQueueExecutor and StreamingDataflowWorker to actually limit memory from windmill (#16901) Currently, because the queue is only limited by number of elements, there can be up to (num threads + queue size) elements outstanding at a time, which for large work items will almost certainly OOM the worker. This change both makes this limit explicit and adds a 50% JVM max memory limit on outstanding WorkItems to push back on windmill before workers run out of memory. --- .../options/DataflowPipelineDebugOptions.java | 24 +++ .../worker/StreamingDataflowWorker.java | 64 ++++---- .../worker/util/BoundedQueueExecutor.java | 148 +++++++++++++----- .../worker/StreamingDataflowWorkerTest.java | 16 +- 4 files changed, 171 insertions(+), 81 deletions(-) diff --git a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowPipelineDebugOptions.java b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowPipelineDebugOptions.java index 264827f33aa69..2995a485527cd 100644 --- a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowPipelineDebugOptions.java +++ b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowPipelineDebugOptions.java @@ -177,6 +177,30 @@ public Dataflow create(PipelineOptions options) { void setNumberOfWorkerHarnessThreads(int value); + /** + * Maximum number of bundles outstanding from windmill before the worker stops requesting. + * + *

      If <= 0, use the default value of 100 + getNumberOfWorkerHarnessThreads() + */ + @Description( + "Maximum number of bundles outstanding from windmill before the worker stops requesting.") + @Default.Integer(0) + int getMaxBundlesFromWindmillOutstanding(); + + void setMaxBundlesFromWindmillOutstanding(int value); + + /** + * Maximum number of bytes outstanding from windmill before the worker stops requesting. + * + *

      If <= 0, use the default value of 50% of jvm memory. + */ + @Description( + "Maximum number of bytes outstanding from windmill before the worker stops requesting. If <= 0, use the default value of 50% of jvm memory.") + @Default.Long(0) + long getMaxBytesFromWindmillOutstanding(); + + void setMaxBytesFromWindmillOutstanding(long value); + /** * If {@literal true}, save a heap dump before killing a thread or process which is GC thrashing * or out of memory. The location of the heap file will either be echoed back to the user, or the 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 236e47715a367..c49865aa7511c 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 @@ -191,10 +191,6 @@ public class StreamingDataflowWorker { // Maximum number of threads for processing. Currently each thread processes one key at a time. static final int MAX_PROCESSING_THREADS = 300; static final long THREAD_EXPIRATION_TIME_SEC = 60; - // Maximum work units retrieved from Windmill and queued before processing. Limiting this delays - // retrieving extra work from Windmill without working on it, leading to better - // prioritization / utilization. - static final int MAX_WORK_UNITS_QUEUED = 100; static final long TARGET_COMMIT_BUNDLE_BYTES = 32 << 20; static final int MAX_COMMIT_QUEUE_BYTES = 500 << 20; // 500MB static final int NUM_COMMIT_STREAMS = 1; @@ -210,9 +206,6 @@ public class StreamingDataflowWorker { // Matches kWindmillCounterUpdate in workflow_worker_service_multi_hubs.cc. private static final String WINDMILL_COUNTER_UPDATE_WORK_ID = "3"; - /** Maximum number of items to return in a GetWork request. */ - private static final long MAX_GET_WORK_ITEMS = MAX_WORK_UNITS_QUEUED + MAX_PROCESSING_THREADS; - /** Maximum number of failure stacktraces to report in each update sent to backend. */ private static final int MAX_FAILURES_TO_REPORT_IN_UPDATE = 1000; @@ -666,7 +659,8 @@ public static StreamingDataflowWorker fromDataflowWorkerHarnessOptions( chooseMaximumNumberOfThreads(), THREAD_EXPIRATION_TIME_SEC, TimeUnit.SECONDS, - MAX_WORK_UNITS_QUEUED, + chooseMaximumBundlesOutstanding(), + chooseMaximumBytesOutstanding(), threadFactory); maxSinkBytes = @@ -785,6 +779,22 @@ private int chooseMaximumNumberOfThreads() { return MAX_PROCESSING_THREADS; } + private int chooseMaximumBundlesOutstanding() { + int maxBundles = options.getMaxBundlesFromWindmillOutstanding(); + if (maxBundles > 0) { + return maxBundles; + } + return chooseMaximumNumberOfThreads() + 100; + } + + private long chooseMaximumBytesOutstanding() { + long maxMem = options.getMaxBytesFromWindmillOutstanding(); + if (maxMem > 0) { + return maxMem; + } + return Runtime.getRuntime().maxMemory() / 2; + } + void addStateNameMappings(Map nameMap) { stateNameMap.putAll(nameMap); } @@ -804,7 +814,7 @@ public void setMaxWorkItemCommitBytes(int maxWorkItemCommitBytes) { @VisibleForTesting public boolean workExecutorIsEmpty() { - return workUnitExecutor.getQueue().isEmpty(); + return workUnitExecutor.executorQueueIsEmpty(); } public void start() { @@ -949,9 +959,6 @@ public void stop() { memoryMonitor.stop(); memoryMonitorThread.join(); workUnitExecutor.shutdown(); - if (!workUnitExecutor.awaitTermination(5, TimeUnit.MINUTES)) { - throw new RuntimeException("Work executor did not terminate within 5 minutes"); - } for (ComputationState state : computationMap.values()) { state.close(); } @@ -1065,7 +1072,7 @@ void streamingDispatchLoop() { windmillServer.getWorkStream( Windmill.GetWorkRequest.newBuilder() .setClientId(clientId) - .setMaxItems(MAX_GET_WORK_ITEMS) + .setMaxItems(chooseMaximumBundlesOutstanding()) .setMaxBytes(MAX_GET_WORK_FETCH_BYTES) .build(), (String computation, @@ -1236,7 +1243,8 @@ private void callFinalizeCallbacks(Windmill.WorkItem work) { } catch (Throwable t) { LOG.error("Source checkpoint finalization failed:", t); } - }); + }, + 0); } } } @@ -1548,7 +1556,7 @@ private void process( if (retryLocally) { // Try again after some delay and at the end of the queue to avoid a tight loop. sleep(retryLocallyDelayMs); - workUnitExecutor.forceExecute(work); + workUnitExecutor.forceExecute(work, work.getWorkItem().getSerializedSize()); } else { // Consider the item invalid. It will eventually be retried by Windmill if it still needs to // be processed. @@ -1726,7 +1734,7 @@ private Windmill.GetWorkResponse getWork() { return windmillServer.getWork( Windmill.GetWorkRequest.newBuilder() .setClientId(clientId) - .setMaxItems(MAX_GET_WORK_ITEMS) + .setMaxItems(chooseMaximumBundlesOutstanding()) .setMaxBytes(MAX_GET_WORK_FETCH_BYTES) .build()); } @@ -2285,7 +2293,7 @@ public boolean activateWork(ShardedKey shardedKey, Work work) { // Fall through to execute without the lock held. } } - executor.execute(work); + executor.execute(work, work.getWorkItem().getSerializedSize()); return true; } @@ -2327,7 +2335,7 @@ public void completeWork(ShardedKey shardedKey, long workToken) { } } if (nextWork != null) { - executor.forceExecute(nextWork); + executor.forceExecute(nextWork, nextWork.getWorkItem().getSerializedSize()); } } @@ -2511,27 +2519,9 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro } private class MetricsDataProvider implements StatusDataProvider { - @Override public void appendSummaryHtml(PrintWriter writer) { - writer.println( - "Worker Threads: " - + workUnitExecutor.getPoolSize() - + "/" - + workUnitExecutor.getMaximumPoolSize() - + "
      "); - writer.println("Active Threads: " + workUnitExecutor.getActiveCount() + "
      "); - writer.println( - "Work Queue Size: " - + workUnitExecutor.getQueue().size() - + "/" - + MAX_WORK_UNITS_QUEUED - + "
      "); - writer.print("Commit Queue: "); - appendHumanizedBytes(commitQueue.weight(), writer); - writer.print(", "); - writer.print(commitQueue.size()); - writer.println(" elements
      "); + writer.println(workUnitExecutor.summaryHtml()); writer.print("Active commit: "); appendHumanizedBytes(activeCommitBytes.get(), writer); diff --git a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java index 9a51e8c18febe..29a4ea7c5355c 100644 --- a/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java +++ b/runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java @@ -18,62 +18,138 @@ package org.apache.beam.runners.dataflow.worker.util; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.util.concurrent.Monitor; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.util.concurrent.Monitor.Guard; -/** Executor that blocks on execute() if its queue is full. */ +/** An executor for executing work on windmill items. */ @SuppressWarnings({ "nullness" // TODO(https://issues.apache.org/jira/browse/BEAM-10402) }) -public class BoundedQueueExecutor extends ThreadPoolExecutor { - private static class ReducableSemaphore extends Semaphore { - ReducableSemaphore(int permits) { - super(permits); - } - - @Override - public void reducePermits(int permits) { - super.reducePermits(permits); - } - } +public class BoundedQueueExecutor { + private final ThreadPoolExecutor executor; + private final int maximumElementsOutstanding; + private final long maximumBytesOutstanding; - private ReducableSemaphore semaphore; + private final Monitor monitor = new Monitor(); + private int elementsOutstanding = 0; + private long bytesOutstanding = 0; public BoundedQueueExecutor( int maximumPoolSize, long keepAliveTime, TimeUnit unit, - int maximumQueueSize, + int maximumElementsOutstanding, + long maximumBytesOutstanding, ThreadFactory threadFactory) { - super( - maximumPoolSize, - maximumPoolSize, - keepAliveTime, - unit, - new LinkedBlockingQueue(), - threadFactory); - this.semaphore = new ReducableSemaphore(maximumQueueSize); - allowCoreThreadTimeOut(true); + executor = + new ThreadPoolExecutor( + maximumPoolSize, + maximumPoolSize, + keepAliveTime, + unit, + new LinkedBlockingQueue<>(), + threadFactory); + executor.allowCoreThreadTimeOut(true); + this.maximumElementsOutstanding = maximumElementsOutstanding; + this.maximumBytesOutstanding = maximumBytesOutstanding; } - // Before adding a Runnable to the queue, acquire the semaphore. - @Override - public void execute(Runnable r) { - semaphore.acquireUninterruptibly(); - super.execute(r); + // Before adding a Work to the queue, check that there are enough bytes of space or no other + // outstanding elements of work. + public void execute(Runnable work, long workBytes) { + monitor.enterWhenUninterruptibly( + new Guard(monitor) { + @Override + public boolean isSatisfied() { + return elementsOutstanding == 0 + || (bytesAvailable() >= workBytes + && elementsOutstanding < maximumElementsOutstanding); + } + }); + executeLockHeld(work, workBytes); } // Forcibly add something to the queue, ignoring the length limit. - public void forceExecute(Runnable r) { - semaphore.reducePermits(1); - super.execute(r); + public void forceExecute(Runnable work, long workBytes) { + monitor.enter(); + executeLockHeld(work, workBytes); + } + + public void shutdown() throws InterruptedException { + executor.shutdown(); + if (!executor.awaitTermination(5, TimeUnit.MINUTES)) { + throw new RuntimeException("Work executor did not terminate within 5 minutes"); + } + } + + public boolean executorQueueIsEmpty() { + return executor.getQueue().isEmpty(); + } + + public String summaryHtml() { + monitor.enter(); + try { + StringBuilder builder = new StringBuilder(); + builder.append("Worker Threads: "); + builder.append(executor.getPoolSize()); + builder.append("/"); + builder.append(executor.getMaximumPoolSize()); + builder.append("
      /n"); + + builder.append("Active Threads: "); + builder.append(executor.getActiveCount()); + builder.append("
      /n"); + + builder.append("Work Queue Size: "); + builder.append(elementsOutstanding); + builder.append("/"); + builder.append(maximumElementsOutstanding); + builder.append("
      /n"); + + builder.append("Work Queue Bytes: "); + builder.append(bytesOutstanding); + builder.append("/"); + builder.append(maximumBytesOutstanding); + builder.append("
      /n"); + + return builder.toString(); + } finally { + monitor.leave(); + } + } + + private void executeLockHeld(Runnable work, long workBytes) { + bytesOutstanding += workBytes; + ++elementsOutstanding; + monitor.leave(); + + try { + executor.execute( + () -> { + try { + work.run(); + } finally { + decrementCounters(workBytes); + } + }); + } catch (RuntimeException e) { + // If the execute() call threw an exception, decrement counters here. + decrementCounters(workBytes); + throw e; + } + } + + private void decrementCounters(long workBytes) { + monitor.enter(); + --elementsOutstanding; + bytesOutstanding -= workBytes; + monitor.leave(); } - // Release the semaphore after taking a Runnable off the queue. - @Override - public void beforeExecute(Thread t, Runnable r) { - semaphore.release(); + private long bytesAvailable() { + return maximumBytesOutstanding - bytesOutstanding; } } 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 c175d634508e6..c383e5d16e599 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 @@ -2693,14 +2693,14 @@ public void testActiveWork() throws Exception { MockWork m1 = new MockWork(1); assertTrue(computationState.activateWork(key1, m1)); - Mockito.verify(mockExecutor).execute(m1); + Mockito.verify(mockExecutor).execute(m1, m1.getWorkItem().getSerializedSize()); computationState.completeWork(key1, 1); Mockito.verifyNoMoreInteractions(mockExecutor); // Verify work queues. MockWork m2 = new MockWork(2); assertTrue(computationState.activateWork(key1, m2)); - Mockito.verify(mockExecutor).execute(m2); + Mockito.verify(mockExecutor).execute(m2, m2.getWorkItem().getSerializedSize()); MockWork m3 = new MockWork(3); assertTrue(computationState.activateWork(key1, m3)); Mockito.verifyNoMoreInteractions(mockExecutor); @@ -2708,19 +2708,19 @@ public void testActiveWork() throws Exception { // Verify another key is a separate queue. MockWork m4 = new MockWork(4); assertTrue(computationState.activateWork(key2, m4)); - Mockito.verify(mockExecutor).execute(m4); + Mockito.verify(mockExecutor).execute(m4, m4.getWorkItem().getSerializedSize()); computationState.completeWork(key2, 4); Mockito.verifyNoMoreInteractions(mockExecutor); computationState.completeWork(key1, 2); - Mockito.verify(mockExecutor).forceExecute(m3); + Mockito.verify(mockExecutor).forceExecute(m3, m3.getWorkItem().getSerializedSize()); computationState.completeWork(key1, 3); Mockito.verifyNoMoreInteractions(mockExecutor); // Verify duplicate work dropped. MockWork m5 = new MockWork(5); computationState.activateWork(key1, m5); - Mockito.verify(mockExecutor).execute(m5); + Mockito.verify(mockExecutor).execute(m5, m5.getWorkItem().getSerializedSize()); assertFalse(computationState.activateWork(key1, m5)); Mockito.verifyNoMoreInteractions(mockExecutor); computationState.completeWork(key1, 5); @@ -2743,14 +2743,14 @@ public void testActiveWorkForShardedKeys() throws Exception { MockWork m1 = new MockWork(1); assertTrue(computationState.activateWork(key1Shard1, m1)); - Mockito.verify(mockExecutor).execute(m1); + Mockito.verify(mockExecutor).execute(m1, m1.getWorkItem().getSerializedSize()); computationState.completeWork(key1Shard1, 1); Mockito.verifyNoMoreInteractions(mockExecutor); // Verify work queues. MockWork m2 = new MockWork(2); assertTrue(computationState.activateWork(key1Shard1, m2)); - Mockito.verify(mockExecutor).execute(m2); + Mockito.verify(mockExecutor).execute(m2, m2.getWorkItem().getSerializedSize()); MockWork m3 = new MockWork(3); assertTrue(computationState.activateWork(key1Shard1, m3)); Mockito.verifyNoMoreInteractions(mockExecutor); @@ -2760,7 +2760,7 @@ public void testActiveWorkForShardedKeys() throws Exception { assertFalse(computationState.activateWork(key1Shard1, m4)); Mockito.verifyNoMoreInteractions(mockExecutor); assertTrue(computationState.activateWork(key1Shard2, m4)); - Mockito.verify(mockExecutor).execute(m4); + Mockito.verify(mockExecutor).execute(m4, m4.getWorkItem().getSerializedSize()); // Verify duplicate work dropped assertFalse(computationState.activateWork(key1Shard2, m4)); From 0fe57b13d58badf6da8739cdc95ce03c7eb6d87f Mon Sep 17 00:00:00 2001 From: Matt Casters Date: Thu, 24 Feb 2022 19:38:51 +0100 Subject: [PATCH 27/61] [BEAM-1857] Add Neo4jIO (#15916) * BEAM-1857 : Add Neo4jIO * BEAM-1857 : Add Neo4jIO (checker fixes BEAM-10615) * BEAM-1857 : Add Neo4jIO (spotless) * BEAM-1857 : Add Neo4jIO (code review feedback response, comment 1) * BEAM-1857 : Add Neo4jIO (spotless) * BEAM-1857 : Add Neo4jIO (convert parameters mapper to stateless function) * BEAM-1857 : Add Neo4jIO (ibzib code review response) * BEAM-1857 : Add Neo4jIO (default to random ports for neo4j container) * BEAM-1857 : Add Neo4jIO (checker framework warning fixes) * BEAM-1857 : Add Neo4jIO (simplified API with neo4j-java-driver 4.4.2) * BEAM-1857 : Add Neo4jIO (avoid security settings issues on GCP Dataflow) * BEAM-1857 : Add Neo4jIO (java checker framework fix + extra javadoc) * [BEAM-1857] : Add Neo4jIO (gradle fixes) * BEAM-1857 : Add Neo4jIO (IT gradle fix) * BEAM-1857 : Add Neo4jIO (IT gradle fix) * BEAM-1857 : Add Neo4jIO (IT docker fix) --- build.gradle.kts | 1 + sdks/java/io/neo4j/OWNERS | 5 + sdks/java/io/neo4j/build.gradle | 39 + .../org/apache/beam/sdk/io/neo4j/Neo4jIO.java | 1221 +++++++++++++++++ .../beam/sdk/io/neo4j/package-info.java | 24 + .../apache/beam/sdk/io/neo4j/Neo4jIOIT.java | 275 ++++ .../apache/beam/sdk/io/neo4j/Neo4jIOTest.java | 117 ++ .../beam/sdk/io/neo4j/Neo4jTestUtil.java | 70 + settings.gradle.kts | 1 + 9 files changed, 1753 insertions(+) create mode 100644 sdks/java/io/neo4j/OWNERS create mode 100644 sdks/java/io/neo4j/build.gradle create mode 100644 sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/Neo4jIO.java create mode 100644 sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/package-info.java create mode 100644 sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOIT.java create mode 100644 sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOTest.java create mode 100644 sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jTestUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index c53f8e9fe6c26..a49109d31cf4d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -193,6 +193,7 @@ tasks.register("javaPostCommit") { dependsOn(":sdks:java:io:amazon-web-services2:integrationTest") dependsOn(":sdks:java:extensions:ml:postCommit") dependsOn(":sdks:java:io:kafka:kafkaVersionsCompatibilityTest") + dependsOn(":sdks:java:io:neo4j:integrationTest") } tasks.register("javaHadoopVersionsTest") { diff --git a/sdks/java/io/neo4j/OWNERS b/sdks/java/io/neo4j/OWNERS new file mode 100644 index 0000000000000..0ff6e82359fb6 --- /dev/null +++ b/sdks/java/io/neo4j/OWNERS @@ -0,0 +1,5 @@ +# See the OWNERS docs at https://s.apache.org/beam-owners + +reviewers: + - mcasters + diff --git a/sdks/java/io/neo4j/build.gradle b/sdks/java/io/neo4j/build.gradle new file mode 100644 index 0000000000000..9d5adfc32b1d0 --- /dev/null +++ b/sdks/java/io/neo4j/build.gradle @@ -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. + */ + +plugins { id 'org.apache.beam.module' } +applyJavaNature(automaticModuleName: 'org.apache.beam.sdk.io.neo4j') +provideIntegrationTestingDependencies() +enableJavaPerformanceTesting() + +description = "Apache Beam :: SDKs :: Java :: IO :: Neo4j" +ext.summary = "IO to read from and write to Neo4j graphs" + +dependencies { + implementation project(path: ":sdks:java:core", configuration: "shadow") + implementation "org.neo4j.driver:neo4j-java-driver:4.4.3" + implementation library.java.slf4j_api + implementation library.java.vendored_guava_26_0_jre + testImplementation library.java.junit + testImplementation library.java.hamcrest + testImplementation project(path: ":sdks:java:io:common", configuration: "testRuntimeMigration") + testImplementation project(path: ":sdks:java:testing:test-utils", configuration: "testRuntimeMigration") + testImplementation "org.testcontainers:neo4j:1.16.2" + testRuntimeOnly library.java.slf4j_jdk14 + testRuntimeOnly project(path: ":runners:direct-java", configuration: "shadow") +} diff --git a/sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/Neo4jIO.java b/sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/Neo4jIO.java new file mode 100644 index 0000000000000..9b011aff410d2 --- /dev/null +++ b/sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/Neo4jIO.java @@ -0,0 +1,1221 @@ +/* + * 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.neo4j; + +import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkArgument; + +import com.google.auto.value.AutoValue; +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.beam.repackaged.core.org.apache.commons.lang3.StringUtils; +import org.apache.beam.sdk.annotations.Experimental; +import org.apache.beam.sdk.coders.Coder; +import org.apache.beam.sdk.harness.JvmInitializer; +import org.apache.beam.sdk.options.ValueProvider; +import org.apache.beam.sdk.schemas.NoSuchSchemaException; +import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.schemas.SchemaRegistry; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.transforms.SerializableFunction; +import org.apache.beam.sdk.transforms.display.DisplayData; +import org.apache.beam.sdk.transforms.display.HasDisplayData; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PDone; +import org.apache.beam.sdk.values.TypeDescriptor; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions; +import org.checkerframework.checker.initialization.qual.Initialized; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Record; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.TransactionWork; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a Beam IO to read from, and write data to, Neo4j. + * + *

      + * + *

      + * + *

      Driver configuration

      + * + *

      To read from or write to Neo4j you have to provide a {@link DriverConfiguration} using
      + * {@link DriverConfiguration#create()} or {@link DriverConfiguration#create(String, String, + * String)} (URL, username and password). Note that subclasses of DriverConfiguration must also be + * {@link Serializable}).
      + * At the level of the Neo4j driver configuration you can specify a Neo4j {@link Config} object with + * {@link DriverConfiguration#withConfig(Config)}. This way you can configure the Neo4j driver + * characteristics. Likewise, you can control the characteristics of Neo4j sessions by optionally + * passing a {@link SessionConfig} object to {@link ReadAll} or {@link WriteUnwind}. For example, + * the session configuration will allow you to target a specific database or set a fetch size. + * Finally, in even rarer cases you might need to configure the various aspects of Neo4j + * transactions, for example their timeout. You can do this with a Neo4j {@link TransactionConfig} + * object. + * + *

      + * + *

      + * + *

      Neo4j Aura

      + * + *

      If you have trouble connecting to a Neo4j Aura database please try to disable a few security + * algorithms in your JVM. This makes sure that the right one is picked to connect: + * + *

      + * + *

      {@code
      + * Security.setProperty(
      + *       "jdk.tls.disabledAlgorithms",
      + *       "SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL");
      + * }
      + * + *

      + * + *

      + * + *

      To execute this code on GCP Dataflow you can create a class which extends {@link + * JvmInitializer} and implement the {@link JvmInitializer#onStartup()} method. You need to annotate + * this new class with {@link com.google.auto.service.AutoService} + * + *

      {@code
      + * @AutoService(value = JvmInitializer.class)
      + * }
      + * + *

      + * + *

      Reading from Neo4j

      + * + *

      {@link Neo4jIO#readAll()} source returns a bounded collection of {@code OuptutT} as a {@code + * PCollection}. OutputT is the type returned by the provided {@link RowMapper}. It accepts + * parameters as input in the form of {@code ParameterT} as a {@code PCollection} + * + *

      The following example reads ages to return the IDs of Person nodes. It runs a Cypher query for + * each provided age. + * + *

      The mapping {@link SerializableFunction} maps input values to each execution of the Cypher + * statement. In the function simply return a map containing the parameters you want to set. + * + *

      The {@link RowMapper} converts output Neo4j {@link Record} values to the output of the source. + * + *

      {@code
      + * pipeline
      + *   .apply(Create.of(40, 50, 60))
      + *   .apply(Neo4jIO.readAll()
      + *     .withDriverConfiguration(Neo4jIO.DriverConfiguration.create("neo4j://localhost:7687", "neo4j", "password"))
      + *     .withCypher("MATCH(n:Person) WHERE n.age = $age RETURN n.id")
      + *     .withReadTransaction()
      + *     .withCoder(StringUtf8Coder.of())
      + *     .withParametersFunction( age -> Collections.singletonMap( "age", age ))
      + *     .withRowMapper( record -> return record.get(0).asString() )
      + *   );
      + * }
      + * + *

      Writing to Neo4j

      + * + *

      The Neo4j {@link WriteUnwind} transform supports writing data to a graph. It writes a {@link + * PCollection} to the graph by collecting a batch of elements after which all elements in the batch + * are written together to Neo4j. + * + *

      Like the source, to configure this sink, you have to provide a {@link DriverConfiguration}. + * + *

      In the following example we'll merge a collection of {@link org.apache.beam.sdk.values.Row} + * into Person nodes. Since this is a Sink it has no output and as such no RowMapper is needed. The + * rows are being used as a container for the parameters of the Cypher statement. The used Cypher in + * question needs to be an UNWIND statement. Like in the read case, the parameters {@link + * SerializableFunction} converts parameter values to a {@link Map}. The difference here is that the + * resulting Map is stored in a {@link List} (containing maps) which in turn is stored in another + * Map under the name provided by the {@link WriteUnwind#withUnwindMapName(String)} method. All of + * this is handled automatically. You do need to provide the unwind map name so that you can + * reference that in the UNWIND statement. + * + *

      + * + *

      For example: + * + *

      {@code
      + * pipeline
      + *   .apply(...)
      + *   .apply(Neo4jIO.writeUnwind()
      + *      .withDriverConfiguration(Neo4jIO.DriverConfiguration.create("neo4j://localhost:7687", "neo4j", "password"))
      + *      .withUnwindMapName("rows")
      + *      .withCypher("UNWIND $rows AS row MERGE(n:Person { id : row.id } ) SET n.firstName = row.first, n.lastName = row.last")
      + *      .withParametersFunction( row -> ImmutableMap.of(
      + *        "id", row.getString("id),
      + *        "first", row.getString("firstName")
      + *        "last", row.getString("lastName")))
      + *    );
      + * }
      + */ +@Experimental(Experimental.Kind.SOURCE_SINK) +public class Neo4jIO { + + private static final Logger LOG = LoggerFactory.getLogger(Neo4jIO.class); + + /** + * Read all rows using a Neo4j Cypher query. + * + * @param Type of the data representing query parameters. + * @param Type of the data to be read. + */ + public static ReadAll readAll() { + return new AutoValue_Neo4jIO_ReadAll.Builder().build(); + } + + /** + * Write all rows using a Neo4j Cypher UNWIND cypher statement. This sets a default batch size of + * 5000. + * + * @param Type of the data representing query parameters. + */ + public static WriteUnwind writeUnwind() { + return new AutoValue_Neo4jIO_WriteUnwind.Builder() + .setBatchSize(ValueProvider.StaticValueProvider.of(5000L)) + .build(); + } + + private static PCollection getOutputPCollection( + PCollection input, + DoFn writeFn, + @Nullable Coder coder) { + PCollection output = input.apply(ParDo.of(writeFn)); + if (coder != null) { + output.setCoder(coder); + try { + TypeDescriptor typeDesc = coder.getEncodedTypeDescriptor(); + SchemaRegistry registry = input.getPipeline().getSchemaRegistry(); + Schema schema = registry.getSchema(typeDesc); + output.setSchema( + schema, + typeDesc, + registry.getToRowFunction(typeDesc), + registry.getFromRowFunction(typeDesc)); + } catch (NoSuchSchemaException e) { + // ignore + } + } + return output; + } + + /** + * An interface used by {@link ReadAll} for converting each row of a Neo4j {@link Result} record + * {@link Record} into an element of the resulting {@link PCollection}. + */ + @FunctionalInterface + public interface RowMapper extends Serializable { + T mapRow(Record record) throws Exception; + } + + /** + * A convenience method to clarify the way {@link ValueProvider} works to the static code checker + * framework for {@link Nullable} values. + * + * @param valueProvider + * @param + * @return The provided value or null if none was specified. + */ + private static T getProvidedValue(@Nullable ValueProvider valueProvider) { + if (valueProvider == null) { + return (T) null; + } + return valueProvider.get(); + } + + /** This describes all the information needed to create a Neo4j {@link Session}. */ + @AutoValue + public abstract static class DriverConfiguration implements Serializable { + public static DriverConfiguration create() { + return new AutoValue_Neo4jIO_DriverConfiguration.Builder() + .build() + .withDefaultConfig(true) + .withConfig(Config.defaultConfig()); + } + + public static DriverConfiguration create(String url, String username, String password) { + checkArgument(url != null, "url can not be null"); + checkArgument(username != null, "username can not be null"); + checkArgument(password != null, "password can not be null"); + return new AutoValue_Neo4jIO_DriverConfiguration.Builder() + .build() + .withDefaultConfig(true) + .withConfig(Config.defaultConfig()) + .withUrl(url) + .withUsername(username) + .withPassword(password); + } + + abstract @Nullable ValueProvider getUrl(); + + abstract @Nullable ValueProvider> getUrls(); + + abstract @Nullable ValueProvider getUsername(); + + abstract @Nullable ValueProvider getPassword(); + + abstract @Nullable Config getConfig(); + + abstract @Nullable ValueProvider getHasDefaultConfig(); + + abstract Builder builder(); + + public DriverConfiguration withUrl(String url) { + return withUrl(ValueProvider.StaticValueProvider.of(url)); + } + + public DriverConfiguration withUrl(ValueProvider url) { + Preconditions.checkArgument( + url != null, "a neo4j connection URL can not be empty or null", url); + Preconditions.checkArgument( + StringUtils.isNotEmpty(url.get()), + "a neo4j connection URL can not be empty or null", + url); + return builder().setUrl(url).build(); + } + + public DriverConfiguration withUrls(List urls) { + return withUrls(ValueProvider.StaticValueProvider.of(urls)); + } + + public DriverConfiguration withUrls(ValueProvider> urls) { + Preconditions.checkArgument( + urls != null, "a list of neo4j connection URLs can not be empty or null", urls); + Preconditions.checkArgument( + urls.get() != null && !urls.get().isEmpty(), + "a neo4j connection URL can not be empty or null", + urls); + return builder().setUrls(urls).build(); + } + + public DriverConfiguration withConfig(Config config) { + return builder().setConfig(config).build(); + } + + public DriverConfiguration withUsername(String username) { + return withUsername(ValueProvider.StaticValueProvider.of(username)); + } + + public DriverConfiguration withUsername(ValueProvider username) { + Preconditions.checkArgument(username != null, "neo4j username can not be null", username); + Preconditions.checkArgument( + username.get() != null, "neo4j username can not be null", username); + return builder().setUsername(username).build(); + } + + public DriverConfiguration withPassword(String password) { + return withPassword(ValueProvider.StaticValueProvider.of(password)); + } + + public DriverConfiguration withPassword(ValueProvider password) { + Preconditions.checkArgument(password != null, "neo4j password can not be null", password); + Preconditions.checkArgument( + password.get() != null, "neo4j password can not be null", password); + return builder().setPassword(password).build(); + } + + public DriverConfiguration withDefaultConfig(boolean useDefault) { + return withDefaultConfig(ValueProvider.StaticValueProvider.of(useDefault)); + } + + public DriverConfiguration withDefaultConfig(ValueProvider useDefault) { + Preconditions.checkArgument( + useDefault != null, "withDefaultConfig parameter useDefault can not be null", useDefault); + Preconditions.checkArgument( + useDefault.get() != null, + "withDefaultConfig parameter useDefault can not be null", + useDefault); + return builder().setHasDefaultConfig(useDefault).build(); + } + + void populateDisplayData(DisplayData.Builder builder) { + builder.addIfNotNull(DisplayData.item("neo4j-url", getUrl())); + builder.addIfNotNull(DisplayData.item("neo4j-username", getUsername())); + builder.addIfNotNull( + DisplayData.item( + "neo4j-password", getPassword() != null ? "" : "")); + } + + Driver buildDriver() { + // Create the Neo4j Driver + // This uses the provided Neo4j configuration along with URLs, username and password + // + Config config = getConfig(); + if (config == null) { + throw new RuntimeException("please provide a neo4j config"); + } + // We're trying to work around a subtle serialisation bug in the Neo4j Java driver. + // The fix is work in progress. For now, we harden our code to avoid + // wild goose chases. + // + Boolean hasDefaultConfig = getProvidedValue(getHasDefaultConfig()); + if (hasDefaultConfig != null && hasDefaultConfig) { + config = Config.defaultConfig(); + } + + // Get the list of the URI to connect with + // + List uris = new ArrayList<>(); + String url = getProvidedValue(getUrl()); + if (url != null) { + try { + uris.add(new URI(url)); + } catch (URISyntaxException e) { + throw new RuntimeException("Error creating URI from URL '" + url + "'", e); + } + } + List providedUrls = getProvidedValue(getUrls()); + if (providedUrls != null) { + for (String providedUrl : providedUrls) { + try { + uris.add(new URI(providedUrl)); + } catch (URISyntaxException e) { + throw new RuntimeException( + "Error creating URI '" + + providedUrl + + "' from a list of " + + providedUrls.size() + + " URLs", + e); + } + } + } + + // A specific routing driver can be used to connect to specific clustered configurations. + // Often we don't need it because the Java driver automatically can figure this out + // automatically. To keep things simple we use the routing driver in case we have more + // than one URL specified. This is an exceptional case. + // + Driver driver; + AuthToken authTokens = + getAuthToken(getProvidedValue(getUsername()), getProvidedValue(getPassword())); + if (uris.size() > 1) { + driver = GraphDatabase.routingDriver(uris, authTokens, config); + } else { + // Just take the first URI that was provided + driver = GraphDatabase.driver(uris.get(0), authTokens, config); + } + + return driver; + } + + /** + * Certain embedded scenarios and so on actually allow for having no authentication at all. + * + * @param username The username if one is needed + * @param password The password if one is needed + * @return The AuthToken + */ + protected AuthToken getAuthToken(String username, String password) { + if (username != null && password != null) { + return AuthTokens.basic(username, password); + } else { + return AuthTokens.none(); + } + } + + /** + * The Builder class below is not visible. We use it to service the "with" methods below the + * Builder class. + */ + @AutoValue.Builder + abstract static class Builder { + abstract Builder setUrl(ValueProvider url); + + abstract Builder setUrls(ValueProvider> url); + + abstract Builder setUsername(ValueProvider username); + + abstract Builder setPassword(ValueProvider password); + + abstract Builder setConfig(Config config); + + abstract Builder setHasDefaultConfig(ValueProvider useDefault); + + abstract DriverConfiguration build(); + } + } + + /** This is the class which handles the work behind the {@link #readAll} method. */ + @AutoValue + public abstract static class ReadAll + extends PTransform, PCollection> { + + abstract @Nullable SerializableFunction getDriverProviderFn(); + + abstract @Nullable SessionConfig getSessionConfig(); + + abstract @Nullable TransactionConfig getTransactionConfig(); + + abstract @Nullable ValueProvider getCypher(); + + abstract @Nullable ValueProvider getWriteTransaction(); + + abstract @Nullable RowMapper getRowMapper(); + + abstract @Nullable SerializableFunction> + getParametersFunction(); + + abstract @Nullable Coder getCoder(); + + abstract @Nullable ValueProvider getLogCypher(); + + abstract Builder toBuilder(); + + public ReadAll withDriverConfiguration(DriverConfiguration config) { + return toBuilder() + .setDriverProviderFn(new DriverProviderFromDriverConfiguration(config)) + .build(); + } + + public ReadAll withCypher(String cypher) { + checkArgument( + cypher != null, "Neo4jIO.readAll().withCypher(query) called with null cypher query"); + return withCypher(ValueProvider.StaticValueProvider.of(cypher)); + } + + public ReadAll withCypher(ValueProvider cypher) { + checkArgument(cypher != null, "Neo4jIO.readAll().withCypher(cypher) called with null cypher"); + return toBuilder().setCypher(cypher).build(); + } + + public ReadAll withSessionConfig(SessionConfig sessionConfig) { + checkArgument( + sessionConfig != null, + "Neo4jIO.readAll().withSessionConfig(sessionConfig) called with null sessionConfig"); + return toBuilder().setSessionConfig(sessionConfig).build(); + } + + public ReadAll withTransactionConfig(TransactionConfig transactionConfig) { + checkArgument( + transactionConfig != null, + "Neo4jIO.readAll().withTransactionConfig(transactionConfig) called with null transactionConfig"); + return toBuilder().setTransactionConfig(transactionConfig).build(); + } + + public ReadAll withRowMapper(RowMapper rowMapper) { + checkArgument( + rowMapper != null, + "Neo4jIO.readAll().withRowMapper(rowMapper) called with null rowMapper"); + return toBuilder().setRowMapper(rowMapper).build(); + } + + public ReadAll withParametersFunction( + SerializableFunction> parametersFunction) { + checkArgument( + parametersFunction != null, + "Neo4jIO.readAll().withParametersFunction(parametersFunction) called with null parametersFunction"); + return toBuilder().setParametersFunction(parametersFunction).build(); + } + + public ReadAll withCoder(Coder coder) { + checkArgument(coder != null, "Neo4jIO.readAll().withCoder(coder) called with null coder"); + return toBuilder().setCoder(coder).build(); + } + + public ReadAll withReadTransaction() { + return toBuilder() + .setWriteTransaction(ValueProvider.StaticValueProvider.of(Boolean.FALSE)) + .build(); + } + + public ReadAll withWriteTransaction() { + return toBuilder() + .setWriteTransaction(ValueProvider.StaticValueProvider.of(Boolean.TRUE)) + .build(); + } + + public ReadAll withCypherLogging() { + return toBuilder().setLogCypher(ValueProvider.StaticValueProvider.of(Boolean.TRUE)).build(); + } + + @Override + public PCollection expand(PCollection input) { + + final SerializableFunction driverProviderFn = getDriverProviderFn(); + final RowMapper rowMapper = getRowMapper(); + SerializableFunction> parametersFunction = + getParametersFunction(); + + final String cypher = getProvidedValue(getCypher()); + checkArgument(cypher != null, "please provide a cypher statement to execute"); + + SessionConfig sessionConfig = getSessionConfig(); + if (sessionConfig == null) { + // Create a default session configuration as recommended by Neo4j + // + sessionConfig = SessionConfig.defaultConfig(); + } + + TransactionConfig transactionConfig = getTransactionConfig(); + if (transactionConfig == null) { + transactionConfig = TransactionConfig.empty(); + } + + Boolean writeTransaction = getProvidedValue(getWriteTransaction()); + if (writeTransaction == null) { + writeTransaction = Boolean.FALSE; + } + + Boolean logCypher = getProvidedValue(getLogCypher()); + if (logCypher == null) { + logCypher = Boolean.FALSE; + } + + if (driverProviderFn == null) { + throw new RuntimeException("please provide a driver provider"); + } + if (rowMapper == null) { + throw new RuntimeException("please provide a row mapper"); + } + if (parametersFunction == null) { + parametersFunction = t -> Collections.emptyMap(); + } + + ReadFn readFn = + new ReadFn<>( + driverProviderFn, + sessionConfig, + transactionConfig, + cypher, + rowMapper, + parametersFunction, + writeTransaction, + logCypher); + + return getOutputPCollection(input, readFn, getCoder()); + } + + @Override + public void populateDisplayData(DisplayData.Builder builder) { + super.populateDisplayData(builder); + String cypher = getProvidedValue(getCypher()); + if (cypher == null) { + cypher = ""; + } + builder.add(DisplayData.item("cypher", cypher)); + SerializableFunction driverProviderFn = getDriverProviderFn(); + if (driverProviderFn != null) { + if (driverProviderFn instanceof HasDisplayData) { + ((HasDisplayData) driverProviderFn).populateDisplayData(builder); + } + } + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setDriverProviderFn( + SerializableFunction driverProviderFn); + + abstract Builder setCypher(ValueProvider cypher); + + abstract Builder setSessionConfig(SessionConfig sessionConfig); + + abstract Builder setTransactionConfig( + TransactionConfig transactionConfig); + + abstract Builder setWriteTransaction( + ValueProvider writeTransaction); + + abstract Builder setRowMapper(RowMapper rowMapper); + + abstract Builder setParametersFunction( + SerializableFunction> parametersFunction); + + abstract Builder setCoder(Coder coder); + + abstract Builder setLogCypher(ValueProvider logCypher); + + abstract ReadAll build(); + } + } + + /** A {@link DoFn} to execute a Cypher query to read from Neo4j. */ + private static class ReadWriteFn extends DoFn { + protected static class DriverSession { + public @Nullable Driver driver; + public @Nullable Session session; + public @NonNull AtomicBoolean closed; + + protected DriverSession(Driver driver, Session session) { + this.driver = driver; + this.session = session; + this.closed = new AtomicBoolean(false); + } + + private DriverSession() { + this.driver = null; + this.session = null; + this.closed = new AtomicBoolean(true); + } + + protected static @NonNull DriverSession emptyClosed() { + return new DriverSession(); + } + } + + protected final @NonNull SerializableFunction driverProviderFn; + protected final @NonNull SessionConfig sessionConfig; + protected final @NonNull TransactionConfig transactionConfig; + + protected transient @NonNull DriverSession driverSession; + + protected ReadWriteFn( + @NonNull SerializableFunction driverProviderFn, + @NonNull SessionConfig sessionConfig, + @NonNull TransactionConfig transactionConfig) { + this.driverProviderFn = driverProviderFn; + this.sessionConfig = sessionConfig; + this.transactionConfig = transactionConfig; + this.driverSession = DriverSession.emptyClosed(); + } + + /** + * Delay the creation of driver and session until we actually have data to do something with. + */ + @Setup + public void setup() {} + + protected @NonNull Driver createDriver() { + Driver driver = driverProviderFn.apply(null); + if (driver == null) { + throw new RuntimeException("null driver given by driver provider"); + } + return driver; + } + + protected @Initialized @NonNull DriverSession buildDriverSession() { + @NonNull Driver driver = createDriver(); + @NonNull Session session = driver.session(sessionConfig); + return new DriverSession(driver, session); + } + + @StartBundle + public void startBundle() { + if (driverSession == null || driverSession.closed.get()) { + driverSession = buildDriverSession(); + } + } + + @FinishBundle + public void finishBundle() { + cleanUpDriverSession(); + } + + @Override + protected void finalize() { + cleanUpDriverSession(); + } + + protected void cleanUpDriverSession() { + if (!driverSession.closed.get()) { + try { + if (driverSession.session != null) { + driverSession.session.close(); + } + if (driverSession.driver != null) { + driverSession.driver.close(); + } + } finally { + driverSession.closed.set(true); + } + } + } + + protected String getParametersString(Map parametersMap) { + StringBuilder parametersString = new StringBuilder(); + parametersMap + .keySet() + .forEach( + key -> { + if (parametersString.length() > 0) { + parametersString.append(','); + } + parametersString.append(key).append('='); + Object value = parametersMap.get(key); + if (value == null) { + parametersString.append(""); + } else { + parametersString.append(value); + } + }); + return parametersString.toString(); + } + } + + /** A {@link DoFn} to execute a Cypher query to read from Neo4j. */ + private static class ReadFn extends ReadWriteFn { + protected final @NonNull String cypher; + protected final @NonNull RowMapper rowMapper; + protected final @Nullable SerializableFunction> + parametersFunction; + + private final boolean writeTransaction; + private final boolean logCypher; + + private ReadFn( + @NonNull SerializableFunction driverProviderFn, + @NonNull SessionConfig sessionConfig, + @NonNull TransactionConfig transactionConfig, + @NonNull String cypher, + @NonNull RowMapper rowMapper, + @Nullable SerializableFunction> parametersFunction, + boolean writeTransaction, + boolean logCypher) { + super(driverProviderFn, sessionConfig, transactionConfig); + this.cypher = cypher; + this.rowMapper = rowMapper; + this.parametersFunction = parametersFunction; + this.writeTransaction = writeTransaction; + this.logCypher = logCypher; + } + + @ProcessElement + public void processElement(ProcessContext context) { + // Map the input data to the parameters map... + // + ParameterT parameters = context.element(); + final Map parametersMap; + if (parametersFunction != null) { + parametersMap = parametersFunction.apply(parameters); + } else { + parametersMap = Collections.emptyMap(); + } + executeReadCypherStatement(context, parametersMap); + } + + private void executeReadCypherStatement( + final ProcessContext processContext, Map parametersMap) { + // The actual "reading" work needs to happen in a transaction. + // We could actually read and write here depending on the type of transaction + // we picked. As long as the Cypher statement returns values it's fine. + // + TransactionWork transactionWork = + transaction -> { + Result result = transaction.run(cypher, parametersMap); + while (result.hasNext()) { + Record record = result.next(); + try { + OutputT outputT = rowMapper.mapRow(record); + processContext.output(outputT); + } catch (Exception e) { + throw new RuntimeException("error mapping Neo4j record to row", e); + } + } + + // We deliver no specific Neo4j transaction output beyond what goes to the context + // output + // + return null; + }; + + if (logCypher) { + String parametersString = getParametersString(parametersMap); + + String readWrite = writeTransaction ? "write" : "read"; + LOG.info( + "Starting a " + + readWrite + + " transaction for cypher: " + + cypher + + ", parameters: " + + parametersString); + } + + // There are 2 ways to do a transaction on Neo4j: read or write + // It's important that the right type is selected, especially in clustered configurations. + // + if (driverSession.session == null) { + throw new RuntimeException("neo4j session was not initialized correctly"); + } else { + if (writeTransaction) { + driverSession.session.writeTransaction(transactionWork, transactionConfig); + } else { + driverSession.session.readTransaction(transactionWork, transactionConfig); + } + } + } + } + + /** + * Wraps a {@link DriverConfiguration} to provide a {@link Driver}. + * + *

      At most a single {@link Driver} instance will be constructed during pipeline execution for + * each unique {@link DriverConfiguration} within the pipeline. + */ + public static class DriverProviderFromDriverConfiguration + implements SerializableFunction, HasDisplayData { + private final DriverConfiguration config; + + private DriverProviderFromDriverConfiguration(DriverConfiguration config) { + this.config = config; + } + + public static SerializableFunction of(DriverConfiguration config) { + return new DriverProviderFromDriverConfiguration(config); + } + + @Override + public Driver apply(Void input) { + return config.buildDriver(); + } + + @Override + public void populateDisplayData(DisplayData.Builder builder) { + config.populateDisplayData(builder); + } + } + + /** This is the class which handles the work behind the {@link #writeUnwind()} method. */ + @AutoValue + public abstract static class WriteUnwind + extends PTransform, PDone> { + + abstract @Nullable SerializableFunction getDriverProviderFn(); + + abstract @Nullable ValueProvider getSessionConfig(); + + abstract @Nullable ValueProvider getCypher(); + + abstract @Nullable ValueProvider getUnwindMapName(); + + abstract @Nullable ValueProvider getTransactionConfig(); + + abstract @Nullable SerializableFunction> + getParametersFunction(); + + abstract @Nullable ValueProvider getBatchSize(); + + abstract @Nullable ValueProvider getLogCypher(); + + abstract Builder toBuilder(); + + public WriteUnwind withDriverConfiguration(DriverConfiguration config) { + return toBuilder() + .setDriverProviderFn(new DriverProviderFromDriverConfiguration(config)) + .build(); + } + + public WriteUnwind withCypher(String cypher) { + checkArgument( + cypher != null, "Neo4jIO.writeUnwind().withCypher(query) called with null cypher query"); + return withCypher(ValueProvider.StaticValueProvider.of(cypher)); + } + + public WriteUnwind withCypher(ValueProvider cypher) { + checkArgument( + cypher != null, "Neo4jIO.writeUnwind().withCypher(cypher) called with null cypher"); + return toBuilder().setCypher(cypher).build(); + } + + public WriteUnwind withUnwindMapName(String mapName) { + checkArgument( + mapName != null, + "Neo4jIO.writeUnwind().withUnwindMapName(query) called with null mapName"); + return withUnwindMapName(ValueProvider.StaticValueProvider.of(mapName)); + } + + public WriteUnwind withUnwindMapName(ValueProvider mapName) { + checkArgument( + mapName != null, + "Neo4jIO.writeUnwind().withUnwindMapName(cypher) called with null mapName"); + return toBuilder().setUnwindMapName(mapName).build(); + } + + public WriteUnwind withTransactionConfig(TransactionConfig transactionConfig) { + checkArgument( + transactionConfig != null, + "Neo4jIO.writeUnwind().withTransactionConfig(config) called with null transactionConfig"); + return withTransactionConfig(ValueProvider.StaticValueProvider.of(transactionConfig)); + } + + public WriteUnwind withTransactionConfig( + ValueProvider transactionConfig) { + checkArgument( + transactionConfig != null, + "Neo4jIO.writeUnwind().withTransactionConfig(config) called with null transactionConfig"); + return toBuilder().setTransactionConfig(transactionConfig).build(); + } + + public WriteUnwind withSessionConfig(SessionConfig sessionConfig) { + checkArgument( + sessionConfig != null, + "Neo4jIO.writeUnwind().withSessionConfig(sessionConfig) called with null sessionConfig"); + return withSessionConfig(ValueProvider.StaticValueProvider.of(sessionConfig)); + } + + public WriteUnwind withSessionConfig(ValueProvider sessionConfig) { + checkArgument( + sessionConfig != null, + "Neo4jIO.writeUnwind().withSessionConfig(sessionConfig) called with null sessionConfig"); + return toBuilder().setSessionConfig(sessionConfig).build(); + } + + // Batch size + public WriteUnwind withBatchSize(long batchSize) { + checkArgument( + batchSize > 0, "Neo4jIO.writeUnwind().withFetchSize(query) called with batchSize<=0"); + return withBatchSize(ValueProvider.StaticValueProvider.of(batchSize)); + } + + public WriteUnwind withBatchSize(ValueProvider batchSize) { + checkArgument( + batchSize != null && batchSize.get() >= 0, + "Neo4jIO.readAll().withBatchSize(query) called with batchSize<=0"); + return toBuilder().setBatchSize(batchSize).build(); + } + + public WriteUnwind withParametersFunction( + SerializableFunction> parametersFunction) { + checkArgument( + parametersFunction != null, + "Neo4jIO.readAll().withParametersFunction(parametersFunction) called with null parametersFunction"); + return toBuilder().setParametersFunction(parametersFunction).build(); + } + + public WriteUnwind withCypherLogging() { + return toBuilder().setLogCypher(ValueProvider.StaticValueProvider.of(Boolean.TRUE)).build(); + } + + @Override + public PDone expand(PCollection input) { + + final SerializableFunction driverProviderFn = getDriverProviderFn(); + final SerializableFunction> parametersFunction = + getParametersFunction(); + SessionConfig sessionConfig = getProvidedValue(getSessionConfig()); + if (sessionConfig == null) { + sessionConfig = SessionConfig.defaultConfig(); + } + TransactionConfig transactionConfig = getProvidedValue(getTransactionConfig()); + if (transactionConfig == null) { + transactionConfig = TransactionConfig.empty(); + } + final String cypher = getProvidedValue(getCypher()); + checkArgument(cypher != null, "please provide an unwind cypher statement to execute"); + final String unwindMapName = getProvidedValue(getUnwindMapName()); + checkArgument(unwindMapName != null, "please provide an unwind map name"); + + Long batchSize = getProvidedValue(getBatchSize()); + if (batchSize == null || batchSize <= 0) { + batchSize = 5000L; + } + + Boolean logCypher = getProvidedValue(getLogCypher()); + if (logCypher == null) { + logCypher = Boolean.FALSE; + } + + if (driverProviderFn == null) { + throw new RuntimeException("please provide a driver provider"); + } + if (parametersFunction == null) { + throw new RuntimeException("please provide a parameters function"); + } + WriteUnwindFn writeFn = + new WriteUnwindFn<>( + driverProviderFn, + sessionConfig, + transactionConfig, + cypher, + parametersFunction, + batchSize, + logCypher, + unwindMapName); + + input.apply(ParDo.of(writeFn)); + + return PDone.in(input.getPipeline()); + } + + @Override + public void populateDisplayData(DisplayData.Builder builder) { + super.populateDisplayData(builder); + builder.add(DisplayData.item("cypher", getCypher())); + + final SerializableFunction driverProviderFn = getDriverProviderFn(); + if (driverProviderFn != null) { + if (driverProviderFn instanceof HasDisplayData) { + ((HasDisplayData) driverProviderFn).populateDisplayData(builder); + } + } + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setDriverProviderFn( + SerializableFunction driverProviderFn); + + abstract Builder setSessionConfig(ValueProvider sessionConfig); + + abstract Builder setTransactionConfig( + ValueProvider transactionConfig); + + abstract Builder setCypher(ValueProvider cypher); + + abstract Builder setUnwindMapName(ValueProvider unwindMapName); + + abstract Builder setParametersFunction( + SerializableFunction> parametersFunction); + + abstract Builder setBatchSize(ValueProvider batchSize); + + abstract Builder setLogCypher(ValueProvider logCypher); + + abstract WriteUnwind build(); + } + } + + /** A {@link DoFn} to execute a Cypher query to read from Neo4j. */ + private static class WriteUnwindFn extends ReadWriteFn { + + private final @NonNull String cypher; + private final @Nullable SerializableFunction> + parametersFunction; + private final boolean logCypher; + private final long batchSize; + private final @NonNull String unwindMapName; + + private long elementsInput; + private boolean loggingDone; + private List> unwindList; + + private WriteUnwindFn( + @NonNull SerializableFunction driverProviderFn, + @NonNull SessionConfig sessionConfig, + @NonNull TransactionConfig transactionConfig, + @NonNull String cypher, + @Nullable SerializableFunction> parametersFunction, + long batchSize, + boolean logCypher, + String unwindMapName) { + super(driverProviderFn, sessionConfig, transactionConfig); + this.cypher = cypher; + this.parametersFunction = parametersFunction; + this.logCypher = logCypher; + this.batchSize = batchSize; + this.unwindMapName = unwindMapName; + + unwindList = new ArrayList<>(); + + elementsInput = 0; + loggingDone = false; + } + + @ProcessElement + public void processElement(ProcessContext context) { + // Map the input data to the parameters map... + // + ParameterT parameters = context.element(); + if (parametersFunction != null) { + // Every input element creates a new Map entry in unwindList + // + unwindList.add(parametersFunction.apply(parameters)); + } else { + // Someone is writing a bunch of static or procedurally generated values to Neo4j + unwindList.add(Collections.emptyMap()); + } + elementsInput++; + + if (elementsInput >= batchSize) { + // Execute the cypher query with the collected parameters map + // + executeCypherUnwindStatement(); + } + } + + private void executeCypherUnwindStatement() { + // In case of errors and no actual input read (error in mapper) we don't have input + // So we don't want to execute any cypher in this case. There's no need to generate even more + // errors + // + if (elementsInput == 0) { + return; + } + + // Add the accumulated list to the overall parameters map + // It contains a single parameter to unwind + // + final Map parametersMap = new HashMap<>(); + parametersMap.put(unwindMapName, unwindList); + + // Every "write" transaction writes a batch of elements to Neo4j. + // The changes to the database are automatically committed. + // + TransactionWork transactionWork = + transaction -> { + Result result = transaction.run(cypher, parametersMap); + while (result.hasNext()) { + // This just consumes any output but the function basically has no output + // To be revisited based on requirements. + // + result.next(); + } + return null; + }; + + if (logCypher && !loggingDone) { + String parametersString = getParametersString(parametersMap); + LOG.info( + "Starting a write transaction for unwind statement cypher: " + + cypher + + ", parameters: " + + parametersString); + loggingDone = true; + } + + if (driverSession.session == null) { + throw new RuntimeException("neo4j session was not initialized correctly"); + } else { + try { + driverSession.session.writeTransaction(transactionWork, transactionConfig); + } catch (Exception e) { + throw new RuntimeException( + "Error writing " + unwindList.size() + " rows to Neo4j with Cypher: " + cypher, e); + } + } + + // Now we need to reset the number of elements read and the parameters map + // + unwindList.clear(); + elementsInput = 0; + } + + @FinishBundle + @Override + public void finishBundle() { + executeCypherUnwindStatement(); + } + } +} diff --git a/sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/package-info.java b/sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/package-info.java new file mode 100644 index 0000000000000..5c8656dea28ed --- /dev/null +++ b/sdks/java/io/neo4j/src/main/java/org/apache/beam/sdk/io/neo4j/package-info.java @@ -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. + */ + +/** Transforms for reading from and writing to from Neo4j. */ +@Experimental(Kind.SOURCE_SINK) +package org.apache.beam.sdk.io.neo4j; + +import org.apache.beam.sdk.annotations.Experimental; +import org.apache.beam.sdk.annotations.Experimental.Kind; diff --git a/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOIT.java b/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOIT.java new file mode 100644 index 0000000000000..fc8d712b6ccae --- /dev/null +++ b/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOIT.java @@ -0,0 +1,275 @@ +/* + * 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.neo4j; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.beam.sdk.PipelineResult; +import org.apache.beam.sdk.coders.SerializableCoder; +import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.testing.PAssert; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.transforms.SerializableFunction; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.Row; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableMap; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Record; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.utility.DockerImageName; + +@RunWith(JUnit4.class) +public class Neo4jIOIT { + + private static Neo4jContainer neo4jContainer; + private static String containerHostname; + private static int containerPort; + + @Rule public transient TestPipeline parameterizedReadPipeline = TestPipeline.create(); + @Rule public transient TestPipeline writeUnwindPipeline = TestPipeline.create(); + @Rule public transient TestPipeline largeWriteUnwindPipeline = TestPipeline.create(); + + @BeforeClass + public static void setup() throws Exception { + neo4jContainer = + new Neo4jContainer<>(DockerImageName.parse("neo4j").withTag(Neo4jTestUtil.NEO4J_VERSION)) + .withStartupAttempts(1) + .withAdminPassword(Neo4jTestUtil.NEO4J_PASSWORD) + .withEnv("NEO4J_dbms_default_listen_address", "0.0.0.0") + .withNetworkAliases(Neo4jTestUtil.NEO4J_NETWORK_ALIAS) + .withSharedMemorySize(256 * 1024 * 1024L); // 256MB + + // Start Neo4j + neo4jContainer.start(); + + // Start with an empty database to use for testing. + // This prevents any possibility of some old data messing up the test results. + // We add a unique constraint to see we're not trying to create nodes twice in the larger test + // below + // + containerHostname = neo4jContainer.getContainerIpAddress(); + containerPort = neo4jContainer.getMappedPort(7687); + + Neo4jTestUtil.executeOnNeo4j( + containerHostname, + containerPort, + "CREATE CONSTRAINT something_id_unique ON (n:Something) ASSERT n.id IS UNIQUE", + true); + } + + @AfterClass + public static void tearDown() { + neo4jContainer.stop(); + neo4jContainer.close(); + } + + private static class ParameterizedReadRowToLineFn extends DoFn { + @DoFn.ProcessElement + public void processElement(ProcessContext context) { + Row row = context.element(); + assert row != null; + int one = row.getInt32(0); + String string = row.getString(1); + context.output(one + "," + string); + } + } + + @Test + public void testParameterizedRead() throws Exception { + PCollection stringsCollections = + parameterizedReadPipeline.apply(Create.of(Arrays.asList("one", "two", "three"))); + + final Schema outputSchema = + Schema.of( + Schema.Field.of("One", Schema.FieldType.INT32), + Schema.Field.of("Str", Schema.FieldType.STRING)); + + SerializableFunction> parametersFunction = + string -> Collections.singletonMap("par1", string); + + Neo4jIO.RowMapper rowMapper = + record -> { + int one = record.get(0).asInt(); + String string = record.get(1).asString(); + return Row.withSchema(outputSchema).attachValues(one, string); + }; + + Neo4jIO.ReadAll read = + Neo4jIO.readAll() + .withCypher("RETURN 1, $par1") + .withDriverConfiguration( + Neo4jTestUtil.getDriverConfiguration(containerHostname, containerPort)) + .withSessionConfig(SessionConfig.forDatabase(Neo4jTestUtil.NEO4J_DATABASE)) + .withRowMapper(rowMapper) + .withParametersFunction(parametersFunction) + .withCoder(SerializableCoder.of(Row.class)) + .withCypherLogging(); + + PCollection outputRows = stringsCollections.apply(read); + + PCollection outputLines = + outputRows.apply(ParDo.of(new ParameterizedReadRowToLineFn())); + + PAssert.that(outputLines).containsInAnyOrder("1,one", "1,two", "1,three"); + + // Now run this pipeline + // + PipelineResult pipelineResult = parameterizedReadPipeline.run(); + + Assert.assertEquals(PipelineResult.State.DONE, pipelineResult.getState()); + } + + @Test + public void testWriteUnwind() throws Exception { + PCollection stringsCollections = + writeUnwindPipeline.apply(Create.of(Arrays.asList("one", "two", "three"))); + + // Every row is represented by a Map in the parameters map. + // We accumulate the rows and 'unwind' those to Neo4j for performance reasons. + // + SerializableFunction> parametersMapper = + name -> Collections.singletonMap("name", name); + + Neo4jIO.WriteUnwind read = + Neo4jIO.writeUnwind() + .withDriverConfiguration( + Neo4jTestUtil.getDriverConfiguration(containerHostname, containerPort)) + .withSessionConfig(SessionConfig.forDatabase(Neo4jTestUtil.NEO4J_DATABASE)) + .withBatchSize(5000) + .withUnwindMapName("rows") + .withCypher("UNWIND $rows AS row MERGE(n:Num { name : row.name })") + .withParametersFunction(parametersMapper) + .withCypherLogging(); + + stringsCollections.apply(read); + + // Now run this pipeline + // + PipelineResult pipelineResult = writeUnwindPipeline.run(); + + Assert.assertEquals(PipelineResult.State.DONE, pipelineResult.getState()); + + // Connect back to the Instance and verify that we have 3 nodes + // + try (Driver driver = Neo4jTestUtil.getDriver(containerHostname, containerPort)) { + try (Session session = Neo4jTestUtil.getSession(driver, true)) { + List names = + session.readTransaction( + tx -> { + List list = new ArrayList<>(); + Result result = tx.run("MATCH(n:Num) RETURN n.name"); + while (result.hasNext()) { + Record record = result.next(); + list.add(record.get(0).asString()); + } + return list; + }); + + assertThat(names, containsInAnyOrder("one", "two", "three")); + } + } + } + + @Test + public void testLargeWriteUnwind() throws Exception { + final int startId = 5000; + final int endId = 6000; + // Create 1000 IDs + List idList = new ArrayList<>(); + for (int id = startId; id < endId; id++) { + idList.add(id); + } + PCollection idCollection = largeWriteUnwindPipeline.apply(Create.of(idList)); + + // Every row is represented by a Map in the parameters map. + // We accumulate the rows and 'unwind' those to Neo4j for performance reasons. + // + SerializableFunction> parametersFunction = + id -> ImmutableMap.of("id", id, "name", "Casters", "firstName", "Matt"); + + // 1000 rows with a batch size of 123 should trigger most scenarios we can think of + // We've put a unique constraint on Something.id + // + Neo4jIO.WriteUnwind read = + Neo4jIO.writeUnwind() + .withDriverConfiguration( + Neo4jTestUtil.getDriverConfiguration(containerHostname, containerPort)) + .withSessionConfig(SessionConfig.forDatabase(Neo4jTestUtil.NEO4J_DATABASE)) + .withBatchSize(123) + .withUnwindMapName("rows") + .withCypher("UNWIND $rows AS row CREATE(n:Something { id : row.id })") + .withParametersFunction(parametersFunction) + .withCypherLogging(); + + idCollection.apply(read); + + // Now run this pipeline + // + PipelineResult pipelineResult = largeWriteUnwindPipeline.run(); + + Assert.assertEquals(PipelineResult.State.DONE, pipelineResult.getState()); + + // Connect back to the Instance and verify that we have 1000 Something nodes + // + try (Driver driver = Neo4jTestUtil.getDriver(containerHostname, containerPort)) { + try (Session session = Neo4jTestUtil.getSession(driver, true)) { + List values = + session.readTransaction( + tx -> { + List v = null; + int nrRows = 0; + Result result = + tx.run("MATCH(n:Something) RETURN count(n), min(n.id), max(n.id)"); + while (result.hasNext()) { + Record record = result.next(); + v = + Arrays.asList( + record.get(0).asInt(), + record.get(1).asInt(), + record.get(2).asInt(), + ++nrRows); + } + return v; + }); + + Assert.assertNotNull(values); + assertThat(values, contains(endId - startId, startId, endId - 1, 1)); + } + } + } +} diff --git a/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOTest.java b/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOTest.java new file mode 100644 index 0000000000000..3ed76a347c4df --- /dev/null +++ b/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jIOTest.java @@ -0,0 +1,117 @@ +/* + * 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.neo4j; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.neo4j.driver.Config; + +@RunWith(JUnit4.class) +public class Neo4jIOTest { + + @Test + public void testDriverConfigurationCreate() throws Exception { + Neo4jIO.DriverConfiguration driverConfiguration = + Neo4jIO.DriverConfiguration.create("someUrl", "username", "password"); + Assert.assertEquals("someUrl", driverConfiguration.getUrl().get()); + Assert.assertEquals("username", driverConfiguration.getUsername().get()); + Assert.assertEquals("password", driverConfiguration.getPassword().get()); + } + + @Test + public void testDriverConfigurationWith() throws Exception { + Neo4jIO.DriverConfiguration driverConfiguration = Neo4jIO.DriverConfiguration.create(); + + Config config = + Config.builder() + .withEncryption() + .withConnectionAcquisitionTimeout(54321L, TimeUnit.MILLISECONDS) + .withConnectionTimeout(43210L, TimeUnit.MILLISECONDS) + .withConnectionLivenessCheckTimeout(32109L, TimeUnit.MILLISECONDS) + .withMaxConnectionLifetime(21098L, TimeUnit.MILLISECONDS) + .withMaxConnectionPoolSize(101) + .build(); + + driverConfiguration = driverConfiguration.withConfig(config); + + Config configVerify = driverConfiguration.getConfig(); + Assert.assertNotNull(configVerify); + Assert.assertEquals(true, configVerify.encrypted()); + + Assert.assertEquals(54321L, configVerify.connectionAcquisitionTimeoutMillis()); + Assert.assertEquals(43210L, configVerify.connectionTimeoutMillis()); + Assert.assertEquals(32109L, configVerify.idleTimeBeforeConnectionTest()); + Assert.assertEquals(21098L, configVerify.maxConnectionLifetimeMillis()); + Assert.assertEquals(101, configVerify.maxConnectionPoolSize()); + + driverConfiguration = driverConfiguration.withUrl("url1"); + Assert.assertEquals("url1", driverConfiguration.getUrl().get()); + + // URL and URLs can be set independently but are both used + driverConfiguration = driverConfiguration.withUrls(Arrays.asList("url2", "url3", "url4")); + Assert.assertEquals(3, driverConfiguration.getUrls().get().size()); + + driverConfiguration = driverConfiguration.withUsername("username"); + Assert.assertEquals("username", driverConfiguration.getUsername().get()); + + driverConfiguration = driverConfiguration.withPassword("password"); + Assert.assertEquals("password", driverConfiguration.getPassword().get()); + } + + @Test + public void testDriverConfigurationErrors() throws Exception { + Neo4jIO.DriverConfiguration driverConfiguration = Neo4jIO.DriverConfiguration.create(); + + try { + driverConfiguration.withUrl((String) null); + Assert.fail("Null URL is not reported"); + } catch (Exception e) { + // OK, error was reported + } + + try { + driverConfiguration.withUsername((String) null); + Assert.fail("Null user is not reported"); + } catch (Exception e) { + // OK, error was reported + } + + try { + driverConfiguration.withUsername(""); + } catch (Exception e) { + throw new AssertionError("Empty user string should not throw an error", e); + } + + try { + driverConfiguration.withPassword((String) null); + Assert.fail("Null password is not reported"); + } catch (Exception e) { + // OK, error was reported + } + + try { + driverConfiguration.withPassword(""); + } catch (Exception e) { + throw new AssertionError("Empty password string should not throw an error", e); + } + } +} diff --git a/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jTestUtil.java b/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jTestUtil.java new file mode 100644 index 0000000000000..adfd0664a2e9c --- /dev/null +++ b/sdks/java/io/neo4j/src/test/java/org/apache/beam/sdk/io/neo4j/Neo4jTestUtil.java @@ -0,0 +1,70 @@ +/* + * 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.neo4j; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; + +public class Neo4jTestUtil { + + public static final String NEO4J_VERSION = "latest"; + public static final String NEO4J_NETWORK_ALIAS = "neo4jcontainer"; + public static final String NEO4J_USERNAME = "neo4j"; + public static final String NEO4J_PASSWORD = "abcd"; + public static final String NEO4J_DATABASE = "neo4j"; + + public static final String getUrl(String hostname, int port) { + return "neo4j://" + hostname + ":" + port; + } + + public static Driver getDriver(String hostname, int port) throws URISyntaxException { + return GraphDatabase.routingDriver( + Arrays.asList(new URI(getUrl(hostname, port))), + AuthTokens.basic(NEO4J_USERNAME, NEO4J_PASSWORD), + Config.builder().build()); + } + + public static Session getSession(Driver driver, boolean withDatabase) { + SessionConfig.Builder builder = SessionConfig.builder(); + if (withDatabase) { + builder = builder.withDatabase(NEO4J_DATABASE); + } + return driver.session(builder.build()); + } + + public static Neo4jIO.DriverConfiguration getDriverConfiguration(String hostname, int port) { + return Neo4jIO.DriverConfiguration.create( + getUrl(hostname, port), NEO4J_USERNAME, NEO4J_PASSWORD); + } + + public static void executeOnNeo4j(String hostname, int port, String cypher, boolean useDatabase) + throws Exception { + try (Driver driver = Neo4jTestUtil.getDriver(hostname, port)) { + try (Session session = Neo4jTestUtil.getSession(driver, useDatabase)) { + session.run(cypher); + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 39ebb59bd685c..6796ad0b09d70 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -180,6 +180,7 @@ include(":sdks:java:io:kinesis:expansion-service") include(":sdks:java:io:kudu") include(":sdks:java:io:mongodb") include(":sdks:java:io:mqtt") +include(":sdks:java:io:neo4j") include(":sdks:java:io:parquet") include(":sdks:java:io:rabbitmq") include(":sdks:java:io:redis") From 5a622286db56535592e99b380308443bfeebf6c2 Mon Sep 17 00:00:00 2001 From: Yichi Zhang Date: Thu, 24 Feb 2022 10:47:40 -0800 Subject: [PATCH 28/61] [BEAM-13767] Migrate serveral portable runner tasks to use configuration avoidance API. (#16837) --- .../beam/gradle/BeamModulePlugin.groovy | 21 +++++++++++-------- runners/flink/flink_runner.gradle | 2 +- .../flink/job-server/flink_job_server.gradle | 5 +++-- runners/samza/build.gradle | 2 +- runners/samza/job-server/build.gradle | 2 +- .../spark/job-server/spark_job_server.gradle | 2 +- runners/spark/spark_runner.gradle | 16 +++++++------- runners/twister2/build.gradle | 4 ++-- .../python/test-suites/portable/common.gradle | 2 +- 9 files changed, 31 insertions(+), 25 deletions(-) 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 6c6b140c6cf56..62ac80008cdc9 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -1333,7 +1333,8 @@ class BeamModulePlugin implements Plugin { } if (configuration.validateShadowJar) { - project.task('validateShadedJarDoesntLeakNonProjectClasses', dependsOn: 'shadowJar') { + def validateShadedJarDoesntLeakNonProjectClasses = project.tasks.register('validateShadedJarDoesntLeakNonProjectClasses') { + dependsOn 'shadowJar' ext.outFile = project.file("${project.reportsDir}/${name}.out") inputs.files(project.configurations.shadow.artifacts.files) .withPropertyName("shadowArtifactsFiles") @@ -1357,10 +1358,10 @@ class BeamModulePlugin implements Plugin { } } } - project.tasks.check.dependsOn project.tasks.validateShadedJarDoesntLeakNonProjectClasses + project.tasks.check.dependsOn validateShadedJarDoesntLeakNonProjectClasses } } else { - project.task("testJar", type: Jar, { + project.tasks.register("testJar", Jar) { group = "Jar" description = "Create a JAR of test classes" classifier = "tests" @@ -1370,7 +1371,7 @@ class BeamModulePlugin implements Plugin { exclude "META-INF/*.SF" exclude "META-INF/*.DSA" exclude "META-INF/*.RSA" - }) + } project.artifacts.testRuntimeMigration project.testJar } @@ -1392,7 +1393,8 @@ class BeamModulePlugin implements Plugin { } } - project.task("jmh", type: JavaExec, dependsOn: project.classes, { + project.tasks.register("jmh", JavaExec) { + dependsOn project.classes mainClass = "org.openjdk.jmh.Main" classpath = project.sourceSets.main.runtimeClasspath // For a list of arguments, see @@ -1424,12 +1426,13 @@ class BeamModulePlugin implements Plugin { args 'org.apache.beam' } args '-foe=true' - }) + } // Single shot of JMH benchmarks ensures that they can execute. // // Note that these tests will fail on JVMs that JMH doesn't support. - project.task("jmhTest", type: JavaExec, dependsOn: project.classes, { + def jmhTest = project.tasks.register("jmhTest", JavaExec) { + dependsOn project.classes mainClass = "org.openjdk.jmh.Main" classpath = project.sourceSets.main.runtimeClasspath @@ -1442,8 +1445,8 @@ class BeamModulePlugin implements Plugin { args '-f=0' args '-wf=0' args '-foe=true' - }) - project.check.dependsOn("jmhTest") + } + project.check.dependsOn jmhTest } project.ext.includeInJavaBom = configuration.publish diff --git a/runners/flink/flink_runner.gradle b/runners/flink/flink_runner.gradle index 2b6392f46b9bc..bc0cc188f0632 100644 --- a/runners/flink/flink_runner.gradle +++ b/runners/flink/flink_runner.gradle @@ -290,7 +290,7 @@ tasks.register('validatesRunner') { // Generates :runners:flink:1.13:runQuickstartJavaFlinkLocal createJavaExamplesArchetypeValidationTask(type: 'Quickstart', runner: 'FlinkLocal') -task examplesIntegrationTest(type: Test) { +tasks.register("examplesIntegrationTest", Test) { group = "Verification" // Disable gradle cache outputs.upToDateWhen { false } diff --git a/runners/flink/job-server/flink_job_server.gradle b/runners/flink/job-server/flink_job_server.gradle index 981a2cd870b16..2b3bbdfceb337 100644 --- a/runners/flink/job-server/flink_job_server.gradle +++ b/runners/flink/job-server/flink_job_server.gradle @@ -212,7 +212,7 @@ project.ext.validatesPortableRunnerBatch = portableValidatesRunnerTask("Batch", project.ext.validatesPortableRunnerStreaming = portableValidatesRunnerTask("Streaming", true, false, false) project.ext.validatesPortableRunnerStreamingCheckpoint = portableValidatesRunnerTask("StreamingCheckpointing", true, true, false) -task validatesPortableRunner() { +tasks.register("validatesPortableRunner") { dependsOn validatesPortableRunnerDocker dependsOn validatesPortableRunnerBatch dependsOn validatesPortableRunnerStreaming @@ -265,7 +265,8 @@ createCrossLanguageValidatesRunnerTask( ) // miniCluster jar starts an embedded Flink cluster intended for use in testing. -task miniCluster(type: Jar, dependsOn: shadowJar) { +tasks.register("miniCluster", Jar) { + dependsOn shadowJar archiveBaseName = "${project.archivesBaseName}-mini-cluster" dependencies { runtimeOnly project(path: flinkRunnerProject, configuration: "miniCluster") diff --git a/runners/samza/build.gradle b/runners/samza/build.gradle index acacf9f8cd607..68ea1780bd465 100644 --- a/runners/samza/build.gradle +++ b/runners/samza/build.gradle @@ -86,7 +86,7 @@ configurations.all { exclude group: "org.slf4j", module: "slf4j-jdk14" } -task validatesRunner(type: Test) { +tasks.register("validatesRunner", Test) { group = "Verification" description "Validates Samza runner" systemProperty "beamTestPipelineOptions", JsonOutput.toJson([ diff --git a/runners/samza/job-server/build.gradle b/runners/samza/job-server/build.gradle index 8302f2d0b7173..a8adca036a674 100644 --- a/runners/samza/job-server/build.gradle +++ b/runners/samza/job-server/build.gradle @@ -185,7 +185,7 @@ def portableValidatesRunnerTask(String name, boolean docker) { project.ext.validatesPortableRunnerDocker = portableValidatesRunnerTask("Docker", true) project.ext.validatesPortableRunnerEmbedded = portableValidatesRunnerTask("Embedded", false) -task validatesPortableRunner() { +tasks.register("validatesPortableRunner") { dependsOn validatesPortableRunnerDocker dependsOn validatesPortableRunnerEmbedded } diff --git a/runners/spark/job-server/spark_job_server.gradle b/runners/spark/job-server/spark_job_server.gradle index a483284f5074b..18ab88f7b0d07 100644 --- a/runners/spark/job-server/spark_job_server.gradle +++ b/runners/spark/job-server/spark_job_server.gradle @@ -213,7 +213,7 @@ project.ext.validatesPortableRunnerDocker= portableValidatesRunnerTask("Docker", project.ext.validatesPortableRunnerBatch = portableValidatesRunnerTask("Batch", false, false) project.ext.validatesPortableRunnerStreaming = portableValidatesRunnerTask("Streaming", true, false) -task validatesPortableRunner() { +tasks.register("validatesPortableRunner") { dependsOn validatesPortableRunnerDocker dependsOn validatesPortableRunnerBatch dependsOn validatesPortableRunnerStreaming diff --git a/runners/spark/spark_runner.gradle b/runners/spark/spark_runner.gradle index 08124142560ad..7d08b582b0780 100644 --- a/runners/spark/spark_runner.gradle +++ b/runners/spark/spark_runner.gradle @@ -237,7 +237,7 @@ hadoopVersions.each {kv -> } } -task validatesRunnerBatch(type: Test) { +def validatesRunnerBatch = tasks.register("validatesRunnerBatch", Test) { group = "Verification" // Disable gradle cache outputs.upToDateWhen { false } @@ -290,7 +290,7 @@ task validatesRunnerBatch(type: Test) { jvmArgs '-Xmx3g' } -task validatesRunnerStreaming(type: Test) { +def validatesRunnerStreaming = tasks.register("validatesRunnerStreaming", Test) { group = "Verification" // Disable gradle cache outputs.upToDateWhen { false } @@ -316,7 +316,7 @@ task validatesRunnerStreaming(type: Test) { } } -task validatesStructuredStreamingRunnerBatch(type: Test) { +tasks.register("validatesStructuredStreamingRunnerBatch", Test) { group = "Verification" // Disable gradle cache outputs.upToDateWhen { false } @@ -385,7 +385,7 @@ task validatesStructuredStreamingRunnerBatch(type: Test) { } } -task validatesRunner { +tasks.register("validatesRunner") { group = "Verification" description "Validates Spark runner" dependsOn validatesRunnerBatch @@ -398,14 +398,15 @@ task validatesRunner { // Generates :runners:spark:*:runQuickstartJavaSpark task createJavaExamplesArchetypeValidationTask(type: 'Quickstart', runner: 'Spark') -task hadoopVersionsTest(group: "Verification") { +tasks.register("hadoopVersionsTest") { + group = "Verification" def taskNames = hadoopVersions.keySet().stream() .map{num -> "hadoopVersion${num}Test"} .collect(Collectors.toList()) dependsOn taskNames } -task examplesIntegrationTest(type: Test) { +tasks.register("examplesIntegrationTest", Test) { group = "Verification" // Disable gradle cache outputs.upToDateWhen { false } @@ -434,7 +435,8 @@ task examplesIntegrationTest(type: Test) { } hadoopVersions.each {kv -> - task "hadoopVersion${kv.key}Test"(type: Test, group: "Verification") { + tasks.register("hadoopVersion${kv.key}Test", Test) { + group = "Verification" description = "Runs Spark tests with Hadoop version $kv.value" classpath = configurations."hadoopVersion$kv.key" + sourceSets.test.runtimeClasspath systemProperty "beam.spark.test.reuseSparkContext", "true" diff --git a/runners/twister2/build.gradle b/runners/twister2/build.gradle index 36671b318d0c7..a363bacb4a718 100644 --- a/runners/twister2/build.gradle +++ b/runners/twister2/build.gradle @@ -68,7 +68,7 @@ dependencies { } } -task validatesRunnerBatch(type: Test) { +def validatesRunnerBatch = tasks.register("validatesRunnerBatch", Test) { group = "Verification" def pipelineOptions = JsonOutput.toJson([ "--runner=Twister2TestRunner", @@ -101,7 +101,7 @@ task validatesRunnerBatch(type: Test) { maxHeapSize = '6g' } -task validatesRunner { +tasks.register("validatesRunner") { group = "Verification" description "Validates Twister2 Runner" dependsOn validatesRunnerBatch diff --git a/sdks/python/test-suites/portable/common.gradle b/sdks/python/test-suites/portable/common.gradle index 037022a12dd78..ae296b157cea1 100644 --- a/sdks/python/test-suites/portable/common.gradle +++ b/sdks/python/test-suites/portable/common.gradle @@ -384,7 +384,7 @@ addTestJavaJarCreator("FlinkRunner", tasks.getByPath(":runners:flink:${latestFli addTestJavaJarCreator("SparkRunner", tasks.getByPath(":runners:spark:2:job-server:shadowJar")) def addTestFlinkUberJar(boolean saveMainSession) { - project.tasks.create(name: "testUberJarFlinkRunner${saveMainSession ? 'SaveMainSession' : ''}") { + project.tasks.register("testUberJarFlinkRunner${saveMainSession ? 'SaveMainSession' : ''}") { dependsOn ":runners:flink:${latestFlinkVersion}:job-server:shadowJar" dependsOn ":runners:flink:${latestFlinkVersion}:job-server:miniCluster" dependsOn pythonContainerTask From cc16171bfa5502b56931798d62e522014fa03e94 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 24 Feb 2022 14:20:33 -0500 Subject: [PATCH 29/61] [BEAM-13996] Removing 'No cluster_manager is associated with the provided pipeline!' logger error --- .../runners/interactive/interactive_beam.py | 4 ---- .../runners/interactive/interactive_beam_test.py | 13 ------------- 2 files changed, 17 deletions(-) diff --git a/sdks/python/apache_beam/runners/interactive/interactive_beam.py b/sdks/python/apache_beam/runners/interactive/interactive_beam.py index 0ae015dbf0238..5eab4893985ab 100644 --- a/sdks/python/apache_beam/runners/interactive/interactive_beam.py +++ b/sdks/python/apache_beam/runners/interactive/interactive_beam.py @@ -420,10 +420,6 @@ def cleanup( self.master_urls.pop(master_url, None) self.master_urls_to_pipelines.pop(master_url, None) self.dataproc_cluster_managers.pop(str(id(pipeline)), None) - else: - _LOGGER.error( - 'No cluster_manager is associated with the provided ' - 'pipeline!') else: cluster_manager_identifiers = set() for cluster_manager in self.dataproc_cluster_managers.values(): diff --git a/sdks/python/apache_beam/runners/interactive/interactive_beam_test.py b/sdks/python/apache_beam/runners/interactive/interactive_beam_test.py index b438980fab1a4..b14848c0afc62 100644 --- a/sdks/python/apache_beam/runners/interactive/interactive_beam_test.py +++ b/sdks/python/apache_beam/runners/interactive/interactive_beam_test.py @@ -311,19 +311,6 @@ def test_clusters_describe(self): self.assertEqual('test-project', clusters.describe()[None] \ ['cluster_metadata'].project_id) - def test_clusters_cleanup_cluster_manager_not_found(self): - clusters = ib.Clusters() - p = beam.Pipeline( - options=PipelineOptions( - project='test-project', - region='test-region', - )) - from apache_beam.runners.interactive.interactive_beam import _LOGGER - with self.assertLogs(_LOGGER, level='ERROR') as context_manager: - clusters.cleanup(p) - self.assertTrue( - 'No cluster_manager is associated' in context_manager.output[0]) - @patch( 'apache_beam.runners.interactive.dataproc.dataproc_cluster_manager.' 'DataprocClusterManager.get_master_url', From 90dd44e81b14bd5a35f808c8962cf22cd69497c8 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Thu, 24 Feb 2022 16:19:26 -0500 Subject: [PATCH 30/61] [BEAM-13906] Improve coverage of errors package (#16934) --- .../pkg/beam/internal/errors/errors_test.go | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/sdks/go/pkg/beam/internal/errors/errors_test.go b/sdks/go/pkg/beam/internal/errors/errors_test.go index 614609ff80b89..26e0057a74679 100644 --- a/sdks/go/pkg/beam/internal/errors/errors_test.go +++ b/sdks/go/pkg/beam/internal/errors/errors_test.go @@ -57,6 +57,9 @@ func TestWrap(t *testing.T) { }, { err: Wrap(Wrap(New(base), msg1), msg2), want: errorStructure{{ERROR, msg2}, {ERROR, msg1}, {ERROR, base}}, + }, { + err: Wrap(nil, msg1), + want: nil, }, } for _, test := range tests { @@ -76,6 +79,13 @@ func TestWrapf(t *testing.T) { } } +func TestWrapf_NilErr(t *testing.T) { + err := Wrapf(nil, "%s %d", "ten", 10) + if err != nil { + t.Errorf(`Wrapf(nil, "%%s %%d", "ten", 10). Want: nil, Got: %q`, err) + } +} + func TestContext(t *testing.T) { tests := []struct { err error @@ -90,6 +100,9 @@ func TestContext(t *testing.T) { }, { err: Wrap(WithContext(WithContext(Wrap(New(base), msg1), ctx1), ctx2), msg2), want: errorStructure{{ERROR, msg2}, {CONTEXT, ctx2}, {CONTEXT, ctx1}, {ERROR, msg1}, {ERROR, base}}, + }, { + err: WithContext(nil, ctx1), + want: nil, }, } for _, test := range tests { @@ -100,6 +113,13 @@ func TestContext(t *testing.T) { } } +func TestWithContextf_NilErr(t *testing.T) { + err := WithContextf(nil, "%s %d", "ten", 10) + if err != nil { + t.Errorf(`WithContextf(nil, "%%s %%d", "ten", 10). Want: nil, Got: %q`, err) + } +} + func TestWithContextf(t *testing.T) { want := fmt.Sprintf("%s %d", "ten", 10) err := WithContextf(New(base), "%s %d", "ten", 10) @@ -129,6 +149,9 @@ func TestTopLevelMsg(t *testing.T) { }, { err: Wrap(SetTopLevelMsg(WithContext(SetTopLevelMsg(New(base), top1), ctx1), top2), msg1), want: top2, + }, { + err: SetTopLevelMsg(nil, top1), + want: "", }, } for _, test := range tests { @@ -147,10 +170,51 @@ func TestSetTopLevelMsgf(t *testing.T) { } } +func TestSetTopLevelMsgf_NilErr(t *testing.T) { + want := "" + err := SetTopLevelMsgf(nil, "%s %d", "ten", 10) + if getTop(err) != want { + t.Errorf("Incorrect formatting. Want: %q, Got: %q", want, getTop(err)) + } +} + +func TestError(t *testing.T) { + tests := []struct { + err error + want string + }{ + { + err: Wrap(New(base), msg1), + want: "message 1\n\tcaused by:\nbase", + }, + { + err: SetTopLevelMsg(New(base), top1), + want: "top level message 1\nFull error:\nbase", + }, + { + err: SetTopLevelMsg(Wrap(Wrap(New(base), msg1), msg2), top1), + want: "top level message 1\nFull error:\nmessage 2\n\tcaused by:\nmessage 1\n\tcaused by:\nbase", + }, + } + + for _, test := range tests { + if be, ok := test.err.(*beamError); ok { + if got, want := be.Error(), test.want; got != want { + t.Errorf("Incorrect formatting. Want: %q, Got: %q", want, got) + } + } else { + t.Errorf("Error should be type *beamError, got: %q", test.err) + } + } +} + // getStructure extracts the structure of an error, outputting a slice that // represents the nested messages in that error in the order they are output // and with the type of message (context or error) described. func getStructure(e error) errorStructure { + if e == nil { + return nil + } var structure errorStructure for { @@ -175,6 +239,10 @@ func getStructure(e error) errorStructure { } func equalStructure(left errorStructure, right errorStructure) bool { + if left == nil || right == nil { + return left == nil && right == nil + } + if len(left) != len(right) { return false } From b4ee2b9d28b3c811b7f59f07ac3816ab3670d802 Mon Sep 17 00:00:00 2001 From: Ritesh Ghorse Date: Thu, 24 Feb 2022 16:48:00 -0500 Subject: [PATCH 31/61] [BEAM-13886] unit tests for trigger package (#16935) --- .../beam/core/graph/window/trigger/trigger.go | 3 + .../core/graph/window/trigger/trigger_test.go | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 sdks/go/pkg/beam/core/graph/window/trigger/trigger_test.go diff --git a/sdks/go/pkg/beam/core/graph/window/trigger/trigger.go b/sdks/go/pkg/beam/core/graph/window/trigger/trigger.go index c12c03aa5e893..148a672fc695b 100644 --- a/sdks/go/pkg/beam/core/graph/window/trigger/trigger.go +++ b/sdks/go/pkg/beam/core/graph/window/trigger/trigger.go @@ -66,6 +66,9 @@ func (t *AfterCountTrigger) ElementCount() int32 { // AfterCount constructs a trigger that fires after // at least `count` number of elements are processed. func AfterCount(count int32) *AfterCountTrigger { + if count < 1 { + panic(fmt.Errorf("trigger.AfterCount(%v) must be a positive integer", count)) + } return &AfterCountTrigger{elementCount: count} } diff --git a/sdks/go/pkg/beam/core/graph/window/trigger/trigger_test.go b/sdks/go/pkg/beam/core/graph/window/trigger/trigger_test.go new file mode 100644 index 0000000000000..a23de354bfd61 --- /dev/null +++ b/sdks/go/pkg/beam/core/graph/window/trigger/trigger_test.go @@ -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. +package trigger + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestAfterCountTrigger(t *testing.T) { + tr := AfterCount(1) + want := int32(1) + if got := tr.ElementCount(); got != want { + t.Errorf("element count not configured correctly. got %v, want %v", got, want) + } +} + +func TestAfterProcessingTimeTrigger(t *testing.T) { + tests := []struct { + tr *AfterProcessingTimeTrigger + tt []TimestampTransform + }{ + { + tr: AfterProcessingTime().PlusDelay(time.Millisecond), + tt: []TimestampTransform{DelayTransform{Delay: 1}}, + }, + { + tr: AfterProcessingTime().PlusDelay(time.Millisecond).AlignedTo(time.Millisecond, time.Time{}), + tt: []TimestampTransform{DelayTransform{Delay: 1}, AlignToTransform{Period: 1, Offset: 0}}, + }, + } + for _, test := range tests { + if diff := cmp.Diff(test.tr.TimestampTransforms(), test.tt); diff != "" { + t.Errorf("timestamp transforms are not equal: %v", diff) + } + } +} + +func TestRepeatTrigger(t *testing.T) { + subTr := AfterCount(2) + tr := Repeat(subTr) + + if got, ok := tr.SubTrigger().(*AfterCountTrigger); ok && got != subTr { + t.Errorf("subtrigger not configured correctly. got %v, want %v", got, subTr) + } +} + +func TestAfterEndOfWindowTrigger(t *testing.T) { + earlyTr := AfterCount(50) + lateTr := Always() + tr := AfterEndOfWindow().EarlyFiring(earlyTr).LateFiring(lateTr) + + if got, ok := tr.Early().(*AfterCountTrigger); ok && got != earlyTr { + t.Errorf("early firing trigger not configured correctly. got %v, want %v", got, earlyTr) + } + if got, ok := tr.Late().(*AlwaysTrigger); ok && got != lateTr { + t.Errorf("late firing trigger not configured correctly. got %v, want %v", got, lateTr) + } +} From f94a00f33aa4cd34a85ef38e8a9496c69e1dd90d Mon Sep 17 00:00:00 2001 From: emily Date: Thu, 24 Feb 2022 13:49:06 -0800 Subject: [PATCH 32/61] [BEAM-4767] Remove beam- prefix from release script tags (#16899) --- release/src/main/scripts/cut_release_branch.sh | 2 +- release/src/main/scripts/set_version.sh | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/release/src/main/scripts/cut_release_branch.sh b/release/src/main/scripts/cut_release_branch.sh index 1e633934bdce8..a7b10530cf4c8 100755 --- a/release/src/main/scripts/cut_release_branch.sh +++ b/release/src/main/scripts/cut_release_branch.sh @@ -125,7 +125,7 @@ echo "==================Current working branch=======================" echo ${RELEASE_BRANCH} echo "===============================================================" -sed -i -e "s/'beam-master-.*'/'beam-${RELEASE}'/g" runners/google-cloud-dataflow-java/build.gradle +sed -i -e "s/'beam-master-.*'/'${RELEASE}'/g" runners/google-cloud-dataflow-java/build.gradle echo "===============Update release branch as following==============" git diff diff --git a/release/src/main/scripts/set_version.sh b/release/src/main/scripts/set_version.sh index 67420d6dda96e..387dd03d69acd 100755 --- a/release/src/main/scripts/set_version.sh +++ b/release/src/main/scripts/set_version.sh @@ -76,9 +76,6 @@ if [[ -z "$IS_SNAPSHOT_VERSION" ]] ; then sed -i -e "s/^__version__ = .*/__version__ = '${TARGET_VERSION}'/" sdks/python/apache_beam/version.py sed -i -e "s/sdk_version=.*/sdk_version=$TARGET_VERSION/" gradle.properties sed -i -e "s/SdkVersion = .*/SdkVersion = \"$TARGET_VERSION\"/" sdks/go/pkg/beam/core/core.go - # TODO: [BEAM-4767] - sed -i -e "s/'dataflow.fnapi_container_version' : .*/'dataflow.fnapi_container_version' : '${TARGET_VERSION}',/" runners/google-cloud-dataflow-java/build.gradle - sed -i -e "s/'dataflow.legacy_container_version' : .*/'dataflow.legacy_container_version' : 'beam-${TARGET_VERSION}',/" runners/google-cloud-dataflow-java/build.gradle else # For snapshot version: # Java/gradle appends -SNAPSHOT From bf020ac1c620c0367244acc6161f228772559f70 Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Thu, 24 Feb 2022 17:01:00 -0500 Subject: [PATCH 33/61] [BEAM-13866] Add small unit tests to errorx, make boolean assignment more clear (#16943) --- sdks/go/pkg/beam/util/errorx/guarded.go | 2 +- sdks/go/pkg/beam/util/errorx/guarded_test.go | 45 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 sdks/go/pkg/beam/util/errorx/guarded_test.go diff --git a/sdks/go/pkg/beam/util/errorx/guarded.go b/sdks/go/pkg/beam/util/errorx/guarded.go index cc0b07b4eee1f..186885b717c62 100644 --- a/sdks/go/pkg/beam/util/errorx/guarded.go +++ b/sdks/go/pkg/beam/util/errorx/guarded.go @@ -39,7 +39,7 @@ func (g *GuardedError) TrySetError(err error) bool { g.mu.Lock() defer g.mu.Unlock() - upd := g.err == nil + upd := (g.err == nil) if upd { g.err = err } diff --git a/sdks/go/pkg/beam/util/errorx/guarded_test.go b/sdks/go/pkg/beam/util/errorx/guarded_test.go new file mode 100644 index 0000000000000..1e9c9b2247060 --- /dev/null +++ b/sdks/go/pkg/beam/util/errorx/guarded_test.go @@ -0,0 +1,45 @@ +// 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 errorx + +import ( + "errors" + "testing" +) + +func TestTrySetError(t *testing.T) { + var gu GuardedError + setErr := errors.New("attempted error") + success := gu.TrySetError(setErr) + if !success { + t.Fatal("got false when trying to set error, want true") + } + if got, want := gu.Error(), setErr; got != want { + t.Errorf("got error %v when checking message, want %v", got, want) + } +} + +func TestTrySetError_bad(t *testing.T) { + setErr := errors.New("old error") + gu := &GuardedError{err: setErr} + success := gu.TrySetError(setErr) + if success { + t.Fatal("got true when trying to set error, want false") + } + if got, want := gu.Error(), setErr; got != want { + t.Errorf("got error %v when checking message, want %v", got, want) + } +} From 8fcb103a7fc246f24e1e4574e59ce4d5dd184d1d Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Thu, 24 Feb 2022 20:31:14 -0500 Subject: [PATCH 34/61] [BEAM-13925] Add most of the supporting files for the pr management automation (#16898) --- .github/REVIEWERS.yml | 21 + scripts/ci/pr-bot/README.md | 45 + scripts/ci/pr-bot/package-lock.json | 2062 +++++++++++++++++ scripts/ci/pr-bot/package.json | 27 + scripts/ci/pr-bot/shared/checks.ts | 109 + scripts/ci/pr-bot/shared/commentStrings.ts | 72 + scripts/ci/pr-bot/shared/constants.ts | 30 + scripts/ci/pr-bot/shared/githubUtils.ts | 96 + scripts/ci/pr-bot/shared/persistentState.ts | 121 + scripts/ci/pr-bot/shared/pr.ts | 89 + scripts/ci/pr-bot/shared/reviewerConfig.ts | 97 + scripts/ci/pr-bot/shared/reviewersForLabel.ts | 102 + scripts/ci/pr-bot/shared/userCommand.ts | 231 ++ scripts/ci/pr-bot/test/prTest.ts | 45 + scripts/ci/pr-bot/test/reviewerConfigTest.ts | 238 ++ .../ci/pr-bot/test/reviewersForLabelTest.ts | 135 ++ scripts/ci/pr-bot/tsconfig.json | 12 + 17 files changed, 3532 insertions(+) create mode 100644 .github/REVIEWERS.yml create mode 100644 scripts/ci/pr-bot/README.md create mode 100644 scripts/ci/pr-bot/package-lock.json create mode 100644 scripts/ci/pr-bot/package.json create mode 100644 scripts/ci/pr-bot/shared/checks.ts create mode 100644 scripts/ci/pr-bot/shared/commentStrings.ts create mode 100644 scripts/ci/pr-bot/shared/constants.ts create mode 100644 scripts/ci/pr-bot/shared/githubUtils.ts create mode 100644 scripts/ci/pr-bot/shared/persistentState.ts create mode 100644 scripts/ci/pr-bot/shared/pr.ts create mode 100644 scripts/ci/pr-bot/shared/reviewerConfig.ts create mode 100644 scripts/ci/pr-bot/shared/reviewersForLabel.ts create mode 100644 scripts/ci/pr-bot/shared/userCommand.ts create mode 100644 scripts/ci/pr-bot/test/prTest.ts create mode 100644 scripts/ci/pr-bot/test/reviewerConfigTest.ts create mode 100644 scripts/ci/pr-bot/test/reviewersForLabelTest.ts create mode 100644 scripts/ci/pr-bot/tsconfig.json diff --git a/.github/REVIEWERS.yml b/.github/REVIEWERS.yml new file mode 100644 index 0000000000000..009489798a457 --- /dev/null +++ b/.github/REVIEWERS.yml @@ -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. + +labels: +- name: "Go" + reviewers: ["damccorm", "lostluck", "jrmccluskey", "youngoli", "riteshghorse"] + exclusionList: [] # These users will never be suggested as reviewers +# I don't know the other areas well enough to assess who the normal committers/contributors who might want to be reviewers are +fallbackReviewers: [] # List of committers to use when no label matches diff --git a/scripts/ci/pr-bot/README.md b/scripts/ci/pr-bot/README.md new file mode 100644 index 0000000000000..d6a55d45e9f13 --- /dev/null +++ b/scripts/ci/pr-bot/README.md @@ -0,0 +1,45 @@ + + +# PR Bot + +This directory holds all the code (except for Actions Workflows) for our PR bot designed to improve the PR experience. +For a list of commands to use when interacting with the bot, see [Commands.md](./Commands.md). +For a design doc explaining the design and implementation, see [Automate Reviewer Assignment](https://docs.google.com/document/d/1FhRPRD6VXkYlLAPhNfZB7y2Yese2FCWBzjx67d3TjBo/edit#) + +## Build/Test + +To build, run: + +``` +npm install +npm run build +``` + +To run the tests: + +``` +npm test +``` + +Before checking in code, run prettier on it: + +``` +npm run format +``` \ No newline at end of file diff --git a/scripts/ci/pr-bot/package-lock.json b/scripts/ci/pr-bot/package-lock.json new file mode 100644 index 0000000000000..cf881686c1405 --- /dev/null +++ b/scripts/ci/pr-bot/package-lock.json @@ -0,0 +1,2062 @@ +{ + "name": "pr-bot", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "pr-bot", + "version": "1.0.0", + "dependencies": { + "@actions/exec": "^1.1.0", + "@actions/github": "^5.0.0", + "@octokit/rest": "^18.12.0", + "js-yaml": "^4.1.0", + "prettier": "^2.5.1" + }, + "devDependencies": { + "@types/mocha": "^9.1.0", + "@types/node": "^16.11.7", + "mocha": "^9.1.3", + "typescript": "4.2.4" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.0.tgz", + "integrity": "sha512-LImpN9AY0J1R1mEYJjVJfSZWU4zYOlEcwSTgPve1rFQqK5AwrEs6uWW5Rv70gbDIQIAUwI86z6B+9mPK4w9Sbg==", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "dependencies": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, + "node_modules/@actions/http-client": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", + "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", + "dependencies": { + "tunnel": "0.0.6" + } + }, + "node_modules/@actions/io": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz", + "integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA==" + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "dependencies": { + "@octokit/types": "^6.34.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "dependencies": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "dependencies": { + "@octokit/openapi-types": "^11.2.0" + } + }, + "node_modules/@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.11.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.25.tgz", + "integrity": "sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ==", + "dev": true + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.0.tgz", + "integrity": "sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.2.0", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@actions/exec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.0.tgz", + "integrity": "sha512-LImpN9AY0J1R1mEYJjVJfSZWU4zYOlEcwSTgPve1rFQqK5AwrEs6uWW5Rv70gbDIQIAUwI86z6B+9mPK4w9Sbg==", + "requires": { + "@actions/io": "^1.0.1" + } + }, + "@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "requires": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, + "@actions/http-client": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", + "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", + "requires": { + "tunnel": "0.0.6" + } + }, + "@actions/io": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz", + "integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA==" + }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "requires": { + "@octokit/types": "^6.34.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "requires": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "requires": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "requires": { + "@octokit/openapi-types": "^11.2.0" + } + }, + "@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "@types/node": { + "version": "16.11.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.25.tgz", + "integrity": "sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ==", + "dev": true + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.0.tgz", + "integrity": "sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.2.0", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/scripts/ci/pr-bot/package.json b/scripts/ci/pr-bot/package.json new file mode 100644 index 0000000000000..5f6ae37a9f024 --- /dev/null +++ b/scripts/ci/pr-bot/package.json @@ -0,0 +1,27 @@ +{ + "name": "pr-bot", + "version": "1.0.0", + "description": "Scripts for the Beam PR bot", + "main": "processNewPrs.js", + "scripts": { + "build": "tsc", + "format": "prettier --write *.ts shared/**/*.ts test/**/*.ts", + "test": "mocha lib/test", + "processNewPrs": "npm run build && node lib/processNewPrs.js", + "processPrUpdate": "npm run build && node lib/processPrUpdate.js", + "gatherMetrics": "npm run build && node lib/gatherMetrics.js" + }, + "dependencies": { + "@actions/exec": "^1.1.0", + "@actions/github": "^5.0.0", + "@octokit/rest": "^18.12.0", + "js-yaml": "^4.1.0", + "prettier": "^2.5.1" + }, + "devDependencies": { + "@types/mocha": "^9.1.0", + "@types/node": "^16.11.7", + "mocha": "^9.1.3", + "typescript": "4.2.4" + } +} diff --git a/scripts/ci/pr-bot/shared/checks.ts b/scripts/ci/pr-bot/shared/checks.ts new file mode 100644 index 0000000000000..51bcf18025928 --- /dev/null +++ b/scripts/ci/pr-bot/shared/checks.ts @@ -0,0 +1,109 @@ +/* + * 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. + */ + +const { getGitHubClient } = require("./githubUtils"); + +export interface CheckStatus { + completed: boolean; + succeeded: boolean; +} + +// Returns the status of the most recent checks runs - +export async function getChecksStatus( + owner: string, + repo: string, + checkSha: string +): Promise { + let checkStatus: CheckStatus = { + completed: true, + succeeded: true, + }; + const mostRecentChecks = await getMostRecentChecks(owner, repo, checkSha); + for (let i = 0; i < mostRecentChecks.length; i++) { + if (mostRecentChecks[i].status != "completed") { + checkStatus.completed = false; + } + if (mostRecentChecks[i].conclusion != "success") { + checkStatus.succeeded = false; + } + } + + return checkStatus; +} + +async function getMostRecentChecks( + owner: string, + repo: string, + checkSha: string +): Promise { + let mostRecentChecks: any[] = []; + const checksByName = await getChecksByName(owner, repo, checkSha); + + const checkNames = Object.keys(checksByName); + for (let i = 0; i < checkNames.length; i++) { + let checks = checksByName[checkNames[i]]; + let mostRecent = checks.sort((a, b) => + a.completionTime > b.completionTime ? 1 : -1 + )[0]; + mostRecentChecks.push(mostRecent); + } + + return mostRecentChecks; +} + +async function getChecksByName( + owner: string, + repo: string, + checkSha: string +): Promise { + const githubClient = getGitHubClient(); + const allChecks = ( + await githubClient.rest.checks.listForRef({ + owner: owner, + repo: repo, + ref: checkSha, + }) + ).data.check_runs; + let checksByName = {}; + allChecks.forEach((checkRun) => { + if (!shouldExcludeCheck(checkRun)) { + let name = checkRun.name; + let check = { + status: checkRun.status, + conclusion: checkRun.conclusion, + completionTime: checkRun.completed_at, + }; + if (!checksByName[name]) { + checksByName[name] = [check]; + } else { + checksByName[name].push(check); + } + } + }); + + return checksByName; +} + +// Returns checks we should exclude because they are flaky or not always predictive of pr mergability. +// Currently just excludes codecov. +function shouldExcludeCheck(check): boolean { + if (check.name.toLowerCase().indexOf("codecov") != -1) { + return true; + } + return false; +} diff --git a/scripts/ci/pr-bot/shared/commentStrings.ts b/scripts/ci/pr-bot/shared/commentStrings.ts new file mode 100644 index 0000000000000..e70a721c95fa7 --- /dev/null +++ b/scripts/ci/pr-bot/shared/commentStrings.ts @@ -0,0 +1,72 @@ +/* + * 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. + */ + +export function allChecksPassed(reviewersToNotify: string[]): string { + return `All checks have passed: @${reviewersToNotify.join(" ")}`; +} + +export function assignCommitter(committer: string): string { + return `R: @${committer} for final approval`; +} + +export function assignReviewer(labelToReviewerMapping: any): string { + let commentString = + "Assigning reviewers. If you would like to opt out of this review, comment `assign to next reviewer`:\n\n"; + + for (let label in labelToReviewerMapping) { + let reviewer = labelToReviewerMapping[label]; + if (label === "no-matching-label") { + commentString += `R: @${reviewer} added as fallback since no labels match configuration\n`; + } else { + commentString += `R: @${reviewer} for label ${label}.\n`; + } + } + + commentString += ` +Available commands: +- \`stop reviewer notifications\` - opt out of the automated review tooling +- \`remind me after tests pass\` - tag the comment author after tests pass +- \`waiting on author\` - shift the attention set back to the author (any comment or push by the author will return the attention set to the reviewers)`; + return commentString; +} + +export function failingChecksCantAssign(): string { + return "Checks are failing. Will not request review until checks are succeeding. If you'd like to override that behavior, comment `assign set of reviewers`"; +} + +export function someChecksFailing(reviewersToNotify: string[]): string { + return `Some checks have failed: @${reviewersToNotify.join(" ")}`; +} + +export function stopNotifications(reason: string): string { + return `Stopping reviewer notifications for this pull request: ${reason}`; +} + +export function remindReviewerAfterTestsPass(requester: string): string { + return `Ok - I'll remind @${requester} after tests pass`; +} + +export function reviewersAlreadyAssigned(reviewers: string[]): string { + return `Reviewers are already assigned to this PR: ${reviewers + .map((reviewer) => "@" + reviewer) + .join(" ")}`; +} + +export function noLegalReviewers(): string { + return "No reviewers could be found from any of the labels on the PR or in the fallback reviewers list. Check the config file to make sure reviewers are configured"; +} diff --git a/scripts/ci/pr-bot/shared/constants.ts b/scripts/ci/pr-bot/shared/constants.ts new file mode 100644 index 0000000000000..859bf8589e623 --- /dev/null +++ b/scripts/ci/pr-bot/shared/constants.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +const path = require("path"); + +export const REPO_OWNER = "apache"; +export const REPO = "beam"; +export const PATH_TO_CONFIG_FILE = path.join( + __dirname, + "../../../../../.github/REVIEWERS.yml" +); +export const PATH_TO_METRICS_CSV = path.resolve( + path.join(__dirname, "../../metrics.csv") +); +export const BOT_NAME = "github-actions"; diff --git a/scripts/ci/pr-bot/shared/githubUtils.ts b/scripts/ci/pr-bot/shared/githubUtils.ts new file mode 100644 index 0000000000000..e650ca52b0d09 --- /dev/null +++ b/scripts/ci/pr-bot/shared/githubUtils.ts @@ -0,0 +1,96 @@ +/* + * 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. + */ + +const { Octokit } = require("@octokit/rest"); +const { REPO_OWNER, REPO } = require("./constants"); + +export interface Label { + name: string; +} + +export function getGitHubClient() { + let auth = process.env["GITHUB_TOKEN"]; + if (!auth) { + throw new Error( + "No github token provided - process.env['GITHUB_TOKEN'] must be set." + ); + } + return new Octokit({ auth }); +} + +export async function addPrComment(pullNumber: number, body: string) { + await getGitHubClient().rest.issues.createComment({ + owner: REPO_OWNER, + repo: REPO, + issue_number: pullNumber, + body, + }); +} + +export async function nextActionReviewers( + pullNumber: number, + existingLabels: Label[] +) { + let newLabels = removeNextActionLabel(existingLabels); + newLabels.push("Next Action: Reviewers"); + await getGitHubClient().rest.issues.setLabels({ + owner: REPO_OWNER, + repo: REPO, + issue_number: pullNumber, + labels: newLabels, + }); +} + +export async function nextActionAuthor( + pullNumber: number, + existingLabels: Label[] +) { + let newLabels = removeNextActionLabel(existingLabels); + newLabels.push("Next Action: Author"); + await getGitHubClient().rest.issues.setLabels({ + owner: REPO_OWNER, + repo: REPO, + issue_number: pullNumber, + labels: newLabels, + }); +} + +export async function checkIfCommitter(username: string): Promise { + const permissionLevel = ( + await getGitHubClient().rest.repos.getCollaboratorPermissionLevel({ + owner: REPO_OWNER, + repo: REPO, + username, + }) + ).data; + + return ( + permissionLevel.permission === "write" || + permissionLevel.permission === "admin" + ); +} + +function removeNextActionLabel(existingLabels: Label[]): string[] { + return existingLabels + .filter( + (label) => + label.name != "Next Action: Reviewers" && + label.name != "Next Action: Author" + ) + .map((label) => label.name); +} diff --git a/scripts/ci/pr-bot/shared/persistentState.ts b/scripts/ci/pr-bot/shared/persistentState.ts new file mode 100644 index 0000000000000..f7fade16f9b93 --- /dev/null +++ b/scripts/ci/pr-bot/shared/persistentState.ts @@ -0,0 +1,121 @@ +/* + * 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. + */ + +const exec = require("@actions/exec"); +const fs = require("fs"); +const path = require("path"); +const { Pr } = require("./pr"); +const { ReviewersForLabel } = require("./reviewersForLabel"); +const { BOT_NAME } = require("./constants"); + +function getPrFileName(prNumber) { + return `pr-${prNumber}.json`.toLowerCase(); +} + +function getReviewersForLabelFileName(label) { + return `reviewers-for-label-${label}.json`.toLowerCase(); +} + +async function commitStateToRepo() { + await exec.exec("git pull origin pr-bot-state"); + await exec.exec("git add state/*"); + await exec.exec(`git commit -m "Updating config from bot" --allow-empty`); + await exec.exec("git push origin pr-bot-state"); +} + +export class PersistentState { + private switchedBranch = false; + + // Returns a Pr object representing the current saved state of the pr. + async getPrState(prNumber: number): Promise { + var fileName = getPrFileName(prNumber); + return new Pr(await this.getState(fileName, "state/pr-state")); + } + + // Writes a Pr object representing the current saved state of the pr to persistent storage. + async writePrState(prNumber: number, newState: any) { + var fileName = getPrFileName(prNumber); + await this.writeState(fileName, "state/pr-state", new Pr(newState)); + } + + // Returns a ReviewersForLabel object representing the current saved state of which reviewers have reviewed recently. + async getReviewersForLabelState( + label: string + ): Promise { + var fileName = getReviewersForLabelFileName(label); + return new ReviewersForLabel(label, await this.getState(fileName, "state")); + } + + // Writes a ReviewersForLabel object representing the current saved state of which reviewers have reviewed recently. + async writeReviewersForLabelState(label: string, newState: any) { + var fileName = getReviewersForLabelFileName(label); + await this.writeState( + fileName, + "state", + new ReviewersForLabel(label, newState) + ); + } + + private async getState(fileName, baseDirectory) { + await this.ensureCorrectBranch(); + fileName = path.join(baseDirectory, fileName); + if (!fs.existsSync(fileName)) { + return null; + } + return JSON.parse(fs.readFileSync(fileName, { encoding: "utf-8" })); + } + + private async writeState(fileName, baseDirectory, state) { + await this.ensureCorrectBranch(); + fileName = path.join(baseDirectory, fileName); + if (!fs.existsSync(baseDirectory)) { + fs.mkdirSync(baseDirectory, { recursive: true }); + } + fs.writeFileSync(fileName, JSON.stringify(state, null, 2), { + encoding: "utf-8", + }); + await commitStateToRepo(); + } + + private async ensureCorrectBranch() { + if (this.switchedBranch) { + return; + } + console.log( + "Switching to branch pr-bot-state for reading/storing persistent state between runs" + ); + try { + await exec.exec(`git config user.name ${BOT_NAME}`); + await exec.exec(`git config user.email ${BOT_NAME}@github.com`); + await exec.exec("git config pull.rebase false"); + await exec.exec("git fetch origin pr-bot-state"); + await exec.exec("git checkout pr-bot-state"); + } catch { + console.log( + "Couldnt find branch pr-bot-state in origin, trying to create it" + ); + try { + await exec.exec("git checkout -b pr-bot-state"); + } catch { + console.log("Creating branch failed, trying a simple checkout."); + await exec.exec("git checkout pr-bot-state"); + } + } + this.switchedBranch = true; + } +} diff --git a/scripts/ci/pr-bot/shared/pr.ts b/scripts/ci/pr-bot/shared/pr.ts new file mode 100644 index 0000000000000..c1419b8419cee --- /dev/null +++ b/scripts/ci/pr-bot/shared/pr.ts @@ -0,0 +1,89 @@ +/* + * 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. + */ + +const github = require("./githubUtils"); + +export class Pr { + public commentedAboutFailingChecks: boolean; + public reviewersAssignedForLabels: { [key: string]: string }; + public nextAction: string; + public stopReviewerNotifications: boolean; + public remindAfterTestsPass: string[]; + + constructor(propertyDictionary) { + this.commentedAboutFailingChecks = false; + this.reviewersAssignedForLabels = {}; // map of label to reviewer + this.nextAction = "Author"; + this.stopReviewerNotifications = false; + this.remindAfterTestsPass = []; // List of handles + + if (!propertyDictionary) { + return; + } + if (propertyDictionary) { + if ("commentedAboutFailingChecks" in propertyDictionary) { + this.commentedAboutFailingChecks = + propertyDictionary["commentedAboutFailingChecks"]; + } + if ("reviewersAssignedForLabels" in propertyDictionary) { + this.reviewersAssignedForLabels = + propertyDictionary["reviewersAssignedForLabels"]; + } + if ("nextAction" in propertyDictionary) { + this.nextAction = propertyDictionary["nextAction"]; + } + if ("stopReviewerNotifications" in propertyDictionary) { + this.stopReviewerNotifications = + propertyDictionary["stopReviewerNotifications"]; + } + if ("remindAfterTestsPass" in propertyDictionary) { + this.remindAfterTestsPass = propertyDictionary["remindAfterTestsPass"]; + } + } + } + + // Returns a label that the reviewer is assigned for. + // If none, returns an empty string + getLabelForReviewer(reviewer: string): string { + const labels = Object.keys(this.reviewersAssignedForLabels); + for (let i = 0; i < labels.length; i++) { + let label = labels[i]; + if (this.reviewersAssignedForLabels[label] === reviewer) { + return label; + } + } + + return ""; + } + + // Returns whether any of the assigned reviewers are committers + async isAnyAssignedReviewerCommitter(): Promise { + const labels = Object.keys(this.reviewersAssignedForLabels); + for (let i = 0; i < labels.length; i++) { + if ( + await github.checkIfCommitter( + this.reviewersAssignedForLabels[labels[i]] + ) + ) { + return true; + } + } + + return false; + } +} diff --git a/scripts/ci/pr-bot/shared/reviewerConfig.ts b/scripts/ci/pr-bot/shared/reviewerConfig.ts new file mode 100644 index 0000000000000..f8300adbdfaf3 --- /dev/null +++ b/scripts/ci/pr-bot/shared/reviewerConfig.ts @@ -0,0 +1,97 @@ +/* + * 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. + */ + +const yaml = require("js-yaml"); +const fs = require("fs"); +import { Label } from "./githubUtils"; + +export class ReviewerConfig { + private config: any; + constructor(pathToConfigFile) { + this.config = yaml.load( + fs.readFileSync(pathToConfigFile, { encoding: "utf-8" }) + ); + } + + // Given a list of labels and an exclusion list of reviewers not to include (e.g. the author) + // returns all possible reviewers for each label + getReviewersForLabels( + labels: Label[], + exclusionList: string[] + ): { [key: string]: string[] } { + let reviewersFound = false; + let labelToReviewerMapping = {}; + labels.forEach((label) => { + let reviewers = this.getReviewersForLabel(label.name, exclusionList); + if (reviewers.length > 0) { + labelToReviewerMapping[label.name] = reviewers; + reviewersFound = true; + } + }); + if (!reviewersFound) { + const fallbackReviewers = this.getFallbackReviewers(exclusionList); + if (fallbackReviewers.length > 0) { + labelToReviewerMapping["no-matching-label"] = + this.getFallbackReviewers(exclusionList); + } + } + return labelToReviewerMapping; + } + + // Get possible reviewers excluding the author. + getReviewersForLabel(label: string, exclusionList: string[]): string[] { + var labelObjects = this.config.labels; + const labelObject = labelObjects.find( + (labelObject) => labelObject.name.toLowerCase() === label.toLowerCase() + ); + if (!labelObject) { + return []; + } + + return this.excludeFromReviewers(labelObject.reviewers, exclusionList); + } + + getExclusionListForLabel(label: string): string[] { + var labelObjects = this.config.labels; + const labelObject = labelObjects.find( + (labelObject) => labelObject.name.toLowerCase() === label.toLowerCase() + ); + return labelObject?.exclusionList ?? []; + } + + // Get fallback reviewers excluding the author. + getFallbackReviewers(exclusionList: string[]): string[] { + return this.excludeFromReviewers( + this.config.fallbackReviewers, + exclusionList + ); + } + + private excludeFromReviewers( + reviewers: string[], + exclusionList: string[] + ): string[] { + if (!exclusionList) { + return reviewers; + } + + return reviewers.filter( + (reviewer) => exclusionList.indexOf(reviewer) == -1 + ); + } +} diff --git a/scripts/ci/pr-bot/shared/reviewersForLabel.ts b/scripts/ci/pr-bot/shared/reviewersForLabel.ts new file mode 100644 index 0000000000000..971f3f1cd7a53 --- /dev/null +++ b/scripts/ci/pr-bot/shared/reviewersForLabel.ts @@ -0,0 +1,102 @@ +/* + * 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. + */ + +const github = require("./githubUtils"); + +export class ReviewersForLabel { + public label: string; + public dateOfLastReviewAssignment: { [key: string]: number }; + + constructor( + label: string, + propertyDictionary: { + dateOfLastReviewAssignment: { [key: string]: number }; + } + ) { + this.label = label; + this.dateOfLastReviewAssignment = {}; // map of reviewer to date + + if (!propertyDictionary) { + return; + } + if ("dateOfLastReviewAssignment" in propertyDictionary) { + this.dateOfLastReviewAssignment = + propertyDictionary["dateOfLastReviewAssignment"]; + } + } + + // Given a list of available reviewers, + // returns the next reviewer up based on who has reviewed least recently. + // Updates this object to reflect their assignment. + assignNextReviewer(availableReviewers: string[]): string { + if (availableReviewers.length === 0) { + throw new Error(`No reviewers available for label ${this.label}`); + } + + if (!this.dateOfLastReviewAssignment[availableReviewers[0]]) { + this.dateOfLastReviewAssignment[availableReviewers[0]] = Date.now(); + return availableReviewers[0]; + } + + let earliestDate = this.dateOfLastReviewAssignment[availableReviewers[0]]; + let earliestReviewer = availableReviewers[0]; + + for (let i = 0; i < availableReviewers.length; i++) { + let availableReviewer = availableReviewers[i]; + if (!this.dateOfLastReviewAssignment[availableReviewer]) { + this.dateOfLastReviewAssignment[availableReviewer] = Date.now(); + return availableReviewer; + } + if (earliestDate > this.dateOfLastReviewAssignment[availableReviewer]) { + earliestDate = this.dateOfLastReviewAssignment[availableReviewer]; + earliestReviewer = availableReviewer; + } + } + + this.dateOfLastReviewAssignment[earliestReviewer] = Date.now(); + return earliestReviewer; + } + + // Given the up to date list of available reviewers (excluding the author), + // returns the next reviewer up based on who has reviewed least recently. + // Updates this object to reflect their assignment. + async assignNextCommitter(availableReviewers: string[]): Promise { + let earliestDate = Date.now(); + let earliestCommitter: string = ""; + + for (let i = 0; i < availableReviewers.length; i++) { + let availableReviewer = availableReviewers[i]; + if (await github.checkIfCommitter(availableReviewer)) { + if (!this.dateOfLastReviewAssignment[availableReviewer]) { + this.dateOfLastReviewAssignment[availableReviewer] = Date.now(); + return availableReviewer; + } + if (earliestDate > this.dateOfLastReviewAssignment[availableReviewer]) { + earliestDate = this.dateOfLastReviewAssignment[availableReviewer]; + earliestCommitter = availableReviewer; + } + } + } + + if (!earliestCommitter) { + throw new Error(`No committers available for label ${this.label}`); + } + this.dateOfLastReviewAssignment[earliestCommitter] = Date.now(); + return earliestCommitter; + } +} diff --git a/scripts/ci/pr-bot/shared/userCommand.ts b/scripts/ci/pr-bot/shared/userCommand.ts new file mode 100644 index 0000000000000..34e2741a3f6d4 --- /dev/null +++ b/scripts/ci/pr-bot/shared/userCommand.ts @@ -0,0 +1,231 @@ +/* + * 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. + */ + +const github = require("./githubUtils"); +const commentStrings = require("./commentStrings"); +const { BOT_NAME } = require("./constants"); +const { StateClient } = require("./persistentState"); +const { ReviewerConfig } = require("./reviewerConfig"); + +// Reads the comment and processes the command if one is contained in it. +// Returns true if it runs a command, false otherwise. +export async function processCommand( + payload: any, + commentAuthor: string, + commentText: string, + stateClient: typeof StateClient, + reviewerConfig: typeof ReviewerConfig +) { + // Don't process any commands from our bot. + if (commentAuthor === BOT_NAME) { + return false; + } + console.log(commentAuthor); + + const pullNumber = payload.issue?.number || payload.pull_request?.number; + commentText = commentText.toLowerCase(); + if (commentText.indexOf("r: @") > -1) { + await manuallyAssignedToReviewer(pullNumber, stateClient); + } else if (commentText.indexOf("assign to next reviewer") > -1) { + await assignToNextReviewer( + payload, + commentAuthor, + pullNumber, + stateClient, + reviewerConfig + ); + } else if (commentText.indexOf("stop reviewer notifications") > -1) { + await stopReviewerNotifications( + pullNumber, + stateClient, + "requested by reviewer" + ); + } else if (commentText.indexOf("remind me after tests pass") > -1) { + await remindAfterTestsPass(pullNumber, commentAuthor, stateClient); + } else if (commentText.indexOf("waiting on author") > -1) { + await waitOnAuthor(payload, pullNumber, stateClient); + } else if (commentText.indexOf("assign set of reviewers") > -1) { + await assignReviewerSet(payload, pullNumber, stateClient, reviewerConfig); + } else { + return false; + } + + return true; +} + +async function assignToNextReviewer( + payload: any, + commentAuthor: string, + pullNumber: number, + stateClient: typeof StateClient, + reviewerConfig: typeof ReviewerConfig +) { + let prState = await stateClient.getPrState(pullNumber); + let labelOfReviewer = prState.getLabelForReviewer(payload.sender.login); + if (labelOfReviewer) { + let reviewersState = await stateClient.getReviewersForLabelState( + labelOfReviewer + ); + const pullAuthor = + payload.issue?.user?.login || payload.pull_request?.user?.login; + let availableReviewers = reviewerConfig.getReviewersForLabel( + labelOfReviewer, + [commentAuthor, pullAuthor] + ); + let chosenReviewer = reviewersState.assignNextReviewer(availableReviewers); + prState.reviewersAssignedForLabels[labelOfReviewer] = chosenReviewer; + + // Comment assigning reviewer + console.log(`Assigning ${chosenReviewer}`); + await github.addPrComment( + pullNumber, + commentStrings.assignReviewer(prState.reviewersAssignedForLabels) + ); + + // Set next action to reviewer + const existingLabels = + payload.issue?.labels || payload.pull_request?.labels; + await github.nextActionReviewers(pullNumber, existingLabels); + prState.nextAction = "Reviewers"; + + // Persist state + await stateClient.writePrState(pullNumber, prState); + await stateClient.writeReviewersForLabelState( + labelOfReviewer, + reviewersState + ); + } +} + +// If they've manually assigned a reviewer, just silence notifications and ignore this pr going forward. +// TODO(damccorm) - we could try to do something more intelligent here like figuring out which label that reviewer belongs to. +async function manuallyAssignedToReviewer( + pullNumber: number, + stateClient: typeof StateClient +) { + await stopReviewerNotifications( + pullNumber, + stateClient, + "review requested by someone other than the bot, ceding control" + ); +} + +async function stopReviewerNotifications( + pullNumber: number, + stateClient: typeof StateClient, + reason: string +) { + let prState = await stateClient.getPrState(pullNumber); + prState.stopReviewerNotifications = true; + await stateClient.writePrState(pullNumber, prState); + + // Comment acknowledging command + await github.addPrComment( + pullNumber, + commentStrings.stopNotifications(reason) + ); +} + +async function remindAfterTestsPass( + pullNumber: number, + username: string, + stateClient: typeof StateClient +) { + let prState = await stateClient.getPrState(pullNumber); + prState.remindAfterTestsPass.push(username); + await stateClient.writePrState(pullNumber, prState); + + // Comment acknowledging command + await github.addPrComment( + pullNumber, + commentStrings.remindReviewerAfterTestsPass(username) + ); +} + +async function waitOnAuthor( + payload: any, + pullNumber: number, + stateClient: typeof StateClient +) { + const existingLabels = payload.issue?.labels || payload.pull_request?.labels; + await github.nextActionAuthor(pullNumber, existingLabels); + let prState = await stateClient.getPrState(pullNumber); + prState.nextAction = "Author"; + await stateClient.writePrState(pullNumber, prState); +} + +async function assignReviewerSet( + payload: any, + pullNumber: number, + stateClient: typeof StateClient, + reviewerConfig: typeof ReviewerConfig +) { + let prState = await stateClient.getPrState(pullNumber); + if (Object.values(prState.reviewersAssignedForLabels).length > 0) { + await github.addPrComment( + pullNumber, + commentStrings.reviewersAlreadyAssigned( + Object.values(prState.reviewersAssignedForLabels) + ) + ); + return; + } + + const existingLabels = payload.issue?.labels || payload.pull_request?.labels; + const pullAuthor = + payload.issue?.user?.login || payload.pull_request?.user?.login; + const reviewersForLabels = reviewerConfig.getReviewersForLabels( + existingLabels, + [pullAuthor] + ); + let reviewerStateToUpdate = {}; + var labels = Object.keys(reviewersForLabels); + if (!labels || labels.length == 0) { + await github.addPrComment( + pullNumber, + commentStrings.noLegalReviewers(existingLabels) + ); + return; + } + for (let i = 0; i < labels.length; i++) { + let label = labels[i]; + let availableReviewers = reviewersForLabels[label]; + let reviewersState = await stateClient.getReviewersForLabelState(label); + let chosenReviewer = reviewersState.assignNextReviewer(availableReviewers); + reviewerStateToUpdate[label] = reviewersState; + prState.reviewersAssignedForLabels[label] = chosenReviewer; + } + console.log(`Assigning reviewers for pr ${pullNumber}`); + await github.addPrComment( + pullNumber, + commentStrings.assignReviewer(prState.reviewersAssignedForLabels) + ); + + github.nextActionReviewers(pullNumber, existingLabels); + prState.nextAction = "Reviewers"; + + await stateClient.writePrState(pullNumber, prState); + let labelsToUpdate = Object.keys(reviewerStateToUpdate); + for (let i = 0; i < labelsToUpdate.length; i++) { + let label = labelsToUpdate[i]; + await stateClient.writeReviewersForLabelState( + label, + reviewerStateToUpdate[label] + ); + } +} diff --git a/scripts/ci/pr-bot/test/prTest.ts b/scripts/ci/pr-bot/test/prTest.ts new file mode 100644 index 0000000000000..b771852584a30 --- /dev/null +++ b/scripts/ci/pr-bot/test/prTest.ts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +var assert = require("assert"); +const { Pr } = require("../shared/pr"); +describe("Pr", function () { + describe("getLabelForReviewer()", function () { + it("should return the label a reviewer is assigned to", function () { + let testPr = new Pr({}); + testPr.reviewersAssignedForLabels = { + Go: "testReviewer1", + Java: "testReviewer2", + Python: "testReviewer3", + }; + assert.equal("Go", testPr.getLabelForReviewer("testReviewer1")); + assert.equal("Java", testPr.getLabelForReviewer("testReviewer2")); + assert.equal("Python", testPr.getLabelForReviewer("testReviewer3")); + }); + + it("should return an empty string when a reviewer is not assigned", function () { + let testPr = new Pr({}); + testPr.reviewersAssignedForLabels = { + Go: "testReviewer1", + Java: "testReviewer2", + Python: "testReviewer3", + }; + assert.equal("", testPr.getLabelForReviewer("testReviewer4")); + }); + }); +}); diff --git a/scripts/ci/pr-bot/test/reviewerConfigTest.ts b/scripts/ci/pr-bot/test/reviewerConfigTest.ts new file mode 100644 index 0000000000000..589aab635bc95 --- /dev/null +++ b/scripts/ci/pr-bot/test/reviewerConfigTest.ts @@ -0,0 +1,238 @@ +/* + * 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. + */ + +var assert = require("assert"); +var fs = require("fs"); +const { ReviewerConfig } = require("../shared/reviewerConfig"); +const configPath = "test-config.yml"; +const configContents = `labels: +- name: "Go" + reviewers: ["testReviewer1", "testReviewer2"] + exclusionList: ["testReviewer3"] # These users will never be suggested as reviewers +# I don't know the other areas well enough to assess who the normal committers/contributors who might want to be reviewers are +- name: "Java" + reviewers: ["testReviewer3", "testReviewer2"] + exclusionList: [] # These users will never be suggested as reviewers +- name: "Python" + reviewers: ["testReviewer4"] + exclusionList: [] # These users will never be suggested as reviewers +fallbackReviewers: ["testReviewer5", "testReviewer1", "testReviewer3"] # List of committers to use when no label matches +`; +describe("ReviewerConfig", function () { + before(function () { + if (fs.existsSync(configPath)) { + fs.rmSync(configPath); + } + fs.writeFileSync(configPath, configContents); + }); + + after(function () { + fs.rmSync(configPath); + }); + + describe("getReviewersForLabels()", function () { + it("should return all reviewers configured for all labels", function () { + const config = new ReviewerConfig(configPath); + const reviewersForLabels = config.getReviewersForLabels( + [{ name: "Go" }, { name: "Java" }], + [] + ); + assert( + reviewersForLabels["Go"].find( + (reviewer) => reviewer === "testReviewer1" + ), + "Return value for Go label should include testReviewer1" + ); + assert( + reviewersForLabels["Go"].find( + (reviewer) => reviewer === "testReviewer2" + ), + "Return value for Go label should include testReviewer2" + ); + assert( + !reviewersForLabels["Go"].find( + (reviewer) => reviewer === "testReviewer3" + ), + "Return value for Go label should not include testReviewer3" + ); + assert( + !reviewersForLabels["Go"].find( + (reviewer) => reviewer === "testReviewer4" + ), + "Return value for Go label should not include testReviewer4" + ); + + assert( + reviewersForLabels["Java"].find( + (reviewer) => reviewer === "testReviewer3" + ), + "Return value for Java label should include testReviewer3" + ); + assert( + reviewersForLabels["Java"].find( + (reviewer) => reviewer === "testReviewer2" + ), + "Return value for Java label should include testReviewer2" + ); + assert( + !reviewersForLabels["Java"].find( + (reviewer) => reviewer === "testReviewer4" + ), + "Return value for Java label should not include testReviewer4" + ); + assert( + !reviewersForLabels["Java"].find( + (reviewer) => reviewer === "testReviewer1" + ), + "Return value for Java label should not include testReviewer1" + ); + assert( + !reviewersForLabels["Java"].find( + (reviewer) => reviewer === "testReviewer5" + ), + "Return value for Java label should not include testReviewer5" + ); + + assert( + Object.keys(reviewersForLabels).indexOf("Python") == -1, + "No reviewers should be included for python" + ); + }); + + it("should return no entry if a label is not configured", function () { + const config = new ReviewerConfig(configPath); + const reviewersForLabels = config.getReviewersForLabels( + [{ name: "FakeLabel" }], + [] + ); + + assert( + !("FakeLabel" in reviewersForLabels), + "FakeLabel should not be included in the returned label map" + ); + }); + + it("should exclude any reviewers who are passed into the exlusionList", function () { + const config = new ReviewerConfig(configPath); + const reviewersForLabels = config.getReviewersForLabels( + [{ name: "Go" }, { name: "Java" }], + ["testReviewer1"] + ); + + assert( + !reviewersForLabels["Go"].find( + (reviewer) => reviewer === "testReviewer1" + ), + "testReviewer1 should have been excluded from the result set" + ); + }); + }); + + describe("getReviewersForLabel()", function () { + it("should return all reviewers configured for a label", function () { + const config = new ReviewerConfig(configPath); + const reviewersForGo = config.getReviewersForLabel("Go", []); + + assert( + reviewersForGo.find((reviewer) => reviewer === "testReviewer1"), + "Return value for Go label should include testReviewer1" + ); + assert( + reviewersForGo.find((reviewer) => reviewer === "testReviewer2"), + "Return value for Go label should include testReviewer2" + ); + assert( + !reviewersForGo.find((reviewer) => reviewer === "testReviewer3"), + "Return value for Go label should not include testReviewer3" + ); + assert( + !reviewersForGo.find((reviewer) => reviewer === "testReviewer4"), + "Return value for Go label should not include testReviewer4" + ); + }); + + it("should return an empty list if a label is not configured", function () { + const config = new ReviewerConfig(configPath); + const reviewersForFakeLabel = config.getReviewersForLabel( + "FakeLabel", + [] + ); + assert.equal(0, reviewersForFakeLabel.length); + }); + + it("should exclude any reviewers who are passed into the exlusionList", function () { + const config = new ReviewerConfig(configPath); + const reviewersForGo = config.getReviewersForLabel("Go", [ + "testReviewer1", + ]); + + assert( + !reviewersForGo.find((reviewer) => reviewer === "testReviewer1"), + "Return value for Go label should not include testReviewer1" + ); + }); + }); + + describe("getExclusionListForLabel()", function () { + it("should get the exclusion list configured for a label", function () { + const config = new ReviewerConfig(configPath); + const goExclusionList = config.getExclusionListForLabel("Go"); + + assert( + goExclusionList.find((reviewer) => reviewer === "testReviewer3"), + "Return value for Go label should include testReviewer3" + ); + assert.equal(1, goExclusionList.length); + }); + }); + + describe("getFallbackReviewers()", function () { + it("should get the configured falback list", function () { + const config = new ReviewerConfig(configPath); + const fallbackReviewers = config.getFallbackReviewers([]); + + assert.equal(3, fallbackReviewers.length); + assert( + fallbackReviewers.find((reviewer) => reviewer === "testReviewer5"), + "Fallback reviewers should include testReviewer5" + ); + assert( + fallbackReviewers.find((reviewer) => reviewer === "testReviewer1"), + "Fallback reviewers should include testReviewer1" + ); + assert( + fallbackReviewers.find((reviewer) => reviewer === "testReviewer3"), + "Fallback reviewers should include testReviewer3" + ); + }); + + it("should not include excluded reviewers", function () { + const config = new ReviewerConfig(configPath); + const fallbackReviewers = config.getFallbackReviewers([ + "testReviewer1", + "testReviewer3", + ]); + + assert.equal(1, fallbackReviewers.length); + assert( + fallbackReviewers.find((reviewer) => reviewer === "testReviewer5"), + "Fallback reviewers should only include testReviewer5" + ); + }); + }); +}); diff --git a/scripts/ci/pr-bot/test/reviewersForLabelTest.ts b/scripts/ci/pr-bot/test/reviewersForLabelTest.ts new file mode 100644 index 0000000000000..f425750505927 --- /dev/null +++ b/scripts/ci/pr-bot/test/reviewersForLabelTest.ts @@ -0,0 +1,135 @@ +/* + * 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. + */ + +var assert = require("assert"); +const { ReviewersForLabel } = require("../shared/reviewersForLabel"); +describe("ReviewersForLabel", function () { + describe("assignNextReviewer()", function () { + it("should repeatedly assign the reviewer who reviewed least recently", function () { + const dateOfLastReviewAssignment = { + testReviewer1: 1, + testReviewer2: 3, + testReviewer3: 4, + testReviewer4: 2, + }; + let reviewersForGo = new ReviewersForLabel("Go", { + dateOfLastReviewAssignment: dateOfLastReviewAssignment, + }); + + assert.equal( + "testReviewer1", + reviewersForGo.assignNextReviewer([ + "testReviewer1", + "testReviewer2", + "testReviewer3", + "testReviewer4", + ]) + ); + assert.equal( + "testReviewer4", + reviewersForGo.assignNextReviewer([ + "testReviewer1", + "testReviewer2", + "testReviewer3", + "testReviewer4", + ]) + ); + assert.equal( + "testReviewer2", + reviewersForGo.assignNextReviewer([ + "testReviewer1", + "testReviewer2", + "testReviewer3", + "testReviewer4", + ]) + ); + assert.equal( + "testReviewer3", + reviewersForGo.assignNextReviewer([ + "testReviewer1", + "testReviewer2", + "testReviewer3", + "testReviewer4", + ]) + ); + assert.equal( + "testReviewer1", + reviewersForGo.assignNextReviewer([ + "testReviewer1", + "testReviewer2", + "testReviewer3", + "testReviewer4", + ]) + ); + }); + + it("should assign a reviewer who hasnt reviewed before", function () { + const dateOfLastReviewAssignment = { + testReviewer1: 1, + testReviewer2: 2, + testReviewer3: 3, + testReviewer4: 4, + }; + let reviewersForGo = new ReviewersForLabel("Go", { + dateOfLastReviewAssignment: dateOfLastReviewAssignment, + }); + + assert.equal( + "testReviewer5", + reviewersForGo.assignNextReviewer([ + "testReviewer1", + "testReviewer2", + "testReviewer3", + "testReviewer4", + "testReviewer5", + ]) + ); + }); + + it("should only assign reviewers in the availableReviewers list", function () { + const dateOfLastReviewAssignment = { + testReviewer1: 1, + testReviewer2: 2, + testReviewer3: 3, + testReviewer4: 4, + }; + let reviewersForGo = new ReviewersForLabel("Go", { + dateOfLastReviewAssignment: dateOfLastReviewAssignment, + }); + + assert.equal( + "testReviewer2", + reviewersForGo.assignNextReviewer(["testReviewer4", "testReviewer2"]) + ); + }); + + it("should throw if no reviewer available", function () { + const dateOfLastReviewAssignment = { + testReviewer1: 1, + testReviewer2: 2, + testReviewer3: 3, + testReviewer4: 4, + }; + let reviewersForGo = new ReviewersForLabel("Go", { + dateOfLastReviewAssignment: dateOfLastReviewAssignment, + }); + + assert.throws(() => reviewersForGo.assignNextReviewer([])); + }); + }); +}); diff --git a/scripts/ci/pr-bot/tsconfig.json b/scripts/ci/pr-bot/tsconfig.json new file mode 100644 index 0000000000000..719f5dff8b9f5 --- /dev/null +++ b/scripts/ci/pr-bot/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "outDir": "./lib" /* Redirect output structure to the directory. */, + "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + }, + "exclude": ["node_modules"] +} From 47ab260aaad6b1014d86dd6cf9274f1045690631 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 25 Feb 2022 18:13:18 +1100 Subject: [PATCH 35/61] Merge pull request #16846 from [BEAM-12164]: Add sad path tests for Cloud Spanner's ReadChangeStream * Add tests and config for retry * lint * add tests * lint * Delete tests not passing * Rebase on apache beam master * review changes * review changes * fix checkstyle --- .../beam/gradle/BeamModulePlugin.groovy | 6 + .../io/google-cloud-platform/build.gradle | 2 + .../sdk/io/gcp/spanner/SpannerAccessor.java | 62 ++- .../sdk/io/gcp/spanner/SpannerConfig.java | 39 ++ .../beam/sdk/io/gcp/spanner/SpannerIO.java | 53 +- .../SpannerChangeStreamErrorTest.java | 497 ++++++++++++++++++ 6 files changed, 633 insertions(+), 26 deletions(-) create mode 100644 sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/changestreams/SpannerChangeStreamErrorTest.java 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 95216658b01a0..b11b590ede869 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -454,8 +454,12 @@ class BeamModulePlugin implements Plugin { def checkerframework_version = "3.10.0" def classgraph_version = "4.8.104" def errorprone_version = "2.10.0" + // Try to keep gax_version consistent with gax-grpc version in google_cloud_platform_libraries_bom + def gax_version = "2.8.1" def google_clients_version = "1.32.1" def google_cloud_bigdataoss_version = "2.2.4" + // Try to keep google_cloud_spanner_version consistent with google_cloud_spanner_bom in google_cloud_platform_libraries_bom + def google_cloud_spanner_version = "6.17.4" def google_code_gson_version = "2.8.9" def google_oauth_clients_version = "1.32.1" // Try to keep grpc_version consistent with gRPC version in google_cloud_platform_libraries_bom @@ -543,6 +547,7 @@ class BeamModulePlugin implements Plugin { flogger_system_backend : "com.google.flogger:flogger-system-backend:0.7.3", gax : "com.google.api:gax", // google_cloud_platform_libraries_bom sets version gax_grpc : "com.google.api:gax-grpc", // google_cloud_platform_libraries_bom sets version + gax_grpc_test : "com.google.api:gax-grpc:$gax_version:testlib", // google_cloud_platform_libraries_bom sets version gax_httpjson : "com.google.api:gax-httpjson", // google_cloud_platform_libraries_bom sets version google_api_client : "com.google.api-client:google-api-client:$google_clients_version", // for the libraries using $google_clients_version below. google_api_client_jackson2 : "com.google.api-client:google-api-client-jackson2:$google_clients_version", @@ -574,6 +579,7 @@ class BeamModulePlugin implements Plugin { // Update libraries-bom version on sdks/java/container/license_scripts/dep_urls_java.yaml google_cloud_platform_libraries_bom : "com.google.cloud:libraries-bom:24.2.0", google_cloud_spanner : "com.google.cloud:google-cloud-spanner", // google_cloud_platform_libraries_bom sets version + google_cloud_spanner_test : "com.google.cloud:google-cloud-spanner:$google_cloud_spanner_version:tests", google_code_gson : "com.google.code.gson:gson:$google_code_gson_version", // google-http-client's version is explicitly declared for sdks/java/maven-archetypes/examples // This version should be in line with the one in com.google.cloud:libraries-bom. diff --git a/sdks/java/io/google-cloud-platform/build.gradle b/sdks/java/io/google-cloud-platform/build.gradle index be4e551cd6b9a..364321e0b27ed 100644 --- a/sdks/java/io/google-cloud-platform/build.gradle +++ b/sdks/java/io/google-cloud-platform/build.gradle @@ -52,6 +52,7 @@ dependencies { // BEAM-13781: gax-grpc's gRPC version was older than Beam declared exclude group: 'io.grpc', module: 'grpc-netty-shaded' } + implementation library.java.gax_grpc_test implementation library.java.gax_httpjson permitUnusedDeclared library.java.gax_httpjson // BEAM-8755 implementation library.java.google_api_client @@ -146,6 +147,7 @@ dependencies { testImplementation library.java.powermock testImplementation library.java.powermock_mockito testImplementation library.java.joda_time + testImplementation library.java.google_cloud_spanner_test testRuntimeOnly library.java.slf4j_jdk14 } diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerAccessor.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerAccessor.java index e34863ea97de0..a4223dc804eaf 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerAccessor.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerAccessor.java @@ -17,6 +17,7 @@ */ package org.apache.beam.sdk.io.gcp.spanner; +import com.google.api.gax.grpc.testing.LocalChannelProvider; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.FixedHeaderProvider; import com.google.api.gax.rpc.ServerStreamingCallSettings; @@ -107,34 +108,59 @@ public static SpannerAccessor getOrCreate(SpannerConfig spannerConfig) { private static SpannerAccessor createAndConnect(SpannerConfig spannerConfig) { SpannerOptions.Builder builder = SpannerOptions.newBuilder(); - ValueProvider commitDeadline = spannerConfig.getCommitDeadline(); - if (commitDeadline != null && commitDeadline.get().getMillis() > 0) { + // Set retryable codes for all API methods + if (spannerConfig.getRetryableCodes() != null) { + builder + .getSpannerStubSettingsBuilder() + .applyToAllUnaryMethods( + input -> { + input.setRetryableCodes(spannerConfig.getRetryableCodes()); + return null; + }); + builder + .getSpannerStubSettingsBuilder() + .executeStreamingSqlSettings() + .setRetryableCodes(spannerConfig.getRetryableCodes()); + } + // Set commit retry settings + UnaryCallSettings.Builder commitSettings = + builder.getSpannerStubSettingsBuilder().commitSettings(); + ValueProvider commitDeadline = spannerConfig.getCommitDeadline(); + if (spannerConfig.getCommitRetrySettings() != null) { + commitSettings.setRetrySettings(spannerConfig.getCommitRetrySettings()); + } else if (commitDeadline != null && commitDeadline.get().getMillis() > 0) { // Set the GRPC deadline on the Commit API call. - UnaryCallSettings.Builder commitSettings = - builder.getSpannerStubSettingsBuilder().commitSettings(); - RetrySettings.Builder commitRetrySettings = commitSettings.getRetrySettings().toBuilder(); + RetrySettings.Builder commitRetrySettingsBuilder = + commitSettings.getRetrySettings().toBuilder(); commitSettings.setRetrySettings( - commitRetrySettings + commitRetrySettingsBuilder .setTotalTimeout(org.threeten.bp.Duration.ofMillis(commitDeadline.get().getMillis())) .setMaxRpcTimeout(org.threeten.bp.Duration.ofMillis(commitDeadline.get().getMillis())) .setInitialRpcTimeout( org.threeten.bp.Duration.ofMillis(commitDeadline.get().getMillis())) .build()); } - // Setting the timeout for streaming read to 2 hours. This is 1 hour by default - // after BEAM 2.20. + + // Set execute streaming sql retry settings ServerStreamingCallSettings.Builder executeStreamingSqlSettings = builder.getSpannerStubSettingsBuilder().executeStreamingSqlSettings(); - RetrySettings.Builder executeSqlStreamingRetrySettings = - executeStreamingSqlSettings.getRetrySettings().toBuilder(); - executeStreamingSqlSettings.setRetrySettings( - executeSqlStreamingRetrySettings - .setInitialRpcTimeout(org.threeten.bp.Duration.ofMinutes(120)) - .setMaxRpcTimeout(org.threeten.bp.Duration.ofMinutes(120)) - .setTotalTimeout(org.threeten.bp.Duration.ofMinutes(120)) - .build()); + if (spannerConfig.getExecuteStreamingSqlRetrySettings() != null) { + executeStreamingSqlSettings.setRetrySettings( + spannerConfig.getExecuteStreamingSqlRetrySettings()); + } else { + // Setting the timeout for streaming read to 2 hours. This is 1 hour by default + // after BEAM 2.20. + RetrySettings.Builder executeSqlStreamingRetrySettings = + executeStreamingSqlSettings.getRetrySettings().toBuilder(); + executeStreamingSqlSettings.setRetrySettings( + executeSqlStreamingRetrySettings + .setInitialRpcTimeout(org.threeten.bp.Duration.ofMinutes(120)) + .setMaxRpcTimeout(org.threeten.bp.Duration.ofMinutes(120)) + .setTotalTimeout(org.threeten.bp.Duration.ofMinutes(120)) + .build()); + } ValueProvider projectId = spannerConfig.getProjectId(); if (projectId != null) { @@ -151,6 +177,10 @@ private static SpannerAccessor createAndConnect(SpannerConfig spannerConfig) { ValueProvider emulatorHost = spannerConfig.getEmulatorHost(); if (emulatorHost != null) { builder.setEmulatorHost(emulatorHost.get()); + if (spannerConfig.getIsLocalChannelProvider() != null + && spannerConfig.getIsLocalChannelProvider().get()) { + builder.setChannelProvider(LocalChannelProvider.create(emulatorHost.get())); + } builder.setCredentials(NoCredentials.getInstance()); } String userAgentString = USER_AGENT_PREFIX + "/" + ReleaseInfo.getReleaseInfo().getVersion(); diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerConfig.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerConfig.java index 7fcc0b5676b92..a00b7896c35af 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerConfig.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerConfig.java @@ -19,6 +19,8 @@ import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkNotNull; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode.Code; import com.google.auto.value.AutoValue; import com.google.cloud.ServiceFactory; import com.google.cloud.spanner.Options.RpcPriority; @@ -28,6 +30,7 @@ import org.apache.beam.sdk.options.ValueProvider; import org.apache.beam.sdk.transforms.display.DisplayData; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.annotations.VisibleForTesting; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableSet; import org.checkerframework.checker.nullness.qual.Nullable; import org.joda.time.Duration; @@ -56,10 +59,18 @@ public abstract class SpannerConfig implements Serializable { public abstract @Nullable ValueProvider getEmulatorHost(); + public abstract @Nullable ValueProvider getIsLocalChannelProvider(); + public abstract @Nullable ValueProvider getCommitDeadline(); public abstract @Nullable ValueProvider getMaxCumulativeBackoff(); + public abstract @Nullable RetrySettings getExecuteStreamingSqlRetrySettings(); + + public abstract @Nullable RetrySettings getCommitRetrySettings(); + + public abstract @Nullable ImmutableSet getRetryableCodes(); + public abstract @Nullable ValueProvider getRpcPriority(); @VisibleForTesting @@ -117,10 +128,19 @@ public abstract static class Builder { abstract Builder setEmulatorHost(ValueProvider emulatorHost); + abstract Builder setIsLocalChannelProvider(ValueProvider isLocalChannelProvider); + abstract Builder setCommitDeadline(ValueProvider commitDeadline); abstract Builder setMaxCumulativeBackoff(ValueProvider maxCumulativeBackoff); + abstract Builder setExecuteStreamingSqlRetrySettings( + RetrySettings executeStreamingSqlRetrySettings); + + abstract Builder setCommitRetrySettings(RetrySettings commitRetrySettings); + + abstract Builder setRetryableCodes(ImmutableSet retryableCodes); + abstract Builder setServiceFactory(ServiceFactory serviceFactory); abstract Builder setRpcPriority(ValueProvider rpcPriority); @@ -160,6 +180,10 @@ public SpannerConfig withEmulatorHost(ValueProvider emulatorHost) { return toBuilder().setEmulatorHost(emulatorHost).build(); } + public SpannerConfig withIsLocalChannelProvider(ValueProvider isLocalChannelProvider) { + return toBuilder().setIsLocalChannelProvider(isLocalChannelProvider).build(); + } + public SpannerConfig withCommitDeadline(Duration commitDeadline) { return withCommitDeadline(ValueProvider.StaticValueProvider.of(commitDeadline)); } @@ -176,6 +200,21 @@ public SpannerConfig withMaxCumulativeBackoff(ValueProvider maxCumulat return toBuilder().setMaxCumulativeBackoff(maxCumulativeBackoff).build(); } + public SpannerConfig withExecuteStreamingSqlRetrySettings( + RetrySettings executeStreamingSqlRetrySettings) { + return toBuilder() + .setExecuteStreamingSqlRetrySettings(executeStreamingSqlRetrySettings) + .build(); + } + + public SpannerConfig withCommitRetrySettings(RetrySettings commitRetrySettings) { + return toBuilder().setCommitRetrySettings(commitRetrySettings).build(); + } + + public SpannerConfig withRetryableCodes(ImmutableSet retryableCodes) { + return toBuilder().setRetryableCodes(retryableCodes).build(); + } + @VisibleForTesting SpannerConfig withServiceFactory(ServiceFactory serviceFactory) { return toBuilder().setServiceFactory(serviceFactory).build(); diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java index 9fd929d097132..863d88ab54e68 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java @@ -23,6 +23,8 @@ import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkArgument; import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkNotNull; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode.Code; import com.google.auto.value.AutoValue; import com.google.cloud.ServiceFactory; import com.google.cloud.Timestamp; @@ -79,6 +81,7 @@ import org.apache.beam.sdk.metrics.Distribution; import org.apache.beam.sdk.metrics.Metrics; import org.apache.beam.sdk.options.ValueProvider; +import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider; import org.apache.beam.sdk.schemas.Schema; import org.apache.beam.sdk.transforms.Create; import org.apache.beam.sdk.transforms.DoFn; @@ -115,6 +118,7 @@ import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.MoreObjects; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Stopwatch; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableSet; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Iterables; import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.primitives.UnsignedBytes; import org.checkerframework.checker.nullness.qual.Nullable; @@ -1349,6 +1353,8 @@ public abstract static class ReadChangeStream abstract @Nullable String getMetadataDatabase(); + abstract @Nullable String getMetadataTable(); + abstract Timestamp getInclusiveStartAt(); abstract @Nullable Timestamp getInclusiveEndAt(); @@ -1370,6 +1376,8 @@ abstract static class Builder { abstract Builder setMetadataDatabase(String metadataDatabase); + abstract Builder setMetadataTable(String metadataTable); + abstract Builder setInclusiveStartAt(Timestamp inclusiveStartAt); abstract Builder setInclusiveEndAt(Timestamp inclusiveEndAt); @@ -1434,6 +1442,10 @@ public ReadChangeStream withMetadataDatabase(String metadataDatabase) { return toBuilder().setMetadataDatabase(metadataDatabase).build(); } + public ReadChangeStream withMetadataTable(String metadataTable) { + return toBuilder().setMetadataTable(metadataTable).build(); + } + /** Specifies the time that the change stream should be read from. */ public ReadChangeStream withInclusiveStartAt(Timestamp timestamp) { return toBuilder().setInclusiveStartAt(timestamp).build(); @@ -1497,7 +1509,8 @@ && getInclusiveStartAt().toSqlTimestamp().after(getInclusiveEndAt().toSqlTimesta final String partitionMetadataDatabaseId = MoreObjects.firstNonNull(getMetadataDatabase(), changeStreamDatabaseId.getDatabase()); final String partitionMetadataTableName = - generatePartitionMetadataTableName(partitionMetadataDatabaseId); + MoreObjects.firstNonNull( + getMetadataTable(), generatePartitionMetadataTableName(partitionMetadataDatabaseId)); if (getTraceSampleProbability() != null) { TraceConfig globalTraceConfig = Tracing.getTraceConfig(); @@ -1511,16 +1524,36 @@ && getInclusiveStartAt().toSqlTimestamp().after(getInclusiveEndAt().toSqlTimesta .spanBuilder("SpannerIO.ReadChangeStream.expand") .setRecordEvents(true) .startScopedSpan()) { - final SpannerConfig changeStreamSpannerConfig = getSpannerConfig(); + SpannerConfig changeStreamSpannerConfig = getSpannerConfig(); + // Set default retryable errors for ReadChangeStream + if (changeStreamSpannerConfig.getRetryableCodes() == null) { + ImmutableSet defaultRetryableCodes = + ImmutableSet.of(Code.UNAVAILABLE, Code.ABORTED); + changeStreamSpannerConfig = + changeStreamSpannerConfig + .toBuilder() + .setRetryableCodes(defaultRetryableCodes) + .build(); + } + // Set default retry timeouts for ReadChangeStream + if (changeStreamSpannerConfig.getExecuteStreamingSqlRetrySettings() == null) { + changeStreamSpannerConfig = + changeStreamSpannerConfig + .toBuilder() + .setExecuteStreamingSqlRetrySettings( + RetrySettings.newBuilder() + .setTotalTimeout(org.threeten.bp.Duration.ofMinutes(5)) + .setInitialRpcTimeout(org.threeten.bp.Duration.ofMinutes(1)) + .setMaxRpcTimeout(org.threeten.bp.Duration.ofMinutes(1)) + .build()) + .build(); + } final SpannerConfig partitionMetadataSpannerConfig = - SpannerConfig.create() - .withProjectId(changeStreamSpannerConfig.getProjectId()) - .withHost(changeStreamSpannerConfig.getHost()) - .withInstanceId(partitionMetadataInstanceId) - .withDatabaseId(partitionMetadataDatabaseId) - .withCommitDeadline(changeStreamSpannerConfig.getCommitDeadline()) - .withEmulatorHost(changeStreamSpannerConfig.getEmulatorHost()) - .withMaxCumulativeBackoff(changeStreamSpannerConfig.getMaxCumulativeBackoff()); + changeStreamSpannerConfig + .toBuilder() + .setInstanceId(StaticValueProvider.of(partitionMetadataInstanceId)) + .setDatabaseId(StaticValueProvider.of(partitionMetadataDatabaseId)) + .build(); final String changeStreamName = getChangeStreamName(); // FIXME: The backend only supports microsecond granularity. Remove when fixed. final Timestamp startTimestamp = TimestampConverter.truncateNanos(getInclusiveStartAt()); diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/changestreams/SpannerChangeStreamErrorTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/changestreams/SpannerChangeStreamErrorTest.java new file mode 100644 index 0000000000000..34d7cd4a91a7d --- /dev/null +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/changestreams/SpannerChangeStreamErrorTest.java @@ -0,0 +1,497 @@ +/* + * 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.gcp.spanner.changestreams; + +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_CREATED_AT; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_END_TIMESTAMP; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_FINISHED_AT; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_HEARTBEAT_MILLIS; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_PARENT_TOKENS; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_PARTITION_TOKEN; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_RUNNING_AT; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_SCHEDULED_AT; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_START_TIMESTAMP; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_STATE; +import static org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.PartitionMetadataAdminDao.COLUMN_WATERMARK; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.grpc.testing.MockServiceHelper; +import com.google.api.gax.retrying.RetrySettings; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.MockSpannerServiceImpl; +import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.cloud.spanner.Statement; +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.Value; +import com.google.spanner.v1.ExecuteSqlRequest; +import com.google.spanner.v1.ResultSet; +import com.google.spanner.v1.ResultSetMetadata; +import com.google.spanner.v1.StructType; +import com.google.spanner.v1.StructType.Field; +import com.google.spanner.v1.Type; +import com.google.spanner.v1.TypeCode; +import io.grpc.Status; +import java.io.Serializable; +import java.util.Collections; +import org.apache.beam.sdk.Pipeline.PipelineExecutionException; +import org.apache.beam.sdk.io.gcp.spanner.SpannerConfig; +import org.apache.beam.sdk.io.gcp.spanner.SpannerIO; +import org.apache.beam.sdk.io.gcp.spanner.changestreams.dao.DaoFactory; +import org.apache.beam.sdk.io.gcp.spanner.changestreams.model.PartitionMetadata.State; +import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableSet; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SpannerChangeStreamErrorTest implements Serializable { + + public static final String SPANNER_HOST = "my-host"; + private static final String TEST_PROJECT = "my-project"; + private static final String TEST_INSTANCE = "my-instance"; + private static final String TEST_DATABASE = "my-database"; + private static final String TEST_TABLE = "my-metadata-table"; + private static final String TEST_CHANGE_STREAM = "my-change-stream"; + + @Rule + public final transient TestPipeline pipeline = + TestPipeline.create().enableAbandonedNodeEnforcement(false); + + @Rule public final transient ExpectedException thrown = ExpectedException.none(); + + private MockSpannerServiceImpl mockSpannerService; + private MockServiceHelper serviceHelper; + + @Before + public void setUp() throws Exception { + mockSpannerService = new MockSpannerServiceImpl(); + serviceHelper = + new MockServiceHelper(SPANNER_HOST, Collections.singletonList(mockSpannerService)); + serviceHelper.start(); + serviceHelper.reset(); + } + + @After + public void tearDown() throws NoSuchFieldException, IllegalAccessException { + serviceHelper.reset(); + serviceHelper.stop(); + mockSpannerService.reset(); + resetDaoFactoryFields(); + } + + @Test + public void testResourceExhaustedDoesNotRetry() { + mockSpannerService.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofStickyException(Status.RESOURCE_EXHAUSTED.asRuntimeException())); + + final Timestamp now = Timestamp.now(); + final Timestamp after3Seconds = + Timestamp.ofTimeSecondsAndNanos(now.getSeconds() + 3, now.getNanos()); + try { + pipeline.apply( + SpannerIO.readChangeStream() + .withSpannerConfig(getSpannerConfig()) + .withChangeStreamName(TEST_CHANGE_STREAM) + .withMetadataDatabase(TEST_DATABASE) + .withInclusiveStartAt(now) + .withInclusiveEndAt(after3Seconds)); + pipeline.run().waitUntilFinish(); + } finally { + thrown.expect(PipelineExecutionException.class); + thrown.expectMessage(ErrorCode.RESOURCE_EXHAUSTED.name()); + } + } + + @Test + public void testUnavailableExceptionRetries() { + mockSpannerService.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofExceptions( + ImmutableSet.of( + Status.UNAVAILABLE.asRuntimeException(), + Status.RESOURCE_EXHAUSTED.asRuntimeException()))); + + final Timestamp now = Timestamp.now(); + final Timestamp after3Seconds = + Timestamp.ofTimeSecondsAndNanos(now.getSeconds() + 3, now.getNanos()); + try { + pipeline.apply( + SpannerIO.readChangeStream() + .withSpannerConfig(getSpannerConfig()) + .withChangeStreamName(TEST_CHANGE_STREAM) + .withMetadataDatabase(TEST_DATABASE) + .withInclusiveStartAt(now) + .withInclusiveEndAt(after3Seconds)); + pipeline.run().waitUntilFinish(); + } finally { + assertThat( + mockSpannerService.countRequestsOfType(ExecuteSqlRequest.class), Matchers.greaterThan(1)); + thrown.expect(PipelineExecutionException.class); + thrown.expectMessage(ErrorCode.RESOURCE_EXHAUSTED.name()); + } + } + + @Test + public void testAbortedExceptionRetries() { + mockSpannerService.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofStickyException(Status.ABORTED.asRuntimeException())); + + final Timestamp now = Timestamp.now(); + final Timestamp after3Seconds = + Timestamp.ofTimeSecondsAndNanos(now.getSeconds() + 3, now.getNanos()); + try { + pipeline.apply( + SpannerIO.readChangeStream() + .withSpannerConfig(getSpannerConfig()) + .withChangeStreamName(TEST_CHANGE_STREAM) + .withMetadataDatabase(TEST_DATABASE) + .withInclusiveStartAt(now) + .withInclusiveEndAt(after3Seconds)); + pipeline.run().waitUntilFinish(); + } finally { + assertThat( + mockSpannerService.countRequestsOfType(ExecuteSqlRequest.class), Matchers.greaterThan(1)); + thrown.expect(PipelineExecutionException.class); + thrown.expectMessage(ErrorCode.ABORTED.name()); + } + } + + @Test + public void testUnknownExceptionDoesNotRetry() { + mockSpannerService.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofStickyException(Status.UNKNOWN.asRuntimeException())); + + final Timestamp now = Timestamp.now(); + final Timestamp after3Seconds = + Timestamp.ofTimeSecondsAndNanos(now.getSeconds() + 3, now.getNanos()); + try { + pipeline.apply( + SpannerIO.readChangeStream() + .withSpannerConfig(getSpannerConfig()) + .withChangeStreamName(TEST_CHANGE_STREAM) + .withMetadataDatabase(TEST_DATABASE) + .withInclusiveStartAt(now) + .withInclusiveEndAt(after3Seconds)); + pipeline.run().waitUntilFinish(); + } finally { + assertThat( + mockSpannerService.countRequestsOfType(ExecuteSqlRequest.class), Matchers.equalTo(1)); + thrown.expect(PipelineExecutionException.class); + thrown.expectMessage(ErrorCode.UNKNOWN.name()); + } + } + + @Test + public void testInvalidRecordReceived() { + final Timestamp now = Timestamp.now(); + final Timestamp after3Seconds = + Timestamp.ofTimeSecondsAndNanos(now.getSeconds() + 3, now.getNanos()); + + mockTableExists(); + ResultSet getPartitionResultSet = mockGetParentPartition(now, after3Seconds); + mockGetWatermark(now); + mockGetPartitionsAfter( + Timestamp.ofTimeSecondsAndNanos(now.getSeconds(), now.getNanos() + 1000), + getPartitionResultSet); + mockGetPartitionsAfter( + Timestamp.ofTimeSecondsAndNanos(now.getSeconds(), now.getNanos() - 1000), + getPartitionResultSet); + mockInvalidChangeStreamRecordReceived(now, after3Seconds); + + try { + pipeline.apply( + SpannerIO.readChangeStream() + .withSpannerConfig(getSpannerConfig()) + .withChangeStreamName(TEST_CHANGE_STREAM) + .withMetadataDatabase(TEST_DATABASE) + .withMetadataTable(TEST_TABLE) + .withInclusiveStartAt(now) + .withInclusiveEndAt(after3Seconds)); + pipeline.run().waitUntilFinish(); + } finally { + thrown.expect(PipelineExecutionException.class); + thrown.expectMessage("Field not found"); + } + } + + private void mockInvalidChangeStreamRecordReceived(Timestamp now, Timestamp after3Seconds) { + Statement changeStreamQueryStatement = + Statement.newBuilder( + "SELECT * FROM READ_my-change-stream( start_timestamp => @startTimestamp, end_timestamp => @endTimestamp, partition_token => @partitionToken, read_options => null, heartbeat_milliseconds => @heartbeatMillis)") + .bind("startTimestamp") + .to(now) + .bind("endTimestamp") + .to(after3Seconds) + .bind("partitionToken") + .to((String) null) + .bind("heartbeatMillis") + .to(500) + .build(); + ResultSetMetadata readChangeStreamResultSetMetadata = + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("COL1") + .setType( + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + Type.newBuilder() + .setCode(TypeCode.STRUCT) + .setStructType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("field_name") + .setType( + Type.newBuilder() + .setCode(TypeCode.STRUCT) + .setStructType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setType( + Type + .newBuilder() + .setCode( + TypeCode + .STRING))))))))))) + .build(); + ResultSet readChangeStreamResultSet = + ResultSet.newBuilder() + .addRows( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue( + "bad_value"))))))))) + .setMetadata(readChangeStreamResultSetMetadata) + .build(); + mockSpannerService.putStatementResult( + StatementResult.query(changeStreamQueryStatement, readChangeStreamResultSet)); + } + + private void mockGetPartitionsAfter(Timestamp timestamp, ResultSet getPartitionResultSet) { + Statement getPartitionsAfterStatement = + Statement.newBuilder( + "SELECT * FROM my-metadata-table WHERE CreatedAt > @timestamp ORDER BY CreatedAt ASC, StartTimestamp ASC") + .bind("timestamp") + .to(Timestamp.ofTimeSecondsAndNanos(timestamp.getSeconds(), timestamp.getNanos())) + .build(); + mockSpannerService.putStatementResult( + StatementResult.query(getPartitionsAfterStatement, getPartitionResultSet)); + } + + private void mockGetWatermark(Timestamp now) { + Statement watermarkStatement = + Statement.newBuilder( + "SELECT Watermark FROM my-metadata-table WHERE State != @state ORDER BY Watermark ASC LIMIT 1") + .bind("state") + .to(State.FINISHED.name()) + .build(); + ResultSetMetadata watermarkResultSetMetadata = + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("Watermark") + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .build()) + .build(); + ResultSet watermarkResultSet = + ResultSet.newBuilder() + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue(now.toString()).build()) + .build()) + .setMetadata(watermarkResultSetMetadata) + .build(); + mockSpannerService.putStatementResult( + StatementResult.query(watermarkStatement, watermarkResultSet)); + } + + private ResultSet mockGetParentPartition(Timestamp now, Timestamp after3Seconds) { + Statement getPartitionStatement = + Statement.newBuilder("SELECT * FROM my-metadata-table WHERE PartitionToken = @partition") + .bind("partition") + .to("Parent0") + .build(); + ResultSet getPartitionResultSet = + ResultSet.newBuilder() + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("Parent0")) + .addValues(Value.newBuilder().setListValue(ListValue.newBuilder().build())) + .addValues(Value.newBuilder().setStringValue(now.toString())) + .addValues(Value.newBuilder().setStringValue(after3Seconds.toString())) + .addValues(Value.newBuilder().setStringValue("500")) + .addValues(Value.newBuilder().setStringValue(State.CREATED.name())) + .addValues(Value.newBuilder().setStringValue(now.toString())) + .addValues(Value.newBuilder().setStringValue(now.toString())) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .build()) + .setMetadata(PARTITION_METADATA_RESULT_SET_METADATA) + .build(); + mockSpannerService.putStatementResult( + StatementResult.query(getPartitionStatement, getPartitionResultSet)); + return getPartitionResultSet; + } + + private void mockTableExists() { + Statement tableExistsStatement = + Statement.of( + "SELECT t.table_name FROM information_schema.tables AS t WHERE t.table_catalog = '' AND t.table_schema = '' AND t.table_name = 'my-metadata-table'"); + ResultSetMetadata tableExistsResultSetMetadata = + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("table_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .build()) + .build(); + ResultSet tableExistsResultSet = + ResultSet.newBuilder() + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue(TEST_TABLE).build()) + .build()) + .setMetadata(tableExistsResultSetMetadata) + .build(); + mockSpannerService.putStatementResult( + StatementResult.query(tableExistsStatement, tableExistsResultSet)); + } + + private SpannerConfig getSpannerConfig() { + RetrySettings quickRetrySettings = + RetrySettings.newBuilder() + .setInitialRetryDelay(org.threeten.bp.Duration.ofMillis(250)) + .setMaxRetryDelay(org.threeten.bp.Duration.ofSeconds(1)) + .setRetryDelayMultiplier(5) + .setTotalTimeout(org.threeten.bp.Duration.ofSeconds(1)) + .build(); + return SpannerConfig.create() + .withEmulatorHost(StaticValueProvider.of(SPANNER_HOST)) + .withIsLocalChannelProvider(StaticValueProvider.of(true)) + .withCommitRetrySettings(quickRetrySettings) + .withExecuteStreamingSqlRetrySettings(quickRetrySettings) + .withProjectId(TEST_PROJECT) + .withInstanceId(TEST_INSTANCE) + .withDatabaseId(TEST_DATABASE); + } + + private static void resetDaoFactoryFields() throws NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field partitionMetadataAdminDaoField = + DaoFactory.class.getDeclaredField("partitionMetadataAdminDao"); + partitionMetadataAdminDaoField.setAccessible(true); + partitionMetadataAdminDaoField.set(null, null); + java.lang.reflect.Field partitionMetadataDaoInstanceField = + DaoFactory.class.getDeclaredField("partitionMetadataDaoInstance"); + partitionMetadataDaoInstanceField.setAccessible(true); + partitionMetadataDaoInstanceField.set(null, null); + java.lang.reflect.Field changeStreamDaoInstanceField = + DaoFactory.class.getDeclaredField("changeStreamDaoInstance"); + changeStreamDaoInstanceField.setAccessible(true); + changeStreamDaoInstanceField.set(null, null); + } + + private static final ResultSetMetadata PARTITION_METADATA_RESULT_SET_METADATA = + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName(COLUMN_PARTITION_TOKEN) + .setType(Type.newBuilder().setCode(TypeCode.STRING)) + .build()) + .addFields( + Field.newBuilder() + .setName(COLUMN_PARENT_TOKENS) + .setType( + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING))) + .build()) + .addFields( + Field.newBuilder() + .setName(COLUMN_START_TIMESTAMP) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .addFields( + Field.newBuilder() + .setName(COLUMN_END_TIMESTAMP) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .addFields( + Field.newBuilder() + .setName(COLUMN_HEARTBEAT_MILLIS) + .setType(Type.newBuilder().setCode(TypeCode.INT64))) + .addFields( + Field.newBuilder() + .setName(COLUMN_STATE) + .setType(Type.newBuilder().setCode(TypeCode.STRING))) + .addFields( + Field.newBuilder() + .setName(COLUMN_WATERMARK) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .addFields( + Field.newBuilder() + .setName(COLUMN_CREATED_AT) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .addFields( + Field.newBuilder() + .setName(COLUMN_SCHEDULED_AT) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .addFields( + Field.newBuilder() + .setName(COLUMN_RUNNING_AT) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .addFields( + Field.newBuilder() + .setName(COLUMN_FINISHED_AT) + .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))) + .build()) + .build(); +} From 63c8e5bcbae018e50f033b9220a81af3fc08cedc Mon Sep 17 00:00:00 2001 From: Miguel Hernandez Date: Fri, 25 Feb 2022 08:53:00 -0600 Subject: [PATCH 36/61] [BEAM-12777] Removed current docs version redirect --- website/www/site/static/.htaccess | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/www/site/static/.htaccess b/website/www/site/static/.htaccess index 45c44bcf57ff6..a2ef056a262cb 100644 --- a/website/www/site/static/.htaccess +++ b/website/www/site/static/.htaccess @@ -20,7 +20,5 @@ RewriteRule ^(.*)$ https://beam.apache.org/$1 [L,R=301] # path /documentation/sdks/(javadoc|pydoc)/.. # The following redirect maintains the previously supported URLs. RedirectMatch permanent "/documentation/sdks/(javadoc|pydoc)(.*)" "https://beam.apache.org/releases/$1$2" -# Keep this updated to point to the current release. -RedirectMatch "/releases/([^/]+)/current(.*)" "https://beam.apache.org/releases/$1/2.36.0$2" RedirectMatch "/contribute/design-documents" "https://cwiki.apache.org/confluence/display/BEAM/Design+Documents" From c75807ca3de6f18f3594162713e28fbdadbe7716 Mon Sep 17 00:00:00 2001 From: scwhittle Date: Fri, 25 Feb 2022 18:35:05 +0100 Subject: [PATCH 37/61] Memoize some objects for timer processing to reduce overhead. (#16207) --- .../core/metrics/SimpleStateRegistry.java | 17 ++++--- .../beam/fn/harness/FnApiDoFnRunner.java | 46 +++++++------------ .../state/FnApiTimerBundleTracker.java | 4 ++ .../control/ProcessBundleHandlerTest.java | 1 + 4 files changed, 33 insertions(+), 35 deletions(-) diff --git a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleStateRegistry.java b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleStateRegistry.java index a9cb6441c3ad0..94744d0cf74a8 100644 --- a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleStateRegistry.java +++ b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleStateRegistry.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.apache.beam.model.pipeline.v1.MetricsApi.MonitoringInfo; import org.apache.beam.vendor.grpc.v1p43p2.com.google.protobuf.ByteString; @@ -64,12 +65,16 @@ public Map getExecutionTimeMonitoringData(ShortIdMap shortId for (SimpleExecutionState state : executionStates) { if (state.getTotalMillis() != 0) { String shortId = state.getTotalMillisShortId(shortIds); - if (result.containsKey(shortId)) { - // This can happen due to flatten unzipping. - result.put(shortId, state.mergeTotalMillisPayload(result.get(shortId))); - } else { - result.put(shortId, state.getTotalMillisPayload()); - } + result.compute( + shortId, + (String k, @Nullable ByteString existing) -> { + if (existing != null) { + // This can happen due to flatten unzipping. + return state.mergeTotalMillisPayload(existing); + } else { + return state.getTotalMillisPayload(); + } + }); } } return result; diff --git a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java index 7a20afbd151e8..89c05b8f9f238 100644 --- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java +++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java @@ -180,6 +180,7 @@ static class Factory @@ -242,6 +243,7 @@ static class Factory onTimerContext; private final OnWindowExpirationContext onWindowExpirationContext; private final FinishBundleArgumentProvider finishBundleArgumentProvider; + private final Duration allowedLateness; /** * Used to guarantee a consistent view of this {@link FnApiDoFnRunner} while setting up for {@link @@ -344,6 +346,7 @@ private interface TriFunction { Map windowingStrategies, Consumer addStartFunction, Consumer addFinishFunction, + Consumer addResetFunction, Consumer addTearDownFunction, Function>> getPCollectionConsumer, TriFunction>, Coder> addPCollectionConsumer, @@ -457,6 +460,13 @@ private interface TriFunction { } timerFamilyInfos = timerFamilyInfosBuilder.build(); + this.mainInputId = ParDoTranslation.getMainInputName(pTransform); + this.allowedLateness = + rehydratedComponents + .getPCollection(pTransform.getInputsOrThrow(mainInputId)) + .getWindowingStrategy() + .getAllowedLateness(); + } catch (IOException exn) { throw new IllegalArgumentException("Malformed ParDoPayload", exn); } @@ -473,12 +483,11 @@ private interface TriFunction { this.bundleFinalizer = bundleFinalizer; this.onTimerContext = new OnTimerContext(); this.onWindowExpirationContext = new OnWindowExpirationContext<>(); + this.timerBundleTracker = + new FnApiTimerBundleTracker( + keyCoder, windowCoder, this::getCurrentKey, () -> currentWindow); + addResetFunction.accept(timerBundleTracker::reset); - try { - this.mainInputId = ParDoTranslation.getMainInputName(pTransform); - } catch (IOException e) { - throw new RuntimeException(e); - } this.mainOutputConsumers = (Collection>>) (Collection) localNameToConsumer.get(mainOutputTag.getId()); @@ -756,9 +765,6 @@ private Object getCurrentKey() { } private void startBundle() { - timerBundleTracker = - new FnApiTimerBundleTracker( - keyCoder, windowCoder, this::getCurrentKey, () -> currentWindow); doFnInvoker.invokeStartBundle(startBundleArgumentProvider); } @@ -1694,14 +1700,9 @@ private void processTimer( // The timerIdOrTimerFamilyId contains either a timerId from timer declaration or // timerFamilyId // from timer family declaration. - String timerId = - timerIdOrTimerFamilyId.startsWith(TimerFamilyDeclaration.PREFIX) - ? "" - : timerIdOrTimerFamilyId; - String timerFamilyId = - timerIdOrTimerFamilyId.startsWith(TimerFamilyDeclaration.PREFIX) - ? timerIdOrTimerFamilyId - : ""; + boolean isFamily = timerIdOrTimerFamilyId.startsWith(TimerFamilyDeclaration.PREFIX); + String timerId = isFamily ? "" : timerIdOrTimerFamilyId; + String timerFamilyId = isFamily ? timerIdOrTimerFamilyId : ""; processTimerDirect(timerFamilyId, timerId, timeDomain, timer); } } @@ -1778,7 +1779,6 @@ private class FnApiTimer implements org.apache.beam.sdk.state.Timer { private final K userKey; private final String dynamicTimerTag; private final TimeDomain timeDomain; - private final Duration allowedLateness; private final Instant fireTimestamp; private final Instant elementTimestampOrTimerHoldTimestamp; private final BoundedWindow boundedWindow; @@ -1817,18 +1817,6 @@ private class FnApiTimer implements org.apache.beam.sdk.state.Timer { throw new IllegalArgumentException( String.format("Unknown or unsupported time domain %s", timeDomain)); } - - try { - this.allowedLateness = - rehydratedComponents - .getPCollection( - pTransform.getInputsOrThrow(ParDoTranslation.getMainInputName(pTransform))) - .getWindowingStrategy() - .getAllowedLateness(); - } catch (IOException e) { - throw new IllegalArgumentException( - String.format("Unable to get allowed lateness for timer %s", timerIdOrFamily)); - } } @Override diff --git a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/FnApiTimerBundleTracker.java b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/FnApiTimerBundleTracker.java index f8a1dc3f9a258..39f735d98efe4 100644 --- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/FnApiTimerBundleTracker.java +++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/FnApiTimerBundleTracker.java @@ -153,6 +153,10 @@ public FnApiTimerBundleTracker( }); } + public void reset() { + timerModifications.clear(); + } + public void timerModified(String timerFamilyOrId, TimeDomain timeDomain, Timer timer) { ByteString keyString = encodedCurrentKeySupplier.get(); ByteString windowString = encodedCurrentWindowSupplier.get(); 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 13b8c24ed74af..9970e802ee340 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 @@ -509,6 +509,7 @@ public void testOrderOfSetupTeardownCalls() throws Exception { PCollection.newBuilder() .setWindowingStrategyId("window-strategy") .setCoderId("2L-output-coder") + .setIsBounded(IsBounded.Enum.BOUNDED) .build()) .putWindowingStrategies( "window-strategy", From daabeba1e9958dea7a132c96f54754b2706e4601 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Fri, 25 Feb 2022 18:37:50 +0100 Subject: [PATCH 38/61] [BEAM-13965] Use TypeDeserializer if type information is available to support polymorphic types. (#16913) --- .../sdk/options/PipelineOptionsFactory.java | 22 ++++---- .../options/PipelineOptionsFactoryTest.java | 50 ++++++++++++++++++- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java index 4cb7eb4eac45f..d014f08ac2b8c 100644 --- a/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java +++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java @@ -36,11 +36,13 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext; import com.fasterxml.jackson.databind.deser.impl.MethodProperty; +import com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.AnnotationCollector; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import com.fasterxml.jackson.databind.introspect.TypeResolutionContext; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.node.TreeTraversingParser; import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; import com.fasterxml.jackson.databind.type.TypeBindings; @@ -1730,21 +1732,23 @@ private static JsonDeserializer computeDeserializerForMethod(Method meth BeanProperty prop = createBeanProperty(method); AnnotatedMember annotatedMethod = prop.getMember(); + DefaultDeserializationContext context = DESERIALIZATION_CONTEXT.get(); Object maybeDeserializerClass = - DESERIALIZATION_CONTEXT - .get() - .getAnnotationIntrospector() - .findDeserializer(annotatedMethod); + context.getAnnotationIntrospector().findDeserializer(annotatedMethod); JsonDeserializer jsonDeserializer = - DESERIALIZATION_CONTEXT - .get() - .deserializerInstance(annotatedMethod, maybeDeserializerClass); + context.deserializerInstance(annotatedMethod, maybeDeserializerClass); if (jsonDeserializer == null) { - jsonDeserializer = - DESERIALIZATION_CONTEXT.get().findContextualValueDeserializer(prop.getType(), prop); + jsonDeserializer = context.findContextualValueDeserializer(prop.getType(), prop); } + + TypeDeserializer typeDeserializer = + context.getFactory().findTypeDeserializer(context.getConfig(), prop.getType()); + if (typeDeserializer != null) { + jsonDeserializer = new TypeWrappedDeserializer(typeDeserializer, jsonDeserializer); + } + return jsonDeserializer; } catch (JsonMappingException e) { throw new RuntimeException(e); diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/options/PipelineOptionsFactoryTest.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/options/PipelineOptionsFactoryTest.java index 94fd3f41faacd..ffdfbc8681a13 100644 --- a/sdks/java/core/src/test/java/org/apache/beam/sdk/options/PipelineOptionsFactoryTest.java +++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/options/PipelineOptionsFactoryTest.java @@ -39,6 +39,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; @@ -60,7 +62,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import org.apache.beam.model.jobmanagement.v1.JobApi.PipelineOptionDescriptor; import org.apache.beam.model.jobmanagement.v1.JobApi.PipelineOptionType; @@ -1070,6 +1071,53 @@ public void testComplexTypes() { assertEquals("value2", options.getObjectValue().get().value2); } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) + @JsonSubTypes({ + @JsonSubTypes.Type(value = PolymorphicTypeOne.class, name = "one"), + @JsonSubTypes.Type(value = PolymorphicTypeTwo.class, name = "two") + }) + public abstract static class PolymorphicType { + String key; + + @JsonProperty("key") + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + } + + public static class PolymorphicTypeOne extends PolymorphicType {} + + public static class PolymorphicTypeTwo extends PolymorphicType {} + + public interface PolymorphicTypes extends PipelineOptions { + PolymorphicType getObject(); + + void setObject(PolymorphicType value); + + ValueProvider getObjectValue(); + + void setObjectValue(ValueProvider value); + } + + @Test + public void testPolymorphicType() { + String[] args = + new String[] { + "--object={\"key\":\"value\",\"@type\":\"one\"}", + "--objectValue={\"key\":\"value\",\"@type\":\"two\"}" + }; + PolymorphicTypes options = PipelineOptionsFactory.fromArgs(args).as(PolymorphicTypes.class); + assertEquals("value", options.getObject().key); + assertEquals(PolymorphicTypeOne.class, options.getObject().getClass()); + + assertEquals("value", options.getObjectValue().get().key); + assertEquals(PolymorphicTypeTwo.class, options.getObjectValue().get().getClass()); + } + @Test public void testMissingArgument() { String[] args = new String[] {}; From 75c877f2eb8faf12173fb9b6834378719966eb7c Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 25 Feb 2022 14:15:47 -0500 Subject: [PATCH 39/61] [BEAM-13912] Add more coverage for dataflow.go (#16903) --- sdks/go/pkg/beam/runners/dataflow/dataflow.go | 99 +++++++------ .../beam/runners/dataflow/dataflow_test.go | 135 +++++++++++++++++- 2 files changed, 188 insertions(+), 46 deletions(-) diff --git a/sdks/go/pkg/beam/runners/dataflow/dataflow.go b/sdks/go/pkg/beam/runners/dataflow/dataflow.go index 4cd877f5afde0..1a7da076755a0 100644 --- a/sdks/go/pkg/beam/runners/dataflow/dataflow.go +++ b/sdks/go/pkg/beam/runners/dataflow/dataflow.go @@ -159,8 +159,59 @@ func Execute(ctx context.Context, p *beam.Pipeline) (beam.PipelineResult, error) panic("Beam has not been initialized. Call beam.Init() before pipeline construction.") } - // (1) Gather job options + beam.PipelineOptions.LoadOptionsFromFlags(flagFilter) + opts, err := getJobOptions(ctx) + if err != nil { + return nil, err + } + + // (1) Build and submit + // NOTE(herohde) 10/8/2018: the last segment of the names must be "worker" and "dataflow-worker.jar". + id := fmt.Sprintf("go-%v-%v", atomic.AddInt32(&unique, 1), time.Now().UnixNano()) + + modelURL := gcsx.Join(*stagingLocation, id, "model") + workerURL := gcsx.Join(*stagingLocation, id, "worker") + jarURL := gcsx.Join(*stagingLocation, id, "dataflow-worker.jar") + xlangURL := gcsx.Join(*stagingLocation, id, "xlang") + + edges, _, err := p.Build() + if err != nil { + return nil, err + } + artifactURLs, err := dataflowlib.ResolveXLangArtifacts(ctx, edges, opts.Project, xlangURL) + if err != nil { + return nil, errors.WithContext(err, "resolving cross-language artifacts") + } + opts.ArtifactURLs = artifactURLs + environment, err := graphx.CreateEnvironment(ctx, jobopts.GetEnvironmentUrn(ctx), getContainerImage) + if err != nil { + return nil, errors.WithContext(err, "creating environment for model pipeline") + } + model, err := graphx.Marshal(edges, &graphx.Options{Environment: environment}) + if err != nil { + return nil, errors.WithContext(err, "generating model pipeline") + } + err = pipelinex.ApplySdkImageOverrides(model, jobopts.GetSdkImageOverrides()) + if err != nil { + return nil, errors.WithContext(err, "applying container image overrides") + } + + if *dryRun { + log.Info(ctx, "Dry-run: not submitting job!") + + log.Info(ctx, proto.MarshalTextString(model)) + job, err := dataflowlib.Translate(ctx, model, opts, workerURL, jarURL, modelURL) + if err != nil { + return nil, err + } + dataflowlib.PrintJob(ctx, job) + return nil, nil + } + return dataflowlib.Execute(ctx, model, opts, workerURL, jarURL, modelURL, *endpoint, *executeAsync) +} + +func getJobOptions(ctx context.Context) (*dataflowlib.JobOptions, error) { project := gcpopts.GetProjectFromFlagOrEnvironment(ctx) if project == "" { return nil, errors.New("no Google Cloud project specified. Use --project=") @@ -254,51 +305,9 @@ func Execute(ctx context.Context, p *beam.Pipeline) (beam.PipelineResult, error) opts.TempLocation = gcsx.Join(*stagingLocation, "tmp") } - // (1) Build and submit - // NOTE(herohde) 10/8/2018: the last segment of the names must be "worker" and "dataflow-worker.jar". - id := fmt.Sprintf("go-%v-%v", atomic.AddInt32(&unique, 1), time.Now().UnixNano()) - - modelURL := gcsx.Join(*stagingLocation, id, "model") - workerURL := gcsx.Join(*stagingLocation, id, "worker") - jarURL := gcsx.Join(*stagingLocation, id, "dataflow-worker.jar") - xlangURL := gcsx.Join(*stagingLocation, id, "xlang") - - edges, _, err := p.Build() - if err != nil { - return nil, err - } - artifactURLs, err := dataflowlib.ResolveXLangArtifacts(ctx, edges, opts.Project, xlangURL) - if err != nil { - return nil, errors.WithContext(err, "resolving cross-language artifacts") - } - opts.ArtifactURLs = artifactURLs - environment, err := graphx.CreateEnvironment(ctx, jobopts.GetEnvironmentUrn(ctx), getContainerImage) - if err != nil { - return nil, errors.WithContext(err, "creating environment for model pipeline") - } - model, err := graphx.Marshal(edges, &graphx.Options{Environment: environment}) - if err != nil { - return nil, errors.WithContext(err, "generating model pipeline") - } - err = pipelinex.ApplySdkImageOverrides(model, jobopts.GetSdkImageOverrides()) - if err != nil { - return nil, errors.WithContext(err, "applying container image overrides") - } - - if *dryRun { - log.Info(ctx, "Dry-run: not submitting job!") - - log.Info(ctx, proto.MarshalTextString(model)) - job, err := dataflowlib.Translate(ctx, model, opts, workerURL, jarURL, modelURL) - if err != nil { - return nil, err - } - dataflowlib.PrintJob(ctx, job) - return nil, nil - } - - return dataflowlib.Execute(ctx, model, opts, workerURL, jarURL, modelURL, *endpoint, *executeAsync) + return opts, nil } + func gcsRecorderHook(opts []string) perf.CaptureHook { bucket, prefix, err := gcsx.ParseObject(opts[0]) if err != nil { diff --git a/sdks/go/pkg/beam/runners/dataflow/dataflow_test.go b/sdks/go/pkg/beam/runners/dataflow/dataflow_test.go index 1e2844630c4b3..568860194ec18 100644 --- a/sdks/go/pkg/beam/runners/dataflow/dataflow_test.go +++ b/sdks/go/pkg/beam/runners/dataflow/dataflow_test.go @@ -15,7 +15,12 @@ package dataflow -import "testing" +import ( + "github.com/apache/beam/sdks/v2/go/pkg/beam/options/gcpopts" + "github.com/apache/beam/sdks/v2/go/pkg/beam/options/jobopts" + "sort" + "testing" +) func TestDontUseFlagAsPipelineOption(t *testing.T) { f := "dummy_flag" @@ -27,3 +32,131 @@ func TestDontUseFlagAsPipelineOption(t *testing.T) { t.Fatalf("%q should be in the filter, but isn't set", f) } } + +func TestGetJobOptions(t *testing.T) { + *labels = `{"label1": "val1", "label2": "val2"}` + *stagingLocation = "gs://testStagingLocation" + *autoscalingAlgorithm = "NONE" + *minCPUPlatform = "testPlatform" + + *gcpopts.Project = "testProject" + *gcpopts.Region = "testRegion" + + *jobopts.Experiments = "use_runner_v2,use_portable_job_submission" + *jobopts.JobName = "testJob" + + opts, err := getJobOptions(nil) + if err != nil { + t.Fatalf("getJobOptions() returned error %q, want %q", err, "nil") + } + if got, want := opts.Name, "testJob"; got != want { + t.Errorf("getJobOptions().Name = %q, want %q", got, want) + } + if got, want := len(opts.Experiments), 3; got != want { + t.Errorf("len(getJobOptions().Experiments) = %q, want %q", got, want) + } else { + sort.Strings(opts.Experiments) + expectedExperiments := []string{"min_cpu_platform=testPlatform", "use_portable_job_submission", "use_runner_v2"} + for i := 0; i < 3; i++ { + if got, want := opts.Experiments[i], expectedExperiments[i]; got != want { + t.Errorf("getJobOptions().Experiments = %q, want %q", got, want) + } + } + } + if got, want := opts.Project, "testProject"; got != want { + t.Errorf("getJobOptions().Project = %q, want %q", got, want) + } + if got, want := opts.Region, "testRegion"; got != want { + t.Errorf("getJobOptions().Region = %q, want %q", got, want) + } + if got, want := len(opts.Labels), 2; got != want { + t.Errorf("len(getJobOptions().Labels) = %q, want %q", got, want) + } else { + if got, want := opts.Labels["label1"], "val1"; got != want { + t.Errorf("getJobOptions().Labels[\"label1\"] = %q, want %q", got, want) + } + if got, want := opts.Labels["label2"], "val2"; got != want { + t.Errorf("getJobOptions().Labels[\"label2\"] = %q, want %q", got, want) + } + } + if got, want := opts.TempLocation, "gs://testStagingLocation/tmp"; got != want { + t.Errorf("getJobOptions().TempLocation = %q, want %q", got, want) + } +} + +func TestGetJobOptions_NoExperimentsSet(t *testing.T) { + *labels = `{"label1": "val1", "label2": "val2"}` + *stagingLocation = "gs://testStagingLocation" + *autoscalingAlgorithm = "NONE" + *minCPUPlatform = "" + + *gcpopts.Project = "testProject" + *gcpopts.Region = "testRegion" + + *jobopts.Experiments = "" + *jobopts.JobName = "testJob" + + opts, err := getJobOptions(nil) + + if err != nil { + t.Fatalf("getJobOptions() returned error %q, want %q", err, "nil") + } + if got, want := len(opts.Experiments), 2; got != want { + t.Fatalf("len(getJobOptions().Experiments) = %q, want %q", got, want) + } + sort.Strings(opts.Experiments) + expectedExperiments := []string{"use_portable_job_submission", "use_unified_worker"} + for i := 0; i < 2; i++ { + if got, want := opts.Experiments[i], expectedExperiments[i]; got != want { + t.Errorf("getJobOptions().Experiments = %q, want %q", got, want) + } + } +} + +func TestGetJobOptions_NoStagingLocation(t *testing.T) { + *stagingLocation = "" + *gcpopts.Project = "testProject" + *gcpopts.Region = "testRegion" + + _, err := getJobOptions(nil) + if err == nil { + t.Fatalf("getJobOptions() returned error nil, want an error") + } +} + +func TestGetJobOptions_InvalidAutoscaling(t *testing.T) { + *labels = `{"label1": "val1", "label2": "val2"}` + *stagingLocation = "gs://testStagingLocation" + *autoscalingAlgorithm = "INVALID" + *minCPUPlatform = "testPlatform" + + *gcpopts.Project = "testProject" + *gcpopts.Region = "testRegion" + + *jobopts.Experiments = "use_runner_v2,use_portable_job_submission" + *jobopts.JobName = "testJob" + + _, err := getJobOptions(nil) + if err == nil { + t.Fatalf("getJobOptions() returned error nil, want an error") + } +} + +func TestGetJobOptions_DockerNoImage(t *testing.T) { + *jobopts.EnvironmentType = "docker" + *jobopts.EnvironmentConfig = "testContainerImage" + + if got, want := getContainerImage(nil), "testContainerImage"; got != want { + t.Fatalf("getContainerImage() = %q, want %q", got, want) + } +} + +func TestGetJobOptions_DockerWithImage(t *testing.T) { + *jobopts.EnvironmentType = "docker" + *jobopts.EnvironmentConfig = "testContainerImage" + *image = "testContainerImageOverride" + + if got, want := getContainerImage(nil), "testContainerImageOverride"; got != want { + t.Fatalf("getContainerImage() = %q, want %q", got, want) + } +} From ba2aef5972eca7322ae68f81bafd52d894e43edf Mon Sep 17 00:00:00 2001 From: AlikRodriguez <74626882+AlikRodriguez@users.noreply.github.com> Date: Fri, 25 Feb 2022 15:22:12 -0600 Subject: [PATCH 40/61] [BEAM-12563] swaplevel general function for dataframe and series (#15944) * swaplevel general function for dataframe and series * pandas doctest * doctest * Update frames.py * lint error --- sdks/python/apache_beam/dataframe/frames.py | 10 ++++++++++ sdks/python/apache_beam/dataframe/frames_test.py | 10 ++++++++++ .../apache_beam/dataframe/pandas_doctests_test.py | 2 -- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/sdks/python/apache_beam/dataframe/frames.py b/sdks/python/apache_beam/dataframe/frames.py index 3cbf613ddec79..00961a6f1b2f3 100644 --- a/sdks/python/apache_beam/dataframe/frames.py +++ b/sdks/python/apache_beam/dataframe/frames.py @@ -229,6 +229,16 @@ def droplevel(self, level, axis): preserves_partition_by=partitionings.Arbitrary() if axis in (1, 'column') else partitionings.Singleton())) + @frame_base.with_docs_from(pd.DataFrame) + @frame_base.args_to_kwargs(pd.DataFrame) + def swaplevel(self, **kwargs): + return frame_base.DeferredFrame.wrap( + expressions.ComputedExpression( + 'swaplevel', + lambda df: df.swaplevel(**kwargs), [self._expr], + requires_partition_by=partitionings.Arbitrary(), + preserves_partition_by=partitionings.Arbitrary())) + @frame_base.with_docs_from(pd.DataFrame) @frame_base.args_to_kwargs(pd.DataFrame) @frame_base.populate_defaults(pd.DataFrame) diff --git a/sdks/python/apache_beam/dataframe/frames_test.py b/sdks/python/apache_beam/dataframe/frames_test.py index c493db5e1b006..ca4f561c9ab77 100644 --- a/sdks/python/apache_beam/dataframe/frames_test.py +++ b/sdks/python/apache_beam/dataframe/frames_test.py @@ -624,6 +624,16 @@ def test_merge_same_key_suffix_collision(self): nonparallel=True, check_proxy=False) + def test_swaplevel(self): + df = pd.DataFrame( + {"Grade": ["A", "B", "A", "C"]}, + index=[ + ["Final exam", "Final exam", "Coursework", "Coursework"], + ["History", "Geography", "History", "Geography"], + ["January", "February", "March", "April"], + ]) + self._run_test(lambda df: df.swaplevel(), df) + def test_value_counts_with_nans(self): # similar to doctests that verify value_counts, but include nan values to # make sure we handle them correctly. diff --git a/sdks/python/apache_beam/dataframe/pandas_doctests_test.py b/sdks/python/apache_beam/dataframe/pandas_doctests_test.py index 1a30aa85db337..19113af53e4fc 100644 --- a/sdks/python/apache_beam/dataframe/pandas_doctests_test.py +++ b/sdks/python/apache_beam/dataframe/pandas_doctests_test.py @@ -269,7 +269,6 @@ def test_dataframe_tests(self): # frames_test.py::DeferredFrameTest::test_groupby_transform_sum "df.groupby('Date')['Data'].transform('sum')", ], - 'pandas.core.frame.DataFrame.swaplevel': ['*'], 'pandas.core.frame.DataFrame.melt': ['*'], 'pandas.core.frame.DataFrame.reindex_axis': ['*'], 'pandas.core.frame.DataFrame.round': [ @@ -511,7 +510,6 @@ def test_series_tests(self): 'ser.groupby(["a", "b", "a", np.nan]).mean()', 'ser.groupby(["a", "b", "a", np.nan], dropna=False).mean()', ], - 'pandas.core.series.Series.swaplevel' :['*'] }, skip={ # Relies on setting values with iloc From feda3a8bea6dff11f28c5bb8d768ab29740cc32b Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Fri, 25 Feb 2022 17:39:07 -0500 Subject: [PATCH 41/61] [BEAM-14001] Update coder.go unit tests (#16952) --- .../pkg/beam/core/runtime/exec/coder_test.go | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/sdks/go/pkg/beam/core/runtime/exec/coder_test.go b/sdks/go/pkg/beam/core/runtime/exec/coder_test.go index 25812aca4e560..02d1f81da7e05 100644 --- a/sdks/go/pkg/beam/core/runtime/exec/coder_test.go +++ b/sdks/go/pkg/beam/core/runtime/exec/coder_test.go @@ -25,6 +25,7 @@ import ( "github.com/apache/beam/sdks/v2/go/pkg/beam/core/util/reflectx" "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/coderx" ) @@ -54,6 +55,9 @@ func TestCoders(t *testing.T) { return &coder.Coder{Kind: coder.Custom, Custom: c, T: typex.New(reflectx.String)} }(), val: &FullValue{Elm: "myString"}, + }, { + coder: &coder.Coder{Kind: coder.LP, Components: []*coder.Coder{coder.NewString()}}, + val: &FullValue{Elm: "myString"}, }, { coder: coder.NewKV([]*coder.Coder{coder.NewVarInt(), coder.NewBool()}), val: &FullValue{Elm: int64(72), Elm2: false}, @@ -64,6 +68,18 @@ func TestCoders(t *testing.T) { coder.NewDouble(), coder.NewBool()})}), val: &FullValue{Elm: int64(42), Elm2: &FullValue{Elm: float64(3.14), Elm2: true}}, + }, { + coder: &coder.Coder{Kind: coder.Window, Window: coder.NewGlobalWindow()}, + val: &FullValue{Windows: []typex.Window{window.GlobalWindow{}}}, + }, { + coder: &coder.Coder{Kind: coder.Window, Window: coder.NewIntervalWindow()}, + val: &FullValue{Windows: []typex.Window{window.IntervalWindow{Start: 0, End: 100}}}, + }, { + coder: coder.NewW(coder.NewVarInt(), coder.NewGlobalWindow()), + val: &FullValue{Elm: int64(13), Windows: []typex.Window{window.GlobalWindow{}}}, + }, { + coder: coder.NewPW(coder.NewString(), coder.NewGlobalWindow()), + val: &FullValue{Elm: "myString" /*Windowing info isn't encoded for PW so we can omit it here*/}, }, } { t.Run(fmt.Sprintf("%v", test.coder), func(t *testing.T) { @@ -116,4 +132,140 @@ func compareFV(t *testing.T, got *FullValue, want *FullValue) { t.Errorf("got %v [type: %s], want %v [type %s]", got, reflect.TypeOf(got), wantFv, reflect.TypeOf(wantFv)) } + + // Check if the desired FV has windowing information + if want.Windows != nil { + if gotLen, wantLen := len(got.Windows), len(want.Windows); gotLen != wantLen { + t.Fatalf("got %d windows in FV, want %v", gotLen, wantLen) + } + for i := range want.Windows { + if gotWin, wantWin := got.Windows[i], want.Windows[i]; !wantWin.Equals(gotWin) { + t.Errorf("got window %v at position %d, want %v", gotWin, i, wantWin) + } + } + } +} + +func TestIterableCoder(t *testing.T) { + cod := coder.NewI(coder.NewVarInt()) + wantVals := []int64{8, 24, 72} + val := &FullValue{Elm: wantVals} + + var buf bytes.Buffer + enc := MakeElementEncoder(cod) + if err := enc.Encode(val, &buf); err != nil { + t.Fatalf("Couldn't encode value: %v", err) + } + + dec := MakeElementDecoder(cod) + result, err := dec.Decode(&buf) + if err != nil { + t.Fatalf("Couldn't decode value: %v", err) + } + + gotVals, ok := result.Elm.([]int64) + if !ok { + t.Fatalf("got output element %v, want []int64", result.Elm) + } + + if got, want := len(gotVals), len(wantVals); got != want { + t.Errorf("got %d elements in iterable, want %d", got, want) + } + + for i := range gotVals { + if got, want := gotVals[i], wantVals[i]; got != want { + t.Errorf("got %d at position %d, want %d", got, i, want) + } + } +} + +// TODO(BEAM-10660): Update once proper timer support is added +func TestTimerCoder(t *testing.T) { + var buf bytes.Buffer + tCoder := coder.NewT(coder.NewVarInt(), coder.NewGlobalWindow()) + wantVal := &FullValue{Elm: int64(13)} + + enc := MakeElementEncoder(tCoder) + if err := enc.Encode(wantVal, &buf); err != nil { + t.Fatalf("Couldn't encode value: %v", err) + } + + dec := MakeElementDecoder(tCoder) + result, err := dec.Decode(&buf) + if err != nil { + t.Fatalf("Couldn't decode value: %v", err) + } + + compareFV(t, result, wantVal) +} + +type namedTypeForTest struct { + A, B int64 + C string +} + +func TestRowCoder(t *testing.T) { + var buf bytes.Buffer + rCoder := coder.NewR(typex.New(reflect.TypeOf((*namedTypeForTest)(nil)))) + wantStruct := &namedTypeForTest{A: int64(8), B: int64(24), C: "myString"} + wantVal := &FullValue{Elm: wantStruct} + + enc := MakeElementEncoder(rCoder) + if err := enc.Encode(wantVal, &buf); err != nil { + t.Fatalf("Couldn't encode value: %v", err) + } + + dec := MakeElementDecoder(rCoder) + result, err := dec.Decode(&buf) + if err != nil { + t.Fatalf("Couldn't decode value: %v", err) + } + gotPtr, ok := result.Elm.(*namedTypeForTest) + gotStruct := *gotPtr + if !ok { + t.Fatalf("got %v, want namedTypeForTest struct", result.Elm) + } + if got, want := gotStruct.A, wantStruct.A; got != want { + t.Errorf("got A field value %d, want %d", got, want) + } + if got, want := gotStruct.B, wantStruct.B; got != want { + t.Errorf("got B field value %d, want %d", got, want) + } + if got, want := gotStruct.C, wantStruct.C; got != want { + t.Errorf("got C field value %v, want %v", got, want) + } +} + +func TestPaneCoder(t *testing.T) { + pn := coder.NewPane(0x04) + val := &FullValue{Pane: pn} + cod := &coder.Coder{Kind: coder.PaneInfo} + + var buf bytes.Buffer + enc := MakeElementEncoder(cod) + if err := enc.Encode(val, &buf); err != nil { + t.Fatalf("Couldn't encode value: %v", err) + } + + dec := MakeElementDecoder(cod) + result, err := dec.Decode(&buf) + if err != nil { + t.Fatalf("Couldn't decode value: %v", err) + } + + if got, want := result.Pane.Timing, pn.Timing; got != want { + t.Errorf("got pane timing %v, want %v", got, want) + } + if got, want := result.Pane.IsFirst, pn.IsFirst; got != want { + t.Errorf("got IsFirst %v, want %v", got, want) + } + if got, want := result.Pane.IsLast, pn.IsLast; got != want { + t.Errorf("got IsLast %v, want %v", got, want) + } + if got, want := result.Pane.Index, pn.Index; got != want { + t.Errorf("got pane index %v, want %v", got, want) + } + if got, want := result.Pane.NonSpeculativeIndex, pn.NonSpeculativeIndex; got != want { + t.Errorf("got pane non-speculative index %v, want %v", got, want) + } } From 9ef324791667a6343ce194be9ae553d8ebafd84c Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 25 Feb 2022 17:46:27 -0500 Subject: [PATCH 42/61] [BEAM-13910] Improve coverage of gcsx package (#16942) --- sdks/go/pkg/beam/util/gcsx/example_test.go | 47 +++++++++++ sdks/go/pkg/beam/util/gcsx/gcs_test.go | 91 +++++++++++++++++----- 2 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 sdks/go/pkg/beam/util/gcsx/example_test.go diff --git a/sdks/go/pkg/beam/util/gcsx/example_test.go b/sdks/go/pkg/beam/util/gcsx/example_test.go new file mode 100644 index 0000000000000..52664a054a878 --- /dev/null +++ b/sdks/go/pkg/beam/util/gcsx/example_test.go @@ -0,0 +1,47 @@ +// 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 gcsx_test + +import ( + "context" + "time" + + "cloud.google.com/go/storage" + "github.com/apache/beam/sdks/v2/go/pkg/beam/util/gcsx" +) + +func Example() { + ctx := context.Background() + c, err := gcsx.NewClient(ctx, storage.ScopeReadOnly) + if err != nil { + // do something + } + + buckets, object, err := gcsx.ParseObject("gs://some-bucket/some-object") + if err != nil { + // do something + } + + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + bytes, err := gcsx.ReadObject(ctx, c, buckets, object) + if err != nil { + // do something + } + + _ = bytes +} diff --git a/sdks/go/pkg/beam/util/gcsx/gcs_test.go b/sdks/go/pkg/beam/util/gcsx/gcs_test.go index 52664a054a878..90fb4b59f2fe8 100644 --- a/sdks/go/pkg/beam/util/gcsx/gcs_test.go +++ b/sdks/go/pkg/beam/util/gcsx/gcs_test.go @@ -13,35 +13,86 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gcsx_test +package gcsx import ( - "context" - "time" + "strings" + "testing" - "cloud.google.com/go/storage" - "github.com/apache/beam/sdks/v2/go/pkg/beam/util/gcsx" + "github.com/apache/beam/sdks/v2/go/pkg/beam/internal/errors" ) -func Example() { - ctx := context.Background() - c, err := gcsx.NewClient(ctx, storage.ScopeReadOnly) - if err != nil { - // do something +func TestMakeObject(t *testing.T) { + if got, want := MakeObject("some-bucket", "some/path"), "gs://some-bucket/some/path"; got != want { + t.Fatalf("MakeObject() Got: %v Want: %v", got, want) } +} - buckets, object, err := gcsx.ParseObject("gs://some-bucket/some-object") - if err != nil { - // do something +func TestParseObject(t *testing.T) { + tests := []struct { + object string + bucket string + path string + err error + }{ + { + object: "gs://some-bucket/some-object", + bucket: "some-bucket", + path: "some-object", + err: nil, + }, + { + object: "gs://some-bucket", + bucket: "some-bucket", + path: "", + err: nil, + }, + { + object: "gs://", + bucket: "", + path: "", + err: errors.Errorf("object gs:// must have bucket"), + }, + { + object: "other://some-bucket/some-object", + bucket: "", + path: "", + err: errors.Errorf("object other://some-bucket/some-object must have 'gs' scheme"), + }, } - ctx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() - - bytes, err := gcsx.ReadObject(ctx, c, buckets, object) - if err != nil { - // do something + for _, test := range tests { + if bucket, path, err := ParseObject(test.object); bucket != test.bucket || path != test.path || (err != nil && test.err == nil) || (err == nil && test.err != nil) { + t.Errorf("ParseObject(%v) Got: %v, %v, %v Want: %v, %v, %v", test.object, bucket, path, err, test.bucket, test.path, test.err) + } } +} - _ = bytes +func TestJoin(t *testing.T) { + tests := []struct { + object string + elms []string + result string + }{ + { + object: "gs://some-bucket/some-object", + elms: []string{"some/path", "more/pathing"}, + result: "gs://some-bucket/some-object/some/path/more/pathing", + }, + { + object: "gs://some-bucket/some-object", + elms: []string{"some/path"}, + result: "gs://some-bucket/some-object/some/path", + }, + { + object: "gs://some-bucket/some-object", + elms: []string{}, + result: "gs://some-bucket/some-object", + }, + } + for _, test := range tests { + if got, want := Join(test.object, test.elms...), test.result; got != want { + t.Errorf("Join(%v, %v) Got: %v Want: %v", test.object, strings.Join(test.elms, ", "), got, want) + } + } } From 939af65e68b07889ec7225d379f55f2a0f5f137a Mon Sep 17 00:00:00 2001 From: Lukasz Cwik Date: Fri, 25 Feb 2022 17:59:57 -0800 Subject: [PATCH 43/61] [BEAM-13015] Use a DirectExecutor for state since we are just completing futures on the callback. (#16745) --- .../EmbeddedEnvironmentFactory.java | 4 +- .../control/RemoteExecutionTest.java | 4 +- .../portability/PortableRunnerTest.java | 6 +- .../sdk/fn/channel/ManagedChannelFactory.java | 152 ++++++++++-------- .../test/InProcessManagedChannelFactory.java | 41 ----- .../logging/BeamFnLoggingClientBenchmark.java | 3 +- .../beam/fn/harness/FnApiDoFnRunner.java | 18 +-- .../org/apache/beam/fn/harness/FnHarness.java | 3 +- .../state/BeamFnStateGrpcClientCache.java | 17 +- .../control/BeamFnControlClientTest.java | 6 +- .../state/BeamFnStateGrpcClientCacheTest.java | 45 ++++-- .../status/BeamFnStatusClientTest.java | 7 +- 12 files changed, 149 insertions(+), 157 deletions(-) delete mode 100644 sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/test/InProcessManagedChannelFactory.java diff --git a/runners/java-fn-execution/src/main/java/org/apache/beam/runners/fnexecution/environment/EmbeddedEnvironmentFactory.java b/runners/java-fn-execution/src/main/java/org/apache/beam/runners/fnexecution/environment/EmbeddedEnvironmentFactory.java index 1e09d99674a26..bd164fe26b61b 100644 --- a/runners/java-fn-execution/src/main/java/org/apache/beam/runners/fnexecution/environment/EmbeddedEnvironmentFactory.java +++ b/runners/java-fn-execution/src/main/java/org/apache/beam/runners/fnexecution/environment/EmbeddedEnvironmentFactory.java @@ -37,11 +37,11 @@ import org.apache.beam.runners.fnexecution.logging.GrpcLoggingService; import org.apache.beam.runners.fnexecution.provisioning.StaticGrpcProvisionService; import org.apache.beam.sdk.fn.IdGenerator; +import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; import org.apache.beam.sdk.fn.server.GrpcFnServer; import org.apache.beam.sdk.fn.server.InProcessServerFactory; import org.apache.beam.sdk.fn.server.ServerFactory; import org.apache.beam.sdk.fn.stream.OutboundObserverFactory; -import org.apache.beam.sdk.fn.test.InProcessManagedChannelFactory; import org.apache.beam.sdk.options.PipelineOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,7 +106,7 @@ public RemoteEnvironment createEnvironment(Environment environment, String worke loggingServer.getApiServiceDescriptor(), controlServer.getApiServiceDescriptor(), null, - InProcessManagedChannelFactory.create(), + ManagedChannelFactory.createInProcess(), OutboundObserverFactory.clientDirect(), Caches.fromOptions(options)); } catch (NoClassDefFoundError e) { diff --git a/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java b/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java index 3a38e31f07f2b..59e61c1109a1e 100644 --- a/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java +++ b/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java @@ -98,12 +98,12 @@ import org.apache.beam.sdk.coders.CoderException; import org.apache.beam.sdk.coders.KvCoder; import org.apache.beam.sdk.coders.StringUtf8Coder; +import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; import org.apache.beam.sdk.fn.data.FnDataReceiver; import org.apache.beam.sdk.fn.server.GrpcContextHeaderAccessorProvider; import org.apache.beam.sdk.fn.server.GrpcFnServer; import org.apache.beam.sdk.fn.server.InProcessServerFactory; import org.apache.beam.sdk.fn.stream.OutboundObserverFactory; -import org.apache.beam.sdk.fn.test.InProcessManagedChannelFactory; import org.apache.beam.sdk.metrics.Metrics; import org.apache.beam.sdk.options.ExperimentalOptions; import org.apache.beam.sdk.options.PipelineOptions; @@ -219,7 +219,7 @@ public void launchSdkHarness(PipelineOptions options) throws Exception { loggingServer.getApiServiceDescriptor(), controlServer.getApiServiceDescriptor(), null, - InProcessManagedChannelFactory.create(), + ManagedChannelFactory.createInProcess(), OutboundObserverFactory.clientDirect(), Caches.eternal()); } catch (Exception e) { diff --git a/runners/portability/java/src/test/java/org/apache/beam/runners/portability/PortableRunnerTest.java b/runners/portability/java/src/test/java/org/apache/beam/runners/portability/PortableRunnerTest.java index 98f098bc4bd56..ef1aea4aa749d 100644 --- a/runners/portability/java/src/test/java/org/apache/beam/runners/portability/PortableRunnerTest.java +++ b/runners/portability/java/src/test/java/org/apache/beam/runners/portability/PortableRunnerTest.java @@ -38,7 +38,7 @@ import org.apache.beam.runners.portability.testing.TestJobService; import org.apache.beam.sdk.PipelineResult; import org.apache.beam.sdk.PipelineResult.State; -import org.apache.beam.sdk.fn.test.InProcessManagedChannelFactory; +import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; import org.apache.beam.sdk.metrics.MetricQueryResults; import org.apache.beam.sdk.options.PipelineOptions; import org.apache.beam.sdk.options.PipelineOptionsFactory; @@ -94,7 +94,7 @@ public class PortableRunnerTest implements Serializable { @Test public void stagesAndRunsJob() throws Exception { createJobServer(JobState.Enum.DONE, JobApi.MetricResults.getDefaultInstance()); - PortableRunner runner = PortableRunner.create(options, InProcessManagedChannelFactory.create()); + PortableRunner runner = PortableRunner.create(options, ManagedChannelFactory.createInProcess()); State state = runner.run(p).waitUntilFinish(); assertThat(state, is(State.DONE)); } @@ -103,7 +103,7 @@ public void stagesAndRunsJob() throws Exception { public void extractsMetrics() throws Exception { JobApi.MetricResults metricResults = generateMetricResults(); createJobServer(JobState.Enum.DONE, metricResults); - PortableRunner runner = PortableRunner.create(options, InProcessManagedChannelFactory.create()); + PortableRunner runner = PortableRunner.create(options, ManagedChannelFactory.createInProcess()); PipelineResult result = runner.run(p); result.waitUntilFinish(); MetricQueryResults metricQueryResults = result.metrics().allMetrics(); diff --git a/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java b/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java index e9a9e218911c0..6d774b142444a 100644 --- a/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java +++ b/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java @@ -17,104 +17,122 @@ */ package org.apache.beam.sdk.fn.channel; +import avro.shaded.com.google.common.collect.ImmutableList; import java.net.SocketAddress; +import java.util.Collections; import java.util.List; import org.apache.beam.model.pipeline.v1.Endpoints.ApiServiceDescriptor; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.ClientInterceptor; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.ManagedChannel; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.ManagedChannelBuilder; +import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.inprocess.InProcessChannelBuilder; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.netty.NettyChannelBuilder; import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.epoll.EpollDomainSocketChannel; import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.epoll.EpollEventLoopGroup; import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.epoll.EpollSocketChannel; import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.unix.DomainSocketAddress; -/** A Factory which creates an underlying {@link ManagedChannel} implementation. */ -public abstract class ManagedChannelFactory { +/** A Factory which creates {@link ManagedChannel} instances. */ +public class ManagedChannelFactory { + /** + * Creates a {@link ManagedChannel} relying on the {@link ManagedChannelBuilder} to choose the + * channel type. + */ public static ManagedChannelFactory createDefault() { - return new Default(); + return new ManagedChannelFactory(Type.DEFAULT, Collections.emptyList(), false); } + /** + * Creates a {@link ManagedChannelFactory} backed by an {@link EpollDomainSocketChannel} if the + * address is a {@link DomainSocketAddress}. Otherwise creates a {@link ManagedChannel} backed by + * an {@link EpollSocketChannel}. + */ public static ManagedChannelFactory createEpoll() { org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.epoll.Epoll.ensureAvailability(); - return new Epoll(); + return new ManagedChannelFactory(Type.EPOLL, Collections.emptyList(), false); } - public ManagedChannel forDescriptor(ApiServiceDescriptor apiServiceDescriptor) { - return builderFor(apiServiceDescriptor).build(); + /** Creates a {@link ManagedChannel} using an in-process channel. */ + public static ManagedChannelFactory createInProcess() { + return new ManagedChannelFactory(Type.IN_PROCESS, Collections.emptyList(), false); } - /** Create a {@link ManagedChannelBuilder} for the provided {@link ApiServiceDescriptor}. */ - protected abstract ManagedChannelBuilder builderFor(ApiServiceDescriptor descriptor); + public ManagedChannel forDescriptor(ApiServiceDescriptor apiServiceDescriptor) { + ManagedChannelBuilder channelBuilder; + switch (type) { + case EPOLL: + SocketAddress address = SocketAddressFactory.createFrom(apiServiceDescriptor.getUrl()); + channelBuilder = + NettyChannelBuilder.forAddress(address) + .channelType( + address instanceof DomainSocketAddress + ? EpollDomainSocketChannel.class + : EpollSocketChannel.class) + .eventLoopGroup(new EpollEventLoopGroup()); + break; - /** - * Returns a {@link ManagedChannelFactory} like this one, but which will apply the provided {@link - * ClientInterceptor ClientInterceptors} to any channel it creates. - */ - public ManagedChannelFactory withInterceptors(List interceptors) { - return new InterceptedManagedChannelFactory(this, interceptors); - } + case DEFAULT: + channelBuilder = ManagedChannelBuilder.forTarget(apiServiceDescriptor.getUrl()); + break; - /** - * Creates a {@link ManagedChannel} backed by an {@link EpollDomainSocketChannel} if the address - * is a {@link DomainSocketAddress}. Otherwise creates a {@link ManagedChannel} backed by an - * {@link EpollSocketChannel}. - */ - private static class Epoll extends ManagedChannelFactory { - @Override - public ManagedChannelBuilder builderFor(ApiServiceDescriptor apiServiceDescriptor) { - SocketAddress address = SocketAddressFactory.createFrom(apiServiceDescriptor.getUrl()); - return NettyChannelBuilder.forAddress(address) - .channelType( - address instanceof DomainSocketAddress - ? EpollDomainSocketChannel.class - : EpollSocketChannel.class) - .eventLoopGroup(new EpollEventLoopGroup()) - .usePlaintext() - // Set the message size to max value here. The actual size is governed by the - // buffer size in the layers above. - .maxInboundMessageSize(Integer.MAX_VALUE); + case IN_PROCESS: + channelBuilder = InProcessChannelBuilder.forName(apiServiceDescriptor.getUrl()); + break; + + default: + throw new IllegalStateException("Unknown type " + type); } - } - /** - * Creates a {@link ManagedChannel} relying on the {@link ManagedChannelBuilder} to create - * instances. - */ - private static class Default extends ManagedChannelFactory { - @Override - public ManagedChannelBuilder builderFor(ApiServiceDescriptor apiServiceDescriptor) { - return ManagedChannelBuilder.forTarget(apiServiceDescriptor.getUrl()) - .usePlaintext() - // Set the message size to max value here. The actual size is governed by the - // buffer size in the layers above. - .maxInboundMessageSize(Integer.MAX_VALUE); + channelBuilder = + channelBuilder + .usePlaintext() + // Set the message size to max value here. The actual size is governed by the + // buffer size in the layers above. + .maxInboundMessageSize(Integer.MAX_VALUE) + .intercept(interceptors); + if (directExecutor) { + channelBuilder = channelBuilder.directExecutor(); } + return channelBuilder.build(); } - private static class InterceptedManagedChannelFactory extends ManagedChannelFactory { - private final ManagedChannelFactory channelFactory; - private final List interceptors; + /** The channel type. */ + private enum Type { + EPOLL, + DEFAULT, + IN_PROCESS, + } - private InterceptedManagedChannelFactory( - ManagedChannelFactory managedChannelFactory, List interceptors) { - this.channelFactory = managedChannelFactory; - this.interceptors = interceptors; - } + private final Type type; + private final List interceptors; + private final boolean directExecutor; - @Override - public ManagedChannel forDescriptor(ApiServiceDescriptor apiServiceDescriptor) { - return builderFor(apiServiceDescriptor).intercept(interceptors).build(); - } + private ManagedChannelFactory( + Type type, List interceptors, boolean directExecutor) { + this.type = type; + this.interceptors = interceptors; + this.directExecutor = directExecutor; + } - @Override - protected ManagedChannelBuilder builderFor(ApiServiceDescriptor descriptor) { - return channelFactory.builderFor(descriptor); - } + /** + * Returns a {@link ManagedChannelFactory} like this one, but which will apply the provided {@link + * ClientInterceptor ClientInterceptors} to any channel it creates. + */ + public ManagedChannelFactory withInterceptors(List interceptors) { + return new ManagedChannelFactory( + type, + ImmutableList.builder() + .addAll(this.interceptors) + .addAll(interceptors) + .build(), + directExecutor); + } - @Override - public ManagedChannelFactory withInterceptors(List interceptors) { - return new InterceptedManagedChannelFactory(channelFactory, interceptors); - } + /** + * Returns a {@link ManagedChannelFactory} like this one, but will construct the channel to use + * the direct executor. + */ + public ManagedChannelFactory withDirectExecutor() { + return new ManagedChannelFactory(type, interceptors, true); } } diff --git a/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/test/InProcessManagedChannelFactory.java b/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/test/InProcessManagedChannelFactory.java deleted file mode 100644 index f5b7c6e860d53..0000000000000 --- a/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/test/InProcessManagedChannelFactory.java +++ /dev/null @@ -1,41 +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. - */ -package org.apache.beam.sdk.fn.test; - -import org.apache.beam.model.pipeline.v1.Endpoints.ApiServiceDescriptor; -import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; -import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.ManagedChannelBuilder; -import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.inprocess.InProcessChannelBuilder; - -/** - * A {@link ManagedChannelFactory} that uses in-process channels. - * - *

      The channel builder uses {@link ApiServiceDescriptor#getUrl()} as the unique in-process name. - */ -public class InProcessManagedChannelFactory extends ManagedChannelFactory { - public static ManagedChannelFactory create() { - return new InProcessManagedChannelFactory(); - } - - private InProcessManagedChannelFactory() {} - - @Override - public ManagedChannelBuilder builderFor(ApiServiceDescriptor apiServiceDescriptor) { - return InProcessChannelBuilder.forName(apiServiceDescriptor.getUrl()); - } -} diff --git a/sdks/java/harness/jmh/src/main/java/org/apache/beam/fn/harness/jmh/logging/BeamFnLoggingClientBenchmark.java b/sdks/java/harness/jmh/src/main/java/org/apache/beam/fn/harness/jmh/logging/BeamFnLoggingClientBenchmark.java index ca9648b4b1d56..f1ef8a6dd9d61 100644 --- a/sdks/java/harness/jmh/src/main/java/org/apache/beam/fn/harness/jmh/logging/BeamFnLoggingClientBenchmark.java +++ b/sdks/java/harness/jmh/src/main/java/org/apache/beam/fn/harness/jmh/logging/BeamFnLoggingClientBenchmark.java @@ -31,7 +31,6 @@ import org.apache.beam.runners.core.metrics.MonitoringInfoConstants; import org.apache.beam.runners.core.metrics.SimpleExecutionState; import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; -import org.apache.beam.sdk.fn.test.InProcessManagedChannelFactory; import org.apache.beam.sdk.options.PipelineOptionsFactory; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.Server; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.inprocess.InProcessServerBuilder; @@ -89,7 +88,7 @@ public ManageLoggingClientAndService() { ApiServiceDescriptor.newBuilder() .setUrl(BeamFnLoggingClientBenchmark.class.getName() + "#" + UUID.randomUUID()) .build(); - ManagedChannelFactory managedChannelFactory = InProcessManagedChannelFactory.create(); + ManagedChannelFactory managedChannelFactory = ManagedChannelFactory.createInProcess(); loggingService = new CallCountLoggingService(); server = InProcessServerBuilder.forName(apiServiceDescriptor.getUrl()) diff --git a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java index 89c05b8f9f238..e73968854b6bb 100644 --- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java +++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnApiDoFnRunner.java @@ -994,13 +994,12 @@ public void onClaimFailed(PositionT position) {} /** Internal class to hold the primary and residual roots when converted to an input element. */ @AutoValue @AutoValue.CopyAnnotations - @SuppressWarnings({"rawtypes"}) abstract static class WindowedSplitResult { public static WindowedSplitResult forRoots( - WindowedValue primaryInFullyProcessedWindowsRoot, - WindowedValue primarySplitRoot, - WindowedValue residualSplitRoot, - WindowedValue residualInUnprocessedWindowsRoot) { + WindowedValue primaryInFullyProcessedWindowsRoot, + WindowedValue primarySplitRoot, + WindowedValue residualSplitRoot, + WindowedValue residualInUnprocessedWindowsRoot) { return new AutoValue_FnApiDoFnRunner_WindowedSplitResult( primaryInFullyProcessedWindowsRoot, primarySplitRoot, @@ -1008,18 +1007,17 @@ public static WindowedSplitResult forRoots( residualInUnprocessedWindowsRoot); } - public abstract @Nullable WindowedValue getPrimaryInFullyProcessedWindowsRoot(); + public abstract @Nullable WindowedValue getPrimaryInFullyProcessedWindowsRoot(); - public abstract @Nullable WindowedValue getPrimarySplitRoot(); + public abstract @Nullable WindowedValue getPrimarySplitRoot(); - public abstract @Nullable WindowedValue getResidualSplitRoot(); + public abstract @Nullable WindowedValue getResidualSplitRoot(); - public abstract @Nullable WindowedValue getResidualInUnprocessedWindowsRoot(); + public abstract @Nullable WindowedValue getResidualInUnprocessedWindowsRoot(); } @AutoValue @AutoValue.CopyAnnotations - @SuppressWarnings({"rawtypes"}) abstract static class SplitResultsWithStopIndex { public static SplitResultsWithStopIndex of( WindowedSplitResult windowSplit, diff --git a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java index 5e2fa13a0e01b..4e35e0199967e 100644 --- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java +++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java @@ -242,8 +242,7 @@ public static void main( new BeamFnDataGrpcClient(options, channelFactory::forDescriptor, outboundObserverFactory); BeamFnStateGrpcClientCache beamFnStateGrpcClientCache = - new BeamFnStateGrpcClientCache( - idGenerator, channelFactory::forDescriptor, outboundObserverFactory); + new BeamFnStateGrpcClientCache(idGenerator, channelFactory, outboundObserverFactory); FinalizeBundleHandler finalizeBundleHandler = new FinalizeBundleHandler(options.as(GcsOptions.class).getExecutorService()); diff --git a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java index abbe032b0ca6d..d028ef61d454c 100644 --- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java +++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java @@ -22,13 +22,12 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; import org.apache.beam.model.fnexecution.v1.BeamFnApi.StateRequest; import org.apache.beam.model.fnexecution.v1.BeamFnApi.StateResponse; import org.apache.beam.model.fnexecution.v1.BeamFnStateGrpc; -import org.apache.beam.model.pipeline.v1.Endpoints; import org.apache.beam.model.pipeline.v1.Endpoints.ApiServiceDescriptor; import org.apache.beam.sdk.fn.IdGenerator; +import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; import org.apache.beam.sdk.fn.stream.OutboundObserverFactory; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.ManagedChannel; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.stub.StreamObserver; @@ -47,22 +46,24 @@ public class BeamFnStateGrpcClientCache { private static final Logger LOG = LoggerFactory.getLogger(BeamFnStateGrpcClientCache.class); private final ConcurrentMap cache; - private final Function channelFactory; + private final ManagedChannelFactory channelFactory; private final OutboundObserverFactory outboundObserverFactory; private final IdGenerator idGenerator; public BeamFnStateGrpcClientCache( IdGenerator idGenerator, - Function channelFactory, + ManagedChannelFactory channelFactory, OutboundObserverFactory outboundObserverFactory) { this.idGenerator = idGenerator; - this.channelFactory = channelFactory; + // We use the directExecutor because we just complete futures when handling responses. + // This showed a 1-2% improvement in the ProcessBundleBenchmark#testState* benchmarks. + this.channelFactory = channelFactory.withDirectExecutor(); this.outboundObserverFactory = outboundObserverFactory; this.cache = new ConcurrentHashMap<>(); } /** - * ( Creates or returns an existing {@link BeamFnStateClient} depending on whether the passed in + * Creates or returns an existing {@link BeamFnStateClient} depending on whether the passed in * {@link ApiServiceDescriptor} currently has a {@link BeamFnStateClient} bound to the same * channel. */ @@ -86,7 +87,7 @@ private class GrpcStateClient implements BeamFnStateClient { private GrpcStateClient(ApiServiceDescriptor apiServiceDescriptor) { this.apiServiceDescriptor = apiServiceDescriptor; this.outstandingRequests = new ConcurrentHashMap<>(); - this.channel = channelFactory.apply(apiServiceDescriptor); + this.channel = channelFactory.forDescriptor(apiServiceDescriptor); this.outboundObserver = outboundObserverFactory.outboundObserverFor( BeamFnStateGrpc.newStub(channel)::state, new InboundObserver()); @@ -135,6 +136,8 @@ private synchronized void closeAndCleanUp(RuntimeException cause) { * *

      Also propagates server side failures and closes completing any outstanding requests * exceptionally. + * + *

      This implementation must never block since we use a direct executor. */ private class InboundObserver implements StreamObserver { @Override diff --git a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/BeamFnControlClientTest.java b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/BeamFnControlClientTest.java index 9ca5efc32a776..a84d1a0b58af9 100644 --- a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/BeamFnControlClientTest.java +++ b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/BeamFnControlClientTest.java @@ -40,8 +40,8 @@ import org.apache.beam.model.fnexecution.v1.BeamFnApi.RegisterRequest; import org.apache.beam.model.fnexecution.v1.BeamFnControlGrpc; import org.apache.beam.model.pipeline.v1.Endpoints; +import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; import org.apache.beam.sdk.fn.stream.OutboundObserverFactory; -import org.apache.beam.sdk.fn.test.InProcessManagedChannelFactory; import org.apache.beam.sdk.fn.test.TestStreams; import org.apache.beam.sdk.function.ThrowingFunction; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.Server; @@ -142,7 +142,7 @@ public StreamObserver control( BeamFnControlClient client = new BeamFnControlClient( apiServiceDescriptor, - InProcessManagedChannelFactory.create(), + ManagedChannelFactory.createInProcess(), OutboundObserverFactory.trivial(), executor, handlers); @@ -216,7 +216,7 @@ public StreamObserver control( BeamFnControlClient client = new BeamFnControlClient( apiServiceDescriptor, - InProcessManagedChannelFactory.create(), + ManagedChannelFactory.createInProcess(), OutboundObserverFactory.trivial(), executor, handlers); diff --git a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCacheTest.java b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCacheTest.java index 2fc2a0b12984e..e62df4669049d 100644 --- a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCacheTest.java +++ b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCacheTest.java @@ -34,13 +34,12 @@ import org.apache.beam.model.fnexecution.v1.BeamFnStateGrpc; import org.apache.beam.model.pipeline.v1.Endpoints; import org.apache.beam.sdk.fn.IdGenerators; +import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; import org.apache.beam.sdk.fn.stream.OutboundObserverFactory; import org.apache.beam.sdk.fn.test.TestStreams; -import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.ManagedChannel; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.Server; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.Status; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.StatusRuntimeException; -import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.inprocess.InProcessChannelBuilder; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.inprocess.InProcessServerBuilder; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.stub.CallStreamObserver; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.stub.StreamObserver; @@ -60,7 +59,6 @@ public class BeamFnStateGrpcClientCacheTest { private static final String SERVER_ERROR = "SERVER ERROR"; private Endpoints.ApiServiceDescriptor apiServiceDescriptor; - private ManagedChannel testChannel; private Server testServer; private BeamFnStateGrpcClientCache clientCache; private BlockingQueue> outboundServerObservers; @@ -75,7 +73,7 @@ public void setUp() throws Exception { apiServiceDescriptor = Endpoints.ApiServiceDescriptor.newBuilder() - .setUrl(this.getClass().getName() + "-" + UUID.randomUUID().toString()) + .setUrl(this.getClass().getName() + "-" + UUID.randomUUID()) .build(); testServer = InProcessServerBuilder.forName(apiServiceDescriptor.getUrl()) @@ -91,29 +89,48 @@ public StreamObserver state( .build(); testServer.start(); - testChannel = InProcessChannelBuilder.forName(apiServiceDescriptor.getUrl()).build(); - clientCache = new BeamFnStateGrpcClientCache( IdGenerators.decrementingLongs(), - (Endpoints.ApiServiceDescriptor descriptor) -> testChannel, + ManagedChannelFactory.createInProcess(), OutboundObserverFactory.trivial()); } @After public void tearDown() throws Exception { testServer.shutdownNow(); - testChannel.shutdownNow(); } @Test public void testCachingOfClient() throws Exception { - assertSame( - clientCache.forApiServiceDescriptor(apiServiceDescriptor), - clientCache.forApiServiceDescriptor(apiServiceDescriptor)); - assertNotSame( - clientCache.forApiServiceDescriptor(apiServiceDescriptor), - clientCache.forApiServiceDescriptor(Endpoints.ApiServiceDescriptor.getDefaultInstance())); + Endpoints.ApiServiceDescriptor otherApiServiceDescriptor = + Endpoints.ApiServiceDescriptor.newBuilder() + .setUrl(apiServiceDescriptor.getUrl() + "-other") + .build(); + Server testServer2 = + InProcessServerBuilder.forName(otherApiServiceDescriptor.getUrl()) + .addService( + new BeamFnStateGrpc.BeamFnStateImplBase() { + @Override + public StreamObserver state( + StreamObserver outboundObserver) { + throw new IllegalStateException("Unexpected in test."); + } + }) + .build(); + testServer2.start(); + + try { + + assertSame( + clientCache.forApiServiceDescriptor(apiServiceDescriptor), + clientCache.forApiServiceDescriptor(apiServiceDescriptor)); + assertNotSame( + clientCache.forApiServiceDescriptor(apiServiceDescriptor), + clientCache.forApiServiceDescriptor(otherApiServiceDescriptor)); + } finally { + testServer2.shutdownNow(); + } } @Test diff --git a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/status/BeamFnStatusClientTest.java b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/status/BeamFnStatusClientTest.java index 50b728a63debe..c0229f23fbe3d 100644 --- a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/status/BeamFnStatusClientTest.java +++ b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/status/BeamFnStatusClientTest.java @@ -44,7 +44,6 @@ import org.apache.beam.model.pipeline.v1.Endpoints; import org.apache.beam.runners.core.metrics.ExecutionStateTracker; import org.apache.beam.sdk.fn.channel.ManagedChannelFactory; -import org.apache.beam.sdk.fn.test.InProcessManagedChannelFactory; import org.apache.beam.sdk.fn.test.TestStreams; import org.apache.beam.sdk.options.PipelineOptionsFactory; import org.apache.beam.vendor.grpc.v1p43p2.io.grpc.Server; @@ -81,7 +80,7 @@ public void testActiveBundleState() { when(handler.getBundleProcessorCache()).thenReturn(processorCache); when(processorCache.getActiveBundleProcessors()).thenReturn(bundleProcessorMap); - ManagedChannelFactory channelFactory = InProcessManagedChannelFactory.create(); + ManagedChannelFactory channelFactory = ManagedChannelFactory.createInProcess(); BeamFnStatusClient client = new BeamFnStatusClient( apiServiceDescriptor, @@ -125,7 +124,7 @@ public StreamObserver workerStatus( try { BundleProcessorCache processorCache = mock(BundleProcessorCache.class); when(processorCache.getActiveBundleProcessors()).thenReturn(Collections.emptyMap()); - ManagedChannelFactory channelFactory = InProcessManagedChannelFactory.create(); + ManagedChannelFactory channelFactory = ManagedChannelFactory.createInProcess(); new BeamFnStatusClient( apiServiceDescriptor, channelFactory::forDescriptor, @@ -144,7 +143,7 @@ public StreamObserver workerStatus( @Test public void testCacheStatsExist() { - ManagedChannelFactory channelFactory = InProcessManagedChannelFactory.create(); + ManagedChannelFactory channelFactory = ManagedChannelFactory.createInProcess(); BeamFnStatusClient client = new BeamFnStatusClient( apiServiceDescriptor, From 2b012d08c10a6fd9358dcee60293d1064c8c6d81 Mon Sep 17 00:00:00 2001 From: tvalentyn Date: Mon, 28 Feb 2022 08:23:40 -0800 Subject: [PATCH 44/61] Build wheels for Python 3.9 --- .github/workflows/build_wheels.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index ef40d5c6f93af..a85a8c729a659 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -207,13 +207,13 @@ jobs: strategy: matrix: os_python: [ - {"os": "ubuntu-latest", "python": "cp36-* cp37-* cp38-*"}, - {"os": "macos-latest", "python": "cp36-* cp37-* cp38-*"}, - {"os": "windows-latest", "python": "cp36-* cp37-* cp38-*"}, + {"os": "ubuntu-latest", "python": "cp36-* cp37-* cp38-* cp39-*"}, + {"os": "macos-latest", "python": "cp36-* cp37-* cp38-* cp39-*"}, + {"os": "windows-latest", "python": "cp36-* cp37-* cp38-* cp39-*"}, ] arch: [auto] include: - - os_python: {"os": "ubuntu-latest", "python": "cp36-* cp37-* cp38-*"} + - os_python: {"os": "ubuntu-latest", "python": "cp36-* cp37-* cp38-* cp39-*"} arch: aarch64 steps: - name: Download python source distribution from artifacts From 07024513d7775a599c9c90768a6903909a0d1656 Mon Sep 17 00:00:00 2001 From: Alexander Zhuravlev Date: Mon, 28 Feb 2022 20:50:51 +0400 Subject: [PATCH 45/61] Merge pull request #16892 from [BEAM-13755] [Playground] Scroll the opened example to the context line * [BEAM-13755] Added contextLine parameter to examples, reworked text scrolling * [BEAM-13755] Improving text scrolling * [BEAM-13755] Updated text scrolling in the editor textarea * [BEAM-13755] Fixed PR Remarks --- .../editor/components/editor_textarea.dart | 94 +++++++++++++------ .../examples/models/example_model.dart | 2 + .../example_client/grpc_example_client.dart | 1 + 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/playground/frontend/lib/modules/editor/components/editor_textarea.dart b/playground/frontend/lib/modules/editor/components/editor_textarea.dart index 42e505dadafed..cef01cf346040 100644 --- a/playground/frontend/lib/modules/editor/components/editor_textarea.dart +++ b/playground/frontend/lib/modules/editor/components/editor_textarea.dart @@ -31,7 +31,6 @@ import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/modules/sdk/models/sdk.dart'; import 'package:provider/provider.dart'; -const kNumberOfStringsToSkip = 16; const kJavaRegExp = r'import\s[A-z.0-9]*\;\n\n[(\/\*\*)|(public)|(class)]'; const kPythonRegExp = r'[^\S\r\n](import|as)[^\S\r\n][A-z]*\n\n'; const kGoRegExp = r'[^\S\r\n]+\' @@ -39,6 +38,7 @@ const kGoRegExp = r'[^\S\r\n]+\' r'.*' r'"' r'\n\)\n\n'; +const kAdditionalLinesForScrolling = 4; class EditorTextArea extends StatefulWidget { final SDK sdk; @@ -65,6 +65,7 @@ class EditorTextArea extends StatefulWidget { class _EditorTextAreaState extends State { CodeController? _codeController; var focusNode = FocusNode(); + final GlobalKey codeFieldKey = LabeledGlobalKey('CodeFieldKey'); @override void initState() { @@ -86,10 +87,6 @@ class _EditorTextAreaState extends State { webSpaceFix: false, ); - if (widget.enableScrolling) { - _setTextScrolling(); - } - super.didChangeDependencies(); } @@ -102,6 +99,8 @@ class _EditorTextAreaState extends State { @override Widget build(BuildContext context) { + WidgetsBinding.instance!.addPostFrameCallback((_) => _setTextScrolling()); + return Semantics( container: true, textField: true, @@ -112,6 +111,7 @@ class _EditorTextAreaState extends State { child: FocusScope( node: FocusScopeNode(canRequestFocus: widget.isEditable), child: CodeField( + key: codeFieldKey, focusNode: focusNode, enabled: widget.enabled, controller: _codeController!, @@ -133,44 +133,78 @@ class _EditorTextAreaState extends State { focusNode.requestFocus(); if (_codeController!.text.isNotEmpty) { _codeController!.selection = TextSelection.fromPosition( - TextPosition(offset: _findOffset()), + TextPosition( + offset: _getOffset(), + ), ); } } - _findOffset() { + int _getOffset() { + int contextLine = _getIndexOfContextLine(); + String pattern = _getPattern(_getQntOfStringsOnScreen()); + + if (pattern == '' || pattern == '}') { + return _codeController!.text.lastIndexOf(pattern); + } + return _codeController!.text.indexOf( - _skipStrings(kNumberOfStringsToSkip), - _getPositionAfterImportsAndLicenses(widget.sdk), + pattern, + contextLine, ); } - String _skipStrings(int qntOfStrings) { - List strings = _codeController!.text - .substring(_getPositionAfterImportsAndLicenses(widget.sdk)) - .split('\n'); + String _getPattern(int qntOfStrings) { + int contextLineIndex = _getIndexOfContextLine(); + List stringsAfterContextLine = + _codeController!.text.substring(contextLineIndex).split('\n'); + String result = - strings.length > qntOfStrings ? strings[qntOfStrings] : strings.last; - if (result == '') { - return _skipStrings(qntOfStrings - 1); - } else { - return result; + stringsAfterContextLine.length + kAdditionalLinesForScrolling > + qntOfStrings + ? _getResultSubstring(stringsAfterContextLine, qntOfStrings) + : stringsAfterContextLine.last; + + return result; + } + + int _getQntOfStringsOnScreen() { + RenderBox rBox = + codeFieldKey.currentContext?.findRenderObject() as RenderBox; + double height = rBox.size.height * .75; + + return height ~/ kCodeFontSize; + } + + int _getIndexOfContextLine() { + int ctxLineNumber = widget.example!.contextLine; + String contextLine = _codeController!.text.split('\n')[ctxLineNumber]; + + while (contextLine == '') { + ctxLineNumber -= 1; + contextLine = _codeController!.text.split('\n')[ctxLineNumber]; } + + return _codeController!.text.indexOf(contextLine); } - int _getPositionAfterImportsAndLicenses(SDK sdk) { - switch (sdk) { - case SDK.java: - return _codeController!.text.lastIndexOf(RegExp(kJavaRegExp)); - case SDK.python: - return _codeController!.text.lastIndexOf(RegExp(kPythonRegExp)); - case SDK.go: - return _codeController!.text.lastIndexOf(RegExp(kGoRegExp)); - case SDK.scio: - return _codeController!.text.indexOf( - _codeController!.text.split('\n')[0], - ); + // This function made for more accuracy in the process of finding an exact line. + String _getResultSubstring( + List stringsAfterContextLine, + int qntOfStrings, + ) { + StringBuffer result = StringBuffer(); + + for (int i = qntOfStrings - kAdditionalLinesForScrolling; + i < qntOfStrings + kAdditionalLinesForScrolling; + i++) { + if (i == stringsAfterContextLine.length - 1) { + return result.toString(); + } + result.write(stringsAfterContextLine[i] + '\n'); } + + return result.toString(); } _getLanguageFromSdk() { diff --git a/playground/frontend/lib/modules/examples/models/example_model.dart b/playground/frontend/lib/modules/examples/models/example_model.dart index e6f850ae4815e..cd752c2872ac9 100644 --- a/playground/frontend/lib/modules/examples/models/example_model.dart +++ b/playground/frontend/lib/modules/examples/models/example_model.dart @@ -43,6 +43,7 @@ class ExampleModel with Comparable { final String name; final String path; final String description; + final int contextLine; bool isMultiFile; String? link; String? source; @@ -56,6 +57,7 @@ class ExampleModel with Comparable { required this.path, required this.description, required this.type, + this.contextLine = 1, this.isMultiFile = false, this.link, this.source, diff --git a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart index 672b2e52cd6cb..8c9f71e5633a2 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart @@ -223,6 +223,7 @@ class GrpcExampleClient implements ExampleClient { description: example.description, type: _exampleTypeFromString(example.type), path: example.cloudPath, + contextLine: example.contextLine, pipelineOptions: example.pipelineOptions, isMultiFile: example.multifile, link: example.link, From 75b41005a6fb439200498f1aa6f815de53b2cde3 Mon Sep 17 00:00:00 2001 From: Aydar Zainutdinov Date: Mon, 28 Feb 2022 19:51:35 +0300 Subject: [PATCH 46/61] Merge pull request #16880 from [BEAM-13963][Playground] Get bucket name from environment variable * [BEAM-13963][Playground] Move name of the bucket to the environment_service * [BEAM-13963][Playground] fix typos --- playground/backend/cmd/server/controller.go | 10 +++--- playground/backend/cmd/server/server.go | 6 ++-- playground/backend/containers/go/Dockerfile | 1 + playground/backend/containers/java/Dockerfile | 1 + .../backend/containers/python/Dockerfile | 1 + .../backend/containers/router/Dockerfile | 1 + playground/backend/containers/scio/Dockerfile | 1 + .../cloud_bucket/precompiled_objects.go | 31 ++++++++++--------- .../cloud_bucket/precompiled_objects_test.go | 7 +++-- .../internal/environment/application.go | 11 ++++++- .../environment/environment_service.go | 5 ++- .../environment/environment_service_test.go | 6 ++-- .../utils/precompiled_objects_utils.go | 4 +-- 13 files changed, 53 insertions(+), 32 deletions(-) diff --git a/playground/backend/cmd/server/controller.go b/playground/backend/cmd/server/controller.go index 21fa1ccd33b5c..9397523be0f41 100644 --- a/playground/backend/cmd/server/controller.go +++ b/playground/backend/cmd/server/controller.go @@ -263,7 +263,7 @@ func (controller *playgroundController) GetPrecompiledObjects(ctx context.Contex catalog, err := controller.cacheService.GetCatalog(ctx) if err != nil { logger.Errorf("GetPrecompiledObjects(): cache error: %s", err.Error()) - catalog, err = utils.GetCatalogFromStorage(ctx) + catalog, err = utils.GetCatalogFromStorage(ctx, controller.env.ApplicationEnvs.BucketName()) if err != nil { return nil, errors.InternalError("Error during getting Precompiled Objects", "Error with cloud connection") } @@ -279,7 +279,7 @@ func (controller *playgroundController) GetPrecompiledObjects(ctx context.Contex // GetPrecompiledObjectCode returns the code of the specific example func (controller *playgroundController) GetPrecompiledObjectCode(ctx context.Context, info *pb.GetPrecompiledObjectCodeRequest) (*pb.GetPrecompiledObjectCodeResponse, error) { cd := cloud_bucket.New() - codeString, err := cd.GetPrecompiledObject(ctx, info.GetCloudPath()) + codeString, err := cd.GetPrecompiledObject(ctx, info.GetCloudPath(), controller.env.ApplicationEnvs.BucketName()) if err != nil { logger.Errorf("GetPrecompiledObjectCode(): cloud storage error: %s", err.Error()) return nil, errors.InternalError("Error during getting Precompiled Object's code", "Error with cloud connection") @@ -291,7 +291,7 @@ func (controller *playgroundController) GetPrecompiledObjectCode(ctx context.Con // GetPrecompiledObjectOutput returns the output of the compiled and run example func (controller *playgroundController) GetPrecompiledObjectOutput(ctx context.Context, info *pb.GetPrecompiledObjectOutputRequest) (*pb.GetPrecompiledObjectOutputResponse, error) { cd := cloud_bucket.New() - output, err := cd.GetPrecompiledObjectOutput(ctx, info.GetCloudPath()) + output, err := cd.GetPrecompiledObjectOutput(ctx, info.GetCloudPath(), controller.env.ApplicationEnvs.BucketName()) if err != nil { logger.Errorf("GetPrecompiledObjectOutput(): cloud storage error: %s", err.Error()) return nil, errors.InternalError("Error during getting Precompiled Object's output", "Error with cloud connection") @@ -303,7 +303,7 @@ func (controller *playgroundController) GetPrecompiledObjectOutput(ctx context.C // GetPrecompiledObjectLogs returns the logs of the compiled and run example func (controller *playgroundController) GetPrecompiledObjectLogs(ctx context.Context, info *pb.GetPrecompiledObjectLogsRequest) (*pb.GetPrecompiledObjectLogsResponse, error) { cd := cloud_bucket.New() - logs, err := cd.GetPrecompiledObjectLogs(ctx, info.GetCloudPath()) + logs, err := cd.GetPrecompiledObjectLogs(ctx, info.GetCloudPath(), controller.env.ApplicationEnvs.BucketName()) if err != nil { logger.Errorf("GetPrecompiledObjectLogs(): cloud storage error: %s", err.Error()) return nil, errors.InternalError("Error during getting Precompiled Object's logs", "Error with cloud connection") @@ -315,7 +315,7 @@ func (controller *playgroundController) GetPrecompiledObjectLogs(ctx context.Con // GetPrecompiledObjectGraph returns the graph of the compiled and run example func (controller *playgroundController) GetPrecompiledObjectGraph(ctx context.Context, info *pb.GetPrecompiledObjectGraphRequest) (*pb.GetPrecompiledObjectGraphResponse, error) { cb := cloud_bucket.New() - logs, err := cb.GetPrecompiledObjectGraph(ctx, info.GetCloudPath()) + logs, err := cb.GetPrecompiledObjectGraph(ctx, info.GetCloudPath(), controller.env.ApplicationEnvs.BucketName()) if err != nil { logger.Errorf("GetPrecompiledObjectGraph(): cloud storage error: %s", err.Error()) return nil, errors.InternalError("Error during getting Precompiled Object's graph", "Error with cloud connection") diff --git a/playground/backend/cmd/server/server.go b/playground/backend/cmd/server/server.go index 44591cb1a168e..03cc61f1516c2 100644 --- a/playground/backend/cmd/server/server.go +++ b/playground/backend/cmd/server/server.go @@ -53,7 +53,7 @@ func runServer() error { // Examples catalog should be retrieved and saved to cache only if the server doesn't suppose to run code, i.e. SDK is unspecified if envService.BeamSdkEnvs.ApacheBeamSdk == pb.Sdk_SDK_UNSPECIFIED { - err = setupExamplesCatalog(ctx, cacheService) + err = setupExamplesCatalog(ctx, cacheService, envService.ApplicationEnvs.BucketName()) if err != nil { return err } @@ -119,8 +119,8 @@ func setupCache(ctx context.Context, appEnv environment.ApplicationEnvs) (cache. } // setupExamplesCatalog saves precompiled objects catalog from storage to cache -func setupExamplesCatalog(ctx context.Context, cacheService cache.Cache) error { - catalog, err := utils.GetCatalogFromStorage(ctx) +func setupExamplesCatalog(ctx context.Context, cacheService cache.Cache, bucketName string) error { + catalog, err := utils.GetCatalogFromStorage(ctx, bucketName) if err != nil { return err } diff --git a/playground/backend/containers/go/Dockerfile b/playground/backend/containers/go/Dockerfile index 8d71737b4df7c..b7243db14cdc7 100644 --- a/playground/backend/containers/go/Dockerfile +++ b/playground/backend/containers/go/Dockerfile @@ -54,6 +54,7 @@ ENV SERVER_IP=0.0.0.0 ENV SERVER_PORT=8080 ENV APP_WORK_DIR=/opt/playground/backend/ ENV BEAM_SDK="SDK_GO" +ENV BUCKET_NAME="playground-precompiled-objects" ## Copy build result COPY src/configs /opt/playground/backend/configs/ diff --git a/playground/backend/containers/java/Dockerfile b/playground/backend/containers/java/Dockerfile index 334fdb28eccd0..603cca4b8cb74 100644 --- a/playground/backend/containers/java/Dockerfile +++ b/playground/backend/containers/java/Dockerfile @@ -45,6 +45,7 @@ ENV SERVER_IP=0.0.0.0 ENV SERVER_PORT=8080 ENV APP_WORK_DIR=/opt/playground/backend/ ENV BEAM_SDK="SDK_JAVA" +ENV BUCKET_NAME="playground-precompiled-objects" # Copy build result COPY --from=build /go/bin/server_java_backend /opt/playground/backend/ diff --git a/playground/backend/containers/python/Dockerfile b/playground/backend/containers/python/Dockerfile index aac53bb5de676..65e28fac1ec04 100644 --- a/playground/backend/containers/python/Dockerfile +++ b/playground/backend/containers/python/Dockerfile @@ -42,6 +42,7 @@ ENV SERVER_IP=0.0.0.0 ENV SERVER_PORT=8080 ENV APP_WORK_DIR=/opt/playground/backend/ ENV BEAM_SDK="SDK_PYTHON" +ENV BUCKET_NAME="playground-precompiled-objects" # Copy build result COPY --from=build /go/bin/server_python_backend /opt/playground/backend/ diff --git a/playground/backend/containers/router/Dockerfile b/playground/backend/containers/router/Dockerfile index d10745c81a2b1..d123ca17ebd52 100644 --- a/playground/backend/containers/router/Dockerfile +++ b/playground/backend/containers/router/Dockerfile @@ -59,6 +59,7 @@ ENV SERVER_IP=0.0.0.0 ENV SERVER_PORT=8080 ENV APP_WORK_DIR=/opt/playground/backend/ ENV BEAM_SDK="SDK_UNSPECIFIED" +ENV BUCKET_NAME="playground-precompiled-objects" # Copy build result COPY --from=build /go/bin/server_go_backend /opt/playground/backend/ diff --git a/playground/backend/containers/scio/Dockerfile b/playground/backend/containers/scio/Dockerfile index ecec756177d66..8031b1fdb2734 100644 --- a/playground/backend/containers/scio/Dockerfile +++ b/playground/backend/containers/scio/Dockerfile @@ -40,6 +40,7 @@ ENV SERVER_IP=0.0.0.0 ENV SERVER_PORT=8080 ENV APP_WORK_DIR=/opt/playground/backend/ ENV BEAM_SDK="SDK_SCIO" +ENV BUCKET_NAME="playground-precompiled-objects" # Copy build result COPY --from=build /go/bin/server_scio_backend /opt/playground/backend/ diff --git a/playground/backend/internal/cloud_bucket/precompiled_objects.go b/playground/backend/internal/cloud_bucket/precompiled_objects.go index 078dad02a2289..09dda526a9a27 100644 --- a/playground/backend/internal/cloud_bucket/precompiled_objects.go +++ b/playground/backend/internal/cloud_bucket/precompiled_objects.go @@ -32,7 +32,6 @@ import ( ) const ( - BucketName = "playground-precompiled-objects" OutputExtension = "output" LogsExtension = "log" GraphExtension = "graph" @@ -114,12 +113,12 @@ func New() *CloudStorage { } // GetPrecompiledObject returns the source code of the example -func (cd *CloudStorage) GetPrecompiledObject(ctx context.Context, precompiledObjectPath string) (string, error) { +func (cd *CloudStorage) GetPrecompiledObject(ctx context.Context, precompiledObjectPath, bucketName string) (string, error) { extension, err := getFileExtensionBySdk(precompiledObjectPath) if err != nil { return "", err } - data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, extension) + data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, extension, bucketName) if err != nil { return "", err } @@ -128,8 +127,8 @@ func (cd *CloudStorage) GetPrecompiledObject(ctx context.Context, precompiledObj } // GetPrecompiledObjectOutput returns the run output of the example -func (cd *CloudStorage) GetPrecompiledObjectOutput(ctx context.Context, precompiledObjectPath string) (string, error) { - data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, OutputExtension) +func (cd *CloudStorage) GetPrecompiledObjectOutput(ctx context.Context, precompiledObjectPath, bucketName string) (string, error) { + data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, OutputExtension, bucketName) if err != nil { return "", err } @@ -138,8 +137,8 @@ func (cd *CloudStorage) GetPrecompiledObjectOutput(ctx context.Context, precompi } // GetPrecompiledObjectLogs returns the logs of the example -func (cd *CloudStorage) GetPrecompiledObjectLogs(ctx context.Context, precompiledObjectPath string) (string, error) { - data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, LogsExtension) +func (cd *CloudStorage) GetPrecompiledObjectLogs(ctx context.Context, precompiledObjectPath, bucketName string) (string, error) { + data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, LogsExtension, bucketName) if err != nil { return "", err } @@ -148,8 +147,8 @@ func (cd *CloudStorage) GetPrecompiledObjectLogs(ctx context.Context, precompile } // GetPrecompiledObjectGraph returns the graph of the example -func (cd *CloudStorage) GetPrecompiledObjectGraph(ctx context.Context, precompiledObjectPath string) (string, error) { - data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, GraphExtension) +func (cd *CloudStorage) GetPrecompiledObjectGraph(ctx context.Context, precompiledObjectPath, bucketName string) (string, error) { + data, err := cd.getFileFromBucket(ctx, precompiledObjectPath, GraphExtension, bucketName) if err != nil { return "", err } @@ -157,7 +156,7 @@ func (cd *CloudStorage) GetPrecompiledObjectGraph(ctx context.Context, precompil } // GetPrecompiledObjects returns stored at the cloud storage bucket precompiled objects for the target category -func (cd *CloudStorage) GetPrecompiledObjects(ctx context.Context, targetSdk pb.Sdk, targetCategory string) (*SdkToCategories, error) { +func (cd *CloudStorage) GetPrecompiledObjects(ctx context.Context, targetSdk pb.Sdk, targetCategory, bucketName string) (*SdkToCategories, error) { client, err := storage.NewClient(ctx, option.WithoutAuthentication()) if err != nil { return nil, fmt.Errorf("storage.NewClient: %v", err) @@ -168,7 +167,7 @@ func (cd *CloudStorage) GetPrecompiledObjects(ctx context.Context, targetSdk pb. defer cancel() precompiledObjects := make(SdkToCategories, 0) - bucket := client.Bucket(BucketName) + bucket := client.Bucket(bucketName) dirs, err := cd.getPrecompiledObjectsDirs(ctx, targetSdk, bucket) if err != nil { @@ -229,7 +228,11 @@ func (cd *CloudStorage) getPrecompiledObjectsDirs(ctx context.Context, targetSdk break } if err != nil { - return nil, fmt.Errorf("Bucket(%q).Objects: %v", BucketName, err) + bucketAttrs, errWithAttrs := bucket.Attrs(ctx) + if errWithAttrs != nil { + return nil, fmt.Errorf("error during receiving bucket's attributes: %s", err) + } + return nil, fmt.Errorf("Bucket(%q).Objects: %v", bucketAttrs.Name, err) } path := attrs.Name if isPathToPrecompiledObjectFile(path) { @@ -258,7 +261,7 @@ func appendPrecompiledObject(objectInfo ObjectInfo, sdkToCategories *SdkToCatego } // getFileFromBucket receives the file from the bucket by its name -func (cd *CloudStorage) getFileFromBucket(ctx context.Context, pathToObject string, extension string) ([]byte, error) { +func (cd *CloudStorage) getFileFromBucket(ctx context.Context, pathToObject string, extension, bucketName string) ([]byte, error) { client, err := storage.NewClient(ctx, option.WithoutAuthentication()) if err != nil { return nil, fmt.Errorf("storage.NewClient: %v", err) @@ -268,7 +271,7 @@ func (cd *CloudStorage) getFileFromBucket(ctx context.Context, pathToObject stri ctx, cancel := context.WithTimeout(ctx, Timeout) defer cancel() - bucket := client.Bucket(BucketName) + bucket := client.Bucket(bucketName) filePath := getFullFilePath(pathToObject, extension) rc, err := bucket.Object(filePath).NewReader(ctx) diff --git a/playground/backend/internal/cloud_bucket/precompiled_objects_test.go b/playground/backend/internal/cloud_bucket/precompiled_objects_test.go index 84794f5270abc..30beb10ea2b52 100644 --- a/playground/backend/internal/cloud_bucket/precompiled_objects_test.go +++ b/playground/backend/internal/cloud_bucket/precompiled_objects_test.go @@ -25,6 +25,7 @@ import ( const ( precompiledObjectPath = "SDK_JAVA/MinimalWordCount" targetSdk = pb.Sdk_SDK_UNSPECIFIED + defaultBucketName = "playground-precompiled-objects" ) var bucket *CloudStorage @@ -240,18 +241,18 @@ func Test_getFileExtensionBySdk(t *testing.T) { func Benchmark_GetPrecompiledObjects(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = bucket.GetPrecompiledObjects(ctx, targetSdk, "") + _, _ = bucket.GetPrecompiledObjects(ctx, targetSdk, "", defaultBucketName) } } func Benchmark_GetPrecompiledObjectOutput(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = bucket.GetPrecompiledObjectOutput(ctx, precompiledObjectPath) + _, _ = bucket.GetPrecompiledObjectOutput(ctx, precompiledObjectPath, defaultBucketName) } } func Benchmark_GetPrecompiledObject(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = bucket.GetPrecompiledObject(ctx, precompiledObjectPath) + _, _ = bucket.GetPrecompiledObject(ctx, precompiledObjectPath, defaultBucketName) } } diff --git a/playground/backend/internal/environment/application.go b/playground/backend/internal/environment/application.go index 7b0318bf11882..13fbcb6e09990 100644 --- a/playground/backend/internal/environment/application.go +++ b/playground/backend/internal/environment/application.go @@ -99,10 +99,13 @@ type ApplicationEnvs struct { // pipelinesFolder is name of folder in which the pipelines resources are stored pipelinesFolder string + + // bucketName is a name of the GCS's bucket with examples + bucketName string } // NewApplicationEnvs constructor for ApplicationEnvs -func NewApplicationEnvs(workingDir, launchSite, projectId, pipelinesFolder string, cacheEnvs *CacheEnvs, pipelineExecuteTimeout time.Duration) *ApplicationEnvs { +func NewApplicationEnvs(workingDir, launchSite, projectId, pipelinesFolder string, cacheEnvs *CacheEnvs, pipelineExecuteTimeout time.Duration, bucketName string) *ApplicationEnvs { return &ApplicationEnvs{ workingDir: workingDir, cacheEnvs: cacheEnvs, @@ -110,6 +113,7 @@ func NewApplicationEnvs(workingDir, launchSite, projectId, pipelinesFolder strin launchSite: launchSite, projectId: projectId, pipelinesFolder: pipelinesFolder, + bucketName: bucketName, } } @@ -142,3 +146,8 @@ func (ae *ApplicationEnvs) GoogleProjectId() string { func (ae *ApplicationEnvs) PipelinesFolder() string { return ae.pipelinesFolder } + +// BucketName returns name of the GCS's bucket with examples +func (ae *ApplicationEnvs) BucketName() string { + return ae.bucketName +} diff --git a/playground/backend/internal/environment/environment_service.go b/playground/backend/internal/environment/environment_service.go index 9f4c8a5eaafe1..16f81221b7df2 100644 --- a/playground/backend/internal/environment/environment_service.go +++ b/playground/backend/internal/environment/environment_service.go @@ -60,6 +60,8 @@ const ( jsonExt = ".json" configFolderName = "configs" defaultNumOfParallelJobs = 20 + bucketNameKey = "BUCKET_NAME" + defaultBucketName = "playground-precompiled-objects" ) // Environment operates with environment structures: NetworkEnvs, BeamEnvs, ApplicationEnvs @@ -101,6 +103,7 @@ func GetApplicationEnvsFromOsEnvs() (*ApplicationEnvs, error) { launchSite := getEnv(launchSiteKey, defaultLaunchSite) projectId := os.Getenv(projectIdKey) pipelinesFolder := getEnv(pipelinesFolderKey, defaultPipelinesFolder) + bucketName := getEnv(bucketNameKey, defaultBucketName) if value, present := os.LookupEnv(cacheKeyExpirationTimeKey); present { if converted, err := time.ParseDuration(value); err == nil { @@ -118,7 +121,7 @@ func GetApplicationEnvsFromOsEnvs() (*ApplicationEnvs, error) { } if value, present := os.LookupEnv(workingDirKey); present { - return NewApplicationEnvs(value, launchSite, projectId, pipelinesFolder, NewCacheEnvs(cacheType, cacheAddress, cacheExpirationTime), pipelineExecuteTimeout), nil + return NewApplicationEnvs(value, launchSite, projectId, pipelinesFolder, NewCacheEnvs(cacheType, cacheAddress, cacheExpirationTime), pipelineExecuteTimeout, bucketName), nil } return nil, errors.New("APP_WORK_DIR env should be provided with os.env") } diff --git a/playground/backend/internal/environment/environment_service_test.go b/playground/backend/internal/environment/environment_service_test.go index 94bc2eda194a3..aef7186f9d242 100644 --- a/playground/backend/internal/environment/environment_service_test.go +++ b/playground/backend/internal/environment/environment_service_test.go @@ -93,7 +93,7 @@ func TestNewEnvironment(t *testing.T) { {name: "create env service with default envs", want: &Environment{ NetworkEnvs: *NewNetworkEnvs(defaultIp, defaultPort, defaultProtocol), BeamSdkEnvs: *NewBeamEnvs(defaultSdk, executorConfig, preparedModDir, 0), - ApplicationEnvs: *NewApplicationEnvs("/app", defaultLaunchSite, defaultProjectId, defaultPipelinesFolder, &CacheEnvs{defaultCacheType, defaultCacheAddress, defaultCacheKeyExpirationTime}, defaultPipelineExecuteTimeout), + ApplicationEnvs: *NewApplicationEnvs("/app", defaultLaunchSite, defaultProjectId, defaultPipelinesFolder, &CacheEnvs{defaultCacheType, defaultCacheAddress, defaultCacheKeyExpirationTime}, defaultPipelineExecuteTimeout, defaultBucketName), }}, } for _, tt := range tests { @@ -101,7 +101,7 @@ func TestNewEnvironment(t *testing.T) { if got := NewEnvironment( *NewNetworkEnvs(defaultIp, defaultPort, defaultProtocol), *NewBeamEnvs(defaultSdk, executorConfig, preparedModDir, 0), - *NewApplicationEnvs("/app", defaultLaunchSite, defaultProjectId, defaultPipelinesFolder, &CacheEnvs{defaultCacheType, defaultCacheAddress, defaultCacheKeyExpirationTime}, defaultPipelineExecuteTimeout)); !reflect.DeepEqual(got, tt.want) { + *NewApplicationEnvs("/app", defaultLaunchSite, defaultProjectId, defaultPipelinesFolder, &CacheEnvs{defaultCacheType, defaultCacheAddress, defaultCacheKeyExpirationTime}, defaultPipelineExecuteTimeout, defaultBucketName)); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewEnvironment() = %v, want %v", got, tt.want) } }) @@ -210,7 +210,7 @@ func Test_getApplicationEnvsFromOsEnvs(t *testing.T) { }{ { name: "working dir is provided", - want: NewApplicationEnvs("/app", defaultLaunchSite, defaultProjectId, defaultPipelinesFolder, &CacheEnvs{defaultCacheType, defaultCacheAddress, defaultCacheKeyExpirationTime}, defaultPipelineExecuteTimeout), + want: NewApplicationEnvs("/app", defaultLaunchSite, defaultProjectId, defaultPipelinesFolder, &CacheEnvs{defaultCacheType, defaultCacheAddress, defaultCacheKeyExpirationTime}, defaultPipelineExecuteTimeout, defaultBucketName), wantErr: false, envsToSet: map[string]string{workingDirKey: "/app", launchSiteKey: defaultLaunchSite, projectIdKey: defaultProjectId}, }, diff --git a/playground/backend/internal/utils/precompiled_objects_utils.go b/playground/backend/internal/utils/precompiled_objects_utils.go index 4fbef4b411313..9f38c7d0026a4 100644 --- a/playground/backend/internal/utils/precompiled_objects_utils.go +++ b/playground/backend/internal/utils/precompiled_objects_utils.go @@ -44,9 +44,9 @@ func PutPrecompiledObjectsToCategory(categoryName string, precompiledObjects *cl } // GetCatalogFromStorage returns the precompiled objects catalog from the cloud storage -func GetCatalogFromStorage(ctx context.Context) ([]*pb.Categories, error) { +func GetCatalogFromStorage(ctx context.Context, bucketName string) ([]*pb.Categories, error) { bucket := cloud_bucket.New() - sdkToCategories, err := bucket.GetPrecompiledObjects(ctx, pb.Sdk_SDK_UNSPECIFIED, "") + sdkToCategories, err := bucket.GetPrecompiledObjects(ctx, pb.Sdk_SDK_UNSPECIFIED, "", bucketName) if err != nil { logger.Errorf("GetPrecompiledObjects(): cloud storage error: %s", err.Error()) return nil, err From a441847f3dec71123c1da06236cebcc51d3ef0ab Mon Sep 17 00:00:00 2001 From: Aydar Zainutdinov Date: Mon, 28 Feb 2022 19:52:50 +0300 Subject: [PATCH 47/61] Merge pull request #16870 from [BEAM-13874][Playground] Tag multifile examples * add tag for WindowedWordCount * Add tag for TfIdf.java Add tag for TopWikipediaSessions.java Add tag for TrafficMaxLaneFlow.java Add tag for TrafficRoutes.java Update tag for WindowedWordCount.java Update tag for ReadFromText python kata * Update tag for TopWikipediaSessions.java Add tag for user_score.py * [BEAM-13874][Playground] Update description for tags * [BEAM-13874][Playground] Update tag's description * [BEAM-13874][Playground] Update tags category --- .../apache/beam/examples/WindowedWordCount.java | 13 +++++++++++++ .../org/apache/beam/examples/complete/TfIdf.java | 12 ++++++++++++ .../examples/complete/TopWikipediaSessions.java | 13 +++++++++++++ .../beam/examples/complete/TrafficMaxLaneFlow.java | 13 +++++++++++++ .../beam/examples/complete/TrafficRoutes.java | 14 ++++++++++++++ .../katas/python/IO/TextIO/ReadFromText/task.py | 2 +- .../examples/complete/game/user_score.py | 13 +++++++++++++ 7 files changed, 79 insertions(+), 1 deletion(-) diff --git a/examples/java/src/main/java/org/apache/beam/examples/WindowedWordCount.java b/examples/java/src/main/java/org/apache/beam/examples/WindowedWordCount.java index 769b5662c4cef..d04dc176be3dd 100644 --- a/examples/java/src/main/java/org/apache/beam/examples/WindowedWordCount.java +++ b/examples/java/src/main/java/org/apache/beam/examples/WindowedWordCount.java @@ -17,6 +17,19 @@ */ package org.apache.beam.examples; +// beam-playground: +// name: WindowedWordCount +// description: An example that counts words in text, and can run over either +// unbounded or bounded input collections. +// multifile: true +// pipeline_options: --output output.txt +// context_line: 103 +// categories: +// - Combiners +// - Options +// - Windowing +// - Quickstart + import java.io.IOException; import java.util.concurrent.ThreadLocalRandom; import org.apache.beam.examples.common.ExampleBigQueryTableOptions; diff --git a/examples/java/src/main/java/org/apache/beam/examples/complete/TfIdf.java b/examples/java/src/main/java/org/apache/beam/examples/complete/TfIdf.java index fe176ec51aca0..76115dc81681b 100644 --- a/examples/java/src/main/java/org/apache/beam/examples/complete/TfIdf.java +++ b/examples/java/src/main/java/org/apache/beam/examples/complete/TfIdf.java @@ -17,6 +17,18 @@ */ package org.apache.beam.examples.complete; +// beam-playground: +// name: TfIdf +// description: An example that computes a basic TF-IDF search table for a directory or +// GCS prefix. +// multifile: true +// pipeline_options: --output output.txt +// context_line: 97 +// categories: +// - Combiners +// - Options +// - Joins + import java.io.File; import java.io.IOException; import java.net.URI; diff --git a/examples/java/src/main/java/org/apache/beam/examples/complete/TopWikipediaSessions.java b/examples/java/src/main/java/org/apache/beam/examples/complete/TopWikipediaSessions.java index 01cd3a223b795..f02e04b7ff595 100644 --- a/examples/java/src/main/java/org/apache/beam/examples/complete/TopWikipediaSessions.java +++ b/examples/java/src/main/java/org/apache/beam/examples/complete/TopWikipediaSessions.java @@ -17,6 +17,19 @@ */ package org.apache.beam.examples.complete; +// beam-playground: +// name: TopWikipediaSessions +// description: An example that reads Wikipedia edit data from Cloud Storage and computes +// the user with the longest string of edits separated by no more than an hour within +// each month. +// multifile: true +// pipeline_options: --output output.txt +// context_line: 84 +// categories: +// - Combiners +// - Options +// - Windowing + import com.google.api.services.bigquery.model.TableRow; import java.io.IOException; import java.math.BigDecimal; diff --git a/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficMaxLaneFlow.java b/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficMaxLaneFlow.java index 23a02a84065a1..f80f264dff6a3 100644 --- a/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficMaxLaneFlow.java +++ b/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficMaxLaneFlow.java @@ -17,6 +17,19 @@ */ package org.apache.beam.examples.complete; +// beam-playground: +// name: TrafficMaxLaneFlow +// description: An example that analyzes traffic sensor data using SlidingWindows. For each +// window, it finds the lane that had the highest flow recorded, for each sensor station. +// It writes those max values along with auxiliary info to a BigQuery table. +// multifile: true +// context_line: 92 +// categories: +// - Combiners +// - Streaming +// - Options +// - Windowing + import com.google.api.services.bigquery.model.TableFieldSchema; import com.google.api.services.bigquery.model.TableReference; import com.google.api.services.bigquery.model.TableRow; diff --git a/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficRoutes.java b/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficRoutes.java index bc30b56b28858..832f6fc74f230 100644 --- a/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficRoutes.java +++ b/examples/java/src/main/java/org/apache/beam/examples/complete/TrafficRoutes.java @@ -17,6 +17,20 @@ */ package org.apache.beam.examples.complete; +// beam-playground: +// name: TrafficRoutes +// description: An example that analyzes traffic sensor data using SlidingWindows. +// For each window, it calculates the average speed over the window for some small set +// of predefined 'routes', and looks for 'slowdowns' in those routes. It writes its +// results to a BigQuery table. +// multifile: true +// context_line: 97 +// categories: +// - Combiners +// - Streaming +// - Options +// - Windowing + import com.google.api.services.bigquery.model.TableFieldSchema; import com.google.api.services.bigquery.model.TableReference; import com.google.api.services.bigquery.model.TableRow; diff --git a/learning/katas/python/IO/TextIO/ReadFromText/task.py b/learning/katas/python/IO/TextIO/ReadFromText/task.py index e3e8deff4c307..1121779328bf7 100644 --- a/learning/katas/python/IO/TextIO/ReadFromText/task.py +++ b/learning/katas/python/IO/TextIO/ReadFromText/task.py @@ -17,7 +17,7 @@ # beam-playground: # name: ReadFromText # description: Task from katas to read from text files. -# multifile: false +# multifile: true # context_line: 29 # categories: # - IO diff --git a/sdks/python/apache_beam/examples/complete/game/user_score.py b/sdks/python/apache_beam/examples/complete/game/user_score.py index 6a97d7e2ed3b9..be8e9799fe2cd 100644 --- a/sdks/python/apache_beam/examples/complete/game/user_score.py +++ b/sdks/python/apache_beam/examples/complete/game/user_score.py @@ -56,6 +56,19 @@ # pytype: skip-file +# beam-playground: +# name: UserScore +# description: batch processing; reading input from Google Cloud Storage or a +# from a local text file, and writing output to a text file; using +# standalone DoFns; use of the CombinePerKey transform. +# multifile: true +# pipeline_options: --output output.txt +# context_line: 81 +# categories: +# - Batch +# - Combiners +# - Options + import argparse import csv import logging From af50774d9c9082648cfeefbc81730a7e4a893682 Mon Sep 17 00:00:00 2001 From: Alexander Zhuravlev Date: Mon, 28 Feb 2022 20:56:13 +0400 Subject: [PATCH 48/61] Merge pull request #16910 from [BEAM-13724] [Playground] Get the default example on the frontend * [BEAM-13724] Created getDefaultExample method, updated catalog methods (now it loads asynchronously) * [BEAM-13724] Fixed "example == null" bug, that appears after getting into the playground with the "Try in playground" button * [BEAM-13724] Fixed PR Remarks --- .../example_client/example_client.dart | 5 ++ .../example_client/grpc_example_client.dart | 24 +++++++- .../repositories/example_repository.dart | 8 +++ .../models/get_default_example_response.dart | 25 +++++++++ .../modules/sdk/components/sdk_selector.dart | 2 +- .../components/playground_page_providers.dart | 21 +++++-- .../playground/states/examples_state.dart | 55 ++++++++++++------- 7 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 playground/frontend/lib/modules/examples/repositories/models/get_default_example_response.dart diff --git a/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart b/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart index 204ab49a8ac68..389cf54859f78 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart @@ -17,6 +17,7 @@ */ import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; +import 'package:playground/modules/examples/repositories/models/get_default_example_response.dart'; import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; import 'package:playground/modules/examples/repositories/models/get_example_response.dart'; import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; @@ -29,6 +30,10 @@ abstract class ExampleClient { Future getExample(GetExampleRequestWrapper request); + Future getDefaultExample( + GetExampleRequestWrapper request, + ); + Future getExampleOutput(GetExampleRequestWrapper request); Future getExampleLogs(GetExampleRequestWrapper request); diff --git a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart index 8c9f71e5633a2..f7ead0ad9f316 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart @@ -17,13 +17,14 @@ */ import 'package:grpc/grpc_web.dart'; -import 'package:playground/config.g.dart'; import 'package:playground/api/iis_workaround_channel.dart'; import 'package:playground/api/v1/api.pbgrpc.dart' as grpc; +import 'package:playground/config.g.dart'; import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; import 'package:playground/modules/examples/models/category_model.dart'; import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/modules/examples/repositories/example_client/example_client.dart'; +import 'package:playground/modules/examples/repositories/models/get_default_example_response.dart'; import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; import 'package:playground/modules/examples/repositories/models/get_example_response.dart'; import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; @@ -54,6 +55,19 @@ class GrpcExampleClient implements ExampleClient { ); } + @override + Future getDefaultExample( + GetExampleRequestWrapper request, + ) { + return _runSafely( + () => _defaultClient + .getDefaultPrecompiledObject( + _getDefaultExampleRequestToGrpcRequest(request)) + .then((response) => GetDefaultExampleResponse( + _toExampleModel(response.precompiledObject))), + ); + } + @override Future getExample(GetExampleRequestWrapper request) { return _runSafely( @@ -126,6 +140,14 @@ class GrpcExampleClient implements ExampleClient { : _getGrpcSdk(request.sdk!); } + grpc.GetDefaultPrecompiledObjectRequest + _getDefaultExampleRequestToGrpcRequest( + GetExampleRequestWrapper request, + ) { + return grpc.GetDefaultPrecompiledObjectRequest() + ..sdk = _getGrpcSdk(request.sdk); + } + grpc.GetPrecompiledObjectCodeRequest _getExampleCodeRequestToGrpcRequest( GetExampleRequestWrapper request, ) { diff --git a/playground/frontend/lib/modules/examples/repositories/example_repository.dart b/playground/frontend/lib/modules/examples/repositories/example_repository.dart index b45f70180a16c..83365d9372072 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_repository.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_repository.dart @@ -17,6 +17,7 @@ */ import 'package:playground/modules/examples/models/category_model.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/modules/examples/repositories/example_client/example_client.dart'; import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; @@ -36,6 +37,13 @@ class ExampleRepository { return result.categories; } + Future getDefaultExample( + GetExampleRequestWrapper request, + ) async { + final result = await _client.getDefaultExample(request); + return result.example; + } + Future getExampleSource(GetExampleRequestWrapper request) async { final result = await _client.getExample(request); return result.code; diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_default_example_response.dart b/playground/frontend/lib/modules/examples/repositories/models/get_default_example_response.dart new file mode 100644 index 0000000000000..bec04a028d960 --- /dev/null +++ b/playground/frontend/lib/modules/examples/repositories/models/get_default_example_response.dart @@ -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. + */ + +import 'package:playground/modules/examples/models/example_model.dart'; + +class GetDefaultExampleResponse { + final ExampleModel example; + + GetDefaultExampleResponse(this.example); +} diff --git a/playground/frontend/lib/modules/sdk/components/sdk_selector.dart b/playground/frontend/lib/modules/sdk/components/sdk_selector.dart index 015095697f131..c19f718d6a6fe 100644 --- a/playground/frontend/lib/modules/sdk/components/sdk_selector.dart +++ b/playground/frontend/lib/modules/sdk/components/sdk_selector.dart @@ -68,7 +68,7 @@ class SDKSelector extends StatelessWidget { close(); setSdk(value); setExample( - state.defaultExamplesMap![value] ?? + state.defaultExamplesMap[value] ?? ExampleModel( name: kEmptyExampleName, path: '', diff --git a/playground/frontend/lib/pages/playground/components/playground_page_providers.dart b/playground/frontend/lib/pages/playground/components/playground_page_providers.dart index 2ad2951eb4408..3fc209ffb3e5e 100644 --- a/playground/frontend/lib/pages/playground/components/playground_page_providers.dart +++ b/playground/frontend/lib/pages/playground/components/playground_page_providers.dart @@ -57,19 +57,18 @@ class PlaygroundPageProviders extends StatelessWidget { return PlaygroundState(codeRepository: kCodeRepository); } - if (exampleState.sdkCategories != null && - playground.selectedExample == null) { - final example = _getExample(exampleState, playground); + if (playground.selectedExample == null) { final newPlayground = PlaygroundState( codeRepository: kCodeRepository, sdk: playground.sdk, selectedExample: null, ); + final example = _getExample(exampleState, newPlayground); if (example != null) { exampleState .loadExampleInfo( example, - playground.sdk, + newPlayground.sdk, ) .then((exampleWithInfo) => newPlayground.setExample(exampleWithInfo)); @@ -95,17 +94,27 @@ class PlaygroundPageProviders extends StatelessWidget { PlaygroundState playground, ) { final examplePath = Uri.base.queryParameters[kExampleParam]; + + if (exampleState.defaultExamplesMap.isEmpty) { + exampleState.loadDefaultExamples(); + } + + if (examplePath?.isEmpty ?? true) { + return exampleState.defaultExamplesMap[playground.sdk]; + } + final allExamples = exampleState.sdkCategories?.values .expand((sdkCategory) => sdkCategory.map((e) => e.examples)) .expand((element) => element) .toList(); + if (allExamples?.isEmpty ?? true) { return null; } - final defaultExample = exampleState.defaultExamplesMap![playground.sdk]!; + return allExamples?.firstWhere( (example) => example.path == examplePath, - orElse: () => defaultExample, + orElse: () => exampleState.defaultExample!, ); } } diff --git a/playground/frontend/lib/pages/playground/states/examples_state.dart b/playground/frontend/lib/pages/playground/states/examples_state.dart index 71b626590499e..365df31189743 100644 --- a/playground/frontend/lib/pages/playground/states/examples_state.dart +++ b/playground/frontend/lib/pages/playground/states/examples_state.dart @@ -27,7 +27,8 @@ import 'package:playground/modules/sdk/models/sdk.dart'; class ExampleState with ChangeNotifier { final ExampleRepository _exampleRepository; Map>? sdkCategories; - Map? defaultExamplesMap; + Map defaultExamplesMap = {}; + ExampleModel? defaultExample; bool isSelectorOpened = false; ExampleState(this._exampleRepository); @@ -36,6 +37,11 @@ class ExampleState with ChangeNotifier { _loadCategories(); } + setSdkCategories(Map> map) { + sdkCategories = map; + notifyListeners(); + } + List? getCategories(SDK sdk) { return sdkCategories?[sdk] ?? []; } @@ -58,7 +64,6 @@ class ExampleState with ChangeNotifier { ); } - Future getExampleGraph(String id, SDK sdk) async { return await _exampleRepository.getExampleGraph( GetExampleRequestWrapper(id, sdk), @@ -82,12 +87,12 @@ class ExampleState with ChangeNotifier { return example; } - _loadCategories() async { - sdkCategories = await _exampleRepository.getListOfExamples( - GetListOfExamplesRequestWrapper(sdk: null, category: null), - ); - await _loadDefaultExamples(sdkCategories); - notifyListeners(); + _loadCategories() { + _exampleRepository + .getListOfExamples( + GetListOfExamplesRequestWrapper(sdk: null, category: null), + ) + .then((map) => setSdkCategories(map)); } changeSelectorVisibility() { @@ -95,18 +100,30 @@ class ExampleState with ChangeNotifier { notifyListeners(); } - _loadDefaultExamples(sdkCategories) async { - defaultExamplesMap = {}; - List> entries = []; - for (SDK sdk in SDK.values) { - ExampleModel? defaultExample = sdkCategories![sdk]?.first.examples.first; - if (defaultExample != null) { - // load source and output async - loadExampleInfo(defaultExample, sdk); - entries.add(MapEntry(sdk, defaultExample)); - } + loadDefaultExamples() async { + if (defaultExamplesMap.isNotEmpty) { + return; + } + + List> defaultExamples = []; + + for (var value in SDK.values) { + defaultExamples.add( + MapEntry( + value, + await _exampleRepository.getDefaultExample( + // First parameter is an empty string, because we don't need path to get the default example. + GetExampleRequestWrapper('', value), + ), + ), + ); + } + + defaultExamplesMap.addEntries(defaultExamples); + for (var entry in defaultExamplesMap.entries) { + loadExampleInfo(entry.value, entry.key) + .then((value) => defaultExamplesMap[entry.key] = value); } - defaultExamplesMap?.addEntries(entries); notifyListeners(); } } From 643359463c94ea628dbf6cccb035f7c280d6b4c2 Mon Sep 17 00:00:00 2001 From: Lukasz Cwik Date: Mon, 28 Feb 2022 10:10:41 -0800 Subject: [PATCH 49/61] [BEAM-14008] Fix incorrect guava import (#16966) --- .../org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java b/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java index 6d774b142444a..4b172f169fb2a 100644 --- a/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java +++ b/sdks/java/fn-execution/src/main/java/org/apache/beam/sdk/fn/channel/ManagedChannelFactory.java @@ -17,7 +17,6 @@ */ package org.apache.beam.sdk.fn.channel; -import avro.shaded.com.google.common.collect.ImmutableList; import java.net.SocketAddress; import java.util.Collections; import java.util.List; @@ -31,6 +30,7 @@ import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.epoll.EpollEventLoopGroup; import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.epoll.EpollSocketChannel; import org.apache.beam.vendor.grpc.v1p43p2.io.netty.channel.unix.DomainSocketAddress; +import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.ImmutableList; /** A Factory which creates {@link ManagedChannel} instances. */ public class ManagedChannelFactory { From d9d3c93c69c04c132310fe70be0ac243757460f7 Mon Sep 17 00:00:00 2001 From: Niel Markwick Date: Mon, 28 Feb 2022 20:47:44 +0100 Subject: [PATCH 50/61] Fix ignored exception in BatchSpannerRead. (#16960) Fix code and tests: failures to read from Spanner were ignored, and the "ok" serviceCallMwtric was updated before the read took place. --- .../sdk/io/gcp/spanner/BatchSpannerRead.java | 3 +- .../sdk/io/gcp/spanner/SpannerIOReadTest.java | 127 +++++++++++++++--- 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/BatchSpannerRead.java b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/BatchSpannerRead.java index efc983e39f5ea..810f7ce8aaaea 100644 --- a/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/BatchSpannerRead.java +++ b/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/BatchSpannerRead.java @@ -218,7 +218,6 @@ public void processElement(ProcessContext c) throws Exception { BatchReadOnlyTransaction batchTx = spannerAccessor.getBatchClient().batchReadOnlyTransaction(tx.transactionId()); - serviceCallMetric.call("ok"); Partition p = c.element(); try (ResultSet resultSet = batchTx.execute(p)) { while (resultSet.next()) { @@ -227,7 +226,9 @@ public void processElement(ProcessContext c) throws Exception { } } catch (SpannerException e) { serviceCallMetric.call(e.getErrorCode().getGrpcStatusCode().toString()); + throw (e); } + serviceCallMetric.call("ok"); } private ServiceCallMetric createServiceCallMetric( diff --git a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIOReadTest.java b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIOReadTest.java index c80490ea472f3..48f7fed7feee6 100644 --- a/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIOReadTest.java +++ b/sdks/java/io/google-cloud-platform/src/test/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIOReadTest.java @@ -34,6 +34,7 @@ import com.google.cloud.spanner.Partition; import com.google.cloud.spanner.PartitionOptions; import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.Struct; @@ -41,6 +42,7 @@ import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Value; import com.google.protobuf.ByteString; +import io.grpc.Status.Code; import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; @@ -49,6 +51,7 @@ import org.apache.beam.runners.core.metrics.MetricsContainerImpl; import org.apache.beam.runners.core.metrics.MonitoringInfoConstants; import org.apache.beam.runners.core.metrics.MonitoringInfoMetricName; +import org.apache.beam.sdk.Pipeline.PipelineExecutionException; import org.apache.beam.sdk.io.gcp.spanner.SpannerIO.Read; import org.apache.beam.sdk.metrics.MetricsEnvironment; import org.apache.beam.sdk.testing.PAssert; @@ -293,7 +296,7 @@ public void runReadWithPriority() throws Exception { } @Test - public void testQueryMetrics() throws Exception { + public void testQueryMetricsFail() throws Exception { Timestamp timestamp = Timestamp.ofTimeMicroseconds(12345); TimestampBound timestampBound = TimestampBound.ofReadTimestamp(timestamp); @@ -322,25 +325,74 @@ public void testQueryMetrics() throws Exception { any(PartitionOptions.class), eq(Statement.of("SELECT * FROM users")), any(ReadQueryUpdateTransactionOption.class))) - .thenReturn(Arrays.asList(fakePartition, fakePartition)); + .thenReturn(Arrays.asList(fakePartition)); when(mockBatchTx.execute(any(Partition.class))) .thenThrow( SpannerExceptionFactory.newSpannerException( - ErrorCode.DEADLINE_EXCEEDED, "Simulated Timeout 1")) - .thenThrow( - SpannerExceptionFactory.newSpannerException( - ErrorCode.DEADLINE_EXCEEDED, "Simulated Timeout 2")) + ErrorCode.DEADLINE_EXCEEDED, "Simulated Timeout 1")); + try { + pipeline.run(); + } catch (PipelineExecutionException e) { + if (e.getCause() instanceof SpannerException + && ((SpannerException) e.getCause()).getErrorCode().getGrpcStatusCode() + == Code.DEADLINE_EXCEEDED) { + // expected + } else { + throw e; + } + } + verifyMetricWasSet("test", "aaa", "123", "deadline_exceeded", null, 1); + verifyMetricWasSet("test", "aaa", "123", "ok", null, 0); + } + + @Test + public void testQueryMetricsSucceed() throws Exception { + Timestamp timestamp = Timestamp.ofTimeMicroseconds(12345); + TimestampBound timestampBound = TimestampBound.ofReadTimestamp(timestamp); + + SpannerConfig spannerConfig = getSpannerConfig(); + + pipeline.apply( + "read q", + SpannerIO.read() + .withSpannerConfig(spannerConfig) + .withQuery("SELECT * FROM users") + .withQueryName("queryName") + .withTimestampBound(timestampBound)); + + FakeBatchTransactionId id = new FakeBatchTransactionId("runQueryTest"); + when(mockBatchTx.getBatchTransactionId()).thenReturn(id); + + when(serviceFactory.mockBatchClient().batchReadOnlyTransaction(timestampBound)) + .thenReturn(mockBatchTx); + when(serviceFactory.mockBatchClient().batchReadOnlyTransaction(any(BatchTransactionId.class))) + .thenReturn(mockBatchTx); + + Partition fakePartition = + FakePartitionFactory.createFakeQueryPartition(ByteString.copyFromUtf8("one")); + + when(mockBatchTx.partitionQuery( + any(PartitionOptions.class), + eq(Statement.of("SELECT * FROM users")), + any(ReadQueryUpdateTransactionOption.class))) + .thenReturn(Arrays.asList(fakePartition, fakePartition)); + when(mockBatchTx.execute(any(Partition.class))) .thenReturn( ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(0, 2)), - ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(2, 6))); + ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(2, 4)), + ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(4, 6))) + .thenReturn( + ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(0, 2)), + ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(2, 4)), + ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(4, 6))); pipeline.run(); - verifyMetricWasSet("test", "aaa", "123", "deadline_exceeded", null, 2); + verifyMetricWasSet("test", "aaa", "123", "deadline_exceeded", null, 0); verifyMetricWasSet("test", "aaa", "123", "ok", null, 2); } @Test - public void testReadMetrics() throws Exception { + public void testReadMetricsFail() throws Exception { Timestamp timestamp = Timestamp.ofTimeMicroseconds(12345); TimestampBound timestampBound = TimestampBound.ofReadTimestamp(timestamp); @@ -371,21 +423,66 @@ public void testReadMetrics() throws Exception { eq(KeySet.all()), eq(Arrays.asList("id", "name")), any(ReadQueryUpdateTransactionOption.class))) - .thenReturn(Arrays.asList(fakePartition, fakePartition, fakePartition)); + .thenReturn(Arrays.asList(fakePartition)); when(mockBatchTx.execute(any(Partition.class))) .thenThrow( SpannerExceptionFactory.newSpannerException( - ErrorCode.DEADLINE_EXCEEDED, "Simulated Timeout 1")) - .thenThrow( - SpannerExceptionFactory.newSpannerException( - ErrorCode.DEADLINE_EXCEEDED, "Simulated Timeout 2")) + ErrorCode.DEADLINE_EXCEEDED, "Simulated Timeout 1")); + try { + pipeline.run(); + } catch (PipelineExecutionException e) { + if (e.getCause() instanceof SpannerException + && ((SpannerException) e.getCause()).getErrorCode().getGrpcStatusCode() + == Code.DEADLINE_EXCEEDED) { + // expected + } else { + throw e; + } + } + verifyMetricWasSet("test", "aaa", "123", "deadline_exceeded", null, 1); + verifyMetricWasSet("test", "aaa", "123", "ok", null, 0); + } + + @Test + public void testReadMetricsSucceed() throws Exception { + Timestamp timestamp = Timestamp.ofTimeMicroseconds(12345); + TimestampBound timestampBound = TimestampBound.ofReadTimestamp(timestamp); + + SpannerConfig spannerConfig = getSpannerConfig(); + + pipeline.apply( + "read q", + SpannerIO.read() + .withSpannerConfig(spannerConfig) + .withTable("users") + .withColumns("id", "name") + .withTimestampBound(timestampBound)); + + FakeBatchTransactionId id = new FakeBatchTransactionId("runReadTest"); + when(mockBatchTx.getBatchTransactionId()).thenReturn(id); + + when(serviceFactory.mockBatchClient().batchReadOnlyTransaction(timestampBound)) + .thenReturn(mockBatchTx); + when(serviceFactory.mockBatchClient().batchReadOnlyTransaction(any(BatchTransactionId.class))) + .thenReturn(mockBatchTx); + + Partition fakePartition = + FakePartitionFactory.createFakeReadPartition(ByteString.copyFromUtf8("one")); + + when(mockBatchTx.partitionRead( + any(PartitionOptions.class), + eq("users"), + eq(KeySet.all()), + eq(Arrays.asList("id", "name")), + any(ReadQueryUpdateTransactionOption.class))) + .thenReturn(Arrays.asList(fakePartition, fakePartition, fakePartition)); + when(mockBatchTx.execute(any(Partition.class))) .thenReturn( ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(0, 2)), ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(2, 4)), ResultSets.forRows(FAKE_TYPE, FAKE_ROWS.subList(4, 6))); pipeline.run(); - verifyMetricWasSet("test", "aaa", "123", "deadline_exceeded", null, 2); verifyMetricWasSet("test", "aaa", "123", "ok", null, 3); } From 5a8bb08b0ac7ddeabafec8f45ceed4dffc5ac931 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 28 Feb 2022 21:16:13 -0500 Subject: [PATCH 51/61] [BEAM-13917] Improve coverage of databaseio package (#16956) --- sdks/go.mod | 1 + sdks/go.sum | 8 +- .../pkg/beam/io/databaseio/database_test.go | 86 +++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 sdks/go/pkg/beam/io/databaseio/database_test.go diff --git a/sdks/go.mod b/sdks/go.mod index ce8703ff82e97..49315401b4b42 100644 --- a/sdks/go.mod +++ b/sdks/go.mod @@ -40,6 +40,7 @@ require ( github.com/linkedin/goavro v2.1.0+incompatible github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/nightlyone/lockfile v1.0.0 + github.com/proullon/ramsql v0.0.0-20211120092837-c8d0a408b939 // indirect github.com/spf13/cobra v1.2.1 github.com/testcontainers/testcontainers-go v0.12.0 golang.org/x/net v0.0.0-20211108170745-6635138e15ea diff --git a/sdks/go.sum b/sdks/go.sum index 1efb69e04fbef..d79b849b5a535 100644 --- a/sdks/go.sum +++ b/sdks/go.sum @@ -272,6 +272,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -459,6 +460,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linkedin/goavro v2.1.0+incompatible h1:DV2aUlj2xZiuxQyvag8Dy7zjY69ENjS66bWkSfdpddY= @@ -588,6 +590,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/proullon/ramsql v0.0.0-20211120092837-c8d0a408b939 h1:mtMU7aT8cTAyNL3O4RyOfe/OOUxwCN525SIbKQoUvw0= +github.com/proullon/ramsql v0.0.0-20211120092837-c8d0a408b939/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1146,10 +1150,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/sdks/go/pkg/beam/io/databaseio/database_test.go b/sdks/go/pkg/beam/io/databaseio/database_test.go new file mode 100644 index 0000000000000..1876f57012159 --- /dev/null +++ b/sdks/go/pkg/beam/io/databaseio/database_test.go @@ -0,0 +1,86 @@ +// 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 databaseio + +import ( + "database/sql" + "reflect" + "testing" + + "github.com/apache/beam/sdks/v2/go/pkg/beam" + _ "github.com/apache/beam/sdks/v2/go/pkg/beam/runners/direct" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/passert" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/ptest" + _ "github.com/proullon/ramsql/driver" +) + +type Address struct { + Street string + Street_number int +} + +func TestRead(t *testing.T) { + db, err := sql.Open("ramsql", "user:password@/dbname") + if err != nil { + t.Fatalf("Test infra failure: Failed to open database with error %v", err) + } + defer db.Close() + if err = insertTestData(db); err != nil { + t.Fatalf("Test infra failure: Failed to create/populate table with error %v", err) + } + + p, s := beam.NewPipelineWithRoot() + elements := Read(s, "ramsql", "user:password@/dbname", "address", reflect.TypeOf(Address{})) + passert.Count(s, elements, "NumElements", 2) + passert.Equals(s, elements, Address{Street: "orchard lane", Street_number: 1}, Address{Street: "morris st", Street_number: 200}) + + ptest.RunAndValidate(t, p) +} + +func TestQuery(t *testing.T) { + db, err := sql.Open("ramsql", "user:password@/dbname2") + if err != nil { + t.Fatalf("Test infra failure: Failed to open database with error %v", err) + } + defer db.Close() + if err = insertTestData(db); err != nil { + t.Fatalf("Test infra failure: Failed to create/populate table with error %v", err) + } + + p, s := beam.NewPipelineWithRoot() + read := Query(s, "ramsql", "user:password@/dbname2", "SELECT * FROM address WHERE street_number < 10", reflect.TypeOf(Address{})) + + passert.Count(s, read, "NumElementsFromRead", 1) + passert.Equals(s, read, Address{Street: "orchard lane", Street_number: 1}) + + ptest.RunAndValidate(t, p) +} + +func insertTestData(db *sql.DB) error { + _, err := db.Exec("CREATE TABLE address (street TEXT, street_number INT);") + if err != nil { + return err + } + _, err = db.Exec("INSERT INTO address (street, street_number) VALUES ('orchard lane', 1);") + if err != nil { + return err + } + _, err = db.Exec("INSERT INTO address (street, street_number) VALUES ('morris st', 200);") + if err != nil { + return err + } + return nil +} From 3070d5f75ef3753d9341e3b2b3f5481c17ce41e6 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 28 Feb 2022 21:20:00 -0500 Subject: [PATCH 52/61] [BEAM-13925] Add entry files to process new prs and pr updates for PR automation (#16950) --- scripts/ci/pr-bot/.gitignore | 38 ++++ scripts/ci/pr-bot/Commands.md | 32 ++++ scripts/ci/pr-bot/processNewPrs.ts | 226 ++++++++++++++++++++++++ scripts/ci/pr-bot/processPrUpdate.ts | 208 ++++++++++++++++++++++ scripts/ci/pr-bot/shared/githubUtils.ts | 8 + scripts/ci/pr-bot/shared/userCommand.ts | 6 +- 6 files changed, 514 insertions(+), 4 deletions(-) create mode 100644 scripts/ci/pr-bot/.gitignore create mode 100644 scripts/ci/pr-bot/Commands.md create mode 100644 scripts/ci/pr-bot/processNewPrs.ts create mode 100644 scripts/ci/pr-bot/processPrUpdate.ts diff --git a/scripts/ci/pr-bot/.gitignore b/scripts/ci/pr-bot/.gitignore new file mode 100644 index 0000000000000..7b665129e6948 --- /dev/null +++ b/scripts/ci/pr-bot/.gitignore @@ -0,0 +1,38 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.swp + +pids +logs +results +tmp + +# Coverage reports +coverage + +# API keys and secrets +.env + +# Dependency directory +node_modules +bower_components + +# Editors +.idea +*.iml + +# OS metadata +.DS_Store +Thumbs.db + +# Ignore built js files +lib/**/* + +# ignore yarn.lock +yarn.lock \ No newline at end of file diff --git a/scripts/ci/pr-bot/Commands.md b/scripts/ci/pr-bot/Commands.md new file mode 100644 index 0000000000000..83d21cb7836de --- /dev/null +++ b/scripts/ci/pr-bot/Commands.md @@ -0,0 +1,32 @@ + + +# PR Bot Commands + +The following commands are available for interaction with the PR Bot +All commands are case insensitive. + +| Command | Description | +| ----------- | ----------- | +| `r: @username` | Ask someone for a review. This will disable the bot for the PR since it assumes you are able to find a reviewer. | +| `assign to next reviewer` | If someone has been assigned to a PR by the bot, this unassigns them and picks a new reviewer. Useful if you don't have the bandwitdth or context to review. | +| `stop reviewer notifications` | This will disable the bot for the PR. | +| `remind me after tests pass` | This will comment after all checks complete and tag the person who commented the command. | +| `waiting on author` | This shifts the attention set to the author. The author can shift the attention set back to the reviewer by commenting anywhere or pushing. | +| `assign set of reviewers` | If the bot has not yet assigned a set of reviewers to the PR, this command will trigger that happening. | \ No newline at end of file diff --git a/scripts/ci/pr-bot/processNewPrs.ts b/scripts/ci/pr-bot/processNewPrs.ts new file mode 100644 index 0000000000000..411e3b5428ece --- /dev/null +++ b/scripts/ci/pr-bot/processNewPrs.ts @@ -0,0 +1,226 @@ +/* + * 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. + */ + +const github = require("./shared/githubUtils"); +const { getChecksStatus } = require("./shared/checks"); +const commentStrings = require("./shared/commentStrings"); +const { ReviewerConfig } = require("./shared/reviewerConfig"); +const { PersistentState } = require("./shared/persistentState"); +const { Pr } = require("./shared/pr"); +const { REPO_OWNER, REPO, PATH_TO_CONFIG_FILE } = require("./shared/constants"); +import { CheckStatus } from "./shared/checks"; + +/* + * Returns true if the pr needs to be processed or false otherwise. + * We don't need to process PRs that: + * 1) Have WIP in their name + * 2) Are less than 20 minutes old + * 3) Are draft prs + * 4) Are closed + * 5) Have already been processed + * 6) Have notifications stopped + * 7) The pr doesn't contain the go label (temporary). TODO(damccorm) - remove this when we're ready to roll this out to everyone. + * unless we're supposed to remind the user after tests pass + * (in which case that's all we need to do). + */ +function needsProcessed(pull: any, prState: typeof Pr): boolean { + if (!pull.labels.find((label) => label.name.toLowerCase() === "go")) { + console.log( + `Skipping PR ${pull.number} because it doesn't contain the go label` + ); + return false; + } + if (prState.remindAfterTestsPass && prState.remindAfterTestsPass.length > 0) { + return true; + } + if (pull.title.toLowerCase().indexOf("wip") >= 0) { + console.log(`Skipping PR ${pull.number} because it is a WIP`); + return false; + } + let timeCutoff = new Date(new Date().getTime() - 20 * 60000); + if (new Date(pull.created_at) > timeCutoff) { + console.log( + `Skipping PR ${pull.number} because it was created less than 20 minutes ago` + ); + return false; + } + if (pull.state.toLowerCase() !== "open") { + console.log(`Skipping PR ${pull.number} because it is closed`); + return false; + } + if (pull.draft) { + console.log(`Skipping PR ${pull.number} because it is a draft`); + return false; + } + if (Object.keys(prState.reviewersAssignedForLabels).length > 0) { + console.log( + `Skipping PR ${pull.number} because it already has been assigned` + ); + return false; + } + if (prState.stopReviewerNotifications) { + console.log( + `Skipping PR ${pull.number} because reviewer notifications have been stopped` + ); + return false; + } + + return true; +} + +/* + * If the checks passed in via checkstate have completed, notifies the users who have configured notifications. + */ +async function remindIfChecksCompleted( + pull: any, + stateClient: typeof PersistentState, + checkState: CheckStatus, + prState: typeof Pr +) { + console.log( + `Notifying reviewers if checks for PR ${pull.number} have completed, then returning` + ); + if (!checkState.completed) { + return; + } + if (checkState.succeeded) { + await github.addPrComment( + pull.number, + commentStrings.allChecksPassed(prState.remindAfterTestsPass) + ); + } else { + await github.addPrComment( + pull.number, + commentStrings.someChecksFailing(prState.remindAfterTestsPass) + ); + } + prState.remindAfterTestsPass = []; + await stateClient.writePrState(pull.number, prState); +} + +/* + * If we haven't already, let the author know checks are failing. + */ +async function notifyChecksFailed( + pull: any, + stateClient: typeof PersistentState, + prState: typeof Pr +) { + console.log( + `Checks are failing for PR ${pull.number}. Commenting if we haven't already and skipping.` + ); + if (!prState.commentedAboutFailingChecks) { + await github.addPrComment( + pull.number, + commentStrings.failingChecksCantAssign() + ); + } + prState.commentedAboutFailingChecks = true; + await stateClient.writePrState(pull.number, prState); +} + +/* + * Performs all the business logic of processing a new pull request, including: + * 1) Checking if it needs processed + * 2) Reminding reviewers if checks have completed (if they've subscribed to that) + * 3) Picking/assigning reviewers + * 4) Adding "Next Action: Reviewers label" + * 5) Storing the state of the pull request/reviewers in a dedicated branch. + */ +async function processPull( + pull: any, + reviewerConfig: typeof ReviewerConfig, + stateClient: typeof PersistentState +) { + let prState = await stateClient.getPrState(pull.number); + if (!needsProcessed(pull, prState)) { + return; + } + + let checkState = await getChecksStatus(REPO_OWNER, REPO, pull.head.sha); + + if (prState.remindAfterTestsPass && prState.remindAfterTestsPass.length > 0) { + return await remindIfChecksCompleted( + pull, + stateClient, + checkState, + prState + ); + } + + if (!checkState.succeeded) { + return await notifyChecksFailed(pull, stateClient, prState); + } + prState.commentedAboutFailingChecks = false; + + // Pick reviewers to assign. Store them in reviewerStateToUpdate and update the prState object with those reviewers (and their associated labels) + let reviewerStateToUpdate: { [key: string]: typeof ReviewersForLabel } = {}; + const reviewersForLabels: { [key: string]: string[] } = + reviewerConfig.getReviewersForLabels(pull.labels, [pull.user.login]); + var labels = Object.keys(reviewersForLabels); + if (!labels || labels.length === 0) { + return; + } + for (const label of labels) { + let availableReviewers = reviewersForLabels[label]; + let reviewersState = await stateClient.getReviewersForLabelState(label); + let chosenReviewer = reviewersState.assignNextReviewer(availableReviewers); + reviewerStateToUpdate[label] = reviewersState; + prState.reviewersAssignedForLabels[label] = chosenReviewer; + } + + console.log(`Assigning reviewers for PR ${pull.number}`); + await github.addPrComment( + pull.number, + commentStrings.assignReviewer(prState.reviewersAssignedForLabels) + ); + + github.nextActionReviewers(pull.number, pull.labels); + prState.nextAction = "Reviewers"; + + await stateClient.writePrState(pull.number, prState); + let labelsToUpdate = Object.keys(reviewerStateToUpdate); + for (const label of labelsToUpdate) { + await stateClient.writeReviewersForLabelState( + label, + reviewerStateToUpdate[label] + ); + } +} + +async function processNewPrs() { + const githubClient = github.getGitHubClient(); + let reviewerConfig = new ReviewerConfig(PATH_TO_CONFIG_FILE); + let stateClient = new PersistentState(); + + let openPulls = await githubClient.paginate( + "GET /repos/{owner}/{repo}/pulls", + { + owner: REPO_OWNER, + repo: REPO, + } + ); + + for (const pull of openPulls) { + await processPull(pull, reviewerConfig, stateClient); + } +} + +processNewPrs(); + +export {}; diff --git a/scripts/ci/pr-bot/processPrUpdate.ts b/scripts/ci/pr-bot/processPrUpdate.ts new file mode 100644 index 0000000000000..154444ee076b3 --- /dev/null +++ b/scripts/ci/pr-bot/processPrUpdate.ts @@ -0,0 +1,208 @@ +/* + * 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. + */ + +const github = require("@actions/github"); +const commentStrings = require("./shared/commentStrings"); +const { processCommand } = require("./shared/userCommand"); +const { + addPrComment, + nextActionReviewers, + getPullAuthorFromPayload, + getPullNumberFromPayload, +} = require("./shared/githubUtils"); +const { PersistentState } = require("./shared/persistentState"); +const { ReviewerConfig } = require("./shared/reviewerConfig"); +const { PATH_TO_CONFIG_FILE } = require("./shared/constants"); + +const reviewerAction = "Reviewers"; + +async function areReviewersAssigned( + pullNumber: number, + stateClient: typeof PersistentState +): Promise { + const prState = await stateClient.getPrState(pullNumber); + return Object.values(prState.reviewersAssignedForLabels).length > 0; +} + +async function processPrComment( + payload: any, + stateClient: typeof PersistentState, + reviewerConfig: typeof ReviewerConfig +) { + const commentContents = payload.comment.body; + const commentAuthor = payload.sender.login; + console.log(commentContents); + if ( + await processCommand( + payload, + commentAuthor, + commentContents, + stateClient, + reviewerConfig + ) + ) { + // If we've processed a command, don't worry about trying to change the attention set. + // This is not a meaningful push or comment from the author. + console.log("Processed command"); + return; + } + + // If comment was from the author, we should shift attention back to the reviewers. + console.log( + "No command to be processed, checking if we should shift attention to reviewers" + ); + const pullAuthor = getPullAuthorFromPayload(payload); + if (pullAuthor === commentAuthor) { + await setNextActionReviewers(payload, stateClient); + } else { + console.log( + `Comment was from ${commentAuthor}, not author: ${pullAuthor}. No action to take.` + ); + } +} + +/* + * On approval from a reviewer we have assigned, assign committer if one not already assigned + */ +async function processPrReview( + payload: any, + stateClient: typeof PersistentState, + reviewerConfig: typeof ReviewerConfig +) { + if (payload.review.state !== "approved") { + return; + } + + const pullNumber = getPullNumberFromPayload(payload); + if (!(await areReviewersAssigned(pullNumber, stateClient))) { + return; + } + + let prState = await stateClient.getPrState(pullNumber); + // TODO(damccorm) - also check if the author is a committer, if they are don't auto-assign a committer + if (await prState.isAnyAssignedReviewerCommitter()) { + return; + } + + const labelOfReviewer = prState.getLabelForReviewer(payload.sender.login); + if (labelOfReviewer) { + let reviewersState = await stateClient.getReviewersForLabelState( + labelOfReviewer + ); + const availableReviewers = + reviewerConfig.getReviewersForLabel(labelOfReviewer); + const chosenCommitter = await reviewersState.assignNextCommitter( + availableReviewers + ); + prState.reviewersAssignedForLabels[labelOfReviewer] = chosenCommitter; + + // Set next action to committer + await addPrComment( + pullNumber, + commentStrings.assignCommitter(chosenCommitter) + ); + const existingLabels = + payload.issue?.labels || payload.pull_request?.labels; + await nextActionReviewers(pullNumber, existingLabels); + prState.nextAction = reviewerAction; + + // Persist state + await stateClient.writePrState(pullNumber, prState); + await stateClient.writeReviewersForLabelState( + labelOfReviewer, + reviewersState + ); + } +} + +/* + * On pr push or author comment, we should put the attention set back on the reviewers + */ +async function setNextActionReviewers( + payload: any, + stateClient: typeof PersistentState +) { + const pullNumber = getPullNumberFromPayload(payload); + if (!(await areReviewersAssigned(pullNumber, stateClient))) { + console.log("No reviewers assigned, dont need to manipulate attention set"); + return; + } + const existingLabels = payload.issue?.labels || payload.pull_request?.labels; + await nextActionReviewers(pullNumber, existingLabels); + let prState = await stateClient.getPrState(pullNumber); + prState.nextAction = reviewerAction; + await stateClient.writePrState(pullNumber, prState); +} + +async function processPrUpdate() { + const reviewerConfig = new ReviewerConfig(PATH_TO_CONFIG_FILE); + const context = github.context; + console.log("Event context:"); + console.log(context); + const payload = context.payload; + + // TODO(damccorm) - remove this when we roll out to more than go + const existingLabels = payload.issue?.labels || payload.pull_request?.labels; + if (!existingLabels.find((label) => label.name.toLowerCase() === "go")) { + console.log("Does not contain the go label - skipping"); + return; + } + + if (!payload.issue?.pull_request && !payload.pull_request) { + console.log("Issue, not pull request - returning"); + return; + } + const pullNumber = getPullNumberFromPayload(payload); + + const stateClient = new PersistentState(); + const prState = await stateClient.getPrState(pullNumber); + if (prState.stopReviewerNotifications) { + console.log("Notifications have been paused for this pull - skipping"); + return; + } + + switch (github.context.eventName) { + case "pull_request_review_comment": + case "issue_comment": + console.log("Processing comment event"); + if (payload.action !== "created") { + console.log("Comment wasnt just created, skipping"); + return; + } + await processPrComment(payload, stateClient, reviewerConfig); + break; + case "pull_request_review": + console.log("Processing PR review event"); + await processPrReview(payload, stateClient, reviewerConfig); + break; + case "pull_request_target": + if (payload.action === "synchronize") { + console.log("Processing synchronize action"); + await setNextActionReviewers(payload, stateClient); + } + // TODO(damccorm) - it would be good to eventually handle the following events here, even though they're not part of the normal workflow + // review requested, assigned, label added, label removed + break; + default: + console.log("Not a PR comment, push, or review, doing nothing"); + } +} + +processPrUpdate(); + +export {}; diff --git a/scripts/ci/pr-bot/shared/githubUtils.ts b/scripts/ci/pr-bot/shared/githubUtils.ts index e650ca52b0d09..667255b169247 100644 --- a/scripts/ci/pr-bot/shared/githubUtils.ts +++ b/scripts/ci/pr-bot/shared/githubUtils.ts @@ -85,6 +85,14 @@ export async function checkIfCommitter(username: string): Promise { ); } +export function getPullAuthorFromPayload(payload: any) { + return payload.issue?.user?.login || payload.pull_request?.user?.login; +} + +export function getPullNumberFromPayload(payload: any) { + return payload.issue?.number || payload.pull_request?.number; +} + function removeNextActionLabel(existingLabels: Label[]): string[] { return existingLabels .filter( diff --git a/scripts/ci/pr-bot/shared/userCommand.ts b/scripts/ci/pr-bot/shared/userCommand.ts index 34e2741a3f6d4..e32746eb7fce8 100644 --- a/scripts/ci/pr-bot/shared/userCommand.ts +++ b/scripts/ci/pr-bot/shared/userCommand.ts @@ -81,8 +81,7 @@ async function assignToNextReviewer( let reviewersState = await stateClient.getReviewersForLabelState( labelOfReviewer ); - const pullAuthor = - payload.issue?.user?.login || payload.pull_request?.user?.login; + const pullAuthor = github.getPullAuthorFromPayload(payload); let availableReviewers = reviewerConfig.getReviewersForLabel( labelOfReviewer, [commentAuthor, pullAuthor] @@ -187,8 +186,7 @@ async function assignReviewerSet( } const existingLabels = payload.issue?.labels || payload.pull_request?.labels; - const pullAuthor = - payload.issue?.user?.login || payload.pull_request?.user?.login; + const pullAuthor = github.getPullAuthorFromPayload(payload); const reviewersForLabels = reviewerConfig.getReviewersForLabels( existingLabels, [pullAuthor] From 0631a5e99858d45d4c7533b5ac2a03e984701ddb Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 28 Feb 2022 21:26:28 -0500 Subject: [PATCH 53/61] [BEAM-13899] Improve coverage of debug package (#16951) --- sdks/go/pkg/beam/x/debug/head_test.go | 44 +++++++++++ sdks/go/pkg/beam/x/debug/print_test.go | 101 +++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 sdks/go/pkg/beam/x/debug/head_test.go create mode 100644 sdks/go/pkg/beam/x/debug/print_test.go diff --git a/sdks/go/pkg/beam/x/debug/head_test.go b/sdks/go/pkg/beam/x/debug/head_test.go new file mode 100644 index 0000000000000..8aa5b41545daa --- /dev/null +++ b/sdks/go/pkg/beam/x/debug/head_test.go @@ -0,0 +1,44 @@ +// 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 debug + +import ( + "testing" + + "github.com/apache/beam/sdks/v2/go/pkg/beam" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/passert" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/ptest" +) + +func TestHead(t *testing.T) { + p, s, sequence := ptest.CreateList([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + headSequence := Head(s, sequence, 5) + passert.Count(s, headSequence, "NumElements", 5) + passert.Equals(s, headSequence, 1, 2, 3, 4, 5) + + ptest.RunAndValidate(t, p) +} + +func TestHead_KV(t *testing.T) { + p, s, sequence := ptest.CreateList([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + kvSequence := beam.AddFixedKey(s, sequence) + headKvSequence := Head(s, kvSequence, 5) + headSequence := beam.DropKey(s, headKvSequence) + passert.Count(s, headSequence, "NumElements", 5) + passert.Equals(s, headSequence, 1, 2, 3, 4, 5) + + ptest.RunAndValidate(t, p) +} diff --git a/sdks/go/pkg/beam/x/debug/print_test.go b/sdks/go/pkg/beam/x/debug/print_test.go new file mode 100644 index 0000000000000..0bbdee0b6fb9c --- /dev/null +++ b/sdks/go/pkg/beam/x/debug/print_test.go @@ -0,0 +1,101 @@ +// 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 debug + +import ( + "bytes" + "log" + "os" + "strings" + "testing" + + "github.com/apache/beam/sdks/v2/go/pkg/beam" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/ptest" +) + +func TestPrint(t *testing.T) { + p, s, sequence := ptest.CreateList([]string{"abc", "def", "ghi"}) + Print(s, sequence) + + output := captureRunLogging(p) + if !strings.Contains(output, "Elm: abc") { + t.Errorf("Print() should contain \"Elm: abc\", got: %v", output) + } + if !strings.Contains(output, "Elm: def") { + t.Errorf("Print() should contain \"Elm: def\", got: %v", output) + } + if !strings.Contains(output, "Elm: ghi") { + t.Errorf("Print() should contain \"Elm: ghi\", got: %v", output) + } +} + +func TestPrintf(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + sequence := beam.Create(s, "abc", "def", "ghi") + Printf(s, "myformatting - %v", sequence) + + output := captureRunLogging(p) + if !strings.Contains(output, "myformatting - abc") { + t.Errorf("Printf() should contain \"myformatting - abc\", got: %v", output) + } + if !strings.Contains(output, "myformatting - def") { + t.Errorf("Printf() should contain \"myformatting - def\", got: %v", output) + } + if !strings.Contains(output, "myformatting - ghi") { + t.Errorf("Printf() should contain \"myformatting - ghi\", got: %v", output) + } +} + +func TestPrint_KV(t *testing.T) { + p, s, sequence := ptest.CreateList([]string{"abc", "def", "ghi"}) + kvSequence := beam.AddFixedKey(s, sequence) + Print(s, kvSequence) + + output := captureRunLogging(p) + if !strings.Contains(output, "Elm: (0,abc)") { + t.Errorf("Print() should contain \"Elm: (0,abc)\", got: %v", output) + } + if !strings.Contains(output, "Elm: (0,def)") { + t.Errorf("Print() should contain \"Elm: (0,def)\", got: %v", output) + } + if !strings.Contains(output, "Elm: (0,ghi)") { + t.Errorf("Print() should contain \"Elm: (0,ghi)\", got: %v", output) + } +} + +func TestPrint_CoGBK(t *testing.T) { + p, s, sequence := ptest.CreateList([]string{"abc", "def", "ghi"}) + kvSequence := beam.AddFixedKey(s, sequence) + gbkSequence := beam.CoGroupByKey(s, kvSequence) + Print(s, gbkSequence) + + output := captureRunLogging(p) + if !strings.Contains(output, "Elm: (0,[abc def ghi])") { + t.Errorf("Print() should contain \"Elm: (0,[abc def ghi])\", got: %v", output) + } +} + +func captureRunLogging(p *beam.Pipeline) string { + // Pipe output to out + var out bytes.Buffer + log.SetOutput(&out) + + ptest.Run(p) + + // Return to original state + log.SetOutput(os.Stderr) + return out.String() +} From 6b06cbb928706bae7694ea5388ea8d3f97f92cf6 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 28 Feb 2022 21:31:17 -0500 Subject: [PATCH 54/61] [BEAM-13907] Improve coverage of textio package (#16937) --- sdks/go/pkg/beam/io/textio/sdf_test.go | 14 ++++- sdks/go/pkg/beam/io/textio/textio_test.go | 71 ++++++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/sdks/go/pkg/beam/io/textio/sdf_test.go b/sdks/go/pkg/beam/io/textio/sdf_test.go index 881b9ab519992..7b1b416637617 100644 --- a/sdks/go/pkg/beam/io/textio/sdf_test.go +++ b/sdks/go/pkg/beam/io/textio/sdf_test.go @@ -28,9 +28,19 @@ import ( // outputs the correct number of lines for it, even for an exceedingly long // line. func TestReadSdf(t *testing.T) { - f := "../../../../data/textio_test.txt" p, s := beam.NewPipelineWithRoot() - lines := ReadSdf(s, f) + lines := ReadSdf(s, testFilePath) + passert.Count(s, lines, "NumLines", 1) + + if _, err := beam.Run(context.Background(), "direct", p); err != nil { + t.Fatalf("Failed to execute job: %v", err) + } +} + +func TestReadAllSdf(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + files := beam.Create(s, testFilePath) + lines := ReadAllSdf(s, files) passert.Count(s, lines, "NumLines", 1) if _, err := beam.Run(context.Background(), "direct", p); err != nil { diff --git a/sdks/go/pkg/beam/io/textio/textio_test.go b/sdks/go/pkg/beam/io/textio/textio_test.go index 4090535226c77..0cd1699e657ea 100644 --- a/sdks/go/pkg/beam/io/textio/textio_test.go +++ b/sdks/go/pkg/beam/io/textio/textio_test.go @@ -17,20 +17,25 @@ package textio import ( + "errors" + "os" "testing" + "github.com/apache/beam/sdks/v2/go/pkg/beam" _ "github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/local" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/passert" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/ptest" ) -func TestRead(t *testing.T) { - f := "../../../../data/textio_test.txt" +const testFilePath = "../../../../data/textio_test.txt" +func TestReadFn(t *testing.T) { receivedLines := []string{} getLines := func(line string) { receivedLines = append(receivedLines, line) } - err := readFn(nil, f, getLines) + err := readFn(nil, testFilePath, getLines) if err != nil { t.Fatalf("failed with %v", err) } @@ -40,3 +45,63 @@ func TestRead(t *testing.T) { } } + +func TestRead(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + lines := Read(s, testFilePath) + passert.Count(s, lines, "NumLines", 1) + + ptest.RunAndValidate(t, p) +} + +func TestReadAll(t *testing.T) { + p, s, files := ptest.CreateList([]string{testFilePath}) + lines := ReadAll(s, files) + passert.Count(s, lines, "NumLines", 1) + + ptest.RunAndValidate(t, p) +} + +func TestWrite(t *testing.T) { + out := "text.txt" + p, s := beam.NewPipelineWithRoot() + lines := Read(s, testFilePath) + Write(s, out, lines) + + ptest.RunAndValidate(t, p) + + if _, err := os.Stat(out); errors.Is(err, os.ErrNotExist) { + t.Fatalf("Failed to write %v", out) + } + t.Cleanup(func() { + os.Remove(out) + }) + + outfileContents, _ := os.ReadFile(out) + infileContents, _ := os.ReadFile(testFilePath) + if got, want := string(outfileContents), string(infileContents); got != want { + t.Fatalf("Write() wrote the wrong contents. Got: %v Want: %v", got, want) + } +} + +func TestImmediate(t *testing.T) { + f, err := os.CreateTemp("", "test2.txt") + if err != nil { + t.Fatalf("Failed to create temp file, err: %v", err) + } + t.Cleanup(func() { + os.Remove(f.Name()) + }) + if err := os.WriteFile(f.Name(), []byte("hello\ngo\n"), 0644); err != nil { + t.Fatalf("Failed to write file %v, err: %v", f, err) + } + + p, s := beam.NewPipelineWithRoot() + lines, err := Immediate(s, f.Name()) + if err != nil { + t.Fatalf("Failed to insert Immediate: %v", err) + } + passert.Count(s, lines, "NumLines", 2) + + ptest.RunAndValidate(t, p) +} From 6aafcfdff3730cad6179ca7266ff8bfc4858dea7 Mon Sep 17 00:00:00 2001 From: emily Date: Mon, 28 Feb 2022 23:46:44 -0800 Subject: [PATCH 55/61] [BEAM-9150] Fix beam_PostRelease_Python_Candidate (python RC validation scripts) (#16955) --- .../python_release_automation_utils.sh | 35 ++++++++++++++++--- ..._release_candidate_python_mobile_gaming.sh | 10 ++++-- ...run_release_candidate_python_quickstart.sh | 4 +-- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/release/src/main/python-release/python_release_automation_utils.sh b/release/src/main/python-release/python_release_automation_utils.sh index 5d0e1c6eff762..b328c428a7973 100644 --- a/release/src/main/python-release/python_release_automation_utils.sh +++ b/release/src/main/python-release/python_release_automation_utils.sh @@ -228,7 +228,7 @@ function cleanup_pubsub() { # $2 - pid: the pid of running pipeline # $3 - running_job (DataflowRunner only): the job id of streaming pipeline running on DataflowRunner ####################################### -function verify_steaming_result() { +function verify_streaming_result() { retry=3 should_see="Python: " while(( $retry > 0 )); do @@ -295,9 +295,11 @@ function verify_user_score() { function verify_hourly_team_score() { retry=3 should_see='AntiqueBrassPlatypus' + runner=$1 + while(( $retry >= 0 )); do if [[ $retry > 0 ]]; then - bq_pull_result=$(bq head -n 500 $DATASET.hourly_team_score_python_$1) + bq_pull_result=$(bq head -n 500 ${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}) if [[ $bq_pull_result = *"$should_see"* ]]; then echo "SUCCEED: hourly_team_score example successful run on $1-runner" break @@ -307,14 +309,38 @@ function verify_hourly_team_score() { sleep 15 fi else - echo "FAILED: HourlyTeamScore example failed running on $1-runner. \ - Did not found scores of team $should_see in $DATASET.leader_board" + echo "FAILED: HourlyTeamScore example failed running on $runner runner. \ + Did not found scores of team $should_see in ${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}" complete "FAILED" exit 1 fi done } +function cleanup_hourly_team_score() { + retry=3 + runner=$1 + + echo "Removing previously created table ${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}..." + bq rm -q -f -t "${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}" + + while(( $retry >= 0 )); do + if [[ $retry > 0 ]]; then + bq_ls_result=$(bq ls $DATASET) + if [[ $bq_ls_result = *"${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}"* ]]; then + retry=$(($retry-1)) + echo "${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner} not cleaned up yet, waiting" + sleep 1000 + else + echo "Confirmed ${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner} removed before running new test." + break + fi + else + echo "WARNING: Unable to clean up table ${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}. \ + You may need to manually run 'bq rm -r -t ${DATASET}.${HOURLY_TEAM_SCORE_TABLE_PREFIX}_${runner}'." + fi + done +} # Python RC configurations VERSION=$(get_version) @@ -336,4 +362,5 @@ PUBSUB_SUBSCRIPTION='wordstream-python-sub2' # Mobile Gaming Configurations DATASET='beam_postrelease_mobile_gaming' USERSCORE_OUTPUT_PREFIX='python-userscore_result' +HOURLY_TEAM_SCORE_TABLE_PREFIX='hourly_team_score_python' GAME_INPUT_DATA='gs://dataflow-samples/game/5000_gaming_data.csv' diff --git a/release/src/main/python-release/run_release_candidate_python_mobile_gaming.sh b/release/src/main/python-release/run_release_candidate_python_mobile_gaming.sh index 30d8cea2bd951..efd903da48b84 100755 --- a/release/src/main/python-release/run_release_candidate_python_mobile_gaming.sh +++ b/release/src/main/python-release/run_release_candidate_python_mobile_gaming.sh @@ -100,12 +100,15 @@ function verify_userscore_dataflow() { ####################################### function verify_hourlyteamscore_direct() { print_separator "Running HourlyTeamScore example with DirectRunner" + # Clean up old bq tables + cleanup_hourly_team_score "direct" + python -m apache_beam.examples.complete.game.hourly_team_score \ --project=$PROJECT_ID \ --dataset=$DATASET \ --input=$GAME_INPUT_DATA \ --temp_location=gs://$BUCKET_NAME/temp/ \ - --table="hourly_team_score_python_direct" + --table="${HOURLY_TEAM_SCORE_TABLE_PREFIX}_direct" verify_hourly_team_score "direct" } @@ -121,6 +124,9 @@ function verify_hourlyteamscore_direct() { ####################################### function verify_hourlyteamscore_dataflow() { print_separator "Running HourlyTeamScore example with DataflowRunner" + # Clean up old bq tables + cleanup_hourly_team_score "dataflow" + python -m apache_beam.examples.complete.game.hourly_team_score \ --project=$PROJECT_ID \ --region=$REGION_ID \ @@ -129,7 +135,7 @@ function verify_hourlyteamscore_dataflow() { --temp_location=gs://$BUCKET_NAME/temp/ \ --sdk_location $BEAM_PYTHON_SDK \ --input=$GAME_INPUT_DATA \ - --table="hourly_team_score_python_dataflow" + --table="${HOURLY_TEAM_SCORE_TABLE_PREFIX}_dataflow" verify_hourly_team_score "dataflow" } diff --git a/release/src/main/python-release/run_release_candidate_python_quickstart.sh b/release/src/main/python-release/run_release_candidate_python_quickstart.sh index 6f1e10da33b38..3af527acfa2e6 100755 --- a/release/src/main/python-release/run_release_candidate_python_quickstart.sh +++ b/release/src/main/python-release/run_release_candidate_python_quickstart.sh @@ -155,7 +155,7 @@ function verify_streaming_wordcount_direct() { # verify result run_pubsub_publish - verify_steaming_result "DirectRunner" $pid + verify_streaming_result "DirectRunner" $pid kill -9 $pid sleep 10 @@ -194,7 +194,7 @@ function verify_streaming_wordcount_dataflow() { # verify result run_pubsub_publish sleep 420 - verify_steaming_result "DataflowRunner" $pid $running_job + verify_streaming_result "DataflowRunner" $pid $running_job kill -9 $pid gcloud dataflow jobs cancel $running_job From d42d8d762b4d7b82852e8603ebfae25fcbb80e15 Mon Sep 17 00:00:00 2001 From: Benjamin Gonzalez <74670721+benWize@users.noreply.github.com> Date: Tue, 1 Mar 2022 12:35:17 -0600 Subject: [PATCH 56/61] Merge pull request #16850: [BEAM-11205] Upgrade Libraries BOM dependencies to 24.3.0 * Update GCP Libraries BOM version to 24.3.0 * Update associated dependencies --- .../org/apache/beam/gradle/BeamModulePlugin.groovy | 12 ++++++------ .../container/license_scripts/dep_urls_java.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) 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 b11b590ede869..cb5d979536158 100644 --- a/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy @@ -446,7 +446,7 @@ class BeamModulePlugin implements Plugin { // a dependency version which should match across multiple // Maven artifacts. def activemq_version = "5.14.5" - def autovalue_version = "1.8.2" + def autovalue_version = "1.9" def autoservice_version = "1.0.1" def aws_java_sdk_version = "1.12.135" def aws_java_sdk2_version = "2.17.106" @@ -463,7 +463,7 @@ class BeamModulePlugin implements Plugin { def google_code_gson_version = "2.8.9" def google_oauth_clients_version = "1.32.1" // Try to keep grpc_version consistent with gRPC version in google_cloud_platform_libraries_bom - def grpc_version = "1.43.2" + def grpc_version = "1.44.0" def guava_version = "31.0.1-jre" def hadoop_version = "2.10.1" def hamcrest_version = "2.1" @@ -479,7 +479,7 @@ class BeamModulePlugin implements Plugin { def postgres_version = "42.2.16" def powermock_version = "2.0.9" // Try to keep protobuf_version consistent with the protobuf version in google_cloud_platform_libraries_bom - def protobuf_version = "3.19.2" + def protobuf_version = "3.19.3" def quickcheck_version = "0.8" def slf4j_version = "1.7.30" def spark2_version = "2.4.8" @@ -575,9 +575,9 @@ class BeamModulePlugin implements Plugin { google_cloud_pubsub : "com.google.cloud:google-cloud-pubsub", // google_cloud_platform_libraries_bom sets version google_cloud_pubsublite : "com.google.cloud:google-cloud-pubsublite", // google_cloud_platform_libraries_bom sets version // The GCP Libraries BOM dashboard shows the versions set by the BOM: - // https://storage.googleapis.com/cloud-opensource-java-dashboard/com.google.cloud/libraries-bom/24.2.0/artifact_details.html + // https://storage.googleapis.com/cloud-opensource-java-dashboard/com.google.cloud/libraries-bom/24.3.0/artifact_details.html // Update libraries-bom version on sdks/java/container/license_scripts/dep_urls_java.yaml - google_cloud_platform_libraries_bom : "com.google.cloud:libraries-bom:24.2.0", + google_cloud_platform_libraries_bom : "com.google.cloud:libraries-bom:24.3.0", google_cloud_spanner : "com.google.cloud:google-cloud-spanner", // google_cloud_platform_libraries_bom sets version google_cloud_spanner_test : "com.google.cloud:google-cloud-spanner:$google_cloud_spanner_version:tests", google_code_gson : "com.google.code.gson:gson:$google_code_gson_version", @@ -648,7 +648,7 @@ class BeamModulePlugin implements Plugin { nemo_compiler_frontend_beam : "org.apache.nemo:nemo-compiler-frontend-beam:$nemo_version", netty_all : "io.netty:netty-all:$netty_version", netty_handler : "io.netty:netty-handler:$netty_version", - netty_tcnative_boringssl_static : "io.netty:netty-tcnative-boringssl-static:2.0.33.Final", + netty_tcnative_boringssl_static : "io.netty:netty-tcnative-boringssl-static:2.0.46.Final", netty_transport_native_epoll : "io.netty:netty-transport-native-epoll:$netty_version", postgres : "org.postgresql:postgresql:$postgres_version", powermock : "org.powermock:powermock-module-junit4:$powermock_version", diff --git a/sdks/java/container/license_scripts/dep_urls_java.yaml b/sdks/java/container/license_scripts/dep_urls_java.yaml index 03a882303586a..57745777c26fd 100644 --- a/sdks/java/container/license_scripts/dep_urls_java.yaml +++ b/sdks/java/container/license_scripts/dep_urls_java.yaml @@ -42,7 +42,7 @@ jaxen: '1.1.6': type: "3-Clause BSD" libraries-bom: - '24.2.0': + '24.3.0': license: "https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-opensource-java/master/LICENSE" type: "Apache License 2.0" paranamer: From 7735cb9e6c8b7298328ec9e50ee99a430e703a78 Mon Sep 17 00:00:00 2001 From: Pavel Avilov Date: Tue, 1 Mar 2022 22:26:14 +0300 Subject: [PATCH 57/61] Merge pull request #16484 from [BEAM-13633] [Playground] Implement method to get a default example for each SDKs * Implement method to get a default example for each SDKs * Add error handling * Added saving of precompiled objects catalog to cache at the server startup * Added caching of the catalog only in case of unspecified SDK * Update regarding comments * Update regarding comments * Simplified logging regarding comment * Get defaultExamplePath from the corresponding config * Refactoring code * Add the `link` field to response * Remove gjson; Resolve conflicts; * Refactoring code * Getting default precompiled object from cache * Refactoring code * Added saving of precompiled objects catalog to cache at the server startup * Added caching of the catalog only in case of unspecified SDK * Update regarding comments * Update regarding comments * Simplified logging regarding comment * Updates regarding comments * Update for environment_service_test.go * Get default example from catalog * GetCatalogFromCacheOrStorage method * Update licenses * Update licenses; Resolve conflicts; * [BEAM-13633][Playground] Change saving default precompiled objects to the cache * [BEAM-13633][Playground] Change logic of saving and receiving info about default precompiled objects * [BEAM-13633][Playground] Separate for each sdk * [BEAM-13633][Playground] regenerate proto files * Add code of the default example to response * Revert "Add code of the default example to response" This reverts commit da6baa0aaa272190d4a035568a5e4db0b093dfd9. * Refactoring code * Refactoring code; Add test; * Edit commentaries * Refactoring code * Add bucket name to methods Co-authored-by: Artur Khanin Co-authored-by: AydarZaynutdinov Co-authored-by: Pavel Avilov --- playground/api/v1/api.proto | 1 + playground/backend/cmd/server/controller.go | 30 +- playground/backend/cmd/server/server.go | 13 + playground/backend/internal/api/v1/api.pb.go | 398 +++++++++--------- playground/backend/internal/cache/cache.go | 11 +- .../internal/cache/local/local_cache.go | 36 +- .../internal/cache/local/local_cache_test.go | 3 +- .../internal/cache/redis/redis_cache.go | 41 ++ .../cloud_bucket/precompiled_objects.go | 106 ++++- .../cloud_bucket/precompiled_objects_test.go | 4 +- .../utils/precompiled_objects_utils.go | 44 ++ .../utils/precompiled_objects_utils_test.go | 70 ++- playground/frontend/lib/api/v1/api.pb.dart | 14 + .../frontend/lib/api/v1/api.pbjson.dart | 3 +- playground/infrastructure/api/v1/api_pb2.py | 83 ++-- playground/infrastructure/cd_helper.py | 35 ++ playground/infrastructure/config.py | 1 + playground/infrastructure/proxy/allow_list.py | 1 + playground/infrastructure/test_cd_helper.py | 20 +- playground/infrastructure/test_helper.py | 9 +- 20 files changed, 651 insertions(+), 272 deletions(-) diff --git a/playground/api/v1/api.proto b/playground/api/v1/api.proto index 4756518ea38bc..bddb56bda5c70 100644 --- a/playground/api/v1/api.proto +++ b/playground/api/v1/api.proto @@ -165,6 +165,7 @@ message PrecompiledObject{ string link = 6; bool multifile = 7; int32 context_line = 8; + bool default_example = 9; } // Categories represent the array of messages with sdk and categories at this sdk diff --git a/playground/backend/cmd/server/controller.go b/playground/backend/cmd/server/controller.go index 9397523be0f41..303fea8b0547b 100644 --- a/playground/backend/cmd/server/controller.go +++ b/playground/backend/cmd/server/controller.go @@ -260,16 +260,10 @@ func (controller *playgroundController) Cancel(ctx context.Context, info *pb.Can // - If there is no catalog in the cache, gets the catalog from the Storage and saves it to the cache // - If SDK or category is specified in the request, gets the catalog from the cache and filters it by SDK and category func (controller *playgroundController) GetPrecompiledObjects(ctx context.Context, info *pb.GetPrecompiledObjectsRequest) (*pb.GetPrecompiledObjectsResponse, error) { - catalog, err := controller.cacheService.GetCatalog(ctx) + catalog, err := utils.GetCatalogFromCacheOrStorage(ctx, controller.cacheService, controller.env.ApplicationEnvs.BucketName()) if err != nil { - logger.Errorf("GetPrecompiledObjects(): cache error: %s", err.Error()) - catalog, err = utils.GetCatalogFromStorage(ctx, controller.env.ApplicationEnvs.BucketName()) - if err != nil { - return nil, errors.InternalError("Error during getting Precompiled Objects", "Error with cloud connection") - } - if err = controller.cacheService.SetCatalog(ctx, catalog); err != nil { - logger.Errorf("GetPrecompiledObjects(): cache error: %s", err.Error()) - } + logger.Errorf("GetPrecompiledObjects(): error during getting catalog: %s", err.Error()) + return nil, errors.InternalError("Error during getting Precompiled Objects", "Error with cloud connection") } return &pb.GetPrecompiledObjectsResponse{ SdkCategories: utils.FilterCatalog(catalog, info.Sdk, info.Category), @@ -279,7 +273,7 @@ func (controller *playgroundController) GetPrecompiledObjects(ctx context.Contex // GetPrecompiledObjectCode returns the code of the specific example func (controller *playgroundController) GetPrecompiledObjectCode(ctx context.Context, info *pb.GetPrecompiledObjectCodeRequest) (*pb.GetPrecompiledObjectCodeResponse, error) { cd := cloud_bucket.New() - codeString, err := cd.GetPrecompiledObject(ctx, info.GetCloudPath(), controller.env.ApplicationEnvs.BucketName()) + codeString, err := cd.GetPrecompiledObjectCode(ctx, info.GetCloudPath(), controller.env.ApplicationEnvs.BucketName()) if err != nil { logger.Errorf("GetPrecompiledObjectCode(): cloud storage error: %s", err.Error()) return nil, errors.InternalError("Error during getting Precompiled Object's code", "Error with cloud connection") @@ -323,3 +317,19 @@ func (controller *playgroundController) GetPrecompiledObjectGraph(ctx context.Co response := pb.GetPrecompiledObjectGraphResponse{Graph: logs} return &response, nil } + +// GetDefaultPrecompiledObject returns the default precompile object for sdk. +func (controller *playgroundController) GetDefaultPrecompiledObject(ctx context.Context, info *pb.GetDefaultPrecompiledObjectRequest) (*pb.GetDefaultPrecompiledObjectResponse, error) { + switch info.Sdk { + case pb.Sdk_SDK_UNSPECIFIED: + logger.Errorf("GetDefaultPrecompiledObject(): unimplemented sdk: %s\n", info.Sdk) + return nil, errors.InvalidArgumentError("Error during preparing", "Sdk is not implemented yet: %s", info.Sdk.String()) + } + precompiledObject, err := utils.GetDefaultPrecompiledObject(ctx, info.Sdk, controller.cacheService, controller.env.ApplicationEnvs.BucketName()) + if err != nil { + logger.Errorf("GetDefaultPrecompiledObject(): error during getting catalog: %s", err.Error()) + return nil, errors.InternalError("Error during getting Precompiled Objects", "Error with cloud connection") + } + response := pb.GetDefaultPrecompiledObjectResponse{PrecompiledObject: precompiledObject} + return &response, nil +} diff --git a/playground/backend/cmd/server/server.go b/playground/backend/cmd/server/server.go index 03cc61f1516c2..b78aa7b062720 100644 --- a/playground/backend/cmd/server/server.go +++ b/playground/backend/cmd/server/server.go @@ -20,6 +20,7 @@ import ( "beam.apache.org/playground/backend/internal/cache" "beam.apache.org/playground/backend/internal/cache/local" "beam.apache.org/playground/backend/internal/cache/redis" + "beam.apache.org/playground/backend/internal/cloud_bucket" "beam.apache.org/playground/backend/internal/environment" "beam.apache.org/playground/backend/internal/logger" "beam.apache.org/playground/backend/internal/utils" @@ -127,6 +128,18 @@ func setupExamplesCatalog(ctx context.Context, cacheService cache.Cache, bucketN if err = cacheService.SetCatalog(ctx, catalog); err != nil { logger.Errorf("GetPrecompiledObjects(): cache error: %s", err.Error()) } + + bucket := cloud_bucket.New() + defaultPrecompiledObjects, err := bucket.GetDefaultPrecompiledObjects(ctx, bucketName) + if err != nil { + return err + } + for sdk, precompiledObject := range defaultPrecompiledObjects { + if err := cacheService.SetDefaultPrecompiledObject(ctx, sdk, precompiledObject); err != nil { + logger.Errorf("GetPrecompiledObjects(): cache error: %s", err.Error()) + return err + } + } return nil } diff --git a/playground/backend/internal/api/v1/api.pb.go b/playground/backend/internal/api/v1/api.pb.go index 09cf94822dab5..95023091aaca3 100644 --- a/playground/backend/internal/api/v1/api.pb.go +++ b/playground/backend/internal/api/v1/api.pb.go @@ -1203,9 +1203,10 @@ type PrecompiledObject struct { Type PrecompiledObjectType `protobuf:"varint,4,opt,name=type,proto3,enum=api.v1.PrecompiledObjectType" json:"type,omitempty"` PipelineOptions string `protobuf:"bytes,5,opt,name=pipeline_options,json=pipelineOptions,proto3" json:"pipeline_options,omitempty"` // Link to the example in the Beam repository - Link string `protobuf:"bytes,6,opt,name=link,proto3" json:"link,omitempty"` - Multifile bool `protobuf:"varint,7,opt,name=multifile,proto3" json:"multifile,omitempty"` - ContextLine int32 `protobuf:"varint,8,opt,name=context_line,json=contextLine,proto3" json:"context_line,omitempty"` + Link string `protobuf:"bytes,6,opt,name=link,proto3" json:"link,omitempty"` + Multifile bool `protobuf:"varint,7,opt,name=multifile,proto3" json:"multifile,omitempty"` + ContextLine int32 `protobuf:"varint,8,opt,name=context_line,json=contextLine,proto3" json:"context_line,omitempty"` + DefaultExample bool `protobuf:"varint,9,opt,name=default_example,json=defaultExample,proto3" json:"default_example,omitempty"` } func (x *PrecompiledObject) Reset() { @@ -1296,6 +1297,13 @@ func (x *PrecompiledObject) GetContextLine() int32 { return 0 } +func (x *PrecompiledObject) GetDefaultExample() bool { + if x != nil { + return x.DefaultExample + } + return false +} + // Categories represent the array of messages with sdk and categories at this sdk type Categories struct { state protoimpl.MessageState @@ -2066,7 +2074,7 @@ var file_api_v1_api_proto_rawDesc = []byte{ 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x75, 0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9b, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc4, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, @@ -2084,206 +2092,208 @@ var file_api_v1_api_proto_rawDesc = []byte{ 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, - 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x03, 0x73, 0x64, 0x6b, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x64, 0x6b, 0x52, - 0x03, 0x73, 0x64, 0x6b, 0x12, 0x3b, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x43, 0x61, 0x74, - 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, - 0x73, 0x1a, 0x7b, 0x0a, 0x08, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, - 0x64, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, - 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x12, 0x70, 0x72, 0x65, 0x63, - 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x59, - 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x78, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, + 0xe5, 0x01, 0x0a, 0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x03, 0x73, 0x64, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x64, 0x6b, 0x52, 0x03, 0x73, 0x64, 0x6b, 0x12, 0x1a, 0x0a, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x40, 0x0a, 0x1f, 0x47, 0x65, 0x74, - 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, 0x68, 0x22, 0x42, 0x0a, 0x21, 0x47, - 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, 0x68, 0x22, - 0x40, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, - 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, - 0x68, 0x22, 0x41, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, - 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x64, 0x6b, 0x52, 0x03, 0x73, 0x64, 0x6b, 0x12, 0x3b, 0x0a, + 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, + 0x6f, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x0a, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x7b, 0x0a, 0x08, 0x43, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, + 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x70, + 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x12, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x59, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x73, 0x64, 0x6b, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x64, + 0x6b, 0x52, 0x03, 0x73, 0x64, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x22, 0x40, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x50, 0x61, 0x74, 0x68, 0x22, 0x43, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x73, 0x64, - 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x64, 0x6b, 0x52, 0x03, 0x73, 0x64, 0x6b, 0x22, 0x5a, 0x0a, 0x1d, 0x47, 0x65, 0x74, - 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0e, 0x73, 0x64, - 0x6b, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, - 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0d, 0x73, 0x64, 0x6b, 0x43, 0x61, 0x74, 0x65, 0x67, - 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x36, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, - 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3c, 0x0a, - 0x22, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3a, 0x0a, 0x20, 0x47, - 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x39, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, - 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x22, 0x6f, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x70, 0x72, 0x65, - 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x50, 0x61, 0x74, 0x68, 0x22, 0x42, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, 0x68, 0x22, 0x40, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x52, 0x11, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x2a, 0x52, 0x0a, 0x03, 0x53, 0x64, 0x6b, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x44, - 0x4b, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x0c, 0x0a, 0x08, 0x53, 0x44, 0x4b, 0x5f, 0x4a, 0x41, 0x56, 0x41, 0x10, 0x01, 0x12, 0x0a, 0x0a, - 0x06, 0x53, 0x44, 0x4b, 0x5f, 0x47, 0x4f, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x44, 0x4b, - 0x5f, 0x50, 0x59, 0x54, 0x48, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x44, 0x4b, - 0x5f, 0x53, 0x43, 0x49, 0x4f, 0x10, 0x04, 0x2a, 0xb8, 0x02, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, - 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x56, 0x41, 0x4c, 0x49, - 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x14, - 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x49, - 0x4e, 0x47, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, - 0x52, 0x45, 0x50, 0x41, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x4d, - 0x50, 0x49, 0x4c, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x45, - 0x43, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x5f, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x08, 0x12, 0x14, 0x0a, - 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x55, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x10, 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x0a, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x52, 0x55, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0b, 0x12, 0x13, 0x0a, - 0x0f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, - 0x10, 0x0c, 0x2a, 0xae, 0x01, 0x0a, 0x15, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, - 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x23, - 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x42, 0x4a, 0x45, - 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, + 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, 0x68, 0x22, 0x41, 0x0a, 0x20, 0x47, 0x65, + 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x61, 0x74, 0x68, 0x22, 0x43, 0x0a, + 0x22, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x73, 0x64, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x64, 0x6b, 0x52, 0x03, 0x73, + 0x64, 0x6b, 0x22, 0x5a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0e, 0x73, 0x64, 0x6b, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, + 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x52, + 0x0d, 0x73, 0x64, 0x6b, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x36, + 0x0a, 0x20, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3c, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, + 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x22, 0x3a, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x22, 0x39, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x61, 0x70, 0x68, 0x22, 0x6f, 0x0a, 0x23, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, + 0x64, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, + 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x11, 0x70, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2a, 0x52, 0x0a, 0x03, + 0x53, 0x64, 0x6b, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x44, 0x4b, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x44, 0x4b, 0x5f, + 0x4a, 0x41, 0x56, 0x41, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x44, 0x4b, 0x5f, 0x47, 0x4f, + 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x44, 0x4b, 0x5f, 0x50, 0x59, 0x54, 0x48, 0x4f, 0x4e, + 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x44, 0x4b, 0x5f, 0x53, 0x43, 0x49, 0x4f, 0x10, 0x04, + 0x2a, 0xb8, 0x02, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x56, 0x41, + 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x1c, 0x0a, + 0x18, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x49, 0x4e, 0x47, 0x10, + 0x05, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x4d, 0x50, + 0x49, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x10, + 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x49, 0x4e, 0x49, + 0x53, 0x48, 0x45, 0x44, 0x10, 0x08, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x52, 0x55, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x09, 0x12, 0x10, 0x0a, 0x0c, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x0a, 0x12, 0x16, + 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x55, 0x4e, 0x5f, 0x54, 0x49, 0x4d, + 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0b, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0c, 0x2a, 0xae, 0x01, 0x0a, 0x15, + 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x23, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x45, 0x58, 0x41, 0x4d, 0x50, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x52, - 0x45, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4b, 0x41, 0x54, 0x41, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, - 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x42, 0x4a, 0x45, - 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x54, 0x45, 0x53, - 0x54, 0x10, 0x03, 0x32, 0x9b, 0x0b, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x52, 0x75, 0x6e, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, - 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, - 0x0c, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, - 0x6f, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, - 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x72, 0x61, - 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x75, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x13, 0x47, - 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x12, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, - 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, - 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, - 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, - 0x0a, 0x15, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, - 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, - 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, - 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x23, + 0x0a, 0x1f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x42, + 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x41, 0x4d, 0x50, 0x4c, + 0x45, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, 0x49, 0x4c, + 0x45, 0x44, 0x5f, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4b, + 0x41, 0x54, 0x41, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x50, + 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x10, 0x03, 0x32, 0x9b, 0x0b, 0x0a, + 0x11, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x75, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, + 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x75, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, + 0x08, 0x47, 0x65, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x47, + 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, + 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1a, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x22, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x70, 0x61, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, + 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x12, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, + 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, + 0x18, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, - 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, - 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x12, 0x29, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, - 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, + 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x1a, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x29, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, + 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, - 0x72, 0x61, 0x70, 0x68, 0x12, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x70, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x28, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, + 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x76, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x2a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x47, 0x72, 0x61, 0x70, - 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x1b, 0x47, 0x65, 0x74, - 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, - 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, - 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, - 0x6c, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x38, 0x5a, 0x36, 0x62, 0x65, 0x61, 0x6d, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, - 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, - 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x3b, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x38, 0x5a, 0x36, 0x62, 0x65, + 0x61, 0x6d, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x6c, + 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3b, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/playground/backend/internal/cache/cache.go b/playground/backend/internal/cache/cache.go index e207f07f75b9b..2b30f145378bd 100644 --- a/playground/backend/internal/cache/cache.go +++ b/playground/backend/internal/cache/cache.go @@ -62,6 +62,9 @@ const ( // ExamplesCatalog is catalog of examples available in Playground ExamplesCatalog string = "EXAMPLES_CATALOG" + + // DefaultPrecompiledExamples is used to keep default examples + DefaultPrecompiledExamples string = "DEFAULT_PRECOMPILED_OBJECTS" ) // Cache is used to store states and outputs for Apache Beam pipelines that running in Playground @@ -86,6 +89,12 @@ type Cache interface { // SetCatalog adds the given catalog to cache by ExamplesCatalog key. SetCatalog(ctx context.Context, catalog []*pb.Categories) error - // GetCatalog return catalog from cache by ExamplesCatalog key. + // GetCatalog returns catalog from cache by ExamplesCatalog key. GetCatalog(ctx context.Context) ([]*pb.Categories, error) + + // SetDefaultPrecompiledObject adds default precompiled object for SDK into cache. + SetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk, precompiledObject *pb.PrecompiledObject) error + + // GetDefaultPrecompiledObject returns default precompiled object for SDK from cache. + GetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk) (*pb.PrecompiledObject, error) } diff --git a/playground/backend/internal/cache/local/local_cache.go b/playground/backend/internal/cache/local/local_cache.go index bdc149f6a3a1f..64c39b6863ad0 100644 --- a/playground/backend/internal/cache/local/local_cache.go +++ b/playground/backend/internal/cache/local/local_cache.go @@ -31,20 +31,23 @@ const ( type Cache struct { sync.RWMutex - cleanupInterval time.Duration - items map[uuid.UUID]map[cache.SubKey]interface{} - pipelinesExpiration map[uuid.UUID]time.Time - catalog []*pb.Categories + cleanupInterval time.Duration + items map[uuid.UUID]map[cache.SubKey]interface{} + pipelinesExpiration map[uuid.UUID]time.Time + catalog []*pb.Categories + defaultPrecompiledObjects map[pb.Sdk]*pb.PrecompiledObject } func New(ctx context.Context) *Cache { items := make(map[uuid.UUID]map[cache.SubKey]interface{}) pipelinesExpiration := make(map[uuid.UUID]time.Time) + defaultPrecompiledObjects := make(map[pb.Sdk]*pb.PrecompiledObject) ls := &Cache{ - cleanupInterval: cleanupInterval, - items: items, - pipelinesExpiration: pipelinesExpiration, - catalog: nil, + cleanupInterval: cleanupInterval, + items: items, + pipelinesExpiration: pipelinesExpiration, + catalog: nil, + defaultPrecompiledObjects: defaultPrecompiledObjects, } go ls.startGC(ctx) @@ -124,6 +127,23 @@ func (lc *Cache) GetCatalog(ctx context.Context) ([]*pb.Categories, error) { return lc.catalog, nil } +func (lc *Cache) SetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk, precompiledObject *pb.PrecompiledObject) error { + lc.Lock() + defer lc.Unlock() + lc.defaultPrecompiledObjects[sdk] = precompiledObject + return nil +} + +func (lc *Cache) GetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk) (*pb.PrecompiledObject, error) { + lc.RLock() + defer lc.RUnlock() + defaultPrecompiledObject := lc.defaultPrecompiledObjects[sdk] + if defaultPrecompiledObject == nil { + return nil, fmt.Errorf("default precompiled obejct is not found for %s sdk", sdk.String()) + } + return defaultPrecompiledObject, nil +} + func (lc *Cache) startGC(ctx context.Context) { ticker := time.NewTicker(lc.cleanupInterval) for { diff --git a/playground/backend/internal/cache/local/local_cache_test.go b/playground/backend/internal/cache/local/local_cache_test.go index 34b2654ad065c..839d2dd5407a2 100644 --- a/playground/backend/internal/cache/local/local_cache_test.go +++ b/playground/backend/internal/cache/local/local_cache_test.go @@ -19,6 +19,7 @@ import ( pb "beam.apache.org/playground/backend/internal/api/v1" "beam.apache.org/playground/backend/internal/cache" "context" + "fmt" "github.com/google/uuid" "go.uber.org/goleak" "reflect" @@ -625,7 +626,7 @@ func TestNew(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := New(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { + if got := New(tt.args.ctx); !reflect.DeepEqual(fmt.Sprint(got), fmt.Sprint(tt.want)) { t.Errorf("New() = %v, want %v", got, tt.want) } }) diff --git a/playground/backend/internal/cache/redis/redis_cache.go b/playground/backend/internal/cache/redis/redis_cache.go index b3cf206e30ce2..31bc0a479d24e 100644 --- a/playground/backend/internal/cache/redis/redis_cache.go +++ b/playground/backend/internal/cache/redis/redis_cache.go @@ -126,6 +126,47 @@ func (rc *Cache) GetCatalog(ctx context.Context) ([]*pb.Categories, error) { return result, nil } +func (rc *Cache) SetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk, precompiledObject *pb.PrecompiledObject) error { + precompiledObjectMarsh, err := json.Marshal(precompiledObject) + if err != nil { + logger.Errorf("Redis Cache: set default precompiled object: error during marshal precompiled object: %s, err: %s\n", precompiledObject, err.Error()) + return err + } + sdkMarsh, err := json.Marshal(sdk) + if err != nil { + logger.Errorf("Redis Cache: set default precompiled object: error during marshal sdk: %s, err: %s\n", sdk, err.Error()) + return err + } + err = rc.HSet(ctx, cache.DefaultPrecompiledExamples, sdkMarsh, precompiledObjectMarsh).Err() + if err != nil { + logger.Errorf("Redis Cache: set default precompiled object: error during HGet operation, err: %s\n", err.Error()) + return err + } + return nil + +} + +func (rc *Cache) GetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk) (*pb.PrecompiledObject, error) { + sdkMarsh, err := json.Marshal(sdk) + if err != nil { + logger.Errorf("Redis Cache: get default precompiled object: error during marshal sdk: %s, err: %s\n", sdk, err.Error()) + return nil, err + } + + value, err := rc.HGet(ctx, cache.DefaultPrecompiledExamples, string(sdkMarsh)).Result() + if err != nil { + logger.Errorf("Redis Cache: get default precompiled object: error during HGet operation for key: %s, subKey: %s, err: %s\n", cache.DefaultPrecompiledExamples, sdkMarsh, err.Error()) + return nil, err + } + + result := new(pb.PrecompiledObject) + err = json.Unmarshal([]byte(value), &result) + if err != nil { + logger.Errorf("Redis Cache: get default precompiled object: error during unmarshal value, err: %s\n", err.Error()) + } + return result, nil +} + // unmarshalBySubKey unmarshal value by subKey func unmarshalBySubKey(subKey cache.SubKey, value string) (interface{}, error) { var result interface{} diff --git a/playground/backend/internal/cloud_bucket/precompiled_objects.go b/playground/backend/internal/cloud_bucket/precompiled_objects.go index 09dda526a9a27..945c5bcfb229c 100644 --- a/playground/backend/internal/cloud_bucket/precompiled_objects.go +++ b/playground/backend/internal/cloud_bucket/precompiled_objects.go @@ -24,6 +24,7 @@ import ( "fmt" "google.golang.org/api/iterator" "google.golang.org/api/option" + "io" "io/ioutil" "os" "path/filepath" @@ -32,16 +33,17 @@ import ( ) const ( - OutputExtension = "output" - LogsExtension = "log" - GraphExtension = "graph" - MetaInfoName = "meta.info" - Timeout = time.Minute - javaExtension = "java" - goExtension = "go" - pyExtension = "py" - scioExtension = "scala" - separatorsNumber = 3 + OutputExtension = "output" + LogsExtension = "log" + GraphExtension = "graph" + defaultPrecompiledObjectInfo = "defaultPrecompiledObject.info" + MetaInfoName = "meta.info" + Timeout = time.Minute + javaExtension = "java" + goExtension = "go" + pyExtension = "py" + scioExtension = "scala" + separatorsNumber = 3 ) type ObjectInfo struct { @@ -54,6 +56,7 @@ type ObjectInfo struct { Link string `protobuf:"bytes,3,opt,name=link,proto3" json:"link,omitempty"` Multifile bool `protobuf:"varint,7,opt,name=multifile,proto3" json:"multifile,omitempty"` ContextLine int32 `protobuf:"varint,7,opt,name=context_line,proto3" json:"context_line,omitempty"` + DefaultExample bool `protobuf:"varint,7,opt,name=default_example,json=defaultExample,proto3" json:"default_example,omitempty"` } type PrecompiledObjects []ObjectInfo @@ -65,6 +68,7 @@ type SdkToCategories map[string]CategoryToPrecompiledObjects // the bucket where examples are stored would be public, // and it has a specific structure of files, namely: // SDK_JAVA/ +// ----defaultPrecompiledObject.info // ----PRECOMPILED_OBJECT_TYPE_EXAMPLE/ // --------MinimalWordCount/ // ----------- MinimalWordCount.java @@ -82,6 +86,7 @@ type SdkToCategories map[string]CategoryToPrecompiledObjects // --------... // ----... // SDK_GO/ +// ----defaultPrecompiledObject.info // ----PRECOMPILED_OBJECT_TYPE_EXAMPLE/ // --------MinimalWordCount/ // ----------- MinimalWordCount.go @@ -93,6 +98,12 @@ type SdkToCategories map[string]CategoryToPrecompiledObjects // ----PRECOMPILED_OBJECT_TYPE_KATA/ // --------... // ----... +// +// defaultPrecompiledObject.info is a file that contains path to the default example: +// { +// "SDK_JAVA": "SDK_JAVA/PRECOMPILED_OBJECT_TYPE_EXAMPLE/MinimalWordCount" +// } +// // meta.info is a json file that has the following fields: // { // "name": "name of the example", @@ -112,8 +123,8 @@ func New() *CloudStorage { return &CloudStorage{} } -// GetPrecompiledObject returns the source code of the example -func (cd *CloudStorage) GetPrecompiledObject(ctx context.Context, precompiledObjectPath, bucketName string) (string, error) { +// GetPrecompiledObjectCode returns the source code of the example +func (cd *CloudStorage) GetPrecompiledObjectCode(ctx context.Context, precompiledObjectPath, bucketName string) (string, error) { extension, err := getFileExtensionBySdk(precompiledObjectPath) if err != nil { return "", err @@ -210,6 +221,77 @@ func (cd *CloudStorage) GetPrecompiledObjects(ctx context.Context, targetSdk pb. return &precompiledObjects, nil } +// GetDefaultPrecompiledObjects returns the default precompiled objects +func (cd *CloudStorage) GetDefaultPrecompiledObjects(ctx context.Context, bucketName string) (map[pb.Sdk]*pb.PrecompiledObject, error) { + client, err := storage.NewClient(ctx, option.WithoutAuthentication()) + if err != nil { + return nil, fmt.Errorf("storage.NewClient: %v", err) + } + defer client.Close() + bucket := client.Bucket(bucketName) + + paths := make(map[pb.Sdk]string, 0) + for _, sdkName := range pb.Sdk_name { + sdk := pb.Sdk(pb.Sdk_value[sdkName]) + if sdk == pb.Sdk_SDK_UNSPECIFIED { + continue + } + path, err := cd.getDefaultPrecompiledObjectsPath(ctx, bucket, sdk) + if err != nil { + return nil, err + } + paths[sdk] = path + } + + defaultPrecompiledObjects := make(map[pb.Sdk]*pb.PrecompiledObject, 0) + for sdk, path := range paths { + infoPath := filepath.Join(path, MetaInfoName) + rc, err := bucket.Object(infoPath).NewReader(ctx) + if err != nil { + logger.Errorf("Object(%q).NewReader: %v", infoPath, err.Error()) + continue + } + metaFile, err := ioutil.ReadAll(rc) + if err != nil { + logger.Errorf("ioutil.ReadAll: %v", err.Error()) + continue + } + rc.Close() + + precompiledObject := &pb.PrecompiledObject{} + err = json.Unmarshal(metaFile, &precompiledObject) + if err != nil { + logger.Errorf("json.Unmarshal: %v", err.Error()) + return nil, err + } + precompiledObject.CloudPath = path + defaultPrecompiledObjects[sdk] = precompiledObject + } + return defaultPrecompiledObjects, nil +} + +// getDefaultPrecompiledObjectsPath returns path for SDK to the default precompiled object +func (cd *CloudStorage) getDefaultPrecompiledObjectsPath(ctx context.Context, bucket *storage.BucketHandle, sdk pb.Sdk) (string, error) { + pathToFile := fmt.Sprintf("%s/%s", sdk.String(), defaultPrecompiledObjectInfo) + rc, err := bucket.Object(pathToFile).NewReader(ctx) + if err != nil { + logger.Errorf("Object(%q).NewReader: %v", pathToFile, err.Error()) + return "", err + } + + data, err := io.ReadAll(rc) + if err != nil { + logger.Errorf("ioutil.ReadAll: %v", err.Error()) + return "", err + } + + path := make(map[string]string, 0) + if err := json.Unmarshal(data, &path); err != nil { + return "", err + } + return path[sdk.String()], nil +} + // getPrecompiledObjectsDirs finds directories with precompiled objects // Since there is no notion of directory at cloud storage, then // to avoid duplicates of a base path (directory) need to store it in a set/map. diff --git a/playground/backend/internal/cloud_bucket/precompiled_objects_test.go b/playground/backend/internal/cloud_bucket/precompiled_objects_test.go index 30beb10ea2b52..52a8a85a134c1 100644 --- a/playground/backend/internal/cloud_bucket/precompiled_objects_test.go +++ b/playground/backend/internal/cloud_bucket/precompiled_objects_test.go @@ -251,8 +251,8 @@ func Benchmark_GetPrecompiledObjectOutput(b *testing.B) { } } -func Benchmark_GetPrecompiledObject(b *testing.B) { +func Benchmark_GetPrecompiledObjectCode(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = bucket.GetPrecompiledObject(ctx, precompiledObjectPath, defaultBucketName) + _, _ = bucket.GetPrecompiledObjectCode(ctx, precompiledObjectPath, defaultBucketName) } } diff --git a/playground/backend/internal/utils/precompiled_objects_utils.go b/playground/backend/internal/utils/precompiled_objects_utils.go index 9f38c7d0026a4..2b779ab5878d1 100644 --- a/playground/backend/internal/utils/precompiled_objects_utils.go +++ b/playground/backend/internal/utils/precompiled_objects_utils.go @@ -17,9 +17,11 @@ package utils import ( pb "beam.apache.org/playground/backend/internal/api/v1" + "beam.apache.org/playground/backend/internal/cache" "beam.apache.org/playground/backend/internal/cloud_bucket" "beam.apache.org/playground/backend/internal/logger" "context" + "fmt" ) // PutPrecompiledObjectsToCategory adds categories with precompiled objects to protobuf object @@ -38,6 +40,7 @@ func PutPrecompiledObjectsToCategory(categoryName string, precompiledObjects *cl Link: object.Link, Multifile: object.Multifile, ContextLine: object.ContextLine, + DefaultExample: object.DefaultExample, }) } sdkCategory.Categories = append(sdkCategory.Categories, &category) @@ -93,3 +96,44 @@ func FilterCatalog(catalog []*pb.Categories, sdk pb.Sdk, categoryName string) [] } return result } + +// GetDefaultPrecompiledObject returns the default precompiled objects from cache for sdk +func GetDefaultPrecompiledObject(ctx context.Context, sdk pb.Sdk, cacheService cache.Cache, bucketName string) (*pb.PrecompiledObject, error) { + precompiledObject, err := cacheService.GetDefaultPrecompiledObject(ctx, sdk) + if err != nil { + logger.Errorf("GetDefaultPrecompiledObject(): error during getting default precompiled object %s", err.Error()) + bucket := cloud_bucket.New() + defaultPrecompiledObjects, err := bucket.GetDefaultPrecompiledObjects(ctx, bucketName) + if err != nil { + return nil, err + } + for sdk, precompiledObject := range defaultPrecompiledObjects { + if err := cacheService.SetDefaultPrecompiledObject(ctx, sdk, precompiledObject); err != nil { + logger.Errorf("GetPrecompiledObjects(): cache error: %s", err.Error()) + } + } + precompiledObject, ok := defaultPrecompiledObjects[sdk] + if !ok { + return nil, fmt.Errorf("no default precompiled object found for this sdk: %s", sdk) + } + return precompiledObject, nil + } + return precompiledObject, nil +} + +// GetCatalogFromCacheOrStorage returns the precompiled objects catalog from cache +// - If there is no catalog in the cache, gets the catalog from the Storage and saves it to the cache +func GetCatalogFromCacheOrStorage(ctx context.Context, cacheService cache.Cache, bucketName string) ([]*pb.Categories, error) { + catalog, err := cacheService.GetCatalog(ctx) + if err != nil { + logger.Errorf("GetCatalog(): cache error: %s", err.Error()) + catalog, err = GetCatalogFromStorage(ctx, bucketName) + if err != nil { + return nil, err + } + if err = cacheService.SetCatalog(ctx, catalog); err != nil { + logger.Errorf("SetCatalog(): cache error: %s", err.Error()) + } + } + return catalog, nil +} diff --git a/playground/backend/internal/utils/precompiled_objects_utils_test.go b/playground/backend/internal/utils/precompiled_objects_utils_test.go index 71fbe5af16ea7..e1b345a663194 100644 --- a/playground/backend/internal/utils/precompiled_objects_utils_test.go +++ b/playground/backend/internal/utils/precompiled_objects_utils_test.go @@ -17,14 +17,18 @@ package utils import ( pb "beam.apache.org/playground/backend/internal/api/v1" + "beam.apache.org/playground/backend/internal/cache" + "beam.apache.org/playground/backend/internal/cache/local" "beam.apache.org/playground/backend/internal/cloud_bucket" + "beam.apache.org/playground/backend/internal/logger" + "context" "reflect" "testing" ) func TestPutPrecompiledObjectsToCategory(t *testing.T) { precompiledObjectToAdd := &cloud_bucket.PrecompiledObjects{ - {"TestName", "SDK_JAVA/TestCategory/TestName.java", "TestDescription", pb.PrecompiledObjectType_PRECOMPILED_OBJECT_TYPE_EXAMPLE, []string{""}, "", "", false, 1}, + {"TestName", "SDK_JAVA/TestCategory/TestName.java", "TestDescription", pb.PrecompiledObjectType_PRECOMPILED_OBJECT_TYPE_EXAMPLE, []string{""}, "", "", false, 1, false}, } type args struct { categoryName string @@ -205,3 +209,67 @@ func TestFilterPrecompiledObjects(t *testing.T) { }) } } + +func TestGetDefaultPrecompiledObject(t *testing.T) { + ctx := context.Background() + cacheService := local.New(ctx) + defaultPrecompiledObject := &pb.PrecompiledObject{ + CloudPath: "cloudPath", + Name: "Name", + Description: "Description", + Type: pb.PrecompiledObjectType_PRECOMPILED_OBJECT_TYPE_EXAMPLE, + PipelineOptions: "--key value", + Link: "Link", + ContextLine: 1, + DefaultExample: true, + } + err := cacheService.SetDefaultPrecompiledObject(ctx, pb.Sdk_SDK_JAVA, defaultPrecompiledObject) + if err != nil { + logger.Errorf("Error during set up test") + } + + type args struct { + ctx context.Context + sdk pb.Sdk + cacheService cache.Cache + } + tests := []struct { + name string + args args + want *pb.PrecompiledObject + wantErr bool + }{ + { + name: "there is default precompiled object", + args: args{ + ctx: ctx, + sdk: pb.Sdk_SDK_JAVA, + cacheService: cacheService, + }, + want: defaultPrecompiledObject, + wantErr: false, + }, + { + name: "there is no default precompiled object", + args: args{ + ctx: ctx, + sdk: pb.Sdk_SDK_UNSPECIFIED, + cacheService: cacheService, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetDefaultPrecompiledObject(tt.args.ctx, tt.args.sdk, tt.args.cacheService, "") + if (err != nil) != tt.wantErr { + t.Errorf("GetDefaultPrecompiledObject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetDefaultPrecompiledObject() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/playground/frontend/lib/api/v1/api.pb.dart b/playground/frontend/lib/api/v1/api.pb.dart index 3e388da97d126..de916d4552f34 100644 --- a/playground/frontend/lib/api/v1/api.pb.dart +++ b/playground/frontend/lib/api/v1/api.pb.dart @@ -991,6 +991,7 @@ class PrecompiledObject extends $pb.GeneratedMessage { ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'link') ..aOB(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'multifile') ..a<$core.int>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'contextLine', $pb.PbFieldType.O3) + ..aOB(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'defaultExample') ..hasRequiredFields = false ; @@ -1004,6 +1005,7 @@ class PrecompiledObject extends $pb.GeneratedMessage { $core.String? link, $core.bool? multifile, $core.int? contextLine, + $core.bool? defaultExample, }) { final _result = create(); if (cloudPath != null) { @@ -1030,6 +1032,9 @@ class PrecompiledObject extends $pb.GeneratedMessage { if (contextLine != null) { _result.contextLine = contextLine; } + if (defaultExample != null) { + _result.defaultExample = defaultExample; + } return _result; } factory PrecompiledObject.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); @@ -1124,6 +1129,15 @@ class PrecompiledObject extends $pb.GeneratedMessage { $core.bool hasContextLine() => $_has(7); @$pb.TagNumber(8) void clearContextLine() => clearField(8); + + @$pb.TagNumber(9) + $core.bool get defaultExample => $_getBF(8); + @$pb.TagNumber(9) + set defaultExample($core.bool v) { $_setBool(8, v); } + @$pb.TagNumber(9) + $core.bool hasDefaultExample() => $_has(8); + @$pb.TagNumber(9) + void clearDefaultExample() => clearField(9); } class Categories_Category extends $pb.GeneratedMessage { diff --git a/playground/frontend/lib/api/v1/api.pbjson.dart b/playground/frontend/lib/api/v1/api.pbjson.dart index 73986428da058..a952e8404987e 100644 --- a/playground/frontend/lib/api/v1/api.pbjson.dart +++ b/playground/frontend/lib/api/v1/api.pbjson.dart @@ -286,11 +286,12 @@ const PrecompiledObject$json = const { const {'1': 'link', '3': 6, '4': 1, '5': 9, '10': 'link'}, const {'1': 'multifile', '3': 7, '4': 1, '5': 8, '10': 'multifile'}, const {'1': 'context_line', '3': 8, '4': 1, '5': 5, '10': 'contextLine'}, + const {'1': 'default_example', '3': 9, '4': 1, '5': 8, '10': 'defaultExample'}, ], }; /// Descriptor for `PrecompiledObject`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List precompiledObjectDescriptor = $convert.base64Decode('ChFQcmVjb21waWxlZE9iamVjdBIdCgpjbG91ZF9wYXRoGAEgASgJUgljbG91ZFBhdGgSEgoEbmFtZRgCIAEoCVIEbmFtZRIgCgtkZXNjcmlwdGlvbhgDIAEoCVILZGVzY3JpcHRpb24SMQoEdHlwZRgEIAEoDjIdLmFwaS52MS5QcmVjb21waWxlZE9iamVjdFR5cGVSBHR5cGUSKQoQcGlwZWxpbmVfb3B0aW9ucxgFIAEoCVIPcGlwZWxpbmVPcHRpb25zEhIKBGxpbmsYBiABKAlSBGxpbmsSHAoJbXVsdGlmaWxlGAcgASgIUgltdWx0aWZpbGUSIQoMY29udGV4dF9saW5lGAggASgFUgtjb250ZXh0TGluZQ=='); +final $typed_data.Uint8List precompiledObjectDescriptor = $convert.base64Decode('ChFQcmVjb21waWxlZE9iamVjdBIdCgpjbG91ZF9wYXRoGAEgASgJUgljbG91ZFBhdGgSEgoEbmFtZRgCIAEoCVIEbmFtZRIgCgtkZXNjcmlwdGlvbhgDIAEoCVILZGVzY3JpcHRpb24SMQoEdHlwZRgEIAEoDjIdLmFwaS52MS5QcmVjb21waWxlZE9iamVjdFR5cGVSBHR5cGUSKQoQcGlwZWxpbmVfb3B0aW9ucxgFIAEoCVIPcGlwZWxpbmVPcHRpb25zEhIKBGxpbmsYBiABKAlSBGxpbmsSHAoJbXVsdGlmaWxlGAcgASgIUgltdWx0aWZpbGUSIQoMY29udGV4dF9saW5lGAggASgFUgtjb250ZXh0TGluZRInCg9kZWZhdWx0X2V4YW1wbGUYCSABKAhSDmRlZmF1bHRFeGFtcGxl'); @$core.Deprecated('Use categoriesDescriptor instead') const Categories$json = const { '1': 'Categories', diff --git a/playground/infrastructure/api/v1/api_pb2.py b/playground/infrastructure/api/v1/api_pb2.py index 77a0f2b15587a..3108cd7469772 100644 --- a/playground/infrastructure/api/v1/api_pb2.py +++ b/playground/infrastructure/api/v1/api_pb2.py @@ -35,7 +35,7 @@ syntax='proto3', serialized_options=b'Z6beam.apache.org/playground/backend/internal;playground', create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\tapi.proto\x12\x06\x61pi.v1\"R\n\x0eRunCodeRequest\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x18\n\x03sdk\x18\x02 \x01(\x0e\x32\x0b.api.v1.Sdk\x12\x18\n\x10pipeline_options\x18\x03 \x01(\t\"(\n\x0fRunCodeResponse\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"+\n\x12\x43heckStatusRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"5\n\x13\x43heckStatusResponse\x12\x1e\n\x06status\x18\x01 \x01(\x0e\x32\x0e.api.v1.Status\"3\n\x1aGetValidationOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"-\n\x1bGetValidationOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"4\n\x1bGetPreparationOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\".\n\x1cGetPreparationOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"0\n\x17GetCompileOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"*\n\x18GetCompileOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\",\n\x13GetRunOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"&\n\x14GetRunOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"+\n\x12GetRunErrorRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"%\n\x13GetRunErrorResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"\'\n\x0eGetLogsRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"!\n\x0fGetLogsResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"(\n\x0fGetGraphRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"!\n\x10GetGraphResponse\x12\r\n\x05graph\x18\x01 \x01(\t\"&\n\rCancelRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"\x10\n\x0e\x43\x61ncelResponse\"\xc8\x01\n\x11PrecompiledObject\x12\x12\n\ncloud_path\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12+\n\x04type\x18\x04 \x01(\x0e\x32\x1d.api.v1.PrecompiledObjectType\x12\x18\n\x10pipeline_options\x18\x05 \x01(\t\x12\x0c\n\x04link\x18\x06 \x01(\t\x12\x11\n\tmultifile\x18\x07 \x01(\x08\x12\x14\n\x0c\x63ontext_line\x18\x08 \x01(\x05\"\xb2\x01\n\nCategories\x12\x18\n\x03sdk\x18\x01 \x01(\x0e\x32\x0b.api.v1.Sdk\x12/\n\ncategories\x18\x02 \x03(\x0b\x32\x1b.api.v1.Categories.Category\x1aY\n\x08\x43\x61tegory\x12\x15\n\rcategory_name\x18\x01 \x01(\t\x12\x36\n\x13precompiled_objects\x18\x02 \x03(\x0b\x32\x19.api.v1.PrecompiledObject\"J\n\x1cGetPrecompiledObjectsRequest\x12\x18\n\x03sdk\x18\x01 \x01(\x0e\x32\x0b.api.v1.Sdk\x12\x10\n\x08\x63\x61tegory\x18\x02 \x01(\t\"5\n\x1fGetPrecompiledObjectCodeRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\"7\n!GetPrecompiledObjectOutputRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\"5\n\x1fGetPrecompiledObjectLogsRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\"6\n GetPrecompiledObjectGraphRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\">\n\"GetDefaultPrecompiledObjectRequest\x12\x18\n\x03sdk\x18\x01 \x01(\x0e\x32\x0b.api.v1.Sdk\"K\n\x1dGetPrecompiledObjectsResponse\x12*\n\x0esdk_categories\x18\x01 \x03(\x0b\x32\x12.api.v1.Categories\"0\n GetPrecompiledObjectCodeResponse\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\"4\n\"GetPrecompiledObjectOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"2\n GetPrecompiledObjectLogsResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"2\n!GetPrecompiledObjectGraphResponse\x12\r\n\x05graph\x18\x01 \x01(\t\"\\\n#GetDefaultPrecompiledObjectResponse\x12\x35\n\x12precompiled_object\x18\x01 \x01(\x0b\x32\x19.api.v1.PrecompiledObject*R\n\x03Sdk\x12\x13\n\x0fSDK_UNSPECIFIED\x10\x00\x12\x0c\n\x08SDK_JAVA\x10\x01\x12\n\n\x06SDK_GO\x10\x02\x12\x0e\n\nSDK_PYTHON\x10\x03\x12\x0c\n\x08SDK_SCIO\x10\x04*\xb8\x02\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11STATUS_VALIDATING\x10\x01\x12\x1b\n\x17STATUS_VALIDATION_ERROR\x10\x02\x12\x14\n\x10STATUS_PREPARING\x10\x03\x12\x1c\n\x18STATUS_PREPARATION_ERROR\x10\x04\x12\x14\n\x10STATUS_COMPILING\x10\x05\x12\x18\n\x14STATUS_COMPILE_ERROR\x10\x06\x12\x14\n\x10STATUS_EXECUTING\x10\x07\x12\x13\n\x0fSTATUS_FINISHED\x10\x08\x12\x14\n\x10STATUS_RUN_ERROR\x10\t\x12\x10\n\x0cSTATUS_ERROR\x10\n\x12\x16\n\x12STATUS_RUN_TIMEOUT\x10\x0b\x12\x13\n\x0fSTATUS_CANCELED\x10\x0c*\xae\x01\n\x15PrecompiledObjectType\x12\'\n#PRECOMPILED_OBJECT_TYPE_UNSPECIFIED\x10\x00\x12#\n\x1fPRECOMPILED_OBJECT_TYPE_EXAMPLE\x10\x01\x12 \n\x1cPRECOMPILED_OBJECT_TYPE_KATA\x10\x02\x12%\n!PRECOMPILED_OBJECT_TYPE_UNIT_TEST\x10\x03\x32\x9b\x0b\n\x11PlaygroundService\x12:\n\x07RunCode\x12\x16.api.v1.RunCodeRequest\x1a\x17.api.v1.RunCodeResponse\x12\x46\n\x0b\x43heckStatus\x12\x1a.api.v1.CheckStatusRequest\x1a\x1b.api.v1.CheckStatusResponse\x12I\n\x0cGetRunOutput\x12\x1b.api.v1.GetRunOutputRequest\x1a\x1c.api.v1.GetRunOutputResponse\x12:\n\x07GetLogs\x12\x16.api.v1.GetLogsRequest\x1a\x17.api.v1.GetLogsResponse\x12=\n\x08GetGraph\x12\x17.api.v1.GetGraphRequest\x1a\x18.api.v1.GetGraphResponse\x12\x46\n\x0bGetRunError\x12\x1a.api.v1.GetRunErrorRequest\x1a\x1b.api.v1.GetRunErrorResponse\x12^\n\x13GetValidationOutput\x12\".api.v1.GetValidationOutputRequest\x1a#.api.v1.GetValidationOutputResponse\x12\x61\n\x14GetPreparationOutput\x12#.api.v1.GetPreparationOutputRequest\x1a$.api.v1.GetPreparationOutputResponse\x12U\n\x10GetCompileOutput\x12\x1f.api.v1.GetCompileOutputRequest\x1a .api.v1.GetCompileOutputResponse\x12\x37\n\x06\x43\x61ncel\x12\x15.api.v1.CancelRequest\x1a\x16.api.v1.CancelResponse\x12\x64\n\x15GetPrecompiledObjects\x12$.api.v1.GetPrecompiledObjectsRequest\x1a%.api.v1.GetPrecompiledObjectsResponse\x12m\n\x18GetPrecompiledObjectCode\x12\'.api.v1.GetPrecompiledObjectCodeRequest\x1a(.api.v1.GetPrecompiledObjectCodeResponse\x12s\n\x1aGetPrecompiledObjectOutput\x12).api.v1.GetPrecompiledObjectOutputRequest\x1a*.api.v1.GetPrecompiledObjectOutputResponse\x12m\n\x18GetPrecompiledObjectLogs\x12\'.api.v1.GetPrecompiledObjectLogsRequest\x1a(.api.v1.GetPrecompiledObjectLogsResponse\x12p\n\x19GetPrecompiledObjectGraph\x12(.api.v1.GetPrecompiledObjectGraphRequest\x1a).api.v1.GetPrecompiledObjectGraphResponse\x12v\n\x1bGetDefaultPrecompiledObject\x12*.api.v1.GetDefaultPrecompiledObjectRequest\x1a+.api.v1.GetDefaultPrecompiledObjectResponseB8Z6beam.apache.org/playground/backend/internal;playgroundb\x06proto3' + serialized_pb=b'\n\tapi.proto\x12\x06\x61pi.v1\"R\n\x0eRunCodeRequest\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x18\n\x03sdk\x18\x02 \x01(\x0e\x32\x0b.api.v1.Sdk\x12\x18\n\x10pipeline_options\x18\x03 \x01(\t\"(\n\x0fRunCodeResponse\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"+\n\x12\x43heckStatusRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"5\n\x13\x43heckStatusResponse\x12\x1e\n\x06status\x18\x01 \x01(\x0e\x32\x0e.api.v1.Status\"3\n\x1aGetValidationOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"-\n\x1bGetValidationOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"4\n\x1bGetPreparationOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\".\n\x1cGetPreparationOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"0\n\x17GetCompileOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"*\n\x18GetCompileOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\",\n\x13GetRunOutputRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"&\n\x14GetRunOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"+\n\x12GetRunErrorRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"%\n\x13GetRunErrorResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"\'\n\x0eGetLogsRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"!\n\x0fGetLogsResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"(\n\x0fGetGraphRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"!\n\x10GetGraphResponse\x12\r\n\x05graph\x18\x01 \x01(\t\"&\n\rCancelRequest\x12\x15\n\rpipeline_uuid\x18\x01 \x01(\t\"\x10\n\x0e\x43\x61ncelResponse\"\xe1\x01\n\x11PrecompiledObject\x12\x12\n\ncloud_path\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12+\n\x04type\x18\x04 \x01(\x0e\x32\x1d.api.v1.PrecompiledObjectType\x12\x18\n\x10pipeline_options\x18\x05 \x01(\t\x12\x0c\n\x04link\x18\x06 \x01(\t\x12\x11\n\tmultifile\x18\x07 \x01(\x08\x12\x14\n\x0c\x63ontext_line\x18\x08 \x01(\x05\x12\x17\n\x0f\x64\x65\x66\x61ult_example\x18\t \x01(\x08\"\xb2\x01\n\nCategories\x12\x18\n\x03sdk\x18\x01 \x01(\x0e\x32\x0b.api.v1.Sdk\x12/\n\ncategories\x18\x02 \x03(\x0b\x32\x1b.api.v1.Categories.Category\x1aY\n\x08\x43\x61tegory\x12\x15\n\rcategory_name\x18\x01 \x01(\t\x12\x36\n\x13precompiled_objects\x18\x02 \x03(\x0b\x32\x19.api.v1.PrecompiledObject\"J\n\x1cGetPrecompiledObjectsRequest\x12\x18\n\x03sdk\x18\x01 \x01(\x0e\x32\x0b.api.v1.Sdk\x12\x10\n\x08\x63\x61tegory\x18\x02 \x01(\t\"5\n\x1fGetPrecompiledObjectCodeRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\"7\n!GetPrecompiledObjectOutputRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\"5\n\x1fGetPrecompiledObjectLogsRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\"6\n GetPrecompiledObjectGraphRequest\x12\x12\n\ncloud_path\x18\x01 \x01(\t\">\n\"GetDefaultPrecompiledObjectRequest\x12\x18\n\x03sdk\x18\x01 \x01(\x0e\x32\x0b.api.v1.Sdk\"K\n\x1dGetPrecompiledObjectsResponse\x12*\n\x0esdk_categories\x18\x01 \x03(\x0b\x32\x12.api.v1.Categories\"0\n GetPrecompiledObjectCodeResponse\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\"4\n\"GetPrecompiledObjectOutputResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"2\n GetPrecompiledObjectLogsResponse\x12\x0e\n\x06output\x18\x01 \x01(\t\"2\n!GetPrecompiledObjectGraphResponse\x12\r\n\x05graph\x18\x01 \x01(\t\"\\\n#GetDefaultPrecompiledObjectResponse\x12\x35\n\x12precompiled_object\x18\x01 \x01(\x0b\x32\x19.api.v1.PrecompiledObject*R\n\x03Sdk\x12\x13\n\x0fSDK_UNSPECIFIED\x10\x00\x12\x0c\n\x08SDK_JAVA\x10\x01\x12\n\n\x06SDK_GO\x10\x02\x12\x0e\n\nSDK_PYTHON\x10\x03\x12\x0c\n\x08SDK_SCIO\x10\x04*\xb8\x02\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11STATUS_VALIDATING\x10\x01\x12\x1b\n\x17STATUS_VALIDATION_ERROR\x10\x02\x12\x14\n\x10STATUS_PREPARING\x10\x03\x12\x1c\n\x18STATUS_PREPARATION_ERROR\x10\x04\x12\x14\n\x10STATUS_COMPILING\x10\x05\x12\x18\n\x14STATUS_COMPILE_ERROR\x10\x06\x12\x14\n\x10STATUS_EXECUTING\x10\x07\x12\x13\n\x0fSTATUS_FINISHED\x10\x08\x12\x14\n\x10STATUS_RUN_ERROR\x10\t\x12\x10\n\x0cSTATUS_ERROR\x10\n\x12\x16\n\x12STATUS_RUN_TIMEOUT\x10\x0b\x12\x13\n\x0fSTATUS_CANCELED\x10\x0c*\xae\x01\n\x15PrecompiledObjectType\x12\'\n#PRECOMPILED_OBJECT_TYPE_UNSPECIFIED\x10\x00\x12#\n\x1fPRECOMPILED_OBJECT_TYPE_EXAMPLE\x10\x01\x12 \n\x1cPRECOMPILED_OBJECT_TYPE_KATA\x10\x02\x12%\n!PRECOMPILED_OBJECT_TYPE_UNIT_TEST\x10\x03\x32\x9b\x0b\n\x11PlaygroundService\x12:\n\x07RunCode\x12\x16.api.v1.RunCodeRequest\x1a\x17.api.v1.RunCodeResponse\x12\x46\n\x0b\x43heckStatus\x12\x1a.api.v1.CheckStatusRequest\x1a\x1b.api.v1.CheckStatusResponse\x12I\n\x0cGetRunOutput\x12\x1b.api.v1.GetRunOutputRequest\x1a\x1c.api.v1.GetRunOutputResponse\x12:\n\x07GetLogs\x12\x16.api.v1.GetLogsRequest\x1a\x17.api.v1.GetLogsResponse\x12=\n\x08GetGraph\x12\x17.api.v1.GetGraphRequest\x1a\x18.api.v1.GetGraphResponse\x12\x46\n\x0bGetRunError\x12\x1a.api.v1.GetRunErrorRequest\x1a\x1b.api.v1.GetRunErrorResponse\x12^\n\x13GetValidationOutput\x12\".api.v1.GetValidationOutputRequest\x1a#.api.v1.GetValidationOutputResponse\x12\x61\n\x14GetPreparationOutput\x12#.api.v1.GetPreparationOutputRequest\x1a$.api.v1.GetPreparationOutputResponse\x12U\n\x10GetCompileOutput\x12\x1f.api.v1.GetCompileOutputRequest\x1a .api.v1.GetCompileOutputResponse\x12\x37\n\x06\x43\x61ncel\x12\x15.api.v1.CancelRequest\x1a\x16.api.v1.CancelResponse\x12\x64\n\x15GetPrecompiledObjects\x12$.api.v1.GetPrecompiledObjectsRequest\x1a%.api.v1.GetPrecompiledObjectsResponse\x12m\n\x18GetPrecompiledObjectCode\x12\'.api.v1.GetPrecompiledObjectCodeRequest\x1a(.api.v1.GetPrecompiledObjectCodeResponse\x12s\n\x1aGetPrecompiledObjectOutput\x12).api.v1.GetPrecompiledObjectOutputRequest\x1a*.api.v1.GetPrecompiledObjectOutputResponse\x12m\n\x18GetPrecompiledObjectLogs\x12\'.api.v1.GetPrecompiledObjectLogsRequest\x1a(.api.v1.GetPrecompiledObjectLogsResponse\x12p\n\x19GetPrecompiledObjectGraph\x12(.api.v1.GetPrecompiledObjectGraphRequest\x1a).api.v1.GetPrecompiledObjectGraphResponse\x12v\n\x1bGetDefaultPrecompiledObject\x12*.api.v1.GetDefaultPrecompiledObjectRequest\x1a+.api.v1.GetDefaultPrecompiledObjectResponseB8Z6beam.apache.org/playground/backend/internal;playgroundb\x06proto3' ) _SDK = _descriptor.EnumDescriptor( @@ -73,8 +73,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=2050, - serialized_end=2132, + serialized_start=2075, + serialized_end=2157, ) _sym_db.RegisterEnumDescriptor(_SDK) @@ -154,8 +154,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=2135, - serialized_end=2447, + serialized_start=2160, + serialized_end=2472, ) _sym_db.RegisterEnumDescriptor(_STATUS) @@ -190,8 +190,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=2450, - serialized_end=2624, + serialized_start=2475, + serialized_end=2649, ) _sym_db.RegisterEnumDescriptor(_PRECOMPILEDOBJECTTYPE) @@ -932,6 +932,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='default_example', full_name='api.v1.PrecompiledObject.default_example', index=8, + number=9, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -945,7 +952,7 @@ oneofs=[ ], serialized_start=925, - serialized_end=1125, + serialized_end=1150, ) @@ -983,8 +990,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1217, - serialized_end=1306, + serialized_start=1242, + serialized_end=1331, ) _CATEGORIES = _descriptor.Descriptor( @@ -1021,8 +1028,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1128, - serialized_end=1306, + serialized_start=1153, + serialized_end=1331, ) @@ -1060,8 +1067,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1308, - serialized_end=1382, + serialized_start=1333, + serialized_end=1407, ) @@ -1092,8 +1099,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1384, - serialized_end=1437, + serialized_start=1409, + serialized_end=1462, ) @@ -1124,8 +1131,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1439, - serialized_end=1494, + serialized_start=1464, + serialized_end=1519, ) @@ -1156,8 +1163,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1496, - serialized_end=1549, + serialized_start=1521, + serialized_end=1574, ) @@ -1188,8 +1195,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1551, - serialized_end=1605, + serialized_start=1576, + serialized_end=1630, ) @@ -1220,8 +1227,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1607, - serialized_end=1669, + serialized_start=1632, + serialized_end=1694, ) @@ -1252,8 +1259,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1671, - serialized_end=1746, + serialized_start=1696, + serialized_end=1771, ) @@ -1284,8 +1291,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1748, - serialized_end=1796, + serialized_start=1773, + serialized_end=1821, ) @@ -1316,8 +1323,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1798, - serialized_end=1850, + serialized_start=1823, + serialized_end=1875, ) @@ -1348,8 +1355,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1852, - serialized_end=1902, + serialized_start=1877, + serialized_end=1927, ) @@ -1380,8 +1387,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1904, - serialized_end=1954, + serialized_start=1929, + serialized_end=1979, ) @@ -1412,8 +1419,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1956, - serialized_end=2048, + serialized_start=1981, + serialized_end=2073, ) _RUNCODEREQUEST.fields_by_name['sdk'].enum_type = _SDK @@ -1722,8 +1729,8 @@ index=0, serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_start=2627, - serialized_end=4062, + serialized_start=2652, + serialized_end=4087, methods=[ _descriptor.MethodDescriptor( name='RunCode', diff --git a/playground/infrastructure/cd_helper.py b/playground/infrastructure/cd_helper.py index 5797b1f8af1ac..4b19d5514854f 100644 --- a/playground/infrastructure/cd_helper.py +++ b/playground/infrastructure/cd_helper.py @@ -103,12 +103,47 @@ def _save_to_cloud_storage(self, examples: List[Example]): """ self._storage_client = storage.Client() self._bucket = self._storage_client.bucket(Config.BUCKET_NAME) + for example in tqdm(examples): file_names = self._write_to_local_fs(example) + + if example.tag.default_example: + default_example_path = str(Path([*file_names].pop()).parent) + cloud_path = self._write_default_example_path_to_local_fs( + default_example_path) + + self._upload_blob( + source_file=os.path.join(Config.TEMP_FOLDER, cloud_path), + destination_blob_name=cloud_path) + for cloud_file_name, local_file_name in file_names.items(): self._upload_blob( source_file=local_file_name, destination_blob_name=cloud_file_name) + def _write_default_example_path_to_local_fs(self, path: str) -> str: + """ + Write default example path to the file (in temp folder) + + Args: + path: path of the default example + + Returns: name of the file + + """ + sdk = Path(path).parts[0] + cloud_path = os.path.join(sdk, Config.DEFAULT_PRECOMPILED_OBJECT) + + path_to_file = os.path.join(Config.TEMP_FOLDER, sdk) + Path(path_to_file).mkdir(parents=True, exist_ok=True) + + local_path = os.path.join(path_to_file, Config.DEFAULT_PRECOMPILED_OBJECT) + + content = json.dumps({sdk: path}) + with open(local_path, "w", encoding="utf-8") as file: + file.write(content) + + return cloud_path + def _write_to_local_fs(self, example: Example): """ Write code of an example, output and meta info diff --git a/playground/infrastructure/config.py b/playground/infrastructure/config.py index 7cc816ccb3488..e072297486de0 100644 --- a/playground/infrastructure/config.py +++ b/playground/infrastructure/config.py @@ -43,6 +43,7 @@ class Config: Sdk.Name(SDK_SCIO)) BUCKET_NAME = "playground-precompiled-objects" TEMP_FOLDER = "temp" + DEFAULT_PRECOMPILED_OBJECT = "defaultPrecompiledObject.info" SDK_TO_EXTENSION = { SDK_JAVA: "java", SDK_GO: "go", SDK_PYTHON: "py", SDK_SCIO: "scala" } diff --git a/playground/infrastructure/proxy/allow_list.py b/playground/infrastructure/proxy/allow_list.py index 002dc41b1468d..d5261a2e60cf6 100644 --- a/playground/infrastructure/proxy/allow_list.py +++ b/playground/infrastructure/proxy/allow_list.py @@ -27,4 +27,5 @@ "dataflow-samples", "beam-samples", "apache-beam-samples", + "playground-precompiled-objects", ] diff --git a/playground/infrastructure/test_cd_helper.py b/playground/infrastructure/test_cd_helper.py index 681851b544e8d..3628540d0117b 100644 --- a/playground/infrastructure/test_cd_helper.py +++ b/playground/infrastructure/test_cd_helper.py @@ -14,11 +14,12 @@ # limitations under the License. import os +import pathlib import shutil import pytest -from api.v1.api_pb2 import SDK_JAVA, STATUS_UNSPECIFIED, \ +from api.v1.api_pb2 import Sdk, SDK_JAVA, SDK_GO, STATUS_UNSPECIFIED, \ PRECOMPILED_OBJECT_TYPE_UNIT_TEST from cd_helper import CDHelper from config import Config @@ -127,3 +128,20 @@ def test__save_to_cloud_storage(mocker): CDHelper()._save_to_cloud_storage([example]) write_to_os_mock.assert_called_with(example) upload_blob_mock.assert_called_with(source_file="", destination_blob_name="") + + +def test__write_default_example_path_to_local_fs(delete_temp_folder): + """ + Test writing default example link of sdk to + the filesystem (in temp folder) + Args: + delete_temp_folder: python fixture to clean up temp folder + after method execution + """ + sdk = Sdk.Name(SDK_GO) + default_example_path = "SDK_GO/PRECOMPILED_OBJECT_TYPE_EXAMPLE/WordCount" + expected_result = str(pathlib.Path(sdk, Config.DEFAULT_PRECOMPILED_OBJECT)) + cloud_path = CDHelper()._write_default_example_path_to_local_fs( + default_example_path) + assert cloud_path == expected_result + assert os.path.exists(os.path.join("temp", cloud_path)) diff --git a/playground/infrastructure/test_helper.py b/playground/infrastructure/test_helper.py index 2ae71ef7b1387..6c733cc061bf6 100644 --- a/playground/infrastructure/test_helper.py +++ b/playground/infrastructure/test_helper.py @@ -177,7 +177,8 @@ def test__get_example(mock_get_name): "description": "Description", "multifile": "False", "categories": [""], - "pipeline_options": "--option option" + "pipeline_options": "--option option", + "context_line": 1 }, "") @@ -189,7 +190,8 @@ def test__get_example(mock_get_name): filepath="/root/filepath.java", code="data", status=STATUS_UNSPECIFIED, - tag=Tag("Name", "Description", "False", [""], "--option option"), + tag=Tag( + "Name", "Description", "False", [""], "--option option", False, 1), link="https://github.com/apache/beam/blob/master/root/filepath.java") mock_get_name.assert_called_once_with("filepath.java") @@ -245,7 +247,8 @@ def test__validate_with_all_fields(): "description": "Description", "multifile": "true", "categories": ["category"], - "pipeline_options": "--option option" + "pipeline_options": "--option option", + "context_line": 1 } assert _validate(tag, ["category"]) is True From 176f78b1a0df93c62e7709cb07058a2ce08da5b1 Mon Sep 17 00:00:00 2001 From: Ahmet Altay Date: Tue, 1 Mar 2022 11:27:44 -0800 Subject: [PATCH 58/61] Add 2022 events blog post (#16975) --- .../blog/upcoming-events-for-beam-in-2022.md | 82 +++++++++++++++++++ website/www/site/data/authors.yml | 5 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 website/www/site/content/en/blog/upcoming-events-for-beam-in-2022.md diff --git a/website/www/site/content/en/blog/upcoming-events-for-beam-in-2022.md b/website/www/site/content/en/blog/upcoming-events-for-beam-in-2022.md new file mode 100644 index 0000000000000..3d3f35b4e535b --- /dev/null +++ b/website/www/site/content/en/blog/upcoming-events-for-beam-in-2022.md @@ -0,0 +1,82 @@ +--- +title: "Upcoming Events for Beam in 2022" +date: 2022-02-28 00:00:01 -0800 +categories: + - blog +authors: + - hermannb +--- + + +We are so excited to announce the upcoming Beam events for this year! We believe that events are an important mechanism to foster the community around Apache Beam as an Open Source Project. Our events are focused on a developer experience by giving spaces for the community to connect, facilitate collaboration, and enable knowledge sharing. + + + +Here is an overview of some upcoming events and ways for everyone to help foster additional community growth: + +## Beam Summit + +The **[Beam Summit 2022](https://2022.beamsummit.org/)** is approaching! The event will be in a hybrid in-person and virtual format from Austin, TX on July 18-20, 2022. The conference will include three full days of lightning talks, roadmap updates, use cases, demos, and workshops for Beam users of all levels. This is a great opportunity to collaborate, share ideas, and work together in the improvement of the project. + +Check out talks from prior editions of Beam Summit **[here](https://www.youtube.com/watch?v=jses0W4Zalc&list=PL4dEBWmGSIU8vLWF56shrSuTsLXvO6Ex3)**! + +### The Experience + +We are so excited to see some of you in person again and the rest of the community online! The **[Beam Summit Steering Committee](https://2022.beamsummit.org/team/)** in partnership with an event production company is working hard to ensure that we provide the community with the best possible experience, no matter which format you choose to attend in. + +If you have any ideas on how we can make this year’s event better, please **[reach out to us](mailto:contact@beamsummit.org)**! + +### Ways to Help & Participate + +1. **[Submit a proposal](https://sessionize.com/beam-summit-2022)** to talk! The deadline for submissions is _March 15th_. +2. **[Register](https://2022.beamsummit.org/tickets/)** to join as an attendee in person or online. +3. Consider sponsoring the event. If your company is interested in engaging with members of the community, please check out the **[sponsoring prospectus](https://2022.beamsummit.org/sponsors/).** +4. Help us get the word out. Please make sure to let your colleagues and friends know about the Beam Summit. + +Don’t forget to follow our Beam Summit **[Twitter](https://twitter.com/BeamSummit?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor)** and **[LinkedIn](https://www.linkedin.com/company/beam-summit/?viewAsMember=true)** pages to receive event updates! + +## Beam College + +**[Beam College 2022](https://beamcollege.dev/)** is around the corner for the second season of training! The event will be hosted virtually from May 10-13, 2022. The training is focused on providing more hands-on experience around end-to-end code samples in an interactive environment, and helping attendees see the applications of concepts covered in other venues, such as the Beam Summit. + +Check out talks from prior editions of Beam College **[here](https://www.youtube.com/playlist?list=PLjYq1UNvv2UcrfapfgKrnLXtYpkvHmpIh)**! + +This year, the training will consist of learning modules such as: + +* The Data movement ecosystem and distributed processing the Beam way +* Scaling, productionalizing, and developing your Beam pipelines +* Use Cases +* Beam ML Use Cases + +Be sure to check out our **[website](https://beamcollege.dev/)** as we continue updating the schedule and follow our **[Twitter](https://twitter.com/beam_college?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor)** and **[LinkedIn](https://www.linkedin.com/showcase/beam-college/)** pages to receive event updates! + +### Ways to Help & Participate + +1. Interested in instructing? Submit a **[proposal](https://docs.google.com/forms/d/e/1FAIpQLSct6RCrKtgsvxlgngKUGwKoB_iOKihXi1OadKyBQIsi00p3cQ/viewform?usp=sf_link)**! The deadline is: _February 28th._ +2. Enroll in Beam College. Registration is now open on the **[registration page](https://beamcollege.dev/step/2022/)**. +3. Consider partnering with the event. If your company is interested in helping to promote the event and being a part of the branding, please fill out this **form**. +4. Help us get the word out by letting your network know about this exciting opportunity to help users uplevel data processing skills, solve complex data applications, and optimize data pipelines! + +## Beam Meetups + +In partnership with an event production company, Beam will be hosting an average of one virtual Meetup per month. These Meetups will be relaxed presentations on topics or demos followed by a Q&A session. The objective of our virtual meetups is to give the community an update on the most recent Beam features launched within the past six months. These meetups are free and open to the public. + +Check out recordings from previous Meetups **[here](https://www.youtube.com/watch?v=8fNEs7SbefM&list=PL4dEBWmGSIU-cQSpYP7R1lSC6e2K_pTf1)**! + +### Ways to Help & Participate + +1. Are you interested in sharing a feature launch or sharing a step-by-step use case for Beam? Submit a **[talk idea](https://docs.google.com/forms/d/e/1FAIpQLScFg7fmOFc7fTvnJL_dmdhia4HDesW4HYxJsDeulnsHzIzqCg/viewform)**! +2. Register for the events. Registration is now open on the **[registration page](https://clowder.space/projects/apache-beam/)**. +3. Help us get the word out by spreading the word throughout the community to enable more knowledge sharing and collaboration! \ No newline at end of file diff --git a/website/www/site/data/authors.yml b/website/www/site/data/authors.yml index 39f53abad9010..85d37b29b9a32 100644 --- a/website/www/site/data/authors.yml +++ b/website/www/site/data/authors.yml @@ -21,6 +21,7 @@ aljoscha: altay: name: Ahmet Altay email: altay@apache.org + twitter: iridium77 angoenka: name: Ankur Goenka email: goenka@apache.org @@ -208,7 +209,6 @@ yifanzou: name: Yifan Zou email: yifanzou@apache.org twitter: - arturkhanin: name: Artur Khanin email: artur.khanin@akvelon.com @@ -221,3 +221,6 @@ alexkosolapov: name: Alex Kosolapov email: alex.kosolapov@akvelon.com twitter: +hermannb: + name: Brittany Hermann + email: hermannb@google.com From f2db6f15e78ca9623029de55a1aaddf6f8c40a86 Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Tue, 1 Mar 2022 15:56:17 -0500 Subject: [PATCH 59/61] Clean up Go formatter suggestions (#16973) --- .../beam/core/runtime/exec/sideinput_test.go | 40 +++++++++---------- sdks/go/pkg/beam/util/harnessopts/cache.go | 2 +- .../pkg/beam/util/syscallx/syscall_default.go | 1 + .../pkg/beam/util/syscallx/syscall_linux.go | 1 + 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/sdks/go/pkg/beam/core/runtime/exec/sideinput_test.go b/sdks/go/pkg/beam/core/runtime/exec/sideinput_test.go index 30990642aed3d..3293fe44a51ee 100644 --- a/sdks/go/pkg/beam/core/runtime/exec/sideinput_test.go +++ b/sdks/go/pkg/beam/core/runtime/exec/sideinput_test.go @@ -67,26 +67,26 @@ func TestNewSideInputAdapter(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - adapter := NewSideInputAdapter(test.sid, test.sideInputID, test.c, nil) - adapterStruct, ok := adapter.(*sideInputAdapter) - if !ok { - t.Errorf("failed to convert interface to sideInputAdapter struct in test %v", test) - } - if got, want := adapterStruct.sid, test.sid; got != want { - t.Errorf("got SID %v, want %v", got, want) - } - if got, want := adapterStruct.sideInputID, test.sideInputID; got != want { - t.Errorf("got sideInputID %v, want %v", got, want) - } - if got, want := adapterStruct.c, test.c; got != want { - t.Errorf("got coder %v, want %v", got, want) - } - if got, want := reflect.TypeOf(adapterStruct.kc), reflect.TypeOf(test.kc); got != want { - t.Errorf("got ElementEncoder type %v, want %v", got, want) - } - if got, want := reflect.TypeOf(adapterStruct.ec), reflect.TypeOf(test.ec); got != want { - t.Errorf("got ElementDecoder type %v, want %v", got, want) - } + adapter := NewSideInputAdapter(test.sid, test.sideInputID, test.c, nil) + adapterStruct, ok := adapter.(*sideInputAdapter) + if !ok { + t.Errorf("failed to convert interface to sideInputAdapter struct in test %v", test) + } + if got, want := adapterStruct.sid, test.sid; got != want { + t.Errorf("got SID %v, want %v", got, want) + } + if got, want := adapterStruct.sideInputID, test.sideInputID; got != want { + t.Errorf("got sideInputID %v, want %v", got, want) + } + if got, want := adapterStruct.c, test.c; got != want { + t.Errorf("got coder %v, want %v", got, want) + } + if got, want := reflect.TypeOf(adapterStruct.kc), reflect.TypeOf(test.kc); got != want { + t.Errorf("got ElementEncoder type %v, want %v", got, want) + } + if got, want := reflect.TypeOf(adapterStruct.ec), reflect.TypeOf(test.ec); got != want { + t.Errorf("got ElementDecoder type %v, want %v", got, want) + } }) } } diff --git a/sdks/go/pkg/beam/util/harnessopts/cache.go b/sdks/go/pkg/beam/util/harnessopts/cache.go index 117f45f008255..2ad538b73af25 100644 --- a/sdks/go/pkg/beam/util/harnessopts/cache.go +++ b/sdks/go/pkg/beam/util/harnessopts/cache.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package harnessopts defines user-facing entrypoints into Beam hooks +// Package harnessopts defines user-facing entrypoints into Beam hooks // affecting the SDK harness. Call these functions at any time before // submitting your pipeline to a runner, for that pipeline's workers to be affected. package harnessopts diff --git a/sdks/go/pkg/beam/util/syscallx/syscall_default.go b/sdks/go/pkg/beam/util/syscallx/syscall_default.go index a85cd3f0e7e23..55756d0dbd44c 100644 --- a/sdks/go/pkg/beam/util/syscallx/syscall_default.go +++ b/sdks/go/pkg/beam/util/syscallx/syscall_default.go @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package syscallx diff --git a/sdks/go/pkg/beam/util/syscallx/syscall_linux.go b/sdks/go/pkg/beam/util/syscallx/syscall_linux.go index c639f876bebc1..379437ad0a17c 100644 --- a/sdks/go/pkg/beam/util/syscallx/syscall_linux.go +++ b/sdks/go/pkg/beam/util/syscallx/syscall_linux.go @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build linux // +build linux package syscallx From f9084f18721114ce3d169736ab344f8d12682fbb Mon Sep 17 00:00:00 2001 From: Jack McCluskey <34928439+jrmccluskey@users.noreply.github.com> Date: Tue, 1 Mar 2022 15:57:57 -0500 Subject: [PATCH 60/61] [BEAM-14012] Add go fmt to Github Actions (#16978) --- .github/workflows/go_tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go_tests.yml b/.github/workflows/go_tests.yml index f58bfcc8a8a31..b70ec81410491 100644 --- a/.github/workflows/go_tests.yml +++ b/.github/workflows/go_tests.yml @@ -49,5 +49,7 @@ jobs: flags: go files: ./sdks/go/pkg/coverage.txt name: go-unittests + - name: Run fmt + run: cd sdks/go/pkg/beam && go fmt ./...; git diff-index --quiet HEAD || (echo "Run go fmt before checking in changes" && exit 1) - name: Run vet - run: cd sdks/go/pkg/beam && go vet --copylocks=false --unsafeptr=false ./... + run: cd sdks/go/pkg/beam && go vet --copylocks=false --unsafeptr=false ./... || (echo "Run go vet and fix warnings before checking in changes" && exit 1) From fcca54b7636037e6e3448624c50cc5004e2331ee Mon Sep 17 00:00:00 2001 From: Robert Burke Date: Tue, 1 Mar 2022 13:37:30 -0800 Subject: [PATCH 61/61] [BEAM-13911] Add basic tests to Go direct runner. (#16979) --- .../go/pkg/beam/runners/direct/direct_test.go | 455 ++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 sdks/go/pkg/beam/runners/direct/direct_test.go diff --git a/sdks/go/pkg/beam/runners/direct/direct_test.go b/sdks/go/pkg/beam/runners/direct/direct_test.go new file mode 100644 index 0000000000000..b65d33f90919f --- /dev/null +++ b/sdks/go/pkg/beam/runners/direct/direct_test.go @@ -0,0 +1,455 @@ +// 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 direct + +import ( + "context" + "flag" + "fmt" + "os" + "reflect" + "sort" + "testing" + + "github.com/apache/beam/sdks/v2/go/pkg/beam" + "github.com/apache/beam/sdks/v2/go/pkg/beam/core/metrics" + "github.com/google/go-cmp/cmp" +) + +func executeWithT(ctx context.Context, t *testing.T, p *beam.Pipeline) (beam.PipelineResult, error) { + fmt.Println("startingTest - ", t.Name()) + return Execute(ctx, p) +} + +func init() { + beam.RegisterFunction(dofn1) + beam.RegisterFunction(dofn1x2) + beam.RegisterFunction(dofn1x5) + beam.RegisterFunction(dofn2x1) + beam.RegisterFunction(dofn3x1) + beam.RegisterFunction(dofn2x2KV) + beam.RegisterFunction(dofn2) + beam.RegisterFunction(dofnKV) + beam.RegisterFunction(dofnKV2) + beam.RegisterFunction(dofnGBK) + beam.RegisterFunction(dofnGBK2) + beam.RegisterType(reflect.TypeOf((*int64Check)(nil))) + beam.RegisterType(reflect.TypeOf((*stringCheck)(nil))) + + beam.RegisterType(reflect.TypeOf((*testRow)(nil))) + beam.RegisterFunction(dofnKV3) + beam.RegisterFunction(dofnGBK3) + + beam.RegisterFunction(dofn1Counter) + beam.RegisterFunction(dofnSink) +} + +func dofn1(imp []byte, emit func(int64)) { + emit(1) + emit(2) + emit(3) +} + +func dofn1x2(imp []byte, emitA func(int64), emitB func(int64)) { + emitA(1) + emitA(2) + emitA(3) + emitB(4) + emitB(5) + emitB(6) +} + +func dofn1x5(imp []byte, emitA, emitB, emitC, emitD, emitE func(int64)) { + emitA(1) + emitB(2) + emitC(3) + emitD(4) + emitE(5) + emitA(6) + emitB(7) + emitC(8) + emitD(9) + emitE(10) +} + +func dofn2x1(imp []byte, iter func(*int64) bool, emit func(int64)) { + var v, sum int64 + for iter(&v) { + sum += v + } + emit(sum) +} + +func dofn3x1(sum int64, iter1, iter2 func(*int64) bool, emit func(int64)) { + var v int64 + for iter1(&v) { + sum += v + } + for iter2(&v) { + sum += v + } + emit(sum) +} + +func dofn2x2KV(imp []byte, iter func(*string, *int64) bool, emitK func(string), emitV func(int64)) { + var k string + var v, sum int64 + for iter(&k, &v) { + sum += v + emitK(k) + } + emitV(sum) +} + +// int64Check validates that within a single bundle, +// we received the expected int64 values. +type int64Check struct { + Name string + Want []int + got []int +} + +func (fn *int64Check) ProcessElement(v int64, _ func(int64)) { + fn.got = append(fn.got, int(v)) +} + +func (fn *int64Check) FinishBundle(_ func(int64)) error { + sort.Ints(fn.got) + sort.Ints(fn.Want) + if d := cmp.Diff(fn.Want, fn.got); d != "" { + return fmt.Errorf("int64Check[%v] (-want, +got): %v", fn.Name, d) + } + return nil +} + +// stringCheck validates that within a single bundle, +// we received the expected string values. +type stringCheck struct { + Name string + Want []string + got []string +} + +func (fn *stringCheck) ProcessElement(v string, _ func(string)) { + fn.got = append(fn.got, v) +} + +func (fn *stringCheck) FinishBundle(_ func(string)) error { + sort.Strings(fn.got) + sort.Strings(fn.Want) + if d := cmp.Diff(fn.Want, fn.got); d != "" { + return fmt.Errorf("stringCheck[%v] (-want, +got): %v", fn.Name, d) + } + return nil +} + +func dofn2(v int64, emit func(int64)) { + emit(v + 1) +} + +func dofnKV(imp []byte, emit func(string, int64)) { + emit("a", 1) + emit("b", 2) + emit("a", 3) + emit("b", 4) + emit("a", 5) + emit("b", 6) +} + +func dofnKV2(imp []byte, emit func(int64, string)) { + emit(1, "a") + emit(2, "b") + emit(1, "a") + emit(2, "b") + emit(1, "a") + emit(2, "b") +} + +func dofnGBK(k string, vs func(*int64) bool, emit func(int64)) { + var v, sum int64 + for vs(&v) { + sum += v + } + emit(sum) +} + +func dofnGBK2(k int64, vs func(*string) bool, emit func(string)) { + var v, sum string + for vs(&v) { + sum += v + } + emit(sum) +} + +type testRow struct { + A string + B int64 +} + +func dofnKV3(imp []byte, emit func(testRow, testRow)) { + emit(testRow{"a", 1}, testRow{"a", 1}) +} + +func dofnGBK3(k testRow, vs func(*testRow) bool, emit func(string)) { + var v testRow + vs(&v) + emit(fmt.Sprintf("%v: %v", k, v)) +} + +const ( + ns = "directtest" +) + +func dofnSink(ctx context.Context, _ []byte) { + beam.NewCounter(ns, "sunk").Inc(ctx, 73) +} + +func dofn1Counter(ctx context.Context, _ []byte, emit func(int64)) { + beam.NewCounter(ns, "count").Inc(ctx, 1) +} + +func TestRunner_Pipelines(t *testing.T) { + t.Run("simple", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col := beam.ParDo(s, dofn1, imp) + beam.ParDo(s, &int64Check{ + Name: "simple", + Want: []int{1, 2, 3}, + }, col) + + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("sequence", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + beam.Seq(s, imp, dofn1, dofn2, dofn2, dofn2, &int64Check{Name: "sequence", Want: []int{4, 5, 6}}) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("gbk", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col := beam.ParDo(s, dofnKV, imp) + gbk := beam.GroupByKey(s, col) + beam.Seq(s, gbk, dofnGBK, &int64Check{Name: "gbk", Want: []int{9, 12}}) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("gbk2", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col := beam.ParDo(s, dofnKV2, imp) + gbk := beam.GroupByKey(s, col) + beam.Seq(s, gbk, dofnGBK2, &stringCheck{Name: "gbk2", Want: []string{"aaa", "bbb"}}) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("gbk3", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col := beam.ParDo(s, dofnKV3, imp) + gbk := beam.GroupByKey(s, col) + beam.Seq(s, gbk, dofnGBK3, &stringCheck{Name: "gbk3", Want: []string{"{a 1}: {a 1}"}}) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("sink_nooutputs", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + beam.ParDo0(s, dofnSink, imp) + pr, err := executeWithT(context.Background(), t, p) + if err != nil { + t.Fatal(err) + } + qr := pr.Metrics().Query(func(sr metrics.SingleResult) bool { + return sr.Name() == "sunk" + }) + if got, want := qr.Counters()[0].Committed, int64(73); got != want { + t.Errorf("pr.Metrics.Query(Name = \"sunk\")).Committed = %v, want %v", got, want) + } + }) + t.Run("fork_impulse", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col1 := beam.ParDo(s, dofn1, imp) + col2 := beam.ParDo(s, dofn1, imp) + beam.ParDo(s, &int64Check{ + Name: "fork check1", + Want: []int{1, 2, 3}, + }, col1) + beam.ParDo(s, &int64Check{ + Name: "fork check2", + Want: []int{1, 2, 3}, + }, col2) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("fork_postDoFn", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col := beam.ParDo(s, dofn1, imp) + beam.ParDo(s, &int64Check{ + Name: "fork check1", + Want: []int{1, 2, 3}, + }, col) + beam.ParDo(s, &int64Check{ + Name: "fork check2", + Want: []int{1, 2, 3}, + }, col) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("fork_multipleOutputs1", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col1, col2 := beam.ParDo2(s, dofn1x2, imp) + beam.ParDo(s, &int64Check{ + Name: "col1", + Want: []int{1, 2, 3}, + }, col1) + beam.ParDo(s, &int64Check{ + Name: "col2", + Want: []int{4, 5, 6}, + }, col2) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("fork_multipleOutputs2", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col1, col2, col3, col4, col5 := beam.ParDo5(s, dofn1x5, imp) + beam.ParDo(s, &int64Check{ + Name: "col1", + Want: []int{1, 6}, + }, col1) + beam.ParDo(s, &int64Check{ + Name: "col2", + Want: []int{2, 7}, + }, col2) + beam.ParDo(s, &int64Check{ + Name: "col3", + Want: []int{3, 8}, + }, col3) + beam.ParDo(s, &int64Check{ + Name: "col4", + Want: []int{4, 9}, + }, col4) + beam.ParDo(s, &int64Check{ + Name: "col5", + Want: []int{5, 10}, + }, col5) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("flatten", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col1 := beam.ParDo(s, dofn1, imp) + col2 := beam.ParDo(s, dofn1, imp) + flat := beam.Flatten(s, col1, col2) + beam.ParDo(s, &int64Check{ + Name: "flatten check", + Want: []int{1, 1, 2, 2, 3, 3}, + }, flat) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("sideinput_iterable", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col1 := beam.ParDo(s, dofn1, imp) + sum := beam.ParDo(s, dofn2x1, imp, beam.SideInput{Input: col1}) + beam.ParDo(s, &int64Check{ + Name: "iter sideinput check", + Want: []int{6}, + }, sum) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + t.Run("sideinput_iterableKV", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col1 := beam.ParDo(s, dofnKV, imp) + keys, sum := beam.ParDo2(s, dofn2x2KV, imp, beam.SideInput{Input: col1}) + beam.ParDo(s, &stringCheck{ + Name: "iterKV sideinput check K", + Want: []string{"a", "a", "a", "b", "b", "b"}, + }, keys) + beam.ParDo(s, &int64Check{ + Name: "iterKV sideinput check V", + Want: []int{21}, + }, sum) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) + // Validates the waiting on side input readiness in buffer. + t.Run("sideinput_2iterable", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + col0 := beam.ParDo(s, dofn1, imp) + col1 := beam.ParDo(s, dofn1, imp) + col2 := beam.ParDo(s, dofn2, col1) + sum := beam.ParDo(s, dofn3x1, col0, beam.SideInput{Input: col1}, beam.SideInput{Input: col2}) + beam.ParDo(s, &int64Check{ + Name: "iter sideinput check", + Want: []int{16, 17, 18}, + }, sum) + if _, err := executeWithT(context.Background(), t, p); err != nil { + t.Fatal(err) + } + }) +} + +func TestRunner_Metrics(t *testing.T) { + t.Run("counter", func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + imp := beam.Impulse(s) + beam.ParDo(s, dofn1Counter, imp) + pr, err := executeWithT(context.Background(), t, p) + if err != nil { + t.Fatal(err) + } + qr := pr.Metrics().Query(func(sr metrics.SingleResult) bool { + return sr.Name() == "count" + }) + if got, want := qr.Counters()[0].Committed, int64(1); got != want { + t.Errorf("pr.Metrics.Query(Name = \"count\")).Committed = %v, want %v", got, want) + } + }) +} + +func TestMain(m *testing.M) { + // Can't use ptest since it causes a loop. + if !flag.Parsed() { + flag.Parse() + } + beam.Init() + os.Exit(m.Run()) +}

    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%0DMN8z|+~jQc#_h+LD3+>(-d{!`g|7>s77 zFbRNF@mDWu)j=L_;xBVnrb@sr?H4{{LaoD~x0Moy2Dag1p}OK4=pBkl-esZhpE=k!AZn% zu168MP5kt{1$K2wlX@n3802(3|DeZI&nwSl&eiNV&}4FHqlaP2AC+U5|86+oYicG; z@4X0436hbCVf!Z+;1Nj(-^L9%6g})3xfSip7ObxMafhlzjoKULRS<$982Ns`94v{u z4Car3M4WxPo8R9@(iWfF(UtYZvj(2>XSzd?d!@Ajyd2BQ@mc?1TY< zE@rDDrN-yM{}uy^7<&Ky2b5a#E0gZQ-SvMS?tY4|A`v2BEzK`~+-5{ETuU#~l+M>) z5;)*!7w=Mb8P_IWYg}|wA}plS{nQW4V>78Lpz5~npOzjwduIpvYWlCRHRa$t%o%EE-rX&%51@{3}^pRpvU!AY5zR zRpNPTYg?ae+ld4zT__j1#St;A`ikoi(}TI03|2D(#`4RKSeP|7ujDFf?{w_T!yT&l ztz(*CcsGf@><%>V6&RQa#IDYt*JPwgaE=x3pNdo+MG@OO-7P+A`p+{TS^T>^?hcT*Z>C!$+de?6 zR0tFZVW1|fg4qxdQhDFb96Ns{x3gM*GC8-1Pnh6(e{CL_0wGgDL+mt(n!Qv{^?$S2 zEj0_lggLeN(L|HqV6UbZTKnT=A-A^}0-~sPv(HbmMH^}JKG|Q87hRXhw-`Q~B7#@3 z81qMo*@k520r*zhM%Ujf@gOQ(-d=z2dh3wIWEx-c>r9F$$H367h;hbQh|zDQ-wJANUe29 zYM1}ig!tSqJt2%h`dUYT(93z+>2{-aS{F!{Al_E(s6Vzw2?|Zu37$opF-O1jU|7v{* z;*>nQ^f`gC#N|*hZ?mE7!awK|LFl?k*;4{b0EvoSR?s6ZCZzgDNP-mWFuQ1aasb|| zFgjepsJCdew`xJ68gK8B;;vI(S8_&XVX2dgDx(eKQ9&fAf;mnllB&dWk+IMqxNRT& zGGQx?DSoUs-f^<(Ki%^PetodLYkR)nxf&C3*%@ErA*YRHZH!Ta?@dVH6zV`my|wp#OMlb(;_$MXGf5_ zL1uXn!S+`Pl}X-|Ssi5x;UZ9NW`IM1xTL}$Xqp7RAQKIycqOY5%s`JKbB~3FEmfW# zplYT3Iw|^WXau(~!7dGcUQI5kmrV49=&Qi^I)-@I@&trQRqEvL%KhKpTibZ=An!>w zs(oU)`-yM;6unUqRrGxI{O^v?@M0`>{aL0daZ=ZuP`h>i5JKM8qU~?t#{N&dzq%qq zsl7&+Uxo3N7;;=_%{AP8>i=+MX*{T{?Xc=u29gH z#H-GuH`x!@@@IaR>*sParH}Dy6FLJKqga0X_YS8%9M8$ap1h$f@%qtv&k#8n!w7A2 zZbD-&BKV)AO0r}c;>v~N^yCvJs_z3*{MueJ>i4EIV9UgkNhrSxXLu|9O<$ z4&I;olPb&RqU&h}h#cRs9@l{tN0AD7)_FfqTqGKQ7ucB33%pV z=wp6B6>%7c6r1?1m5nS}PBp5a4D;o{m9EP_{qBuE6L8+9V=I-(Ug}G(#l@8&P9jR# zi1Ln=KE-gktr_+;ED|}4goIWy7&kI_HFAIBB*05Ws3%ZX^}t4lML-F%U{x4}pOCsi zN(}xChr^DdPo}w}t_E9g{e=XA@KP>qgpUFiw~Q(Xa~=UK@feZ_QXi=(4}dQW7q!{a zCh0J8N{~r5W<#%)EHQ?aC0;_gIgRvY9J@fyQ-Ocab zSfUt58R~wzR1xXtJG@h*R7PSK?#Xt_TF)e;dcfLU@wKYscFhz}vhb5fBzq^z%oh zaG7R8PL|9+zxi-S5G_JdWFXttFO&~~$9(sC*w6ksDa?GT-f5bFzjRD-`I(cRmvOlo zlN$cAz+gEiHZH*Sk`!%m$-gqZdNqZ*M!s8DP^COLid2J+-MoP6Bc^kp1v?Xj3LcX^ zP~+91&B9-Cb#f1bM~NTng7YrCjI;p|{p0v3j{C>r(h;e$0`?6E}(^}l>L%vI~gTB`1X-gAFirWRtt~tgd?`YOz(Y2G3C3t zJ2>u_16%dd=AjiHg|3aRaEvfk9>%pmfB){23AaoDLZD-9qOhyold6(x2~{BEu<5ia zN{Dw?2U7QEp(~LX@p>z%9Ne)}7Hnlm(GqKW2Y^37cMY6Cab^D;!QTwUY%9~NHOYBZ5bk zXKzAgr0q4lzqr_mzW>PUnR`wX`^Gl_HiB5&jgHXUv*=ZJ8~1&$YWxL4lF|hv-{IV#&1TlYmk4O&Bx-7Qb=6m9 z*r((Q{@a7lIA~6MN!Cf5e`nwigSxD**H~ea>yeQ?E4C2JYmq4C*Pit>k<7SZ%i@wL zMdetoQm%pj7#l0-z>g2Jbd13GsZ3J*8p&Y9Jj7KJ2ukG=B}jpda% zHuOLIcSQ3=Q3I3r-i#4r!GgRXZcvJg<^-uHwWUvS6%90*(4@OSbQwi@4 zD^E3R8BGifg;f4!^zGfa_!0BXnl}YdYHZ-1pROSe5STIkA$C5-raTxd;^1E_V&h$4Ra8NF?IZD@o4J7W(oS2l^euG$!_1EYkq*3whOc#OJEdHl` zv$xm~#O*u6*Yp-rlr~M-zYNVIv<4w=sT>xjdj6cpl^_1s=vZT+@I zO8Iib6c|Bm)Rw-IvGroHRXe|Ij`?*l3(@G=J{RHt!$8yVO~tUkC25BVCK@^op@cOwH7%?x@u@eIR!TC z<=$pCwB6=DU(aZ*tBux*2hNLOB9>8@u+?~vT;DNz{6rJsYO_97G(~?cp!&KV4mIRy z;#UZ0dYE#506y_XS>))zDrVj7IbyVEKkev>Mv|U(G+iob_k1nas9V_Gwal|fQ@~)1 zm|lW9U*P=X53w+Ou>kmG#lBm&e|t9aoD&v2A6Xj@W3$qQ4Op~At{`M|FtARdFP%CU zG=A5bR7*Hy-+TH6DO{XtT3RAlA*Y{&GLBWD)L*0stRXCLJHw|0ss4uiv)EcUc|yC% z2igDVk;nF}F!pL;dKSie`hWB3|Q!0olO^D!ZCMk{~BvRP7^d5~(B964*?4h5gD;PpGtG7mq%a{~tgSP4`Uee$ z+Hx$-D$)s4(zh*_wgt{txAM<(!6j2k=hv?M5LK+HYB(R~>ZhAUi}_cIUn6f`9mvod zjij8zEA`|eE?f0jH)Q%@Xz#o3W?UU*tvipMTGy|k8l0v5^t5<^y#Se2JRdxW@OQ1` zepM@-KOTSsFKNuycd4 zxTu{A)4Zg>lKsBeKBCX#>DQ`pb?Wh*l$0=|8qbDAGI!c2>r(4Y?=X!A?`X*<-k+a8 z-)fe=?5thYR!le>pJ0*IJ-cdq^AcZry}~O0*g4c|9}L50dq38eeJ-XM60JmJ2yo`& za9tg~;4h^@IYO0-L;4Z5znY=sS0vtEOh$3m692oRK60Z)xScG$R6nDtdt01&Pi|ey z*^!zwMSegIcZhN-7FWW`#rx_9IBNT{aoQ}4nw5RopnU!mOX-+lXHJE5ox zi;cV+RLvj<4m@>ALcz>lpx-x01l#xUsrJ|axaxEO)vQYf{}Ux?X^s>4D&n-cT1bbUlrdT92SX!;TtC((q$OJT&ruLO zaV`m)Ud>dKKuU@p=bB>TM|#9sca|@qS>K8=i<3kP!&j-#@T&E7PuY3%>YGoAchU8~P!za(qX|x!u*Z7XF=DQP3MYJ z-uyZbQzgwuC2KBuk9L#0KsJwd))Y;-D056DiNEGBO+#`{BGHWYt} z(m&0&pM+}K)Gi&$F_85B$rj>Fx-eGJz~oYmz_H4O;9)o-vdAX3+~KoM7?ig28o~YSCwv$o!vHD~jfUN)BZ7tk@Y?gn^X==236BqJT7n~Pk zoo-V$y@tf}nQ=7ae{aE(9Q9YoBPQugKTlWxfJvaz-pGq!*x3!5*>Uz-x^VydR#T&5}P+Oj%MprXs0OFw(aTn=CpV8L2nDrF4{- z_Tw^%OK~wa(OI≫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