diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index 180f6eb1cd..7f594c2d7f 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -223,6 +223,12 @@ ${project.version} runtime + + ${project.groupId} + exporter-stdout + ${project.version} + runtime + ${project.groupId} metrics-stream diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index 427e9b3f99..b0df49d630 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -43,8 +43,9 @@ "io.aklivity.zilla:command-stop", "io.aklivity.zilla:command-tune", "io.aklivity.zilla:engine", - "io.aklivity.zilla:exporter-prometheus", "io.aklivity.zilla:exporter-otlp", + "io.aklivity.zilla:exporter-prometheus", + "io.aklivity.zilla:exporter-stdout", "io.aklivity.zilla:guard-jwt", "io.aklivity.zilla:metrics-stream", "io.aklivity.zilla:metrics-http", diff --git a/incubator/catalog-schema-registry.spec/pom.xml b/incubator/catalog-schema-registry.spec/pom.xml index 6e529c8f30..85398279c4 100644 --- a/incubator/catalog-schema-registry.spec/pom.xml +++ b/incubator/catalog-schema-registry.spec/pom.xml @@ -63,6 +63,23 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core schema_registry + io.aklivity.zilla.specs.catalog.schema.registry.internal.types + + + + + validate + generate + + + + com.mycila license-maven-plugin @@ -86,6 +103,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/specs/catalog/schema/registry/internal/types/**/*.class + BUNDLE diff --git a/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl new file mode 100644 index 0000000000..df61e3605c --- /dev/null +++ b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +scope schema_registry +{ + scope event + { + enum SchemaRegistryEventType (uint8) + { + REMOTE_ACCESS_REJECTED (1) + } + + struct SchemaRegistryRemoteAccessRejected extends core::event::Event + { + string8 method; + string16 url; + int16 status; + } + + union SchemaRegistryEvent switch (SchemaRegistryEventType) + { + case REMOTE_ACCESS_REJECTED: SchemaRegistryRemoteAccessRejected remoteAccessRejected; + } + } +} diff --git a/incubator/catalog-schema-registry/pom.xml b/incubator/catalog-schema-registry/pom.xml index 74a7b83a1a..22c39b3d02 100644 --- a/incubator/catalog-schema-registry/pom.xml +++ b/incubator/catalog-schema-registry/pom.xml @@ -24,7 +24,7 @@ 11 11 - 0.90 + 0.80 0 @@ -71,16 +71,12 @@ org.jasig.maven maven-notice-plugin - - com.mycila - license-maven-plugin - ${project.groupId} flyweight-maven-plugin ${project.version} - internal + core schema_registry internal io.aklivity.zilla.runtime.catalog.schema.registry.internal.types @@ -91,6 +87,10 @@ + + com.mycila + license-maven-plugin + maven-checkstyle-plugin diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java index 8affce6dc5..c7ebe83402 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java @@ -40,7 +40,7 @@ public String name() public CatalogContext supply( EngineContext context) { - return new SchemaRegistryCatalogContext(); + return new SchemaRegistryCatalogContext(context); } @Override diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java index cf5c8eb7b6..1dbfd3c22f 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java @@ -15,16 +15,25 @@ package io.aklivity.zilla.runtime.catalog.schema.registry.internal; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.config.CatalogConfig; public class SchemaRegistryCatalogContext implements CatalogContext { + private final EngineContext context; + + public SchemaRegistryCatalogContext( + EngineContext context) + { + this.context = context; + } + @Override public CatalogHandler attach( CatalogConfig catalog) { - return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options)); + return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options), context, catalog.id); } } diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java index c8bc750709..88f47fd777 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java @@ -30,6 +30,7 @@ import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.serializer.RegisterSchemaRequest; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.SchemaRegistryPrefixFW; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer; @@ -51,9 +52,13 @@ public class SchemaRegistryCatalogHandler implements CatalogHandler private final Int2ObjectCache schemas; private final Int2ObjectCache schemaIds; private final long maxAgeMillis; + private final SchemaRegistryEventContext event; + private final long catalogId; public SchemaRegistryCatalogHandler( - SchemaRegistryOptionsConfig config) + SchemaRegistryOptionsConfig config, + EngineContext context, + long catalogId) { this.baseUrl = config.url; this.client = HttpClient.newHttpClient(); @@ -62,6 +67,8 @@ public SchemaRegistryCatalogHandler( this.schemas = new Int2ObjectCache<>(1, 1024, i -> {}); this.schemaIds = new Int2ObjectCache<>(1, 1024, i -> {}); this.maxAgeMillis = config.maxAge.toMillis(); + this.event = new SchemaRegistryEventContext(context); + this.catalogId = catalogId; } @Override @@ -209,12 +216,18 @@ private String sendHttpRequest( try { - HttpResponse response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - return response.statusCode() == 200 ? response.body() : null; + HttpResponse httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + boolean success = httpResponse.statusCode() == 200; + String responseBody = success ? httpResponse.body() : null; + if (!success) + { + event.remoteAccessRejected(catalogId, httpRequest, httpResponse.statusCode()); + } + return responseBody; } catch (Exception ex) { - ex.printStackTrace(System.out); + event.remoteAccessRejected(catalogId, httpRequest, 0); } return null; } diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java new file mode 100644 index 0000000000..42770a31b1 --- /dev/null +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.catalog.schema.registry.internal; + +import java.net.http.HttpRequest; +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.event.SchemaRegistryEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class SchemaRegistryEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final SchemaRegistryEventFW.Builder schemaRegistryEventRW = new SchemaRegistryEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int schemaRegistryTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public SchemaRegistryEventContext( + EngineContext context) + { + this.schemaRegistryTypeId = context.supplyTypeId(SchemaRegistryCatalog.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void remoteAccessRejected( + long catalogId, + HttpRequest httpRequest, + int status) + { + SchemaRegistryEventFW event = schemaRegistryEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .remoteAccessRejected(e -> e + .timestamp(clock.millis()) + .traceId(0L) + .namespacedId(catalogId) + .method(httpRequest.method()) + .url(httpRequest.uri().toString()) + .status((short) status) + ) + .build(); + eventWriter.accept(schemaRegistryTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java index f65af539c1..f04ed60d0b 100644 --- a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java +++ b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.rules.RuleChain.outerRule; +import static org.mockito.Mockito.mock; import java.time.Duration; @@ -35,6 +36,7 @@ import org.kaazing.k3po.junit.rules.K3poRule; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer; @@ -49,6 +51,7 @@ public class SchemaRegistryIT public final TestRule chain = outerRule(k3po).around(timeout); private SchemaRegistryOptionsConfig config; + private EngineContext context = mock(EngineContext.class); @Before public void setup() @@ -69,7 +72,7 @@ public void shouldResolveSchemaViaSchemaId() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); String schema = catalog.resolve(9); @@ -88,7 +91,7 @@ public void shouldResolveSchemaViaSubjectVersion() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); int schemaId = catalog.resolve("items-snapshots-value", "latest"); @@ -109,7 +112,7 @@ public void shouldRegisterSchema() throws Exception String schema = "{\"type\": \"record\",\"name\": \"test\",\"fields\":[{\"type\": \"string\",\"name\": \"field1\"}," + "{\"type\": \"com.acme.Referenced\",\"name\": \"int\"}]}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); int schemaId = catalog.register("items-snapshots-value", "avro", schema); @@ -128,7 +131,7 @@ public void shouldResolveSchemaViaSchemaIdFromCache() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); catalog.resolve(9); @@ -149,7 +152,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); catalog.resolve(catalog.resolve("items-snapshots-value", "latest")); @@ -167,7 +170,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception @Test public void shouldVerifyMaxPadding() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); assertEquals(5, catalog.encodePadding()); } @@ -175,7 +178,7 @@ public void shouldVerifyMaxPadding() @Test public void shouldVerifyEncodedData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); @@ -191,7 +194,7 @@ public void shouldVerifyEncodedData() public void shouldResolveSchemaIdAndProcessData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); @@ -207,7 +210,7 @@ public void shouldResolveSchemaIdAndProcessData() @Test public void shouldResolveSchemaIdFromData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); diff --git a/incubator/exporter-stdout.spec/COPYRIGHT b/incubator/exporter-stdout.spec/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/exporter-stdout.spec/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/exporter-stdout.spec/LICENSE b/incubator/exporter-stdout.spec/LICENSE new file mode 100644 index 0000000000..f3cf11c3ad --- /dev/null +++ b/incubator/exporter-stdout.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/exporter-stdout.spec/NOTICE b/incubator/exporter-stdout.spec/NOTICE new file mode 100644 index 0000000000..cf99769e41 --- /dev/null +++ b/incubator/exporter-stdout.spec/NOTICE @@ -0,0 +1,18 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + diff --git a/incubator/exporter-stdout.spec/NOTICE.template b/incubator/exporter-stdout.spec/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/exporter-stdout.spec/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/exporter-stdout.spec/mvnw b/incubator/exporter-stdout.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/exporter-stdout.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/exporter-stdout.spec/mvnw.cmd b/incubator/exporter-stdout.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/exporter-stdout.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/exporter-stdout.spec/pom.xml b/incubator/exporter-stdout.spec/pom.xml new file mode 100644 index 0000000000..1b07db88bf --- /dev/null +++ b/incubator/exporter-stdout.spec/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + io.aklivity.zilla + specs + develop-SNAPSHOT + ../pom.xml + + + exporter-stdout.spec + zilla::incubator::exporter-stdout.spec + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 1.00 + 0 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + test + + + ${project.groupId} + binding-proxy.spec + ${project.version} + test + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core + io.aklivity.zilla.specs.exporter.stdout.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + **/keys + **/trust + **/signers + + + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/exporter/stdout/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/exporter-stdout.spec/src/main/moditect/module-info.java b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..9b8473aaae --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +open module io.aklivity.zilla.specs.exporter.stdout +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml new file mode 100644 index 0000000000..a6daa035f9 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml @@ -0,0 +1,53 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +guards: + jwt0: + type: jwt + options: + issuer: https://auth.example.com + audience: https://api.example.com + keys: + - kty: RSA + n: qqEu50hX+43Bx4W1UYWnAVKwFm+vDbP0kuIOSLVNa+HKQdHTf+3Sei5UCnkskn796izA29D0DdCy3ET9oaKRHIJyKbqFl0rv6f516QzOoXKC6N01sXBHBE/ovs0wwDvlaW+gFGPgkzdcfUlyrWLDnLV7LcuQymhTND2uH0oR3wJnNENN/OFgM1KGPPDOe19YsIKdLqARgxrhZVsh06OurEviZTXOBFI5r+yac7haDwOQhLHXNv+Y9MNvxs5QLWPFIM3bNUWfYrJnLrs4hGJS+y/KDM9Si+HL30QAFXy4YNO33J8DHjZ7ddG5n8/FqplOKvRtUgjcKWlxoGY4VdVaDQ== + e: AQAB + alg: RS256 + kid: example +bindings: + net0: + type: http + kind: server + options: + versions: + - http/1.1 + authorization: + jwt0: + credentials: + cookies: + access_token: "{credentials}" + headers: + authorization: Bearer {credentials} + query: + access_token: "{credentials}" + routes: + - exit: app0 + guarded: + jwt0: [] diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml new file mode 100644 index 0000000000..7202d45307 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + net0: + type: http + kind: server + options: + versions: + - http/1.1 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml new file mode 100644 index 0000000000..effa3e4a6f --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + net0: + type: http + kind: server + options: + versions: + - h2 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml new file mode 100644 index 0000000000..ff4c4a3aea --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + app0: + type: kafka + kind: client + exit: net0 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml new file mode 100644 index 0000000000..04191ddc52 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml @@ -0,0 +1,28 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + app0: + type: tcp + kind: client + options: + host: localhost + port: 8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml new file mode 100644 index 0000000000..fd7e4b90a0 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml @@ -0,0 +1,38 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +vaults: + server: + type: filesystem + options: + keys: + store: stores/server/keys + type: pkcs12 + password: generated +bindings: + net0: + type: tls + kind: server + vault: server + options: + keys: + - localhost + exit: app0 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore new file mode 100644 index 0000000000..507484f3e9 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore @@ -0,0 +1,2 @@ +*.crt +*.csr diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys new file mode 100644 index 0000000000..d2fa3da9b9 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers new file mode 100644 index 0000000000..58e72c4460 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust new file mode 100644 index 0000000000..bb624ca8c8 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys new file mode 100644 index 0000000000..e10b65832b Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers new file mode 100644 index 0000000000..0c8e698ab1 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust new file mode 100644 index 0000000000..377ed0c4c4 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json new file mode 100644 index 0000000000..f3f7450945 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json @@ -0,0 +1,34 @@ +[ + { + "op": "add", + "path": "/$defs/telemetry/exporter/properties/type/enum/-", + "value": "stdout" + }, + { + "op": "add", + "path": "/$defs/telemetry/exporter/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "stdout" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "stdout" + } + } + } + } + } +] diff --git a/incubator/exporter-stdout/COPYRIGHT b/incubator/exporter-stdout/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/exporter-stdout/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/exporter-stdout/LICENSE b/incubator/exporter-stdout/LICENSE new file mode 100644 index 0000000000..f3cf11c3ad --- /dev/null +++ b/incubator/exporter-stdout/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/exporter-stdout/NOTICE b/incubator/exporter-stdout/NOTICE new file mode 100644 index 0000000000..9024d8926d --- /dev/null +++ b/incubator/exporter-stdout/NOTICE @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + diff --git a/incubator/exporter-stdout/NOTICE.template b/incubator/exporter-stdout/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/exporter-stdout/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/exporter-stdout/mvnw b/incubator/exporter-stdout/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/exporter-stdout/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/exporter-stdout/mvnw.cmd b/incubator/exporter-stdout/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/exporter-stdout/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/exporter-stdout/pom.xml b/incubator/exporter-stdout/pom.xml new file mode 100644 index 0000000000..58bf155c90 --- /dev/null +++ b/incubator/exporter-stdout/pom.xml @@ -0,0 +1,340 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + exporter-stdout + zilla::incubator::exporter-stdout + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.75 + 0 + + + + + ${project.groupId} + exporter-stdout.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + ${project.groupId} + binding-http.spec + ${project.version} + provided + + + ${project.groupId} + binding-kafka.spec + ${project.version} + provided + + + ${project.groupId} + binding-tcp.spec + ${project.version} + provided + + + ${project.groupId} + binding-tls.spec + ${project.version} + provided + + + ${project.groupId} + catalog-schema-registry.spec + ${project.version} + provided + + + ${project.groupId} + guard-jwt.spec + ${project.version} + provided + + + ${project.groupId} + vault-filesystem.spec + ${project.version} + provided + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + ${project.groupId} + binding-http + ${project.version} + test + + + ${project.groupId} + binding-kafka + ${project.version} + test + + + ${project.groupId} + binding-tcp + ${project.version} + test + + + ${project.groupId} + binding-tls + ${project.version} + test + + + ${project.groupId} + catalog-schema-registry + ${project.version} + test + + + ${project.groupId} + guard-jwt + ${project.version} + test + + + ${project.groupId} + model-json + ${project.version} + test + + + ${project.groupId} + vault-filesystem + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http jwt kafka schema_registry tcp tls + io.aklivity.zilla.runtime.exporter.stdout.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + **/org.mockito.plugins.MockMaker + + + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + exporter-stdout.spec + + + ^\Qio/aklivity/zilla/specs/exporter/stdout/\E + io/aklivity/zilla/runtime/exporter/stdout/internal/ + + + + + io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json + ${project.build.directory}/classes + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/exporter/stdout/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java new file mode 100644 index 0000000000..bbc9cfb038 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.io.PrintStream; +import java.lang.reflect.Field; + +import org.agrona.LangUtil; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class StdoutConfiguration extends Configuration +{ + private static final ConfigurationDef STDOUT_CONFIG; + + public static final PropertyDef STDOUT_OUTPUT; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.exporter.stdout"); + STDOUT_OUTPUT = config.property(PrintStream.class, "output", + StdoutConfiguration::decodeOutput, c -> System.out); + STDOUT_CONFIG = config; + } + + public StdoutConfiguration( + Configuration config) + { + super(STDOUT_CONFIG, config); + } + + public PrintStream output() + { + return STDOUT_OUTPUT.get(this); + } + + private static PrintStream decodeOutput( + Configuration config, + String value) + { + try + { + int fieldAt = value.lastIndexOf("."); + Class ownerClass = Class.forName(value.substring(0, fieldAt)); + String fieldName = value.substring(fieldAt + 1); + Field field = ownerClass.getDeclaredField(fieldName); + return (PrintStream) field.get(null); + } + catch (Throwable ex) + { + LangUtil.rethrowUnchecked(ex); + } + return null; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java new file mode 100644 index 0000000000..3e606c2d64 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; + +public class StdoutExporter implements Exporter +{ + public static final String NAME = "stdout"; + + private final StdoutConfiguration config; + + public StdoutExporter( + StdoutConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/stdout.schema.patch.json"); + } + + @Override + public ExporterContext supply( + EngineContext context) + { + return new StdoutExporterContext(config, context); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java new file mode 100644 index 0000000000..bb8d7d3adb --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.util.List; +import java.util.function.LongFunction; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; +import io.aklivity.zilla.runtime.engine.config.AttributeConfig; +import io.aklivity.zilla.runtime.engine.config.ExporterConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler; +import io.aklivity.zilla.runtime.engine.metrics.Collector; +import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig; + +public class StdoutExporterContext implements ExporterContext +{ + private final StdoutConfiguration config; + private final EngineContext context; + + public StdoutExporterContext( + StdoutConfiguration config, + EngineContext context) + { + this.config = config; + this.context = context; + } + + @Override + public ExporterHandler attach( + ExporterConfig exporter, + List attributes, + Collector collector, + LongFunction resolveKind) + { + StdoutExporterConfig stdoutExporter = new StdoutExporterConfig(exporter); + return new StdoutExporterHandler(config, context, stdoutExporter); + } + + @Override + public void detach( + long exporterId) + { + } + + public String supplyQName( + long namespacedId) + { + return context.supplyQName(namespacedId); + } + + public int supplyTypeId( + String name) + { + return context.supplyTypeId(name); + } + + public MessageReader supplyEventReader() + { + return context.supplyEventReader(); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java new file mode 100644 index 0000000000..a22c55f81f --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi; + +public class StdoutExporterFactorySpi implements ExporterFactorySpi +{ + @Override + public String type() + { + return StdoutExporter.NAME; + } + + @Override + public Exporter create( + Configuration config) + { + return new StdoutExporter(new StdoutConfiguration(config)); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java new file mode 100644 index 0000000000..98a0ed322e --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.io.PrintStream; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler; +import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig; +import io.aklivity.zilla.runtime.exporter.stdout.internal.stream.StdoutEventsStream; + +public class StdoutExporterHandler implements ExporterHandler +{ + private final StdoutExporterContext context; + private final PrintStream out; + + private StdoutEventsStream events; + + public StdoutExporterHandler( + StdoutConfiguration config, + EngineContext context, + StdoutExporterConfig exporter) + { + this.context = new StdoutExporterContext(config, context); + this.out = config.output(); + } + + @Override + public void start() + { + events = new StdoutEventsStream(context, out); + } + + @Override + public int export() + { + return events.process(); + } + + @Override + public void stop() + { + this.events = null; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java new file mode 100644 index 0000000000..8f9f7ca38a --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import io.aklivity.zilla.runtime.engine.config.ExporterConfig; + +public class StdoutExporterConfig +{ + private final StdoutOptionsConfig options; + + public StdoutExporterConfig( + ExporterConfig exporter) + { + this.options = (StdoutOptionsConfig)exporter.options; + } + + public StdoutOptionsConfig options() + { + return options; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java new file mode 100644 index 0000000000..43f11bb071 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public class StdoutOptionsConfig extends OptionsConfig +{ +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java new file mode 100644 index 0000000000..fb2bbf8068 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi.Kind.EXPORTER; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporter; + +public class StdoutOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + @Override + public Kind kind() + { + return EXPORTER; + } + + @Override + public String type() + { + return StdoutExporter.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + JsonObjectBuilder object = Json.createObjectBuilder(); + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + return new StdoutOptionsConfig(); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java new file mode 100644 index 0000000000..f8f2a84e8b --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.StringFW; + +public abstract class EventHandler +{ + protected static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z"); + + protected final StdoutExporterContext context; + protected final PrintStream out; + + public EventHandler( + StdoutExporterContext context, + PrintStream out) + { + this.context = context; + this.out = out; + } + + public abstract void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length); + + protected static String asString( + StringFW stringFW) + { + String s = stringFW.asString(); + return s == null ? "" : s; + } + + protected static String identity( + StringFW identity) + { + int length = identity.length(); + return length <= 0 ? "-" : identity.asString(); + } + + protected static String asDateTime( + long timestamp) + { + Instant instant = Instant.ofEpochMilli(timestamp); + OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(instant, ZoneId.systemDefault()); + return offsetDateTime.format(FORMATTER); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java new file mode 100644 index 0000000000..c6e330f377 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; +import org.agrona.collections.Int2ObjectHashMap; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; + +public class StdoutEventsStream +{ + private final MessageReader readEvent; + private final Int2ObjectHashMap eventHandlers; + + public StdoutEventsStream( + StdoutExporterContext context, + PrintStream out) + { + this.readEvent = context.supplyEventReader(); + + final Int2ObjectHashMap eventHandlers = new Int2ObjectHashMap<>(); + eventHandlers.put(context.supplyTypeId("http"), new StdoutHttpHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("jwt"), new StdoutJwtHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("kafka"), new StdoutKafkaHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("schema-registry"), + new StdoutSchemaRegistryHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("tcp"), new StdoutTcpHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("tls"), new StdoutTlsHandler(context, out)::handleEvent); + this.eventHandlers = eventHandlers; + } + + public int process() + { + return readEvent.read(this::handleEvent, 1); + } + + private void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final MessageConsumer handler = eventHandlers.get(msgTypeId); + if (handler != null) + { + handler.accept(msgTypeId, buffer, index, length); + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java new file mode 100644 index 0000000000..86dabe799b --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpEventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpRequestAcceptedFW; + +public class StdoutHttpHandler extends EventHandler +{ + private static final String REQUEST_ACCEPTED_FORMAT = "%s %s [%s] REQUEST_ACCEPTED %s %s %s %s%n"; + + private final HttpEventFW httpEventRO = new HttpEventFW(); + + public StdoutHttpHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final HttpEventFW event = httpEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case REQUEST_ACCEPTED: + { + HttpRequestAcceptedFW e = event.requestAccepted(); + String qname = context.supplyQName(e.namespacedId()); + out.format(REQUEST_ACCEPTED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp()), asString(e.scheme()), + asString(e.method()), asString(e.authority()), asString(e.path())); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java new file mode 100644 index 0000000000..7acf1ee0fe --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtAuthorizationFailedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtEventFW; + +public class StdoutJwtHandler extends EventHandler +{ + private static final String AUTHORIZATION_FAILED_FORMAT = "%s %s [%s] AUTHORIZATION_FAILED%n"; + + private final JwtEventFW jwtEventRO = new JwtEventFW(); + + public StdoutJwtHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + JwtEventFW event = jwtEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case AUTHORIZATION_FAILED: + JwtAuthorizationFailedFW e = event.authorizationFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(AUTHORIZATION_FAILED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp())); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java new file mode 100644 index 0000000000..bae2026051 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaApiVersionRejectedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaEventFW; + +public class StdoutKafkaHandler extends EventHandler +{ + private static final String AUTHORIZATION_FAILED_FORMAT = "%s - [%s] AUTHORIZATION_FAILED%n"; + private static final String API_VERSION_REJECTED_FORMAT = "%s - [%s] API_VERSION_REJECTED %d %d%n"; + + private final KafkaEventFW kafkaEventRO = new KafkaEventFW(); + + public StdoutKafkaHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final KafkaEventFW event = kafkaEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case AUTHORIZATION_FAILED: + { + EventFW e = event.authorizationFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(AUTHORIZATION_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case API_VERSION_REJECTED: + { + KafkaApiVersionRejectedFW e = event.apiVersionRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(API_VERSION_REJECTED_FORMAT, qname, asDateTime(e.timestamp()), e.apiKey(), e.apiVersion()); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java new file mode 100644 index 0000000000..376826b0fd --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryEventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryRemoteAccessRejectedFW; + +public class StdoutSchemaRegistryHandler extends EventHandler +{ + private static final String REMOTE_ACCESS_REJECTED = "%s - [%s] REMOTE_ACCESS_REJECTED %s %s %d%n"; + + private final SchemaRegistryEventFW schemaRegistryEventRO = new SchemaRegistryEventFW(); + + public StdoutSchemaRegistryHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + SchemaRegistryEventFW event = schemaRegistryEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case REMOTE_ACCESS_REJECTED: + SchemaRegistryRemoteAccessRejectedFW e = event.remoteAccessRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(REMOTE_ACCESS_REJECTED, qname, asDateTime(e.timestamp()), asString(e.method()), asString(e.url()), + e.status()); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java new file mode 100644 index 0000000000..0584a4eddf --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpDnsFailedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpEventFW; + +public class StdoutTcpHandler extends EventHandler +{ + private static final String DNS_FAILED_FORMAT = "%s - [%s] DNS_FAILED %s%n"; + + private final TcpEventFW tcpEventRO = new TcpEventFW(); + + public StdoutTcpHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final TcpEventFW event = tcpEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case DNS_FAILED: + TcpDnsFailedFW e = event.dnsFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(DNS_FAILED_FORMAT, qname, asDateTime(e.timestamp()), asString(e.address())); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java new file mode 100644 index 0000000000..a3ecce6abf --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TlsEventFW; + +public class StdoutTlsHandler extends EventHandler +{ + private static final String TLS_FAILED_FORMAT = "%s - [%s] TLS_FAILED%n"; + private static final String PROTOCOL_REJECTED_FORMAT = "%s - [%s] PROTOCOL_REJECTED%n"; + private static final String KEY_REJECTED_FORMAT = "%s - [%s] KEY_REJECTED%n"; + private static final String PEER_NOT_VERIFIED_FORMAT = "%s - [%s] PEER_NOT_VERIFIED%n"; + private static final String HANDSHAKE_FAILED_FORMAT = "%s - [%s] HANDSHAKE_FAILED%n"; + + private final TlsEventFW tlsEventRO = new TlsEventFW(); + + public StdoutTlsHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + TlsEventFW event = tlsEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case TLS_FAILED: + { + EventFW e = event.tlsFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(TLS_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_PROTOCOL_REJECTED: + { + EventFW e = event.tlsProtocolRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(PROTOCOL_REJECTED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_KEY_REJECTED: + { + EventFW e = event.tlsKeyRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(KEY_REJECTED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_PEER_NOT_VERIFIED: + { + EventFW e = event.tlsPeerNotVerified(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(PEER_NOT_VERIFIED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_HANDSHAKE_FAILED: + { + EventFW e = event.tlsHandshakeFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(HANDSHAKE_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/moditect/module-info.java b/incubator/exporter-stdout/src/main/moditect/module-info.java new file mode 100644 index 0000000000..c89775c1c8 --- /dev/null +++ b/incubator/exporter-stdout/src/main/moditect/module-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.exporter.stdout +{ + requires io.aklivity.zilla.runtime.engine; + + provides io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi + with io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter; +} diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..c0bfc37413 --- /dev/null +++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter \ No newline at end of file diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi new file mode 100644 index 0000000000..8f575d444b --- /dev/null +++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi \ No newline at end of file diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java new file mode 100644 index 0000000000..3389e46b3a --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; + +import java.net.URL; + +import org.junit.Test; + +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterFactory; + +public final class StdoutExporterFactoryTest +{ + @Test + public void shouldLoadAndCreate() + { + // GIVEN + Configuration config = new Configuration(); + ExporterFactory factory = ExporterFactory.instantiate(); + + // WHEN + Exporter exporter = factory.create("stdout", config); + ExporterContext context = exporter.supply(mock(EngineContext.class)); + + // THEN + assertThat(exporter, instanceOf(StdoutExporter.class)); + assertThat(exporter.name(), equalTo("stdout")); + assertThat(exporter.type(), instanceOf(URL.class)); + assertThat(context, instanceOf(ExporterContext.class)); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..a0fdab3dbe --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class StdoutOptionsConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new StdoutOptionsConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadOptions() + { + // GIVEN + String text = "{}"; + + // WHEN + StdoutOptionsConfig options = jsonb.fromJson(text, StdoutOptionsConfig.class); + + // THEN + assertThat(options, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + // GIVEN + String expectedText = "{}"; + StdoutOptionsConfig config = new StdoutOptionsConfig(); + + // WHEN + String text = jsonb.toJson(config); + + // THEN + assertThat(text, not(nullValue())); + assertThat(text, equalTo(expectedText)); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java new file mode 100644 index 0000000000..c942d6e141 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_BUFFER_SLOT_CAPACITY; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class Http11EventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/message.format") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/message.format"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_BUFFER_SLOT_CAPACITY, 8192) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/request.with.headers/client", + "${app}/request.with.headers/server" }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void requestWithHeaders() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java new file mode 100644 index 0000000000..e858786c85 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_CONCURRENT_STREAMS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class Http2EventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/message.format") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/message.format"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(HTTP_CONCURRENT_STREAMS, 100) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v2") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/connection.headers/client", + "${app}/connection.headers/server" }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void connectionHeaders() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java new file mode 100644 index 0000000000..41fa36d119 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_SERVER_HEADER; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class JwtEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/authorization"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(HTTP_SERVER_HEADER, "Zilla") + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.authorization.credentials.yaml") + @Specification({ + "${net}/reject.credentials.header/client", + }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldRejectCredentialsHeader() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 user \\[[^\\]]+\\] AUTHORIZATION_FAILED\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java new file mode 100644 index 0000000000..e1e8c9fda7 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class KafkaEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/kafka/streams/network/group.f1.j5.s3.l3.h3") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/kafka/streams/application/group"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(15, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure("zilla.binding.kafka.client.instance.id", + "io.aklivity.zilla.runtime.exporter.stdout.internal.events.KafkaEventsIT::supplyInstanceId") + .configurationRoot("io/aklivity/zilla/specs/binding/kafka/config") + .external("net0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${app}/invalid.describe.config/client", + "${net}/invalid.describe.config/server"}) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldHandleInvalidDescribeConfig() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] API_VERSION_REJECTED 32 0\n")); + } + + public static String supplyInstanceId() + { + return "zilla"; + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java new file mode 100644 index 0000000000..be79fd3ba8 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public final class StdoutOutputRule implements TestRule +{ + public static final PrintStream OUT; + + private static final ByteArrayOutputStream BOS; + + static + { + BOS = new ByteArrayOutputStream(); + OUT = new PrintStream(BOS); + } + + private Pattern expected; + + @Override + public Statement apply( + Statement base, + Description description) + { + return new Statement() + { + @Override + public void evaluate() throws Throwable + { + BOS.reset(); + base.evaluate(); + assertThat(BOS.toString(StandardCharsets.UTF_8), matchesPattern(expected)); + } + }; + } + + public void expect( + Pattern expected) + { + this.expected = expected; + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java new file mode 100644 index 0000000000..989f89eb83 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class TcpEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tcp/streams/network/rfc793") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tcp/streams/application/rfc793"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/tcp/config") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.host.yaml") + @Specification({ + "${app}/connection.failed/client" + }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + @Configure(name = "zilla.engine.host.resolver", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.TcpEventsIT::resolveHost") + public void dnsResolutionFailed() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] DNS_FAILED localhost\n")); + } + + public static InetAddress[] resolveHost( + String host) throws UnknownHostException + { + throw new UnknownHostException(); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java new file mode 100644 index 0000000000..cb201e110c --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class TlsEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tls/streams/network") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tls/streams/application"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/tls/config") + .external("app0") + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/client.hello.malformed/client"}) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldResetMalformedClientHello() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] TLS_FAILED\n")); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/server.handshake.timeout/client"}) + @Configure(name = "zilla.binding.tls.handshake.timeout", value = "1") + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldTimeoutHandshake() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] HANDSHAKE_FAILED\n")); + } +} diff --git a/incubator/pom.xml b/incubator/pom.xml index faa3f73b59..7f57daef52 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -21,6 +21,7 @@ catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec + exporter-stdout.spec model-avro.spec model-core.spec model-json.spec @@ -37,6 +38,7 @@ command-tune exporter-otlp + exporter-stdout model-avro model-core @@ -86,6 +88,11 @@ exporter-otlp ${project.version} + + ${project.groupId} + exporter-stdout + ${project.version} + ${project.groupId} model-avro diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java new file mode 100644 index 0000000000..f4c0ee0fc1 --- /dev/null +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.binding.http.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; +import java.util.Map; + +import org.agrona.concurrent.AtomicBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.http.internal.types.Array32FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.http.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.event.HttpEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.guard.GuardHandler; + +public class HttpEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 2048; + private static final String8FW HEADER_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_METHOD = new String8FW(":method"); + private static final String8FW HEADER_AUTHORITY = new String8FW(":authority"); + private static final String8FW HEADER_PATH = new String8FW(":path"); + + private final AtomicBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final HttpEventFW.Builder httpEventRW = new HttpEventFW.Builder(); + private final int httpTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public HttpEventContext( + EngineContext context) + { + this.httpTypeId = context.supplyTypeId(HttpBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void requestAccepted( + long traceId, + long bindingId, + GuardHandler guard, + long authorization, + Map headers) + { + String identity = guard == null ? null : guard.identity(authorization); + HttpEventFW event = httpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .requestAccepted(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + .scheme(headers.get(":scheme")) + .method(headers.get(":method")) + .authority(headers.get(":authority")) + .path(headers.get(":path")) + ) + .build(); + eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void requestAccepted( + long traceId, + long bindingId, + GuardHandler guard, + long authorization, + Array32FW headers) + { + String identity = guard == null ? null : guard.identity(authorization); + HttpEventFW event = httpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .requestAccepted(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + .scheme(headers.matchFirst(h -> HEADER_SCHEME.equals(h.name())).value().asString()) + .method(headers.matchFirst(h -> HEADER_METHOD.equals(h.name())).value().asString()) + .authority(headers.matchFirst(h -> HEADER_AUTHORITY.equals(h.name())).value().asString()) + .path(headers.matchFirst(h -> HEADER_PATH.equals(h.name())).value().asString()) + ) + .build(); + eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java index 39b9bea739..e8f99951e9 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java @@ -84,6 +84,7 @@ import io.aklivity.zilla.runtime.binding.http.config.HttpVersion; import io.aklivity.zilla.runtime.binding.http.internal.HttpBinding; import io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration; +import io.aklivity.zilla.runtime.binding.http.internal.HttpEventContext; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ContinuationFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2DataFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ErrorCode; @@ -544,6 +545,7 @@ public final class HttpServerFactory implements HttpStreamFactory private final Matcher connectionClose; private final int maximumHeadersSize; private final Long2ObjectHashMap bindings; + private final HttpEventContext event; public HttpServerFactory( HttpConfiguration config, @@ -576,6 +578,7 @@ public HttpServerFactory( this.supplyValidator = context::supplyValidator; this.encodeMax = bufferPool.slotCapacity(); this.bindings = new Long2ObjectHashMap<>(); + this.event = new HttpEventContext(context); this.headers200 = initHeaders(config, STATUS_200); this.headers204 = initHeaders(config, STATUS_204); @@ -1043,7 +1046,7 @@ else if (!isCorsRequestAllowed(server.binding, headers)) final String credentialsMatch = server.credentials.apply(headers::get); if (credentialsMatch != null) { - guard.reauthorize(server.initialId, credentialsMatch); + guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch); } server.doEncodeHeaders(traceId, authorization, budgetId, headers204); } @@ -1055,7 +1058,7 @@ else if (!isCorsRequestAllowed(server.binding, headers)) final String credentialsMatch = server.credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(server.initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch); } } @@ -2268,6 +2271,7 @@ private boolean onDecodeHeaders( final HttpHeaderFW connection = beginEx.headers().matchFirst(h -> HEADER_CONNECTION.equals(h.name())); exchange.responseClosing = connection != null && connectionClose.reset(connection.value().asString()).matches(); + event.requestAccepted(traceId, routedId, guard, authorization, beginEx.headers()); this.exchange = exchange; } return headersValid; @@ -4923,7 +4927,7 @@ else if (headersDecoder.httpError()) else { final Map headers = headersDecoder.headers; - + event.requestAccepted(traceId, routedId, guard, authorization, headers); if (isCorsPreflightRequest(headers)) { if (!endRequest) @@ -4956,7 +4960,7 @@ else if (!isCorsRequestAllowed(binding, headers)) final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - guard.reauthorize(initialId, credentialsMatch); + guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } doEncodeHeaders(traceId, authorization, streamId, headers204, true); } @@ -4968,7 +4972,7 @@ else if (!isCorsRequestAllowed(binding, headers)) final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } @@ -5342,7 +5346,7 @@ private void doEncodePromise( final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java new file mode 100644 index 0000000000..86e8bc3168 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.binding.kafka.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.kafka.internal.types.event.KafkaEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class KafkaEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + private static final int ERROR_NONE = 0; + + private final KafkaEventFW.Builder kafkaEventRW = new KafkaEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int kafkaTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public KafkaEventContext( + EngineContext context) + { + this.kafkaTypeId = context.supplyTypeId(KafkaBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void authorizationFailed( + long traceId, + long bindingId) + { + KafkaEventFW event = kafkaEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .authorizationFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void apiVersionRejected( + long traceId, + long bindingId, + int apiKey, + int apiVersion) + { + KafkaEventFW event = kafkaEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .apiVersionRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .apiKey(apiKey) + .apiVersion(apiVersion) + ) + .build(); + eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java index f1eaf5b83b..95c1a8dc53 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java @@ -965,6 +965,7 @@ public void onDecodeResource( assert resource.equals(this.topic); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -975,6 +976,15 @@ public void onDecodeResource( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, + errorCode); + } + private void onNetwork( int msgTypeId, DirectBuffer buffer, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java index f71de6de0d..b44b82d71c 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java @@ -2949,6 +2949,7 @@ private void onDecodeOffsetsPartition( this.nextOffset = partitionOffset; break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); cleanupApplication(traceId, errorCode); doNetworkEnd(traceId, authorization); break; @@ -2988,6 +2989,10 @@ private void onDecodeFetchPartition( traceId, authorization, 0, EMPTY_OCTETS); } } + else + { + onDecodeResponseErrorCode(traceId, originId, errorCode); + } cleanupApplication(traceId, errorCode); doNetworkEnd(traceId, authorization); @@ -2995,6 +3000,14 @@ private void onDecodeFetchPartition( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, FETCH_API_KEY, FETCH_API_VERSION, errorCode); + } + private void onDecodeFetchTransactionAbort( long traceId, long authorization, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java index ba89f747c1..dfb7a72bd3 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java @@ -878,6 +878,8 @@ private int decodeFindCoordinatorResponse( findCoordinatorResponse.host(), findCoordinatorResponse.port()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, FIND_COORDINATOR_API_KEY, + FIND_COORDINATOR_API_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeClusterReject; break; @@ -1012,6 +1014,7 @@ private int decodeJoinGroupResponse( joinGroupResponse.memberId().asString()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, JOIN_GROUP_API_KEY, JOIN_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1071,6 +1074,7 @@ private int decodeSyncGroupResponse( client.onSyncGroupResponse(traceId, authorization, syncGroupResponse.assignment()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, SYNC_GROUP_API_KEY, SYNC_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1133,6 +1137,7 @@ private int decodeHeartbeatResponse( client.onHeartbeatResponse(traceId, authorization); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, HEARTBEAT_API_KEY, HEARTBEAT_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1197,6 +1202,8 @@ private int decodeLeaveGroupResponse( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, + LEAVE_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; } @@ -1211,6 +1218,8 @@ private int decodeLeaveGroupResponse( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, LEAVE_GROUP_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; } @@ -2565,11 +2574,20 @@ public void onDecodeResource( assert resource.equals(delegate.nodeId); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); onNetworkError(traceId, errorCode); break; } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, errorCode); + } + private void onNetwork( int msgTypeId, DirectBuffer buffer, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java index 94d2598daf..13c016633d 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java @@ -1810,6 +1810,7 @@ private void onDecodeTopic( newPartitions.clear(); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -1830,6 +1831,18 @@ private void onDecodePartition( { newPartitions.put(partitionId, leaderId); } + else + { + onDecodeResponseErrorCode(traceId, originId, partitionError); + } + } + + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, METADATA_API_KEY, METADATA_API_VERSION, errorCode); } @Override diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java index 7fec24693c..b7ca22b1f8 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java @@ -589,6 +589,8 @@ private int decodeOffsetCommitPartition( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_COMMIT_API_KEY, OFFSET_COMMIT_API_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeReject; } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java index 35f415c6f7..4e753c0789 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java @@ -661,6 +661,8 @@ private int decodeOffsetFetchPartition( client.decoder = decodeOffsetFetchPartitions; break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_FETCH_API_KEY, OFFSET_FETCH_API_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeReject; break; diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java index d21855ab20..9be9f026d2 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java @@ -42,6 +42,7 @@ import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaBinding; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration; +import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; @@ -193,6 +194,7 @@ public final class KafkaClientProduceFactory extends KafkaClientSaslHandshaker i private final int decodeMaxBytes; private final int encodeMaxBytes; private final CRC32C crc32c; + private final KafkaEventContext event; public KafkaClientProduceFactory( KafkaConfiguration config, @@ -218,6 +220,7 @@ public KafkaClientProduceFactory( this.encodeMaxBytes = Math.min(config.clientProduceMaxBytes(), encodePool.slotCapacity() - PRODUCE_REQUEST_RECORDS_OFFSET_MAX); this.crc32c = new CRC32C(); + this.event = new KafkaEventContext(context); } @Override @@ -2268,6 +2271,7 @@ private void onDecodeProducePartition( assert partitionId == this.partitionId; break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -2278,6 +2282,14 @@ private void onDecodeProducePartition( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, PRODUCE_API_KEY, PRODUCE_API_VERSION, errorCode); + } + @Override protected void onDecodeSaslResponse( long traceId) diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java index b6a3b266c4..ca61063380 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java @@ -43,6 +43,7 @@ import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.identity.KafkaClientIdSupplier; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration; +import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaScramMechanism; import io.aklivity.zilla.runtime.binding.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.codec.RequestHeaderFW; @@ -64,6 +65,7 @@ public abstract class KafkaClientSaslHandshaker private static final short SASL_AUTHENTICATE_API_VERSION = 1; private static final int ERROR_SASL_AUTHENTICATION_FAILED = 58; private static final int ERROR_NONE = 0; + private static final int ERROR_UNSUPPORTED_VERSION = 35; private static final String CLIENT_KEY = "Client Key"; private static final String SERVER_KEY = "Server Key"; @@ -92,6 +94,7 @@ public abstract class KafkaClientSaslHandshaker private final SaslHandshakeResponseFW saslHandshakeResponseRO = new SaslHandshakeResponseFW(); private final SaslHandshakeMechanismResponseFW saslHandshakeMechanismResponseRO = new SaslHandshakeMechanismResponseFW(); private final SaslAuthenticateResponseFW saslAuthenticateResponseRO = new SaslAuthenticateResponseFW(); + private final KafkaEventContext event; private KafkaSaslClientDecoder decodeSaslPlainAuthenticate = this::decodeSaslPlainAuthenticate; private KafkaSaslClientDecoder decodeSaslScramAuthenticateFirst = this::decodeSaslScramAuthenticateFirst; @@ -125,6 +128,7 @@ public KafkaClientSaslHandshaker( this.writeBuffer = new UnsafeBuffer(new byte[context.writeBuffer().capacity()]); this.nonceSupplier = config.nonceSupplier(); this.clientIdsByServer = new Object2ObjectHashMap<>(); + this.event = new KafkaEventContext(context); } public abstract class KafkaSaslClient @@ -423,6 +427,19 @@ private void doEncodeSaslScramFinalAuthenticateRequest( doDecodeSaslAuthenticateResponse(traceId); } + protected final void onDecodeResponseErrorCode( + long traceId, + long bindingId, + int apiKey, + int apiVersion, + int errorCode) + { + if (errorCode == ERROR_UNSUPPORTED_VERSION) + { + event.apiVersionRejected(traceId, bindingId, apiKey, apiVersion); + } + } + protected abstract void doNetworkData( long traceId, long budgetId, @@ -689,6 +706,10 @@ private int decodeSaslPlainAuthenticate( if (authenticateResponse != null) { final int errorCode = authenticateResponse.errorCode(); + if (errorCode != ERROR_NONE) + { + event.authorizationFailed(traceId, client.originId); + } progress = authenticateResponse.limit(); @@ -723,6 +744,10 @@ private int decodeSaslScramAuthenticateFirst( if (authenticateResponse != null) { final int errorCode = authenticateResponse.errorCode(); + if (errorCode != ERROR_NONE) + { + event.authorizationFailed(traceId, client.originId); + } progress = authenticateResponse.limit(); diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java index 1c681d82ca..7d8e6a7f5b 100644 --- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java +++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java @@ -2910,7 +2910,7 @@ else if (this.authField.equals(MqttConnectProperty.PASSWORD)) if (credentialsMatch != null) { - sessionAuth = guard.reauthorize(initialId, credentialsMatch); + sessionAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } diff --git a/runtime/binding-tcp/pom.xml b/runtime/binding-tcp/pom.xml index c28cdc4b4e..513e5eae6b 100644 --- a/runtime/binding-tcp/pom.xml +++ b/runtime/binding-tcp/pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.89 + 0.88 0 @@ -123,7 +123,7 @@ flyweight-maven-plugin ${project.version} - core proxy + core tcp proxy io.aklivity.zilla.runtime.binding.tcp.internal.types diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java new file mode 100644 index 0000000000..7159725444 --- /dev/null +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.binding.tcp.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.tcp.internal.types.event.TcpEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class TcpEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final TcpEventFW.Builder tcpEventRW = new TcpEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int tcpTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public TcpEventContext( + EngineContext context) + { + this.tcpTypeId = context.supplyTypeId(TcpBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void dnsResolutionFailed( + long traceId, + long bindingId, + String address) + { + TcpEventFW event = tcpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .dnsFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .address(address) + ) + .build(); + eventWriter.accept(tcpTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java index 08afe896f8..cc3d8645f9 100644 --- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java @@ -138,6 +138,7 @@ public MessageConsumer newStream( MessageConsumer application) { final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long traceId = begin.traceId(); final long originId = begin.originId(); final long routedId = begin.routedId(); final long authorization = begin.authorization(); @@ -151,7 +152,7 @@ public MessageConsumer newStream( TcpBindingConfig binding = router.lookup(routedId); if (binding != null) { - route = router.resolve(binding, authorization, beginEx); + route = router.resolve(binding, traceId, authorization, beginEx); } MessageConsumer newStream = null; @@ -257,13 +258,14 @@ private void doNetConnect( state = TcpState.openingInitial(state); net.setOption(SO_KEEPALIVE, options != null && options.keepalive); + networkKey = supplyPollerKey.apply(net); + if (net.connect(remoteAddress)) { onNetConnected(); } else { - networkKey = supplyPollerKey.apply(net); networkKey.handler(OP_CONNECT, this::onNetConnect); networkKey.register(OP_CONNECT); } @@ -283,7 +285,7 @@ private int onNetConnect( net.finishConnect(); onNetConnected(); } - catch (UnresolvedAddressException | IOException ex) + catch (IOException ex) { onNetRejected(); } diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java index ecfb709157..fc343d4e2b 100644 --- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java @@ -30,6 +30,7 @@ import org.agrona.collections.Long2ObjectHashMap; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.internal.TcpEventContext; import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpBindingConfig; import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpRouteConfig; import io.aklivity.zilla.runtime.binding.tcp.internal.types.Array32FW; @@ -49,12 +50,14 @@ public final class TcpClientRouter private final Function resolveHost; private final Long2ObjectHashMap bindings; + private final TcpEventContext event; public TcpClientRouter( EngineContext context) { this.resolveHost = context::resolveHost; this.bindings = new Long2ObjectHashMap<>(); + this.event = new TcpEventContext(context); } public void attach( @@ -71,6 +74,7 @@ public TcpBindingConfig lookup( public InetSocketAddress resolve( TcpBindingConfig binding, + long traceId, long authorization, ProxyBeginExFW beginEx) { @@ -79,86 +83,106 @@ public InetSocketAddress resolve( InetSocketAddress resolved = null; - if (beginEx == null) - { - resolved = options != null ? new InetSocketAddress(options.host, port) : null; - } - else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES) - { - ProxyAddressFW address = beginEx.address(); - resolved = resolveInetSocketAddress(address); - } - else + try { - final ProxyAddressFW address = beginEx.address(); - - for (TcpRouteConfig route : binding.routes) + if (beginEx == null) { - if (!route.authorized(authorization)) - { - continue; - } + InetAddress[] addresses = options != null ? resolveHost(options.host) : null; + resolved = addresses != null ? new InetSocketAddress(addresses[0], port) : null; + } + else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES) + { + ProxyAddressFW address = beginEx.address(); + resolved = resolveInetSocketAddress(address); + } + else + { + final ProxyAddressFW address = beginEx.address(); - Array32FW infos = beginEx.infos(); - ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY); - if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null)) + for (TcpRouteConfig route : binding.routes) { - final List authorities = Arrays - .stream(resolveHost.apply(authorityInfo.authority().asString())) - .map(a -> new InetSocketAddress(a, port)) - .collect(Collectors.toList()); + if (!route.authorized(authorization)) + { + continue; + } - for (InetSocketAddress authority : authorities) + Array32FW infos = beginEx.infos(); + ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY); + if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null)) { - if (route.matchesExplicit(authority)) + final List authorities = Arrays + .stream(resolveHost(authorityInfo.authority().asString())) + .map(a -> new InetSocketAddress(a, port)) + .collect(Collectors.toList()); + + for (InetSocketAddress authority : authorities) { - resolved = authority; - break; + if (route.matchesExplicit(authority)) + { + resolved = authority; + break; + } } } - } - if (resolved == null) - { - resolved = resolve(address, authorization, route::matchesExplicit); - } + if (resolved == null) + { + resolved = resolve(address, authorization, route::matchesExplicit); + } - if (resolved != null) - { - break; + if (resolved != null) + { + break; + } } - } - - if (resolved == null && - options != null && - options.host != null && - !"*".equals(options.host)) - { - final List host = Arrays - .stream(resolveHost.apply(options.host)) - .map(a -> new InetSocketAddress(a, port)) - .collect(Collectors.toList()); - for (TcpRouteConfig route : binding.routes) + if (resolved == null && + options != null && + options.host != null && + !"*".equals(options.host)) { - if (!route.authorized(authorization)) + final List host = Arrays + .stream(resolveHost(options.host)) + .map(a -> new InetSocketAddress(a, port)) + .collect(Collectors.toList()); + + for (TcpRouteConfig route : binding.routes) { - continue; - } + if (!route.authorized(authorization)) + { + continue; + } - resolved = resolve(address, authorization, host::contains); + resolved = resolve(address, authorization, host::contains); - if (resolved != null) - { - break; + if (resolved != null) + { + break; + } } } } } - + catch (TcpDnsFailedException ex) + { + event.dnsResolutionFailed(traceId, binding.id, ex.hostname); + } return resolved; } + private InetAddress[] resolveHost( + String hostname) + { + try + { + return resolveHost.apply(hostname); + } + catch (Throwable ex) + { + throw new TcpDnsFailedException(ex, hostname); + } + } + public void detach( long bindingId) { @@ -291,4 +315,17 @@ private InetSocketAddress resolveInetSocketAddress( return resolved; } + + private static final class TcpDnsFailedException extends RuntimeException + { + private final String hostname; + + TcpDnsFailedException( + Throwable cause, + String hostname) + { + super(cause); + this.hostname = hostname; + } + } } diff --git a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java index cd69d87413..5373187431 100644 --- a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java +++ b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java @@ -21,7 +21,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.rules.RuleChain.outerRule; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; @@ -37,6 +39,7 @@ import io.aklivity.zilla.runtime.engine.test.EngineRule; import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; public class ClientIT { @@ -215,6 +218,18 @@ public void connnectionFailed() throws Exception k3po.finish(); } + @Test + @Configuration("client.host.yaml") + @Specification({ + "${app}/connection.failed/client" + }) + @Configure(name = "zilla.engine.host.resolver", + value = "io.aklivity.zilla.runtime.binding.tcp.internal.streams.ClientIT::resolveHost") + public void dnsResolutionFailed() throws Exception + { + k3po.finish(); + } + @Test @Configuration("client.host.yaml") @Specification({ @@ -326,4 +341,10 @@ public void shouldWriteDataAfterReceiveEnd() throws Exception } } } + + public static InetAddress[] resolveHost( + String host) throws UnknownHostException + { + throw new UnknownHostException(); + } } diff --git a/runtime/binding-tls/pom.xml b/runtime/binding-tls/pom.xml index 6b6779e140..1fd6bcd4d1 100644 --- a/runtime/binding-tls/pom.xml +++ b/runtime/binding-tls/pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.76 + 0.75 0 @@ -109,7 +109,7 @@ flyweight-maven-plugin ${project.version} - core proxy protocol + core proxy tls protocol io.aklivity.zilla.runtime.binding.tls.internal.types diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java new file mode 100644 index 0000000000..22722224ea --- /dev/null +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.binding.tls.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.tls.internal.types.event.TlsEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class TlsEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final TlsEventFW.Builder tlsEventRW = new TlsEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int tlsTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public TlsEventContext( + EngineContext context) + { + this.tlsTypeId = context.supplyTypeId(TlsBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void tlsFailed( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsProtocolRejected( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsProtocolRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsKeyRejected( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsKeyRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsPeerNotVerified( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsPeerNotVerified(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsHandshakeFailed( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsHandshakeFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java index fd022f46d0..b111b35b77 100644 --- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java @@ -37,6 +37,10 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; @@ -44,6 +48,7 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration; +import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig; import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW; @@ -129,6 +134,7 @@ public final class TlsClientFactory implements TlsStreamFactory private final LongUnaryOperator supplyReplyId; private final int initialPadAdjust; private final Long2ObjectHashMap bindings; + private final TlsEventContext event; private final int decodeMax; private final int handshakeMax; @@ -168,6 +174,7 @@ public TlsClientFactory( this.initialPadAdjust = Math.max(context.bufferPool().slotCapacity() >> 14, 1) * MAXIMUM_HEADER_SIZE; this.bindings = new Long2ObjectHashMap<>(); + this.event = new TlsEventContext(context); this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity()); this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer); this.outNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity() << 1); @@ -568,51 +575,80 @@ private int decodeNotHandshaking( try { - final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesProduced = result.bytesProduced(); - final int bytesConsumed = result.bytesConsumed(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesProduced = result.bytesProduced(); + final int bytesConsumed = result.bytesConsumed(); + + switch (result.getStatus()) { - if (!client.stream.isPresent()) + case BUFFER_UNDERFLOW: + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) { - client.onDecodeHandshakeFinished(traceId, budgetId); + if (!client.stream.isPresent()) + { + client.onDecodeHandshakeFinished(traceId, budgetId); + } } - } - if (bytesProduced == 0) - { - client.decoder = decodeHandshake; - progress += bytesConsumed; - } - else - { - assert bytesConsumed == tlsRecordBytes; - assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed); + if (bytesProduced == 0) + { + client.decoder = decodeHandshake; + progress += bytesConsumed; + } + else + { + assert bytesConsumed == tlsRecordBytes; + assert bytesProduced <= bytesConsumed : + String.format("%d <= %d", bytesProduced, bytesConsumed); - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); - client.decodableRecordBytes -= bytesConsumed; - assert client.decodableRecordBytes == 0; + client.decodableRecordBytes -= bytesConsumed; + assert client.decodableRecordBytes == 0; - client.decoder = decodeNotHandshakingUnwrapped; + client.decoder = decodeNotHandshakingUnwrapped; + } + break; + case CLOSED: + assert bytesProduced == 0; + client.onDecodeInboundClosed(traceId); + client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake; + progress += bytesConsumed; + break; } - break; - case CLOSED: - assert bytesProduced == 0; - client.onDecodeInboundClosed(traceId); - client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake; - progress += bytesConsumed; - break; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; } } catch (SSLException ex) @@ -750,37 +786,65 @@ private int decodeHandshakeNeedUnwrap( try { - final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesConsumed = result.bytesConsumed(); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - if (TlsState.replyClosed(client.state)) + final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesConsumed = result.bytesConsumed(); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { + case BUFFER_UNDERFLOW: + if (TlsState.replyClosed(client.state)) + { + client.decoder = decodeIgnoreAll; + } + break; + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + assert bytesProduced == 0; + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + { + client.onDecodeHandshakeFinished(traceId, budgetId); + } + client.decoder = decodeHandshake; + break; + case CLOSED: + assert bytesProduced == 0; + client.onDecodeInboundClosed(traceId); client.decoder = decodeIgnoreAll; + break; } - break; - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - assert bytesProduced == 0; - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) - { - client.onDecodeHandshakeFinished(traceId, budgetId); - } - client.decoder = decodeHandshake; - break; - case CLOSED: - assert bytesProduced == 0; - client.onDecodeInboundClosed(traceId); - client.decoder = decodeIgnoreAll; - break; - } - progress += bytesConsumed; + progress += bytesConsumed; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { @@ -1631,6 +1695,7 @@ private void onNetSignalHandshakeTimeout( final long traceId = signal.traceId(); cleanupNet(traceId); + event.tlsHandshakeFailed(traceId, client.originId); decoder = decodeIgnoreAll; } } @@ -1647,7 +1712,35 @@ private void doNetBegin( try { - tlsEngine.beginHandshake(); + try + { + tlsEngine.beginHandshake(); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { @@ -1998,38 +2091,66 @@ private void doEncodeWrap( try { - loop: - do + try { - final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + loop: + do { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - assert false; - break; - case CLOSED: - assert bytesProduced > 0; - doAppReset(traceId); - state = TlsState.closingReply(state); - break loop; - case OK: - assert bytesProduced > 0 || tlsEngine.isInboundDone(); - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { - if (proactiveReplyBegin) + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + assert false; + break; + case CLOSED: + assert bytesProduced > 0; + doAppReset(traceId); + state = TlsState.closingReply(state); + break loop; + case OK: + assert bytesProduced > 0 || tlsEngine.isInboundDone(); + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) { - onDecodeHandshakeFinished(traceId, budgetId); + if (proactiveReplyBegin) + { + onDecodeHandshakeFinished(traceId, budgetId); + } } + break; } - break; - } - } while (inAppByteBuffer.hasRemaining()); + } while (inAppByteBuffer.hasRemaining()); - final int outNetBytesProduced = outNetByteBuffer.position(); - doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + final int outNetBytesProduced = outNetByteBuffer.position(); + doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java index f78ccc88fb..91d30e9b3d 100644 --- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java @@ -42,7 +42,10 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; @@ -52,6 +55,7 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration; +import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig; import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW; @@ -145,6 +149,7 @@ public final class TlsServerFactory implements TlsStreamFactory private final LongUnaryOperator supplyReplyId; private final int replyPadAdjust; private final Long2ObjectHashMap bindings; + private final TlsEventContext event; private final int decodeMax; private final int handshakeMax; @@ -183,6 +188,7 @@ public TlsServerFactory( this.handshakeMax = Math.min(config.handshakeWindowBytes(), decodeMax); this.handshakeTimeoutMillis = SECONDS.toMillis(config.handshakeTimeout()); this.bindings = new Long2ObjectHashMap<>(); + this.event = new TlsEventContext(context); this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity()); this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer); @@ -523,8 +529,36 @@ private int decodeBeforeHandshake( { try { - server.tlsEngine.beginHandshake(); - server.decoder = decodeHandshake; + try + { + server.tlsEngine.beginHandshake(); + server.decoder = decodeHandshake; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; + } } catch (SSLException | RuntimeException ex) { @@ -605,43 +639,72 @@ private int decodeNotHandshaking( try { - final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesProduced = result.bytesProduced(); - final int bytesConsumed = result.bytesConsumed(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - if (bytesProduced == 0) + final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesProduced = result.bytesProduced(); + final int bytesConsumed = result.bytesConsumed(); + + switch (result.getStatus()) { - server.decoder = decodeHandshake; + case BUFFER_UNDERFLOW: + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + if (bytesProduced == 0) + { + server.decoder = decodeHandshake; + progress += bytesConsumed; + } + else + { + assert bytesConsumed == tlsRecordBytes; + assert bytesProduced <= bytesConsumed : + String.format("%d <= %d", bytesProduced, bytesConsumed); + + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); + + server.decodableRecordBytes -= bytesConsumed; + assert server.decodableRecordBytes == 0; + + server.decoder = decodeNotHandshakingUnwrapped; + } + break; + case CLOSED: + assert bytesProduced == 0; + server.onDecodeInboundClosed(traceId); + server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake; progress += bytesConsumed; + break; } - else - { - assert bytesConsumed == tlsRecordBytes; - assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed); - - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); - - server.decodableRecordBytes -= bytesConsumed; - assert server.decodableRecordBytes == 0; - - server.decoder = decodeNotHandshakingUnwrapped; - } - break; - case CLOSED: - assert bytesProduced == 0; - server.onDecodeInboundClosed(traceId); - server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake; - progress += bytesConsumed; - break; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; } } catch (SSLException | RuntimeException ex) @@ -779,55 +842,83 @@ private int decodeHandshakeNeedUnwrap( try { - final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesConsumed = result.bytesConsumed(); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - if (TlsState.initialClosed(server.state)) - { - server.decoder = decodeIgnoreAll; - } - break; - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); - if (handshakeStatus == HandshakeStatus.FINISHED) - { - server.onDecodeHandshakeFinished(traceId, budgetId); - } + final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesConsumed = result.bytesConsumed(); + final int bytesProduced = result.bytesProduced(); - if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED) - { - final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW - .wrap(buffer, progress, progress + bytesConsumed) - .build(); - final int tlsRecordDataOffset = tlsRecordInfo.limit(); - final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length(); - - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); - server.decoder = decodeNotHandshakingUnwrapped; - } - else + switch (result.getStatus()) { - server.decoder = decodeHandshake; + case BUFFER_UNDERFLOW: + if (TlsState.initialClosed(server.state)) + { + server.decoder = decodeIgnoreAll; + } + break; + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); + if (handshakeStatus == HandshakeStatus.FINISHED) + { + server.onDecodeHandshakeFinished(traceId, budgetId); + } + + if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED) + { + final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW + .wrap(buffer, progress, progress + bytesConsumed) + .build(); + final int tlsRecordDataOffset = tlsRecordInfo.limit(); + final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length(); + + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); + server.decoder = decodeNotHandshakingUnwrapped; + } + else + { + server.decoder = decodeHandshake; + } + + break; + case CLOSED: + assert bytesProduced == 0; + server.onDecodeInboundClosed(traceId); + server.decoder = decodeIgnoreAll; + break; } - break; - case CLOSED: - assert bytesProduced == 0; - server.onDecodeInboundClosed(traceId); - server.decoder = decodeIgnoreAll; - break; + progress += bytesConsumed; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; } - - progress += bytesConsumed; } catch (SSLException | RuntimeException ex) { @@ -1309,6 +1400,7 @@ private void onNetSignalHandshakeTimeout( handshakeTimeoutFutureId = NO_CANCEL_ID; cleanupNet(traceId); + event.tlsHandshakeFailed(traceId, routedId); decoder = decodeIgnoreAll; } } @@ -1652,36 +1744,64 @@ private void doEncodeWrap( try { - loop: - do + try { - final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + loop: + do { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - assert false; - break; - case CLOSED: - assert bytesProduced > 0; - assert tlsEngine.isOutboundDone(); - stream.ifPresent(s -> s.doAppResetLater(traceId)); - state = TlsState.closingReply(state); - break loop; - case OK: - assert bytesProduced > 0 || tlsEngine.isInboundDone(); - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { - onDecodeHandshakeFinished(traceId, budgetId); + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + assert false; + break; + case CLOSED: + assert bytesProduced > 0; + assert tlsEngine.isOutboundDone(); + stream.ifPresent(s -> s.doAppResetLater(traceId)); + state = TlsState.closingReply(state); + break loop; + case OK: + assert bytesProduced > 0 || tlsEngine.isInboundDone(); + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + { + onDecodeHandshakeFinished(traceId, budgetId); + } + break; } - break; - } - } while (inAppByteBuffer.hasRemaining()); + } while (inAppByteBuffer.hasRemaining()); - final int outNetBytesProduced = outNetByteBuffer.position(); - doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + final int outNetBytesProduced = outNetByteBuffer.position(); + doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, routedId); + throw ex; + } } catch (SSLException | RuntimeException ex) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java index 7a8f6935a8..d6d2bda6ea 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java @@ -57,6 +57,8 @@ import org.agrona.concurrent.AgentRunner; import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.catalog.Catalog; import io.aklivity.zilla.runtime.engine.config.KindConfig; import io.aklivity.zilla.runtime.engine.exporter.Exporter; @@ -71,6 +73,7 @@ import io.aklivity.zilla.runtime.engine.internal.registry.FileWatcherTask; import io.aklivity.zilla.runtime.engine.internal.registry.HttpWatcherTask; import io.aklivity.zilla.runtime.engine.internal.registry.WatcherTask; +import io.aklivity.zilla.runtime.engine.internal.types.event.EventFW; import io.aklivity.zilla.runtime.engine.metrics.Collector; import io.aklivity.zilla.runtime.engine.metrics.MetricGroup; import io.aklivity.zilla.runtime.engine.model.Model; @@ -160,7 +163,7 @@ public final class Engine implements Collector, AutoCloseable EngineWorker worker = new EngineWorker(config, tasks, labels, errorHandler, tuning::affinity, bindings, exporters, guards, vaults, catalogs, models, metricGroups, - this, coreIndex, readonly); + this, this::supplyEventReader, coreIndex, readonly); workers.add(worker); } this.workers = workers; @@ -481,6 +484,11 @@ public long[][] histogramIds() return worker.histogramIds(); } + public MessageReader supplyEventReader() + { + return new EventReader(); + } + public String supplyLocalName( long namespacedId) { @@ -495,6 +503,48 @@ public int supplyLabelId( return worker.supplyTypeId(label); } + private final class EventReader implements MessageReader + { + private final EventFW eventRO = new EventFW(); + private int minWorkerIndex; + private long minTimeStamp; + + @Override + public int read( + MessageConsumer handler, + int messageCountLimit) + { + int messagesRead = 0; + boolean empty = false; + while (!empty && messagesRead < messageCountLimit) + { + int eventCount = 0; + minWorkerIndex = 0; + minTimeStamp = Long.MAX_VALUE; + for (int j = 0; j < workers.size(); j++) + { + final int workerIndex = j; + int eventPeeked = workers.get(workerIndex).peekEvent((m, b, i, l) -> + { + eventRO.wrap(b, i, i + l); + if (eventRO.timestamp() < minTimeStamp) + { + minTimeStamp = eventRO.timestamp(); + minWorkerIndex = workerIndex; + } + }); + eventCount += eventPeeked; + } + empty = eventCount == 0; + if (!empty) + { + messagesRead += workers.get(minWorkerIndex).readEvent(handler, 1); + } + } + return messagesRead; + } + } + // visible for testing public final class ContextImpl implements EngineExtContext { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java index 8ef1e2723a..4cd79e9eed 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java @@ -52,6 +52,7 @@ public class EngineConfiguration extends Configuration public static final IntPropertyDef ENGINE_BUFFER_POOL_CAPACITY; public static final IntPropertyDef ENGINE_BUFFER_SLOT_CAPACITY; public static final IntPropertyDef ENGINE_STREAMS_BUFFER_CAPACITY; + public static final IntPropertyDef ENGINE_EVENTS_BUFFER_CAPACITY; public static final IntPropertyDef ENGINE_COUNTERS_BUFFER_CAPACITY; public static final IntPropertyDef ENGINE_BUDGETS_BUFFER_CAPACITY; public static final BooleanPropertyDef ENGINE_TIMESTAMPS; @@ -88,6 +89,8 @@ public class EngineConfiguration extends Configuration ENGINE_BUFFER_SLOT_CAPACITY = config.property("buffer.slot.capacity", 64 * 1024); ENGINE_STREAMS_BUFFER_CAPACITY = config.property("streams.buffer.capacity", EngineConfiguration::defaultStreamsBufferCapacity); + ENGINE_EVENTS_BUFFER_CAPACITY = config.property("events.buffer.capacity", + EngineConfiguration::defaultEventsBufferCapacity); ENGINE_BUDGETS_BUFFER_CAPACITY = config.property("budgets.buffer.capacity", EngineConfiguration::defaultBudgetsBufferCapacity); ENGINE_COUNTERS_BUFFER_CAPACITY = config.property("counters.buffer.capacity", 1024 * 1024); @@ -179,6 +182,11 @@ public int streamsBufferCapacity() return ENGINE_STREAMS_BUFFER_CAPACITY.getAsInt(this); } + public int eventsBufferCapacity() + { + return ENGINE_EVENTS_BUFFER_CAPACITY.getAsInt(this); + } + public int countersBufferCapacity() { return ENGINE_COUNTERS_BUFFER_CAPACITY.getAsInt(this); @@ -276,6 +284,12 @@ private static int defaultStreamsBufferCapacity( return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config); } + private static int defaultEventsBufferCapacity( + Configuration config) + { + return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config); + } + private static int defaultBudgetsBufferCapacity( Configuration config) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java index 2f113352a8..0360890834 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java @@ -18,12 +18,14 @@ import java.net.InetAddress; import java.net.URL; import java.nio.channels.SelectableChannel; +import java.time.Clock; import java.util.function.LongSupplier; import org.agrona.MutableDirectBuffer; import io.aklivity.zilla.runtime.engine.binding.BindingHandler; import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor; import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor; import io.aklivity.zilla.runtime.engine.buffer.BufferPool; @@ -117,6 +119,9 @@ String supplyNamespace( String supplyLocalName( long namespacedId); + String supplyQName( + long namespacedId); + BindingHandler streamFactory(); GuardHandler supplyGuard( @@ -148,4 +153,10 @@ void onExporterAttached( void onExporterDetached( long exporterId); + + MessageConsumer supplyEventWriter(); + + MessageReader supplyEventReader(); + + Clock clock(); } diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java new file mode 100644 index 0000000000..9759b940d5 --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.engine.binding.function; + +public interface MessageReader +{ + int read( + MessageConsumer handler, + int messageCountLimit); +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java index 54c3f372fc..a9da2772fe 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java @@ -30,6 +30,8 @@ public interface GuardHandler * @return the session identifier */ long reauthorize( + long traceId, + long bindingId, long contextId, String credentials); diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java new file mode 100644 index 0000000000..ee3670f16b --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java @@ -0,0 +1,173 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.engine.internal.layouts; + +import static io.aklivity.zilla.runtime.engine.internal.spy.RingBufferSpy.SpyPosition.ZERO; +import static org.agrona.IoUtil.createEmptyFile; +import static org.agrona.IoUtil.mapExistingFile; +import static org.agrona.IoUtil.unmap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.File; +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.AtomicBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.agrona.concurrent.ringbuffer.OneToOneRingBuffer; +import org.agrona.concurrent.ringbuffer.RingBuffer; +import org.agrona.concurrent.ringbuffer.RingBufferDescriptor; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.internal.spy.OneToOneRingBufferSpy; +import io.aklivity.zilla.runtime.engine.internal.spy.RingBufferSpy; + +public final class EventsLayout implements AutoCloseable +{ + private final Path path; + private final long capacity; + + private RingBuffer buffer; + private RingBufferSpy bufferSpy; + + private EventsLayout( + Path path, + long capacity, + RingBuffer buffer, + RingBufferSpy bufferSpy) + { + this.path = path; + this.capacity = capacity; + this.buffer = buffer; + this.bufferSpy = bufferSpy; + } + + @Override + public void close() + { + unmap(buffer.buffer().byteBuffer()); + } + + public void writeEvent( + int msgTypeId, + DirectBuffer recordBuffer, + int index, + int length) + { + boolean success = buffer.write(msgTypeId, recordBuffer, index, length); + if (!success) + { + rotateFile(); + buffer.write(msgTypeId, recordBuffer, index, length); + } + } + + public int readEvent( + MessageConsumer handler, + int messageCountLimit) + { + return bufferSpy.spy(handler, messageCountLimit); + } + + public int peekEvent( + MessageConsumer handler) + { + return bufferSpy.peek(handler); + } + + private void rotateFile() + { + close(); + String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + Path newPath = Path.of(String.format("%s_%s", path, timestamp)); + try + { + Files.move(path, newPath, StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException ex) + { + ex.printStackTrace(); + rethrowUnchecked(ex); + } + buffer = createRingBuffer(path, capacity); + bufferSpy = createRingBufferSpy(path); + } + + private static AtomicBuffer createAtomicBuffer( + Path path, + long capacity, + boolean createFile) + { + final File layoutFile = path.toFile(); + if (createFile) + { + CloseHelper.close(createEmptyFile(layoutFile, capacity + RingBufferDescriptor.TRAILER_LENGTH)); + } + final MappedByteBuffer mappedBuffer = mapExistingFile(layoutFile, "events"); + return new UnsafeBuffer(mappedBuffer); + } + + private static RingBuffer createRingBuffer( + Path path, + long capacity) + { + AtomicBuffer atomicBuffer = createAtomicBuffer(path, capacity, true); + return new OneToOneRingBuffer(atomicBuffer); + } + + private static RingBufferSpy createRingBufferSpy( + Path path) + { + AtomicBuffer atomicBuffer = createAtomicBuffer(path, 0, false); + OneToOneRingBufferSpy spy = new OneToOneRingBufferSpy(atomicBuffer); + spy.spyAt(ZERO); + return spy; + } + + public static final class Builder + { + private long capacity; + private Path path; + + public Builder capacity( + long capacity) + { + this.capacity = capacity; + return this; + } + + public Builder path( + Path path) + { + this.path = path; + return this; + } + + public EventsLayout build() + { + RingBuffer ringBuffer = createRingBuffer(path, capacity); + RingBufferSpy ringBufferSpy = createRingBufferSpy(path); + return new EventsLayout(path, capacity, ringBuffer, ringBufferSpy); + } + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java index 63be6936fb..f07bfe1a2f 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java @@ -42,6 +42,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.channels.SelectableChannel; +import java.time.Clock; import java.time.Duration; import java.util.BitSet; import java.util.Collection; @@ -60,6 +61,7 @@ import java.util.function.LongFunction; import java.util.function.LongSupplier; import java.util.function.LongUnaryOperator; +import java.util.function.Supplier; import org.agrona.DeadlineTimerWheel; import org.agrona.DeadlineTimerWheel.TimerHandler; @@ -86,6 +88,7 @@ import io.aklivity.zilla.runtime.engine.binding.BindingContext; import io.aklivity.zilla.runtime.engine.binding.BindingHandler; import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor; import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor; import io.aklivity.zilla.runtime.engine.buffer.BufferPool; @@ -108,6 +111,7 @@ import io.aklivity.zilla.runtime.engine.internal.exporter.ExporterAgent; import io.aklivity.zilla.runtime.engine.internal.layouts.BudgetsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.BufferPoolLayout; +import io.aklivity.zilla.runtime.engine.internal.layouts.EventsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.StreamsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout; @@ -210,6 +214,8 @@ public class EngineWorker implements EngineContext, Agent private final ScalarsLayout countersLayout; private final ScalarsLayout gaugesLayout; private final HistogramsLayout histogramsLayout; + private final EventsLayout eventsLayout; + private final Supplier supplyEventReader; private long initialId; private long promiseId; private long traceId; @@ -232,6 +238,7 @@ public EngineWorker( Collection models, Collection metricGroups, Collector collector, + Supplier supplyEventReader, int index, boolean readonly) { @@ -285,6 +292,11 @@ public EngineWorker( .readonly(readonly) .build(); + this.eventsLayout = new EventsLayout.Builder() + .path(config.directory().resolve(String.format("events%d", index))) + .capacity(config.eventsBufferCapacity()) + .build(); + this.agentName = String.format("engine/data#%d", index); this.streamsLayout = streamsLayout; this.bufferPoolLayout = bufferPoolLayout; @@ -407,6 +419,7 @@ public EngineWorker( this.idleStrategy = idleStrategy; this.errorHandler = errorHandler; this.exportersById = new Long2ObjectHashMap<>(); + this.supplyEventReader = supplyEventReader; } public static int indexOfId( @@ -441,6 +454,14 @@ public String supplyLocalName( return labels.lookupLabel(NamespacedId.localId(namespacedId)); } + @Override + public String supplyQName( + long namespacedId) + { + return String.format("%s.%s", labels.lookupLabel(NamespacedId.namespaceId(namespacedId)), + labels.lookupLabel(NamespacedId.localId(namespacedId))); + } + @Override public int supplyTypeId( String name) @@ -895,6 +916,18 @@ public LongConsumer supplyHistogramWriter( return histogramsLayout.supplyWriter(bindingId, metricId); } + @Override + public MessageConsumer supplyEventWriter() + { + return this.eventsLayout::writeEvent; + } + + @Override + public Clock clock() + { + return Clock.systemUTC(); + } + private void onSystemMessage( int msgTypeId, DirectBuffer buffer, @@ -1557,6 +1590,24 @@ public MessageConsumer supplyReceiver( return writersByIndex.computeIfAbsent(remoteIndex, supplyWriter); } + public int readEvent( + MessageConsumer handler, + int messageCountLimit) + { + return eventsLayout.readEvent(handler, messageCountLimit); + } + + public int peekEvent( + MessageConsumer handler) + { + return eventsLayout.peekEvent(handler); + } + + public MessageReader supplyEventReader() + { + return supplyEventReader.get(); + } + private MessageConsumer supplyWriter( int index) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java new file mode 100644 index 0000000000..4104b505ff --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java @@ -0,0 +1,167 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.engine.internal.spy; + +import static org.agrona.BitUtil.align; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.ALIGNMENT; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.HEADER_LENGTH; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.lengthOffset; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.typeOffset; +import static org.agrona.concurrent.ringbuffer.RingBuffer.PADDING_MSG_TYPE_ID; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.HEAD_POSITION_OFFSET; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.checkCapacity; + +import java.util.concurrent.atomic.AtomicLong; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.AtomicBuffer; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class OneToOneRingBufferSpy implements RingBufferSpy +{ + private final int capacity; + private final AtomicLong spyPosition; + private final AtomicBuffer buffer; + + public OneToOneRingBufferSpy( + final AtomicBuffer buffer) + { + this.buffer = buffer; + checkCapacity(buffer.capacity()); + capacity = buffer.capacity() - TRAILER_LENGTH; + + buffer.verifyAlignment(); + + spyPosition = new AtomicLong(); + } + + @Override + public void spyAt( + SpyPosition position) + { + switch (position) + { + case ZERO: + spyPosition.lazySet(0); + break; + case HEAD: + spyPosition.lazySet(buffer.getLong(capacity + HEAD_POSITION_OFFSET)); + break; + case TAIL: + spyPosition.lazySet(buffer.getLong(capacity + TAIL_POSITION_OFFSET)); + break; + } + } + + @Override + public DirectBuffer buffer() + { + return buffer; + } + + @Override + public long producerPosition() + { + return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + TAIL_POSITION_OFFSET); + } + + @Override + public long consumerPosition() + { + return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + HEAD_POSITION_OFFSET); + } + + @Override + public int spy( + final MessageConsumer handler) + { + return spy(handler, Integer.MAX_VALUE); + } + + @Override + public int spy( + final MessageConsumer handler, + final int messageCountLimit) + { + int messagesRead = 0; + + final AtomicBuffer buffer = this.buffer; + final long head = spyPosition.get(); + + int bytesRead = 0; + + final int capacity = this.capacity; + final int headIndex = (int)head & (capacity - 1); + final int contiguousBlockLength = capacity - headIndex; + + try + { + while (bytesRead < contiguousBlockLength && messagesRead < messageCountLimit) + { + final int recordIndex = headIndex + bytesRead; + final int recordLength = buffer.getIntVolatile(lengthOffset(recordIndex)); + if (recordLength <= 0) + { + break; + } + + bytesRead += align(recordLength, ALIGNMENT); + + final int messageTypeId = buffer.getInt(typeOffset(recordIndex)); + if (PADDING_MSG_TYPE_ID == messageTypeId) + { + continue; + } + + ++messagesRead; + handler.accept(messageTypeId, buffer, recordIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH); + } + } + finally + { + if (bytesRead != 0) + { + spyPosition.lazySet(head + bytesRead); + } + } + + return messagesRead; + } + + public int peek( + final MessageConsumer handler) + { + final AtomicBuffer buffer = this.buffer; + final long head = spyPosition.get(); + final int capacity = this.capacity; + final int headIndex = (int)head & (capacity - 1); + final int recordLength = buffer.getIntVolatile(lengthOffset(headIndex)); + int messagesPeeked = 0; + if (recordLength > 0) + { + final int messageTypeId = buffer.getInt(typeOffset(headIndex)); + if (PADDING_MSG_TYPE_ID != messageTypeId) + { + handler.accept(messageTypeId, buffer, headIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH); + messagesPeeked = 1; + } + } + return messagesPeeked; + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java new file mode 100644 index 0000000000..a863b76d1e --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.engine.internal.spy; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public interface RingBufferSpy +{ + enum SpyPosition + { + ZERO, + HEAD, + TAIL + } + + void spyAt(SpyPosition position); + + int spy(MessageConsumer handler); + int spy(MessageConsumer handler, int messageCountLimit); + int peek(MessageConsumer handler); + + long producerPosition(); + long consumerPosition(); + + DirectBuffer buffer(); +} diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java index 711dca56e2..4ae65aba4e 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java @@ -34,6 +34,7 @@ import io.aklivity.zilla.runtime.engine.Engine; import io.aklivity.zilla.runtime.engine.EngineConfiguration; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.ext.EngineExtContext; import io.aklivity.zilla.runtime.engine.ext.EngineExtSpi; @@ -250,6 +251,30 @@ public void shouldNotConfigureUnknownScheme() throws Exception } } + @Test + public void shouldReadEvents() + { + List errors = new LinkedList<>(); + EngineConfiguration config = new EngineConfiguration(properties); + try (Engine engine = Engine.builder() + .config(config) + .errorHandler(errors::add) + .build()) + { + engine.start(); + MessageReader events = engine.supplyEventReader(); + events.read((m, b, i, l) -> {}, 1); + } + catch (Throwable ex) + { + errors.add(ex); + } + finally + { + assertThat(errors, empty()); + } + } + public static final class TestEngineExt implements EngineExtSpi { public static volatile CountDownLatch registerLatch = new CountDownLatch(1); diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java new file mode 100644 index 0000000000..a0aeda974b --- /dev/null +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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 io.aklivity.zilla.runtime.engine.internal.layouts; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; + +public class EventsLayoutTest +{ + private static final Path PATH = Paths.get("target/zilla-itests/events0"); + private static final int CAPACITY = 1024; + + private int msgTypeId; + + @Test + public void shouldWriteAndReadEvents() + { + // GIVEN + EventsLayout layout = new EventsLayout.Builder() + .path(PATH) + .capacity(CAPACITY) + .build(); + layout.writeEvent(42, new UnsafeBuffer(), 0, 0); + msgTypeId = 0; + + // WHEN + int count = layout.readEvent(this::readEvent, 1); + + // THEN + assertThat(count, equalTo(1)); + assertThat(msgTypeId, equalTo(42)); + } + + private void readEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + this.msgTypeId = msgTypeId; + } +} diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java similarity index 98% rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java index 9109ab83c2..9d5d5070ae 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.layout.metrics; +package io.aklivity.zilla.runtime.engine.internal.layouts.metrics; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -28,8 +28,6 @@ import org.junit.Test; -import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout; - public class HistogramsLayoutTest { @Test diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java similarity index 96% rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java index bbfdefa615..0a31ac6eb1 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.layout.metrics; +package io.aklivity.zilla.runtime.engine.internal.layouts.metrics; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -28,8 +28,6 @@ import org.junit.Test; -import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout; - public class ScalarsLayoutTest { @Test diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java index 1e872dfcdd..ccc663209e 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java @@ -50,6 +50,8 @@ public TestGuardHandler( @Override public long reauthorize( + long traceId, + long bindingId, long contextId, String credentials) { diff --git a/runtime/guard-jwt/pom.xml b/runtime/guard-jwt/pom.xml index 143b7c3182..fa3544628a 100644 --- a/runtime/guard-jwt/pom.xml +++ b/runtime/guard-jwt/pom.xml @@ -103,6 +103,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core jwt + io.aklivity.zilla.runtime.guard.jwt.internal.types + + + + + generate + + + + com.mycila license-maven-plugin @@ -185,6 +201,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/runtime/guard/jwt/internal/types/**/*.class + BUNDLE diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java new file mode 100644 index 0000000000..99d0fa6146 --- /dev/null +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.guard.jwt.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.guard.jwt.internal.types.event.JwtEventFW; + +public class JwtEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final JwtEventFW.Builder jwtEventRW = new JwtEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int jwtTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public JwtEventContext( + EngineContext context) + { + this.jwtTypeId = context.supplyTypeId(JwtGuard.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void authorizationFailed( + long traceId, + long bindingId, + String identity) + { + JwtEventFW event = jwtEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .authorizationFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + ) + .build(); + eventWriter.accept(jwtTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java index 1412213c25..55c141be78 100644 --- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java @@ -28,12 +28,14 @@ final class JwtGuardContext implements GuardContext { private final Long2ObjectHashMap handlersById; private final LongSupplier supplyAuthorizedId; + private final EngineContext context; JwtGuardContext( Configuration config, EngineContext context) { this.handlersById = new Long2ObjectHashMap<>(); + this.context = context; this.supplyAuthorizedId = context::supplyAuthorizedId; } @@ -42,7 +44,7 @@ public JwtGuardHandler attach( GuardConfig guard) { JwtOptionsConfig options = (JwtOptionsConfig) guard.options; - JwtGuardHandler handler = new JwtGuardHandler(options, supplyAuthorizedId, guard.readURL); + JwtGuardHandler handler = new JwtGuardHandler(options, context, supplyAuthorizedId, guard.readURL); handlersById.put(guard.id, handler); return handler; } diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java index 54f2358905..b34a2edd36 100644 --- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java @@ -42,6 +42,7 @@ import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.lang.JoseException; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.guard.GuardHandler; import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeyConfig; import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeySetConfig; @@ -59,9 +60,11 @@ public class JwtGuardHandler implements GuardHandler private final Long2ObjectHashMap sessionsById; private final LongSupplier supplyAuthorizedId; private final Long2ObjectHashMap sessionStoresByContextId; + private final JwtEventContext event; public JwtGuardHandler( JwtOptionsConfig options, + EngineContext context, LongSupplier supplyAuthorizedId, Function readURL) { @@ -113,14 +116,18 @@ public JwtGuardHandler( this.supplyAuthorizedId = supplyAuthorizedId; this.sessionsById = new Long2ObjectHashMap<>(); this.sessionStoresByContextId = new Long2ObjectHashMap<>(); + this.event = new JwtEventContext(context); } @Override public long reauthorize( + long traceId, + long bindingId, long contextId, String credentials) { JwtSession session = null; + String subject = null; authorize: try @@ -147,6 +154,7 @@ public long reauthorize( String payload = signature.getPayload(); JwtClaims claims = JwtClaims.parse(payload); + subject = claims.getSubject(); NumericDate notBefore = claims.getNotBefore(); NumericDate notAfter = claims.getExpirationTime(); String issuer = claims.getIssuer(); @@ -161,7 +169,6 @@ public long reauthorize( break authorize; } - String subject = claims.getSubject(); List roles = Optional.ofNullable(claims.getClaimValue("scope")) .map(s -> s.toString().intern()) .map(s -> s.split("\\s+")) @@ -185,7 +192,10 @@ public long reauthorize( { // not authorized } - + if (session == null) + { + event.authorizationFailed(traceId, bindingId, subject); + } return session != null ? session.authorized : NOT_AUTHORIZED; } diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java index 88649c6a63..90bd4add79 100644 --- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java +++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java @@ -24,8 +24,11 @@ import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.security.KeyPair; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.function.Function; @@ -34,14 +37,27 @@ import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.lang.JoseException; +import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; import io.aklivity.zilla.runtime.guard.jwt.config.JwtOptionsConfig; public class JwtGuardHandlerTest { private static final Function READ_KEYS_URL = url -> "{}"; + private EngineContext context; + + @Before + public void init() + { + context = mock(EngineContext.class); + when(context.clock()).thenReturn(mock(Clock.class)); + when(context.supplyEventWriter()).thenReturn(mock(MessageConsumer.class)); + } + @Test public void shouldAuthorize() throws Exception { @@ -53,7 +69,7 @@ public void shouldAuthorize() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -66,7 +82,7 @@ public void shouldAuthorize() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, not(equalTo(0L))); assertThat(guard.identity(sessionId), equalTo("testSubject")); @@ -86,7 +102,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -99,7 +115,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); } @@ -115,7 +131,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -127,7 +143,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); } @@ -143,7 +159,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -156,7 +172,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertFalse(guard.challenge(sessionId, now.plusSeconds(5L).toEpochMilli())); } @@ -172,7 +188,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -185,7 +201,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); @@ -201,7 +217,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -209,7 +225,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS512"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -223,7 +239,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -233,7 +249,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception .replaceFirst("\\.[^X]", ".X") .replaceFirst("\\.[^Y]", ".Y"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -247,7 +263,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "not test issuer"); @@ -255,7 +271,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -269,7 +285,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -277,7 +293,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -291,7 +307,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -302,7 +318,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -316,7 +332,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -327,7 +343,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -343,7 +359,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -352,7 +368,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, not(equalTo(0L))); assertFalse(guard.verify(sessionId, asList("read:stream", "write:stream"))); @@ -369,7 +385,7 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -382,12 +398,12 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis())); @@ -404,7 +420,7 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -417,13 +433,13 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); claims.setClaim("scope", "read:stream write:stream"); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis())); @@ -440,7 +456,7 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -453,12 +469,12 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 5L); String tokenPlus5 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus5 = guard.reauthorize(101L, tokenPlus5); + long sessionIdPlus5 = guard.reauthorize(0L, 0L, 101L, tokenPlus5); assertThat(sessionIdPlus5, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -475,7 +491,7 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -488,13 +504,13 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); claims.setClaim("scope", "read:stream"); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -512,7 +528,7 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -525,13 +541,13 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("sub", "otherSubject"); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -549,7 +565,7 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -562,12 +578,12 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(202L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 202L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -585,7 +601,7 @@ public void shouldDeauthorize() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -598,7 +614,7 @@ public void shouldDeauthorize() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); guard.deauthorize(sessionId); } diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java index 9d54435ab1..1f41b642a8 100644 --- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java +++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java @@ -167,7 +167,7 @@ public void shouldNotVerifyRolesWhenInsufficient() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long authorizedId = handler.reauthorize(101L, token); + long authorizedId = handler.reauthorize(0L, 0L, 101L, token); assertFalse(verifier.test(authorizedId)); } @@ -220,7 +220,7 @@ public void shouldVerifyRolesWhenExact() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -272,7 +272,7 @@ public void shouldVerifyRolesWhenSuperset() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -323,7 +323,7 @@ public void shouldVerifyRolesWhenEmpty() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -374,7 +374,7 @@ public void shouldVerifyWhenIndexDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -425,7 +425,7 @@ public void shouldIdentify() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertEquals("testSubject", identifier.apply(sessionId)); } @@ -478,7 +478,7 @@ public void shouldIdentifyWhenIndexDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertEquals("testSubject", identifier.apply(sessionId)); } diff --git a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl index 20887c8bdd..5bffe232f1 100644 --- a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl +++ b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl @@ -49,4 +49,26 @@ scope http HttpHeader[] headers; } } + + scope event + { + enum HttpEventType (uint8) + { + REQUEST_ACCEPTED (1) + } + + struct HttpRequestAccepted extends core::event::Event + { + string8 identity; + string8 scheme; + string8 method; + string16 authority; + string16 path; + } + + union HttpEvent switch (HttpEventType) + { + case REQUEST_ACCEPTED: HttpRequestAccepted requestAccepted; + } + } } diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt index 90377a5931..2369372d0d 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt @@ -33,7 +33,7 @@ connected write "GET / HTTP/1.1" "\r\n" "Host: example.com:9090" "\r\n" - "Authorization: Bearer EXPIRED" "\r\n" + "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n" "\r\n" write flush diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt index 5761cef887..f091a35b5d 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt @@ -32,7 +32,7 @@ connected read "GET / HTTP/1.1" "\r\n" "Host: example.com:9090" "\r\n" - "Authorization: Bearer EXPIRED" "\r\n" + "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n" "\r\n" read closed diff --git a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl index a3c0874d9d..a9f3222680 100644 --- a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl +++ b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl @@ -533,4 +533,25 @@ scope kafka int32 index; } } + + scope event + { + enum KafkaEventType (uint8) + { + AUTHORIZATION_FAILED (1), + API_VERSION_REJECTED (2) + } + + struct KafkaApiVersionRejected extends core::event::Event + { + int32 apiKey; + int32 apiVersion; + } + + union KafkaEvent switch (KafkaEventType) + { + case AUTHORIZATION_FAILED: core::event::Event authorizationFailed; + case API_VERSION_REJECTED: KafkaApiVersionRejected apiVersionRejected; + } + } } diff --git a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl index 26730b7747..9431a6b159 100644 --- a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl +++ b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl @@ -260,6 +260,5 @@ scope mqtt COMPLETE(0), INCOMPLETE(1) } - } } diff --git a/specs/binding-tcp.spec/pom.xml b/specs/binding-tcp.spec/pom.xml index 3a64fc98ba..94ef266ac7 100644 --- a/specs/binding-tcp.spec/pom.xml +++ b/specs/binding-tcp.spec/pom.xml @@ -73,6 +73,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core tcp proxy + io.aklivity.zilla.specs.binding.tcp.internal.types + + + + + generate + + + + com.mycila license-maven-plugin diff --git a/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl new file mode 100644 index 0000000000..a796c117f5 --- /dev/null +++ b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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. + */ +scope tcp +{ + scope event + { + enum TcpEventType (uint8) + { + DNS_FAILED (1) + } + + struct TcpDnsFailed extends core::event::Event + { + string16 address; + } + + union TcpEvent switch (TcpEventType) + { + case DNS_FAILED: TcpDnsFailed dnsFailed; + } + } +} diff --git a/specs/binding-tls.spec/pom.xml b/specs/binding-tls.spec/pom.xml index dd0ca5341e..d38dbf81e7 100644 --- a/specs/binding-tls.spec/pom.xml +++ b/specs/binding-tls.spec/pom.xml @@ -78,6 +78,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core tls proxy + io.aklivity.zilla.specs.binding.tls.internal.types + + + + + generate + + + + com.mycila license-maven-plugin diff --git a/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl new file mode 100644 index 0000000000..c81e96e2c7 --- /dev/null +++ b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity 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. + */ +scope tls +{ + scope event + { + enum TlsEventType (uint8) + { + TLS_FAILED (1), + TLS_PROTOCOL_REJECTED (2), + TLS_KEY_REJECTED (3), + TLS_PEER_NOT_VERIFIED (4), + TLS_HANDSHAKE_FAILED (5) + } + + union TlsEvent switch (TlsEventType) + { + case TLS_FAILED: core::event::Event tlsFailed; + case TLS_PROTOCOL_REJECTED: core::event::Event tlsProtocolRejected; + case TLS_KEY_REJECTED: core::event::Event tlsKeyRejected; + case TLS_PEER_NOT_VERIFIED: core::event::Event tlsPeerNotVerified; + case TLS_HANDSHAKE_FAILED: core::event::Event tlsHandshakeFailed; + } + } +} diff --git a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl index bc78dd0888..05dfc800aa 100644 --- a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl +++ b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl @@ -100,4 +100,14 @@ scope core octets extension; } } + + scope event + { + struct Event + { + int64 timestamp; + int64 traceId; + int64 namespacedId; + } + } } diff --git a/specs/guard-jwt.spec/pom.xml b/specs/guard-jwt.spec/pom.xml index ebe02ac328..ab5d123aed 100644 --- a/specs/guard-jwt.spec/pom.xml +++ b/specs/guard-jwt.spec/pom.xml @@ -63,6 +63,23 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core jwt + io.aklivity.zilla.specs.guard.jwt.internal.types + + + + + validate + generate + + + + com.mycila license-maven-plugin @@ -86,6 +103,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/specs/guard/jwt/internal/types/**/*.class + BUNDLE diff --git a/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl new file mode 100644 index 0000000000..997f9bf859 --- /dev/null +++ b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +scope jwt +{ + scope event + { + enum JwtEventType (uint8) + { + AUTHORIZATION_FAILED (1) + } + + struct JwtAuthorizationFailed extends core::event::Event + { + string8 identity; + } + + union JwtEvent switch (JwtEventType) + { + case AUTHORIZATION_FAILED: JwtAuthorizationFailed authorizationFailed; + } + } +}