From 1996cb49fa0dc3dd4d35d7d2876c9ac3698a450b Mon Sep 17 00:00:00 2001 From: losalex <90795544+losalex@users.noreply.github.com> Date: Tue, 2 Nov 2021 12:02:25 -0700 Subject: [PATCH] feat: Extend a set of options in WriteOption to allow defining the log location as custom project, folder, organization or billing account (#727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Extend a set of options in WriteOption to allow defining the log location as custom project, folder, organization or billing account * Fix a test and small refactor in writeLogEntriesRequest * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Make sure we do not propagate project id from request to log entries during write() and fix tests accordingly to cover all cases * Add an empty log entry to be tested with latest destination handling logic * Address PR comments * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Fix lint errors * Address test related PR comments Co-authored-by: Owl Bot --- .../com/google/cloud/logging/Logging.java | 11 +- .../google/cloud/logging/LoggingHandler.java | 50 +++- .../com/google/cloud/logging/LoggingImpl.java | 24 +- .../cloud/logging/LoggingHandlerTest.java | 45 ++++ .../google/cloud/logging/LoggingImplTest.java | 253 ++++++++++++------ .../com/google/cloud/logging/LoggingTest.java | 33 +++ .../cloud/logging/SerializationTest.java | 3 + 7 files changed, 324 insertions(+), 95 deletions(-) diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java index 7f4d24dd6..0e9bfb590 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java @@ -69,7 +69,8 @@ final class WriteOption extends Option { enum OptionType implements Option.OptionType { LOG_NAME, RESOURCE, - LABELS; + LABELS, + LOG_DESTINATION; @SuppressWarnings("unchecked") T get(Map options) { @@ -105,6 +106,14 @@ public static WriteOption resource(MonitoredResource resource) { public static WriteOption labels(Map labels) { return new WriteOption(OptionType.LABELS, ImmutableMap.copyOf(labels)); } + + /** + * Returns an option to specify a log destination resource path (see {@link LogDestinationName} + * for details) + */ + public static WriteOption destination(LogDestinationName destination) { + return new WriteOption(OptionType.LOG_DESTINATION, destination); + } } /** Fields according to which log entries can be sorted. */ diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java index 7702161c9..db18fe1a1 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java @@ -22,7 +22,9 @@ import com.google.cloud.logging.Logging.WriteOption; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -172,7 +174,7 @@ public LoggingHandler(String log, LoggingOptions options) { * a default resource is created based on the project ID and deployment environment. */ public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource) { - this(log, options, monitoredResource, null); + this(log, options, monitoredResource, null, null); } /** @@ -190,6 +192,27 @@ public LoggingHandler( LoggingOptions options, MonitoredResource monitoredResource, List enhancers) { + this(log, options, monitoredResource, enhancers, null); + } + + /** + * Creates a handler that publishes messages to Cloud Logging. + * + * @param log the name of the log to which log entries are written + * @param options options for the Cloud Logging service + * @param monitoredResource the monitored resource to which log entries refer. If it is null then + * a default resource is created based on the project ID and deployment environment. + * @param enhancers List of {@link LoggingEnhancer} instances used to enhance any{@link LogEntry} + * instances built by this handler. + * @param destination the log destination {@link LogDestinationName} (see 'logName' parameter in + * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry) + */ + public LoggingHandler( + String log, + LoggingOptions options, + MonitoredResource monitoredResource, + List enhancers, + LogDestinationName destination) { try { loggingOptions = options != null ? options : LoggingOptions.getDefaultInstance(); LoggingConfig config = new LoggingConfig(getClass().getName()); @@ -204,17 +227,20 @@ public LoggingHandler( MonitoredResource resource = firstNonNull( monitoredResource, config.getMonitoredResource(loggingOptions.getProjectId())); - defaultWriteOptions = - new WriteOption[] { - WriteOption.logName(logName), - WriteOption.resource(resource), - WriteOption.labels( - ImmutableMap.of( - LEVEL_NAME_KEY, - baseLevel.getName(), - LEVEL_VALUE_KEY, - String.valueOf(baseLevel.intValue()))) - }; + List writeOptions = new ArrayList(); + writeOptions.add(WriteOption.logName(logName)); + writeOptions.add(WriteOption.resource(resource)); + writeOptions.add( + WriteOption.labels( + ImmutableMap.of( + LEVEL_NAME_KEY, + baseLevel.getName(), + LEVEL_VALUE_KEY, + String.valueOf(baseLevel.intValue())))); + if (destination != null) { + writeOptions.add(WriteOption.destination(destination)); + } + defaultWriteOptions = Iterables.toArray(writeOptions, WriteOption.class); getLogging().setFlushSeverity(severityFor(flushLevel)); getLogging().setWriteSynchronicity(config.getSynchronicity()); diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java index ce8e60ff7..40fe150ca 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java @@ -21,6 +21,7 @@ import static com.google.cloud.logging.Logging.ListOption.OptionType.PAGE_SIZE; import static com.google.cloud.logging.Logging.ListOption.OptionType.PAGE_TOKEN; import static com.google.cloud.logging.Logging.WriteOption.OptionType.LABELS; +import static com.google.cloud.logging.Logging.WriteOption.OptionType.LOG_DESTINATION; import static com.google.cloud.logging.Logging.WriteOption.OptionType.LOG_NAME; import static com.google.cloud.logging.Logging.WriteOption.OptionType.RESOURCE; @@ -738,11 +739,13 @@ private static WriteLogEntriesRequest writeLogEntriesRequest( LoggingOptions serviceOptions, Iterable logEntries, Map options) { - String projectId = serviceOptions.getProjectId(); WriteLogEntriesRequest.Builder builder = WriteLogEntriesRequest.newBuilder(); - String logName = LOG_NAME.get(options); + String projectId = serviceOptions.getProjectId(); + + LogName logName = getLogName(projectId, LOG_NAME.get(options), LOG_DESTINATION.get(options)); + if (logName != null) { - builder.setLogName(LogName.ofProjectLogName(projectId, logName).toString()); + builder.setLogName(logName.toString()); } MonitoredResource resource = RESOURCE.get(options); if (resource != null) { @@ -752,10 +755,25 @@ private static WriteLogEntriesRequest writeLogEntriesRequest( if (labels != null) { builder.putAllLabels(labels); } + builder.addAllEntries(Iterables.transform(logEntries, LogEntry.toPbFunction(projectId))); return builder.build(); } + private static LogName getLogName( + String projectId, String logName, LogDestinationName destination) { + if (logName == null) { + return null; + } + + // If no destination specified, fallback to project based log name + if (destination == null) { + return LogName.ofProjectLogName(projectId, logName); + } + + return destination.toLogName(logName); + } + public void write(Iterable logEntries, WriteOption... options) { if (inWriteCall.get() != null) { return; diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java index 5d5285cb8..af4be4cce 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java @@ -310,6 +310,28 @@ public void testPublishCustomResource() { handler.publish(newLogRecord(Level.FINEST, MESSAGE)); } + @Test + public void testPublishCustomResourceWithFolder() { + testPublishCustomResourceWithDestination(FINEST_ENTRY, LogDestinationName.folder("folder")); + } + + @Test + public void testPublishCustomResourceWithBilling() { + testPublishCustomResourceWithDestination( + FINEST_ENTRY, LogDestinationName.billingAccount("billing")); + } + + @Test + public void testPublishCustomResourceWithOrganization() { + testPublishCustomResourceWithDestination( + FINEST_ENTRY, LogDestinationName.organization("organization")); + } + + @Test + public void testPublishCustomResourceWithProject() { + testPublishCustomResourceWithDestination(FINEST_ENTRY, LogDestinationName.project(PROJECT)); + } + @Test public void testPublishKubernetesContainerResource() { expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); @@ -582,4 +604,27 @@ public void testClose() throws Exception { handler.close(); handler.close(); } + + private void testPublishCustomResourceWithDestination( + LogEntry entry, LogDestinationName destination) { + expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + expect(options.getService()).andReturn(logging); + logging.setFlushSeverity(Severity.ERROR); + expectLastCall().once(); + logging.setWriteSynchronicity(Synchronicity.ASYNC); + expectLastCall().once(); + MonitoredResource resource = MonitoredResource.of("custom", ImmutableMap.of()); + logging.write( + ImmutableList.of(entry), + WriteOption.logName(LOG_NAME), + WriteOption.resource(resource), + WriteOption.labels(BASE_SEVERITY_MAP), + WriteOption.destination(destination)); + expectLastCall().once(); + replay(options, logging); + Handler handler = new LoggingHandler(LOG_NAME, options, resource, null, destination); + handler.setLevel(Level.ALL); + handler.setFormatter(new TestFormatter()); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); + } } diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java index 5e286f747..4da20a090 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java @@ -91,7 +91,11 @@ public class LoggingImplTest { private static final String PROJECT = "project"; - private static final String PROJECT_PB = "projects/" + PROJECT; + private static final String ANOTHER_PROJECT = "projectoverride"; + private static final String FOLDER = "folder"; + private static final String BILLING = "billing"; + private static final String ORGANIZATION = "organization"; + private static final String PROJECT_PARENT = "projects/" + PROJECT; private static final String SINK_NAME = "sink"; private static final SinkInfo SINK_INFO = SinkInfo.newBuilder(SINK_NAME, Destination.BucketDestination.of("bucket")) @@ -112,21 +116,53 @@ public class LoggingImplTest { private static final String LOG_NAME2 = "test-list-log-name-2"; private static final String LOG_NAMES_CURSOR = "cursor"; private static final String LOG_NAME = "log"; - private static final String LOG_NAME_PB = "projects/" + PROJECT + "/logs/" + LOG_NAME; + private static final String LOG_NAME_PROJECT_PATH = "projects/" + PROJECT + "/logs/" + LOG_NAME; + private static final String LOG_NAME_ANOTHER_PROJECT_PATH = + "projects/" + ANOTHER_PROJECT + "/logs/" + LOG_NAME; + private static final String LOG_NAME_FOLDER_PATH = "folders/" + FOLDER + "/logs/" + LOG_NAME; + private static final String LOG_NAME_BILLING_PATH = + "billingAccounts/" + BILLING + "/logs/" + LOG_NAME; + private static final String LOG_NAME_ORGANIZATION_PATH = + "organizations/" + ORGANIZATION + "/logs/" + LOG_NAME; private static final MonitoredResource MONITORED_RESOURCE = MonitoredResource.newBuilder("global").addLabel("project-id", PROJECT).build(); private static final LogEntry LOG_ENTRY1 = - LogEntry.newBuilder(StringPayload.of("entry1")) + LogEntry.newBuilder(StringPayload.of("entry-1")) .setLogName(LOG_NAME) .setDestination(LogDestinationName.project(PROJECT)) .setResource(MONITORED_RESOURCE) .build(); private static final LogEntry LOG_ENTRY2 = - LogEntry.newBuilder(StringPayload.of("entry2")) + LogEntry.newBuilder(StringPayload.of("entry-2")) .setLogName(LOG_NAME) .setDestination(LogDestinationName.project(PROJECT)) .setResource(MONITORED_RESOURCE) .build(); + private static final LogEntry LOG_ENTRY_BILLING = + LogEntry.newBuilder(StringPayload.of("entry-billing")) + .setLogName(LOG_NAME) + .setDestination(LogDestinationName.billingAccount(BILLING)) + .setResource(MONITORED_RESOURCE) + .build(); + private static final LogEntry LOG_ENTRY_FOLDER = + LogEntry.newBuilder(StringPayload.of("entry-folder")) + .setLogName(LOG_NAME) + .setDestination(LogDestinationName.folder(FOLDER)) + .setResource(MONITORED_RESOURCE) + .build(); + private static final LogEntry LOG_ENTRY_ORGANIZATION = + LogEntry.newBuilder(StringPayload.of("entry-organization")) + .setLogName(LOG_NAME) + .setDestination(LogDestinationName.organization(ORGANIZATION)) + .setResource(MONITORED_RESOURCE) + .build(); + private static final LogEntry LOG_ENTRY_NO_DESTINATION = + LogEntry.newBuilder(StringPayload.of("entry-no-destination")) + .setLogName(LOG_NAME) + .setResource(MONITORED_RESOURCE) + .build(); + private static final LogEntry LOG_ENTRY_EMPTY = + LogEntry.newBuilder(StringPayload.of("entry-empty")).build(); private static final Function SINK_TO_PB_FUNCTION = new Function() { @Override @@ -181,7 +217,7 @@ public com.google.api.MonitoredResourceDescriptor apply( private Logging logging; private void configureListLogsTests(List returnedList, String cursor) { - ListLogsRequest request = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogsRequest request = ListLogsRequest.newBuilder().setParent(PROJECT_PARENT).build(); ListLogsResponse response = ListLogsResponse.newBuilder().setNextPageToken(cursor).addAllLogNames(returnedList).build(); ApiFuture futureResponse = ApiFutures.immediateFuture(response); @@ -194,9 +230,9 @@ private void configureListLogsTests( List page2ReturnedList, String page1Cursor, String page2Cursor) { - ListLogsRequest request1 = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogsRequest request1 = ListLogsRequest.newBuilder().setParent(PROJECT_PARENT).build(); ListLogsRequest request2 = - ListLogsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(page1Cursor).build(); + ListLogsRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(page1Cursor).build(); ListLogsResponse response1 = ListLogsResponse.newBuilder() .setNextPageToken(page1Cursor) @@ -258,7 +294,7 @@ public void testCreateSink() { LogSink sinkPb = SINK_INFO.toPb(PROJECT); ApiFuture response = ApiFutures.immediateFuture(sinkPb); CreateSinkRequest request = - CreateSinkRequest.newBuilder().setParent(PROJECT_PB).setSink(sinkPb).build(); + CreateSinkRequest.newBuilder().setParent(PROJECT_PARENT).setSink(sinkPb).build(); EasyMock.expect(loggingRpcMock.create(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); @@ -271,7 +307,7 @@ public void testCreateSinkAsync() throws ExecutionException, InterruptedExceptio LogSink sinkPb = SINK_INFO.toPb(PROJECT); ApiFuture response = ApiFutures.immediateFuture(sinkPb); CreateSinkRequest request = - CreateSinkRequest.newBuilder().setParent(PROJECT_PB).setSink(sinkPb).build(); + CreateSinkRequest.newBuilder().setParent(PROJECT_PARENT).setSink(sinkPb).build(); EasyMock.expect(loggingRpcMock.create(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); @@ -394,7 +430,7 @@ public void testListSinks() { String cursor = "cursor"; EasyMock.replay(rpcFactoryMock); logging = options.getService(); - ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PB).build(); + ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of( new Sink(logging, new SinkInfo.BuilderImpl(SINK_INFO)), @@ -417,9 +453,9 @@ public void testListSinksNextPage() { String cursor1 = "cursor"; EasyMock.replay(rpcFactoryMock); logging = options.getService(); - ListSinksRequest request1 = ListSinksRequest.newBuilder().setParent(PROJECT_PB).build(); + ListSinksRequest request1 = ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).build(); ListSinksRequest request2 = - ListSinksRequest.newBuilder().setParent(PROJECT_PB).setPageToken(cursor1).build(); + ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(cursor1).build(); List sinkList1 = ImmutableList.of( new Sink(logging, new SinkInfo.BuilderImpl(SINK_INFO)), @@ -453,7 +489,7 @@ public void testListSinksNextPage() { public void testListSinksEmpty() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); - ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PB).build(); + ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of(); ListSinksResponse response = ListSinksResponse.newBuilder() @@ -478,7 +514,7 @@ public void testListSinksWithOptions() { ListSinksRequest.newBuilder() .setPageToken(cursor) .setPageSize(42) - .setParent(PROJECT_PB) + .setParent(PROJECT_PARENT) .build(); List sinkList = ImmutableList.of( @@ -502,7 +538,7 @@ public void testListSinksAsync() throws ExecutionException, InterruptedException String cursor = "cursor"; EasyMock.replay(rpcFactoryMock); logging = options.getService(); - ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PB).build(); + ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of( new Sink(logging, new SinkInfo.BuilderImpl(SINK_INFO)), @@ -525,9 +561,9 @@ public void testListSinksAsyncNextPage() throws ExecutionException, InterruptedE String cursor1 = "cursor"; EasyMock.replay(rpcFactoryMock); logging = options.getService(); - ListSinksRequest request1 = ListSinksRequest.newBuilder().setParent(PROJECT_PB).build(); + ListSinksRequest request1 = ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).build(); ListSinksRequest request2 = - ListSinksRequest.newBuilder().setParent(PROJECT_PB).setPageToken(cursor1).build(); + ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(cursor1).build(); List sinkList1 = ImmutableList.of( new Sink(logging, new SinkInfo.BuilderImpl(SINK_INFO)), @@ -561,7 +597,7 @@ public void testListSinksAsyncNextPage() throws ExecutionException, InterruptedE public void testListSinksAsyncEmpty() throws ExecutionException, InterruptedException { EasyMock.replay(rpcFactoryMock); logging = options.getService(); - ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PB).build(); + ListSinksRequest request = ListSinksRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of(); ListSinksResponse response = ListSinksResponse.newBuilder() @@ -586,7 +622,7 @@ public void testListSinksWithOptionsAsync() throws ExecutionException, Interrupt ListSinksRequest.newBuilder() .setPageToken(cursor) .setPageSize(42) - .setParent(PROJECT_PB) + .setParent(PROJECT_PARENT) .build(); List sinkList = ImmutableList.of( @@ -611,7 +647,7 @@ public void testCreateMetric() { LogMetric metricPb = METRIC_INFO.toPb(); ApiFuture response = ApiFutures.immediateFuture(metricPb); CreateLogMetricRequest request = - CreateLogMetricRequest.newBuilder().setParent(PROJECT_PB).setMetric(metricPb).build(); + CreateLogMetricRequest.newBuilder().setParent(PROJECT_PARENT).setMetric(metricPb).build(); EasyMock.expect(loggingRpcMock.create(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); @@ -624,7 +660,7 @@ public void testCreateMetricAsync() throws ExecutionException, InterruptedExcept LogMetric metricPb = METRIC_INFO.toPb(); ApiFuture response = ApiFutures.immediateFuture(metricPb); CreateLogMetricRequest request = - CreateLogMetricRequest.newBuilder().setParent(PROJECT_PB).setMetric(metricPb).build(); + CreateLogMetricRequest.newBuilder().setParent(PROJECT_PARENT).setMetric(metricPb).build(); EasyMock.expect(loggingRpcMock.create(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); @@ -756,7 +792,7 @@ public void testListMetrics() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListLogMetricsRequest request = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of( new Metric(logging, new MetricInfo.BuilderImpl(METRIC_INFO)), @@ -780,9 +816,9 @@ public void testListMetricsNextPage() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListLogMetricsRequest request1 = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).build(); ListLogMetricsRequest request2 = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(cursor1).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(cursor1).build(); List sinkList1 = ImmutableList.of( new Metric(logging, new MetricInfo.BuilderImpl(METRIC_INFO)), @@ -818,7 +854,7 @@ public void testListMetricsEmpty() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListLogMetricsRequest request = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of(); ListLogMetricsResponse response = ListLogMetricsResponse.newBuilder() @@ -843,7 +879,7 @@ public void testListMetricsWithOptions() { ListLogMetricsRequest.newBuilder() .setPageToken(cursor) .setPageSize(42) - .setParent(PROJECT_PB) + .setParent(PROJECT_PARENT) .build(); List sinkList = ImmutableList.of( @@ -868,7 +904,7 @@ public void testListMetricsAsync() throws ExecutionException, InterruptedExcepti EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListLogMetricsRequest request = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of( new Metric(logging, new MetricInfo.BuilderImpl(METRIC_INFO)), @@ -892,9 +928,9 @@ public void testListMetricsAsyncNextPage() throws ExecutionException, Interrupte EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListLogMetricsRequest request1 = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).build(); ListLogMetricsRequest request2 = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(cursor1).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(cursor1).build(); List sinkList1 = ImmutableList.of( new Metric(logging, new MetricInfo.BuilderImpl(METRIC_INFO)), @@ -930,7 +966,7 @@ public void testListMetricsAsyncEmpty() throws ExecutionException, InterruptedEx EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListLogMetricsRequest request = - ListLogMetricsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogMetricsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List sinkList = ImmutableList.of(); ListLogMetricsResponse response = ListLogMetricsResponse.newBuilder() @@ -955,7 +991,7 @@ public void testListMetricsWithOptionsAsync() throws ExecutionException, Interru ListLogMetricsRequest.newBuilder() .setPageToken(cursor) .setPageSize(42) - .setParent(PROJECT_PB) + .setParent(PROJECT_PARENT) .build(); List sinkList = ImmutableList.of( @@ -980,7 +1016,10 @@ public void testCreateExclusion() { LogExclusion exclusionPb = EXCLUSION.toProtobuf(); ApiFuture response = ApiFutures.immediateFuture(exclusionPb); CreateExclusionRequest request = - CreateExclusionRequest.newBuilder().setParent(PROJECT_PB).setExclusion(exclusionPb).build(); + CreateExclusionRequest.newBuilder() + .setParent(PROJECT_PARENT) + .setExclusion(exclusionPb) + .build(); EasyMock.expect(loggingRpcMock.create(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); @@ -997,7 +1036,10 @@ public void testCreateExclusionAsync() throws ExecutionException, InterruptedExc LogExclusion exclusionPb = EXCLUSION.toProtobuf(); ApiFuture response = ApiFutures.immediateFuture(exclusionPb); CreateExclusionRequest request = - CreateExclusionRequest.newBuilder().setParent(PROJECT_PB).setExclusion(exclusionPb).build(); + CreateExclusionRequest.newBuilder() + .setParent(PROJECT_PARENT) + .setExclusion(exclusionPb) + .build(); EasyMock.expect(loggingRpcMock.create(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); @@ -1156,7 +1198,7 @@ public void testListExclusions() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListExclusionsRequest request = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List exclusionList = ImmutableList.of( Exclusion.of(EXCLUSION_NAME, EXCLUSION_FILTER), @@ -1180,7 +1222,7 @@ public void testListExclusionEmpty() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListExclusionsRequest request = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List exclusionList = ImmutableList.of(); ListExclusionsResponse response = ListExclusionsResponse.newBuilder() @@ -1202,7 +1244,7 @@ public void testListExclusionNextPage() { EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListExclusionsRequest request1 = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List exclusionList1 = ImmutableList.of(Exclusion.of(EXCLUSION_NAME, EXCLUSION_FILTER)); ListExclusionsResponse response1 = @@ -1211,7 +1253,7 @@ public void testListExclusionNextPage() { .addAllExclusions(Lists.transform(exclusionList1, Exclusion.TO_PROTOBUF_FUNCTION)) .build(); ListExclusionsRequest request2 = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(CURSOR).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(CURSOR).build(); List exclusionList2 = ImmutableList.of(Exclusion.of(EXCLUSION_NAME, EXCLUSION_FILTER)); ListExclusionsResponse response2 = @@ -1242,7 +1284,7 @@ public void testListExclusionWithOptions() { ListExclusionsRequest.newBuilder() .setPageToken(CURSOR) .setPageSize(42) - .setParent(PROJECT_PB) + .setParent(PROJECT_PARENT) .build(); List exclusionList = ImmutableList.of( @@ -1268,7 +1310,7 @@ public void testListExclusionsAsync() throws ExecutionException, InterruptedExce EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListExclusionsRequest request = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List exclusionList = ImmutableList.of( Exclusion.of(EXCLUSION_NAME, EXCLUSION_FILTER), @@ -1292,7 +1334,7 @@ public void testListExclusionAsyncEmpty() throws ExecutionException, Interrupted EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListExclusionsRequest request = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List exclusionList = ImmutableList.of(); ListExclusionsResponse response = ListExclusionsResponse.newBuilder() @@ -1314,7 +1356,7 @@ public void testListExclusionAsyncNextPage() throws ExecutionException, Interrup EasyMock.replay(rpcFactoryMock); logging = options.getService(); ListExclusionsRequest request1 = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).build(); List exclusionList1 = ImmutableList.of(Exclusion.of(EXCLUSION_NAME, EXCLUSION_FILTER)); ListExclusionsResponse response1 = @@ -1323,7 +1365,7 @@ public void testListExclusionAsyncNextPage() throws ExecutionException, Interrup .addAllExclusions(Lists.transform(exclusionList1, Exclusion.TO_PROTOBUF_FUNCTION)) .build(); ListExclusionsRequest request2 = - ListExclusionsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(CURSOR).build(); + ListExclusionsRequest.newBuilder().setParent(PROJECT_PARENT).setPageToken(CURSOR).build(); List exclusionList2 = ImmutableList.of(Exclusion.of(EXCLUSION_NAME, EXCLUSION_FILTER)); ListExclusionsResponse response2 = @@ -1354,7 +1396,7 @@ public void testListExclusionAsyncWithOptions() throws ExecutionException, Inter ListExclusionsRequest.newBuilder() .setPageToken(CURSOR) .setPageSize(42) - .setParent(PROJECT_PB) + .setParent(PROJECT_PARENT) .build(); List exclusionList = ImmutableList.of( @@ -1704,7 +1746,8 @@ public void testListLogsAsyncNextPageWithLogNames() @Test public void testDeleteLog() { - DeleteLogRequest request = DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PB).build(); + DeleteLogRequest request = + DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PROJECT_PATH).build(); ApiFuture response = ApiFutures.immediateFuture(Empty.getDefaultInstance()); EasyMock.expect(loggingRpcMock.delete(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); @@ -1714,7 +1757,8 @@ public void testDeleteLog() { @Test public void testDeleteLog_Null() { - DeleteLogRequest request = DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PB).build(); + DeleteLogRequest request = + DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PROJECT_PATH).build(); EasyMock.expect(loggingRpcMock.delete(request)) .andReturn(ApiFutures.immediateFuture(null)); EasyMock.replay(rpcFactoryMock, loggingRpcMock); @@ -1724,7 +1768,8 @@ public void testDeleteLog_Null() { @Test public void testDeleteLogAsync() throws ExecutionException, InterruptedException { - DeleteLogRequest request = DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PB).build(); + DeleteLogRequest request = + DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PROJECT_PATH).build(); ApiFuture response = ApiFutures.immediateFuture(Empty.getDefaultInstance()); EasyMock.expect(loggingRpcMock.delete(request)).andReturn(response); EasyMock.replay(rpcFactoryMock, loggingRpcMock); @@ -1734,7 +1779,8 @@ public void testDeleteLogAsync() throws ExecutionException, InterruptedException @Test public void testDeleteLogAsync_Null() throws ExecutionException, InterruptedException { - DeleteLogRequest request = DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PB).build(); + DeleteLogRequest request = + DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PROJECT_PATH).build(); EasyMock.expect(loggingRpcMock.delete(request)) .andReturn(ApiFutures.immediateFuture(null)); EasyMock.replay(rpcFactoryMock, loggingRpcMock); @@ -1795,25 +1841,32 @@ public void testWriteLogEntriesWithSeverityFlushEnabled() { @Test public void testWriteLogEntriesWithOptions() { - Map labels = ImmutableMap.of("key", "value"); - WriteLogEntriesRequest request = - WriteLogEntriesRequest.newBuilder() - .putAllLabels(labels) - .setLogName(LOG_NAME_PB) - .setResource(MONITORED_RESOURCE.toPb()) - .addAllEntries( - Iterables.transform( - ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), LogEntry.toPbFunction(PROJECT))) - .build(); - WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); - EasyMock.expect(loggingRpcMock.write(request)).andReturn(ApiFutures.immediateFuture(response)); - EasyMock.replay(rpcFactoryMock, loggingRpcMock); - logging = options.getService(); - logging.write( - ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), - WriteOption.logName(LOG_NAME), - WriteOption.resource(MONITORED_RESOURCE), - WriteOption.labels(labels)); + testWriteLogEntriesWithDestination( + PROJECT, LOG_NAME_PROJECT_PATH, LogDestinationName.project(PROJECT)); + } + + @Test + public void testWriteLogEntriesWithDifferentProjectOptions() { + testWriteLogEntriesWithDestination( + PROJECT, LOG_NAME_ANOTHER_PROJECT_PATH, LogDestinationName.project(ANOTHER_PROJECT)); + } + + @Test + public void testWriteLogEntriesWithFolderOptions() { + testWriteLogEntriesWithDestination( + PROJECT, LOG_NAME_FOLDER_PATH, LogDestinationName.folder(FOLDER)); + } + + @Test + public void testWriteLogEntriesWithBillingOptions() { + testWriteLogEntriesWithDestination( + PROJECT, LOG_NAME_BILLING_PATH, LogDestinationName.billingAccount(BILLING)); + } + + @Test + public void testWriteLogEntriesWithOrganizationOptions() { + testWriteLogEntriesWithDestination( + PROJECT, LOG_NAME_ORGANIZATION_PATH, LogDestinationName.organization(ORGANIZATION)); } @Test @@ -1822,13 +1875,16 @@ public void testWriteLogEntriesAsync() { WriteLogEntriesRequest.newBuilder() .addAllEntries( Iterables.transform( - ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), LogEntry.toPbFunction(PROJECT))) + ImmutableList.of( + LOG_ENTRY1, LOG_ENTRY2, LOG_ENTRY_NO_DESTINATION, LOG_ENTRY_EMPTY), + LogEntry.toPbFunction(PROJECT))) .build(); WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); EasyMock.expect(loggingRpcMock.write(request)).andReturn(ApiFutures.immediateFuture(response)); EasyMock.replay(rpcFactoryMock, loggingRpcMock); logging = options.getService(); - logging.write(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2)); + logging.write( + ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2, LOG_ENTRY_NO_DESTINATION, LOG_ENTRY_EMPTY)); logging.flush(); } @@ -1838,7 +1894,7 @@ public void testWriteLogEntriesAsyncWithOptions() { WriteLogEntriesRequest request = WriteLogEntriesRequest.newBuilder() .putAllLabels(labels) - .setLogName(LOG_NAME_PB) + .setLogName(LOG_NAME_PROJECT_PATH) .setResource(MONITORED_RESOURCE.toPb()) .addAllEntries( Iterables.transform( @@ -1852,7 +1908,8 @@ public void testWriteLogEntriesAsyncWithOptions() { ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), WriteOption.logName(LOG_NAME), WriteOption.resource(MONITORED_RESOURCE), - WriteOption.labels(labels)); + WriteOption.labels(labels), + WriteOption.destination(LogDestinationName.project(PROJECT))); logging.flush(); } @@ -1887,12 +1944,12 @@ public void testListLogEntriesNextPage() throws ExecutionException, InterruptedE LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter(); ListLogEntriesRequest request1 = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(defaultTimeFilter) .build(); ListLogEntriesRequest request2 = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(defaultTimeFilter) .setPageToken(cursor1) .build(); @@ -1931,7 +1988,7 @@ public void testListLogEntriesEmpty() { logging = options.getService(); ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter()) .build(); @@ -1956,7 +2013,7 @@ public void testListLogEntriesWithOptions() { logging = options.getService(); ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setOrderBy("timestamp desc") .setFilter( String.format( @@ -1987,7 +2044,7 @@ public void testListLogEntriesAsync() throws ExecutionException, InterruptedExce logging = options.getService(); ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter()) .build(); List entriesList = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); @@ -2011,12 +2068,12 @@ public void testListLogEntriesAsyncNextPage() { logging = options.getService(); ListLogEntriesRequest request1 = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter()) .build(); ListLogEntriesRequest request2 = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter()) .setPageToken(cursor1) .build(); @@ -2055,7 +2112,7 @@ public void testListLogEntriesAsyncEmpty() throws ExecutionException, Interrupte logging = options.getService(); ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setFilter(LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter()) .build(); List entriesList = ImmutableList.of(); @@ -2083,7 +2140,7 @@ public void testListLogEntriesAsyncWithOptions() throws ExecutionException, Inte LoggingImpl.defaultTimestampFilterCreator.createDefaultTimestampFilter()); ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() - .addResourceNames(PROJECT_PB) + .addResourceNames(PROJECT_PARENT) .setOrderBy("timestamp desc") .setFilter(filter) .build(); @@ -2183,4 +2240,42 @@ public void run() { } assertSame(0, exceptions.get()); } + + private void testWriteLogEntriesWithDestination( + String projectId, String fullLogNamePath, LogDestinationName destination) { + Map labels = ImmutableMap.of("key", "value"); + WriteLogEntriesRequest expectedWriteLogEntriesRequest = + WriteLogEntriesRequest.newBuilder() + .putAllLabels(labels) + .setLogName(fullLogNamePath) + .setResource(MONITORED_RESOURCE.toPb()) + .addAllEntries( + Iterables.transform( + ImmutableList.of( + LOG_ENTRY1, + LOG_ENTRY_BILLING, + LOG_ENTRY_FOLDER, + LOG_ENTRY_ORGANIZATION, + LOG_ENTRY_NO_DESTINATION, + LOG_ENTRY_EMPTY), + LogEntry.toPbFunction(projectId))) + .build(); + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + EasyMock.expect(loggingRpcMock.write(expectedWriteLogEntriesRequest)) + .andReturn(ApiFutures.immediateFuture(response)); + EasyMock.replay(rpcFactoryMock, loggingRpcMock); + logging = options.getService(); + logging.write( + ImmutableList.of( + LOG_ENTRY1, + LOG_ENTRY_BILLING, + LOG_ENTRY_FOLDER, + LOG_ENTRY_ORGANIZATION, + LOG_ENTRY_NO_DESTINATION, + LOG_ENTRY_EMPTY), + WriteOption.logName(LOG_NAME), + WriteOption.resource(MONITORED_RESOURCE), + WriteOption.labels(labels), + WriteOption.destination(destination)); + } } diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java index 95a3741df..f3f68c4ad 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java @@ -39,6 +39,10 @@ public class LoggingTest { private static final String LOG_NAME = "logName"; private static final MonitoredResource RESOURCE = MonitoredResource.of("global", ImmutableMap.of("project_id", "p")); + private static final String PROJECT_NAME = "project"; + private static final String FOLDER_NAME = "folder"; + private static final String ORGANIZATION_NAME = "organization"; + private static final String BILLING_NAME = "billing"; @Test public void testListOption() { @@ -97,11 +101,40 @@ public void testWriteOption() { WriteOption writeOption = WriteOption.labels(LABELS); assertEquals(LABELS, writeOption.getValue()); assertEquals(WriteOption.OptionType.LABELS, writeOption.getOptionType()); + writeOption = WriteOption.logName(LOG_NAME); assertEquals(LOG_NAME, writeOption.getValue()); assertEquals(WriteOption.OptionType.LOG_NAME, writeOption.getOptionType()); + writeOption = WriteOption.resource(RESOURCE); assertEquals(RESOURCE, writeOption.getValue()); assertEquals(WriteOption.OptionType.RESOURCE, writeOption.getOptionType()); } + + @Test + public void testWriteOptionWithDestination() { + WriteOption writeOption = WriteOption.destination(LogDestinationName.project(PROJECT_NAME)); + LogDestinationName resource = (LogDestinationName) writeOption.getValue(); + assertEquals(WriteOption.OptionType.LOG_DESTINATION, writeOption.getOptionType()); + assertEquals(LogDestinationName.DestinationType.PROJECT, resource.getOptionType()); + assertEquals(PROJECT_NAME, resource.getValue()); + + writeOption = WriteOption.destination(LogDestinationName.billingAccount(BILLING_NAME)); + resource = (LogDestinationName) writeOption.getValue(); + assertEquals(WriteOption.OptionType.LOG_DESTINATION, writeOption.getOptionType()); + assertEquals(LogDestinationName.DestinationType.BILLINGACCOUNT, resource.getOptionType()); + assertEquals(BILLING_NAME, resource.getValue()); + + writeOption = WriteOption.destination(LogDestinationName.folder(FOLDER_NAME)); + resource = (LogDestinationName) writeOption.getValue(); + assertEquals(WriteOption.OptionType.LOG_DESTINATION, writeOption.getOptionType()); + assertEquals(LogDestinationName.DestinationType.FOLDER, resource.getOptionType()); + assertEquals(FOLDER_NAME, resource.getValue()); + + writeOption = WriteOption.destination(LogDestinationName.organization(ORGANIZATION_NAME)); + resource = (LogDestinationName) writeOption.getValue(); + assertEquals(WriteOption.OptionType.LOG_DESTINATION, writeOption.getOptionType()); + assertEquals(LogDestinationName.DestinationType.ORGANIZATION, resource.getOptionType()); + assertEquals(ORGANIZATION_NAME, resource.getValue()); + } } diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/SerializationTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/SerializationTest.java index e35d37dae..912dee4e2 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/SerializationTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/SerializationTest.java @@ -72,6 +72,8 @@ public class SerializationTest extends BaseSerializationTest { private static final WriteOption LABELS_OPTION = WriteOption.labels(ImmutableMap.of("key", "val")); private static final WriteOption LOG_OPTION = WriteOption.labels(ImmutableMap.of("key", "val")); + private static final WriteOption DESTINATION_OPTION = + WriteOption.destination(LogDestinationName.project("project")); private static final WriteOption RESOURCE_OPTION = WriteOption.resource(MonitoredResource.of("global", ImmutableMap.of("project_id", "p"))); private static final EntryListOption ENTRY_PAGE_TOKEN_OPTION = EntryListOption.pageToken("token"); @@ -108,6 +110,7 @@ protected Serializable[] serializableObjects() { LABELS_OPTION, LOG_OPTION, RESOURCE_OPTION, + DESTINATION_OPTION, ENTRY_PAGE_TOKEN_OPTION, ENTRY_PAGE_SIZE_OPTION, ENTRY_FILTER_OPTION,