From d9168f1dc208454e531507871cf342b87090d99a Mon Sep 17 00:00:00 2001 From: April M Date: Mon, 14 Oct 2024 15:22:16 -0700 Subject: [PATCH 01/18] random auditing pt 1 --- .../astream-subscriptions-exclusive.adoc | 23 +++++------ .../pages/astream-subscriptions-failover.adoc | 20 +++++----- .../astream-subscriptions-keyshared.adoc | 22 +++++----- .../pages/astream-subscriptions-shared.adoc | 17 ++++---- modules/ROOT/pages/astream-subscriptions.adoc | 12 +++--- modules/ROOT/pages/index.adoc | 6 +-- modules/apis/pages/index.adoc | 27 ++++++++----- modules/developing/pages/astra-cli.adoc | 6 +-- .../developing/pages/astream-functions.adoc | 30 +++++++------- modules/developing/pages/astream-kafka.adoc | 7 ++-- modules/developing/pages/astream-rabbit.adoc | 11 +++-- .../pages/clients/spring-produce-consume.adoc | 2 +- .../pages/configure-pulsar-env.adoc | 4 +- .../pages/gpt-schema-translator.adoc | 17 +++++--- .../pages/produce-consume-astra-portal.adoc | 12 +++--- .../pages/produce-consume-pulsar-client.adoc | 6 +-- modules/developing/pages/using-curl.adoc | 10 ++--- modules/getting-started/pages/index.adoc | 14 ++++--- .../pages/astream-georeplication.adoc | 18 ++------- modules/operations/pages/astream-limits.adoc | 15 ++++--- .../pages/astream-scrape-metrics.adoc | 40 +++++++++++-------- .../operations/pages/astream-token-gen.adoc | 20 +++++----- .../operations/pages/monitoring/index.adoc | 2 +- .../pages/monitoring/integration.adoc | 2 +- .../operations/pages/monitoring/metrics.adoc | 2 +- .../pages/monitoring/namespace-dashboard.adoc | 2 +- .../pages/monitoring/new-relic.adoc | 2 +- .../pages/monitoring/overview-dashboard.adoc | 6 +-- .../pages/monitoring/topic-dashboard.adoc | 2 +- modules/operations/pages/onboarding-faq.adoc | 3 +- modules/ragstack/pages/index.adoc | 4 +- modules/ragstack/pages/quickstart.adoc | 17 ++++---- 32 files changed, 198 insertions(+), 183 deletions(-) diff --git a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc index eebb95e..ca5e85a 100644 --- a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc +++ b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc @@ -2,9 +2,9 @@ :navtitle: Exclusive :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +_Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -An *exclusive* subscription describes a basic publish-subscribe pattern where a *single consumer* subscribes to a *single topic* and consumes from it. + +An _exclusive* subscription_ describes a basic publish-subscribe pattern where a single consumer subscribes to a single topic and consumes from it. This document explains how to use Pulsar's exclusive subscription model to manage your topic consumption. @@ -13,8 +13,9 @@ include::partial$subscription-prereq.adoc[] [#example] == Exclusive subscription example -This example uses the `pulsarConsumer` object in `SimplePulsarConsumer.java` below. + +This example uses the following `pulsarConsumer` object in `SimplePulsarConsumer.java`: +.SimplePulsarConsumer.java [source,java] ---- pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) @@ -33,7 +34,7 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) Pulsar creates an exclusive subscription by default when no `subscriptionType` is declared. ==== -. Open the `pulsar-subscription-example` repo in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. + +. Open the `pulsar-subscription-example` repo in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. The confirmation message and a cursor appear to indicate the consumer is ready. + [source,bash] @@ -54,7 +55,7 @@ The confirmation message and a cursor appear to indicate the consumer is ready. . The consumer begins consuming the produced messages. + -[source,bash] +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":93573631,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":16931522,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -72,15 +73,13 @@ Exception in thread "main" java.lang.IllegalStateException: Cannot connect to pu at com.datastax.pulsar.SimplePulsarConsumer.main(SimplePulsarConsumer.java:53) Caused by: org.apache.pulsar.client.api.PulsarClientException$ConsumerBusyException: {"errorMsg":"Exclusive consumer is already connected","reqId":2964994443801550457, "remote":"", "local":"/192.168.0.95:55777"} ---- - -The second consumer can't subscribe to the topic because the subscription is *exclusive*. + - -In the example above, the consumer didn't declare a subscription type, so Pulsar created an exclusive subscription by default. + ++ +The second consumer can't subscribe to the topic because the subscription is exclusive. ++ +In the example above, the consumer didn't declare a subscription type, so Pulsar created an exclusive subscription by default. To explicitly define an exclusive subscription, add `.subscriptionType(SubscriptionType.Exclusive)` to the consumer. -== What's next - -For more on subscriptions, see: +== See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-shared.adoc[Shared subscriptions] diff --git a/modules/ROOT/pages/astream-subscriptions-failover.adoc b/modules/ROOT/pages/astream-subscriptions-failover.adoc index 1993ea2..b855dfb 100644 --- a/modules/ROOT/pages/astream-subscriptions-failover.adoc +++ b/modules/ROOT/pages/astream-subscriptions-failover.adoc @@ -2,10 +2,12 @@ :navtitle: Failover :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +_Subscriptions_*_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -If an xref:astream-subscriptions-exclusive.adoc[exclusive] consumer fails, the topic backlog balloons with stale, unacknowledged messages. Pulsar solves this problem with *failover* subscriptions. + -In *failover* subscriptions, Pulsar designates one *primary* consumer and multiple *standby* consumers. If the primary consumer disconnects, the standby consumers begin consuming the subsequent unacknowledged messages. + +If an xref:astream-subscriptions-exclusive.adoc[exclusive] consumer fails, the topic backlog balloons with stale, unacknowledged messages. +Pulsar solves this problem with failover subscriptions. +In _failover subscriptions_, Pulsar designates one primary consumer and multiple standby consumers. +If the primary consumer disconnects, the standby consumers begin consuming the subsequent unacknowledged messages. This document explains how to use Pulsar's failover subscription model to manage your topic consumption. @@ -30,8 +32,8 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- -. Open `pulsar-subscription-example` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. + -This is the *primary* consumer. + +. Open `pulsar-subscription-example` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. +This is the primary consumer. The confirmation message and a cursor appear to indicate the consumer is ready. + [source,bash] @@ -55,7 +57,7 @@ The confirmation message and a cursor appear to indicate the consumer is ready. . The primary consumer begins consuming messages. + -[source,bash] +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":50585599,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":98055337,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -66,7 +68,7 @@ The confirmation message and a cursor appear to indicate the consumer is ready. . Return to the *primary* consumer and `Ctrl+C` to stop it. The *backup* consumer begins consuming right where the first consumer left off. + -[source,bash] +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":73260535,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":42372149,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -80,9 +82,7 @@ Follow along with this video from our *Five Minutes About Pulsar* series to see video::ckB87OLs5eM[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -== What's next? - -For more on subscriptions, see: +== See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] diff --git a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc index 02ad377..ac9e64d 100644 --- a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc @@ -2,10 +2,12 @@ :navtitle: Key_Shared :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model trades an increased message processing rate for ordering guarantees. In a round-robin delivery, there's no way for the broker to know which messages are going to which consumer. + +*Key_Shared* subscriptions allow multiple consumers to subscribe to a topic, but with additional metadata in the form of *keys* which link messages to specific consumers. -Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model trades an increased message processing rate for ordering guarantees. In a round-robin delivery, there's no way for the broker to know which messages are going to which consumer. + -*Key_Shared* subscriptions allow multiple consumers to subscribe to a topic, but with additional metadata in the form of *keys* which link messages to specific consumers. + *Keys* are generated with *hashing*, which converts arbitrary values like "topic-name" or JSON blobs into fixed integer values. These hashed values are then assigned to subscribed consumers in one of two ways: * *Auto hash* - uses *consistent hashing* to balance range values across available consumers, without requiring manual setup of hash ranges. @@ -18,8 +20,10 @@ include::partial$subscription-prereq.adoc[] [#example] == Key_Shared subscription example -To try out a Pulsar Key_Shared subscription, add `.subscriptionType(SubscriptionType.Key_Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`. + -You must also tell Pulsar what `keySharedPolicy` this subscription will use. The example below uses the auto-hashing `keySharedPolicy`. +To try out a Pulsar Key_Shared subscription, add `.subscriptionType(SubscriptionType.Key_Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`. + +You must also tell Pulsar what `keySharedPolicy` this subscription will use. +The example below uses the auto-hashing `keySharedPolicy`. [source,java] ---- @@ -84,7 +88,7 @@ pulsarProducer = pulsarClient .keySharedPolicy(KeySharedPolicy.stickyHashRange().ranges(Range.of(0,65535))) ---- -. Open `pulsar-examples` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. + +. Open `pulsar-examples` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. The confirmation message and a cursor appear to indicate the consumer is ready. + [source,bash] @@ -106,7 +110,7 @@ The confirmation message and a cursor appear to indicate the consumer is ready. . The consumer begins receiving messages. + -[source,bash] +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":55794190,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":41791865,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -134,9 +138,7 @@ Follow along with this video from our *Five Minutes About Pulsar* series to see video::_49wlA53L_8[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -== What's next? - -For more on subscriptions, see: +== See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] diff --git a/modules/ROOT/pages/astream-subscriptions-shared.adoc b/modules/ROOT/pages/astream-subscriptions-shared.adoc index bd910b8..3e94de5 100644 --- a/modules/ROOT/pages/astream-subscriptions-shared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-shared.adoc @@ -2,9 +2,10 @@ :navtitle: Shared :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +A *shared* subscription allows *multiple consumers* to consume messages from a *single topic* in round-robin fashion. -A *shared* subscription allows *multiple consumers* to consume messages from a *single topic* in round-robin fashion. + More consumers in a shared subscription can increase your Pulsar deployment's rate of message consumption, but at the cost of losing message ordering guarantees and acknowledgement schemes. This document explains how to use Pulsar's shared subscription model to manage your topic consumption. @@ -30,7 +31,7 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- -. Open `pulsar-subscription-example` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. + +. Open `pulsar-subscription-example` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. The confirmation message and a cursor appear to indicate the consumer is ready. + [source,bash] @@ -53,7 +54,7 @@ The confirmation message and a cursor appear to indicate the consumer is ready. . The consumer begins receiving messages. + -[source,bash] +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":59819331,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":31365142,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -62,13 +63,13 @@ The confirmation message and a cursor appear to indicate the consumer is ready. . Open a new terminal window and run `SimplePulsarConsumer.java`. The new consumer subscribes to the topic and consumes a message. + -[source,bash] +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":70129519,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":48206643,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. Open as many terminals as you'd like and run `SimplePulsarConsumer.java`. All the consumers subscribe to the topic and consume messages in a round-robin fashion. +. Open as many terminals as you'd like and run `SimplePulsarConsumer.java`. All the consumers subscribe to the topic and consume messages in a round-robin fashion. If you run this test with xref:astream-subscriptions-exclusive.adoc[exclusive subscriptions], you can't attach more than once subscriber to the exclusive topic. Since this test uses shared subscriptions, you can attach multiple consumers to the topic. @@ -79,9 +80,7 @@ Follow along with this video from our *Five Minutes About Pulsar* series to see video::mmukXqGsauA[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -== What's next? - -For more on subscriptions, see: +== See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] diff --git a/modules/ROOT/pages/astream-subscriptions.adoc b/modules/ROOT/pages/astream-subscriptions.adoc index 1f1a246..9b1c180 100644 --- a/modules/ROOT/pages/astream-subscriptions.adoc +++ b/modules/ROOT/pages/astream-subscriptions.adoc @@ -2,7 +2,9 @@ :navtitle: Pulsar subscriptions overview :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. + +== Subscription metadata Subscriptions are managed in the broker as a collection of metadata about a topic and its subscribed consumers. This metadata includes: @@ -23,15 +25,15 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.BYTES) .subscribe(); ---- -Read on to use Pulsar's four types of subscriptions to manage your topic consumption. +== Subscription types * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] * xref:astream-subscriptions-shared.adoc[Shared subscriptions] * xref:astream-subscriptions-failover.adoc[Failover subscriptions] * xref:astream-subscriptions-keyshared.adoc[Key_shared subscriptions] -== What's next? +== See also -* Follow our xref:getting-started:index.adoc[simple guide] to get started with Astra now. -* For using Kafka with {product_name}, see xref:developing:astream-kafka.adoc[Starlight for Kafka]. +* xref:getting-started:index.adoc[] +* xref:developing:astream-kafka.adoc[] diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index 5dba272..0335a1e 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -41,7 +41,7 @@ always up to date by leveraging the event stream processing capabilities of {pro For details about about the {product_name} limits, see xref:operations:astream-limits.adoc[{product_name} Limits]. -== What's next? +== See also -* Follow our xref:getting-started:index.adoc[simple guide] to get started with Astra now. -* Browse the xref:apis:index.adoc[{product_name} API References]. +* xref:getting-started:index.adoc[] +* xref:apis:index.adoc[] diff --git a/modules/apis/pages/index.adoc b/modules/apis/pages/index.adoc index 641c1ac..f0d13b2 100644 --- a/modules/apis/pages/index.adoc +++ b/modules/apis/pages/index.adoc @@ -3,17 +3,26 @@ :description: Astra provides APIs to manage both DB and Streaming instances :page-tag: astra-streaming,dev,develop,pulsar -Management of Pulsar tenants and its resources are seperated into 2 APIs. The DevOps API is used to manage higher level objects associated with your account. The Pulsar Admin API is used to manage specific resources such as namespaces, topics, and subscriptions within a specific tenant. +You use two APIs to manage Pulsar tenants and their resources. -xref:astra-streaming:apis:attachment$devops.html[{product_name} DevOps API,role=external] +== {astra_stream} DevOps API -The Astra Streaming DevOps API is used to manage change data capture (CDC) settings, Pulsar tenants, geo-replications, Pulsar stats, and Pulsar tokens. This API uses your Astra Org token for bearer authentication. +Use the xref:astra-streaming:apis:attachment$devops.html[{astra_stream} DevOps API] to manage higher level objects associated with your account, such as the change data capture (CDC) settings, Pulsar tenants, geo-replications, Pulsar stats, and Pulsar tokens. -xref:astra-streaming:apis:attachment$pulsar-admin.html[{product_name} Pulsar Admin API,role=external] +This API uses an {astra_db} application token for authentication. -The Pulsar Admin API is used to manage Pulsar resources such as namespaces, topics, and subscriptions. This API uses your Pulsar token for bearer authentication. +== {astra_stream} Pulsar Admin API -[NOTE] -The open source Pulsar project offers https://pulsar.apache.org/admin-rest-api[documentation about the Pulsar Admin API]. However, the Pulsar Admin API in Astra Streaming is slightly different. In OSS Pulsar you manage instances, the clusters within each instance, the tenants in the cluster, etc. + - + -However, in Astra Streaming clusters are provided to you as a managed service, so you manage the tenants and resources within those tenants. Some endpoints have not been implemented in the Astra Streaming Pulsar Admin API because they are not applicable to the managed service. +Use the xref:astra-streaming:apis:attachment$pulsar-admin.html[{astra_stream} Pulsar Admin API] to manage specific resources within a specific tenant, such as namespaces, topics, and subscriptions. + +This API uses your Pulsar token for authentication. + +=== OSS Pulsar Admin API + +The https://pulsar.apache.org/admin-rest-api[open source Pulsar project's Pulsar Admin API] isn't the same as the {astra_stream} Pulsar Admin API. + +In OSS Pulsar you manage instances, the clusters within each instance, the tenants in the cluster, and so on. +In {astra_stream}, clusters are a managed service. +You manage only the tenants and resources within those tenants. + +Some OSS Pulsar Admin API endpoints aren't supported in the {astra_stream} Pulsar Admin API because they don't apply to the {astra_stream} managed service. \ No newline at end of file diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index 9fdb753..99327d9 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -70,9 +70,7 @@ Result:: -- ==== -== What's next? +== See also -* See the {astra_cli} https://docs.datastax.com/en/astra-cli/docs/0.2/[documentation]. - -* See the https://awesome-astra.github.io/docs/pages/astra/astra-cli/[Astra CLI install instructions]. +* https://docs.datastax.com/en/astra-cli/docs/0.2/[{astra_cli} documentation] diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index be490c2..fc4725b 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -24,8 +24,8 @@ Unqualified orgs can still use xref:streaming-learning:functions:index.adoc[tran Astra Streaming supports Python-based Pulsar Functions. These functions can be packaged in a ZIP file and deployed to {product_name} or Pulsar. The same ZIP file can be deployed to either environment. -We’ll create a ZIP file in the proper format, then use the pulsar-admin command to deploy the ZIP. -We’ll pass a “create function" configuration file (a .yaml file) as a parameter to pulsar-admin, which defines the Pulsar Function options and parameters. +We'll create a ZIP file in the proper format, then use the pulsar-admin command to deploy the ZIP. +We'll pass a create function YAML configuration file as a parameter to pulsar-admin, which defines the Pulsar Function options and parameters. Assuming the ZIP file is named `testpythonfunction.zip`, an unzipped `testpythonfunction.zip` folder looks like this: @@ -92,10 +92,9 @@ src === Deploy a Python function with configuration file -. Create a deployment configuration file. In this example we'll call this file “func-create-config.yaml”. -This file will be passed to the pulsar-admin create function command. + -The contents of the YAML file should be: +. Create a deployment configuration file with contents similar to the following `func-create-config.yaml` example: + +.func-create-config.yaml [source,yaml] ---- py: @@ -112,6 +111,8 @@ logTopic: userConfig: logging_level: ERROR ---- ++ +This file will be passed to the pulsar-admin create function command. . Use pulsar-admin to deploy the Python ZIP to Astra Streaming or Pulsar. The command below assumes you've properly configured the client.conf file for pulsar-admin commands against your Pulsar cluster. See the documentation xref:configure-pulsar-env.adoc[here] for more information. @@ -121,7 +122,7 @@ The command below assumes you've properly configured the client.conf file for pu bin/pulsar-admin functions create --function-config-file ---- -. Check results: Go to the {astra_ui} to see your newly deployed function listed under the “Functions” tab for your Tenant. See <> for more information on testing and monitoring your function in {product_name}. +. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product_name}. .. You can also use the pulsar-admin command to list your functions: + @@ -135,8 +136,8 @@ bin/pulsar-admin functions list --tenant --namespace Astra Streaming supports Java-based Pulsar Functions which are packaged in a JAR file. The JAR can be deployed to Astra Streaming or Pulsar. The same JAR file can be deployed to either environment. -We’ll create a JAR file using Maven, then use the pulsar-admin command to deploy the JAR. -We’ll pass a "create function" configuration file (a .yaml file) as a parameter to pulsar-admin, which defines the Pulsar function options and parameters. +We'll create a JAR file using Maven, then use the pulsar-admin command to deploy the JAR. +We'll pass a create function YAML configuration file as a parameter to pulsar-admin, which defines the Pulsar function options and parameters. . To deploy a JAR, first create the proper JAR with the Java code of the Pulsar Function. An example pom.xml file is shown below: @@ -232,10 +233,9 @@ Result:: -- ==== -. Create a deployment configuration file. In this example we'll call this file “func-create-config.yaml”. -This file will be passed to the pulsar-admin create function command. + -The contents of the YAML file should be: +. Create a deployment configuration file with contents similar to the following `func-create-config.yaml` example: + +.func-create-config.yaml [source,yaml] ---- jar: @@ -253,9 +253,11 @@ userConfig: logging_level: ERROR ---- + +This file will be passed to the pulsar-admin create function command. ++ [NOTE] ==== -Astra Streaming requires the “inputs” topic to have a message schema defined before deploying the function. Otherwise, deployment errors may occur. Use the {astra_ui} to define the message schema for a topic. +Astra Streaming requires the `inputs` topic to have a message schema defined before deploying the function. Otherwise, deployment errors may occur. Use the {astra_ui} to define the message schema for a topic. ==== + . Use pulsar-admin to deploy your new JAR to Astra Streaming or Pulsar. @@ -266,7 +268,7 @@ The command below assumes you've properly configured the client.conf file for pu bin/pulsar-admin functions create --function-config-file ---- -. Check results: Go to the {astra_ui} to see your newly deployed function listed under the “Functions” tab for your Tenant. See <> for more information on testing and monitoring your function in {product_name}. +. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product_name}. .. You can also use the pulsar-admin command to list your functions: + @@ -373,7 +375,7 @@ Upgrade your organization to a qualified organization by: == Testing Your Function -Triggering a function is a convenient way to test that the function is working. When you trigger a function, you are publishing a message on the function’s input topic, which triggers the function to run. If the function has an output topic and the function returns data to the output topic, that data is displayed. +Triggering a function is a convenient way to test that the function is working. When you trigger a function, you are publishing a message on the function's input topic, which triggers the function to run. If the function has an output topic and the function returns data to the output topic, that data is displayed. Send a test value with Pulsar CLI's `trigger` to test a function you've set up. diff --git a/modules/developing/pages/astream-kafka.adoc b/modules/developing/pages/astream-kafka.adoc index 7713a68..c3bf86f 100644 --- a/modules/developing/pages/astream-kafka.adoc +++ b/modules/developing/pages/astream-kafka.adoc @@ -100,8 +100,7 @@ Follow along with this video from our *Five Minutes About Pulsar* series to migr video::Qy2ZlelLjXg[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -== What's next? +== See also -* {kafka_for_astra} is based on the DataStax https://github.com/datastax/starlight-for-kafka[Starlight for Kafka] project. - -* Follow our xref:getting-started:index.adoc[simple guide] to get started with Astra now. +* https://github.com/datastax/starlight-for-kafka[{company} Starlight for Kafka project] +* xref:getting-started:index.adoc[] diff --git a/modules/developing/pages/astream-rabbit.adoc b/modules/developing/pages/astream-rabbit.adoc index c704929..64b318b 100644 --- a/modules/developing/pages/astream-rabbit.adoc +++ b/modules/developing/pages/astream-rabbit.adoc @@ -156,13 +156,12 @@ You should see new topics called `amq.default.__queuename` and `amq.default_rout |not used |Name of the header |channel.exchange_declare(exchange='header_logs', exchange_type='headers') -channel.basic_publish(exchange='header_logs', +channel.basic_publish(exchange='header_logs'), |=== -== What's Next? - -* {starlight_rabbitmq} is based on the DataStax https://github.com/datastax/starlight-for-rabbitmq[Starlight for RabbitMQ] project. -* Follow our xref:getting-started:index.adoc[simple guide] to get started with Astra now. -* For using Kafka with {product_name}, see xref:astream-kafka.adoc[Starlight for Kafka]. +== See also +* https://github.com/datastax/starlight-for-rabbitmq[{company} Starlight for RabbitMQ project] +* xref:getting-started:index.adoc[] +* xref:astream-kafka.adoc[Starlight for Kafka] \ No newline at end of file diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index 0222341..833f980 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -112,7 +112,7 @@ Message received: Hello World ==== . Remember to check in your {product_name} instance to see the message you sent to the newly created subscription "my-subscription". -== What's next? +== See also * xref:developing:configure-pulsar-env.adoc[] * xref:developing:astream-functions.adoc[] diff --git a/modules/developing/pages/configure-pulsar-env.adoc b/modules/developing/pages/configure-pulsar-env.adoc index b69d87e..21f9b18 100644 --- a/modules/developing/pages/configure-pulsar-env.adoc +++ b/modules/developing/pages/configure-pulsar-env.adoc @@ -98,9 +98,7 @@ Produce a message: ./bin/pulsar-client produce /default/ --messages "Hi there" --num-produce 1 ---- -== What's next? - -Now that your configuration is set up and validated, see: +== See also * xref:produce-consume-astra-portal.adoc[] * xref:produce-consume-pulsar-client.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/gpt-schema-translator.adoc b/modules/developing/pages/gpt-schema-translator.adoc index 663d5d7..5d28de9 100644 --- a/modules/developing/pages/gpt-schema-translator.adoc +++ b/modules/developing/pages/gpt-schema-translator.adoc @@ -493,8 +493,11 @@ passportnumber=value.passportNumber, age=value.age, firstname=value.firstName, l Notice that the `firstname` value becomes `firstName` because the Pulsar topic JSON schema supersedes the Cassandra table schema. == No schema on Pulsar topic -Even with no schema declared in the Pulsar topic, the schema translator will still provide a schema mapping that mirrors the values of your Cassandra table schema, without using GPT. + -For example, starting with this schema on a Cassandra table: + +If you don't declare a schema in the Pulsar topic, the schema translator provides a default schema mapping that mirrors the values of your Cassandra table schema, without using GPT. + +For example, assuming you have the following schema on a Cassandra table: + [source,cql] ---- { @@ -528,13 +531,15 @@ For example, starting with this schema on a Cassandra table: } ---- -Since you have an available schema in your Cassandra table, the button prompt will change to "Generate Mapping" to let you know you can create a topic schema from the Cassandra table schema. -Click on this button to generate a schema for the Pulsar topic from your Cassandra table schema. +Since you have an available schema in your Cassandra table, you can click *Generate Mapping* to create a Pulsar topic schema from the Cassandra table schema: + [source,bash] ---- passportnumber=value.passportnumber, age=value.age, firstname=value.firstname, lastname=value.lastname ---- -== What's next? +If there is no *Generate Mapping* button, then this function isn't available. + +== See also -For more on managing schema, see xref:streaming-learning:use-cases-architectures:change-data-capture/index.adoc[]. \ No newline at end of file +* xref:streaming-learning:use-cases-architectures:change-data-capture/index.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/produce-consume-astra-portal.adoc b/modules/developing/pages/produce-consume-astra-portal.adoc index fad4320..a49e1b1 100644 --- a/modules/developing/pages/produce-consume-astra-portal.adoc +++ b/modules/developing/pages/produce-consume-astra-portal.adoc @@ -35,7 +35,8 @@ Type the message "Hi there" in the *Send message* box and click the "Send" butto a|image:test-message-input.png[Send message in Astra Streaming] |=== -A message will be produced (ie: sent) to your selected topic and the consumer will consume (ie: retrieve) the message. + +A message will be produced (ie: sent) to your selected topic and the consumer will consume (ie: retrieve) the message. + The result is a chat style write and read. [width=80%] @@ -43,10 +44,11 @@ The result is a chat style write and read. a|image:try-me-test-message.png[Test message in Astra Streaming] |=== -{emoji-tada} Congratulations! You have sent and received your first message. + +{emoji-tada} Congratulations! You have sent and received your first message. + Now it's time to take your skills further {emoji-rocket}. -== What's next? +== See also -xref:astream-functions.adoc[] + -xref:astream-cdc.adoc[] \ No newline at end of file +* xref:astream-functions.adoc[] +* xref:astream-cdc.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/produce-consume-pulsar-client.adoc b/modules/developing/pages/produce-consume-pulsar-client.adoc index 9248724..12b8152 100644 --- a/modules/developing/pages/produce-consume-pulsar-client.adoc +++ b/modules/developing/pages/produce-consume-pulsar-client.adoc @@ -39,7 +39,7 @@ include::{astra-streaming-examples-repo}/pulsar-client/consume-output.sh[] If you made it here then your produce & consume journey was successful. Congrats{emoji-tada}! Now it's time to take your skills further {emoji-rocket}{emoji-rocket}. -== What's next? +== See also -xref:astream-functions.adoc[] + -xref:astream-cdc.adoc[] +* xref:astream-functions.adoc[] +* xref:astream-cdc.adoc[] diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index 601b317..b5a5708 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -8,12 +8,14 @@ When you create a tenant in Astra Streaming, all supporting APIs are enabled. To interact with your Astra Streaming tenant you will need two pieces of information: a token and the service URL. This guide will show you how to gather that information and test your connection. -TIP: The https://pulsar.apache.org/docs/2.10.x/reference-rest-api-overview/[Pulsar documentation]} has a full reference for each API supported in a cluster. + +TIP: The https://pulsar.apache.org/docs/2.10.x/reference-rest-api-overview/[Pulsar documentation] has a full reference for each API supported in a cluster. + This reference will be helpful as you look to automate certain actions in your Astra Streaming tenant. == Finding your tenant's web service URL -First, you need to know where to send your curl request. This is known as the "Web Service URL". + +First, you need to know where to send your curl request. +This is known as the "Web Service URL". [NOTE] ==== @@ -90,9 +92,7 @@ The output should be a very long JSON-formatted string containing details about TIP: If your curl command doesn't go so smoothly, add `-v` to your call. + The `-v` is for verbose, and prints more detail about what problem has occurred. -== What's next? - -Now it's time to put your tenant's API to use! Here are a few suggestions. +== See also * xref:streaming-learning:pulsar-io:connectors/index.adoc[] * xref:developing:astream-functions.adoc[] \ No newline at end of file diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index 6141768..1ba149b 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -271,13 +271,15 @@ Choose the right one for your needs (they all end up doing the same thing). [cols=3*,frame=none,grid=none] |=== -| -xref:developing:produce-consume-astra-portal.adoc[Astra Portal] + +a|xref:developing:produce-consume-astra-portal.adoc[Astra Portal] + [.small]##Using Astra Streaming's "Try Me" feature in the UI## -| -xref:developing:produce-consume-pulsar-client.adoc[Pulsar Client] + + +a|xref:developing:produce-consume-pulsar-client.adoc[Pulsar Client] + [.small]##Use the cli provided within Pulsar to interact with the topic## -| -xref:developing:clients/index.adoc[Runtime Clients] + + +a|xref:developing:clients/index.adoc[Runtime Clients] + [.small]##Create a client application that interacts with Pulsar (C#, Java, Python, etc)## |=== \ No newline at end of file diff --git a/modules/operations/pages/astream-georeplication.adoc b/modules/operations/pages/astream-georeplication.adoc index de10d2e..0f93521 100644 --- a/modules/operations/pages/astream-georeplication.adoc +++ b/modules/operations/pages/astream-georeplication.adoc @@ -73,23 +73,15 @@ To disable replication on a namespace, select **Disable Replication**. [#test] == Test georeplicated clusters -. Verify your user token can access tenant metadata. + -`allowedClusters` lists the clusters the tenant can be replicated to. +. Verify your user token can access tenant metadata. + -[tabs] -==== -Command:: -+ --- [source,bash] ---- bin/pulsar-admin tenants get ---- --- + -Result:: +In the result, the `allowedClusters` are the clusters the tenant can be replicated to: + --- [source,json] ---- { @@ -97,8 +89,6 @@ Result:: "allowedClusters" : [ "pulsar-gcp-useast4-staging", "pulsar-aws-useast1-staging", "pulsar-gcp-useast1-staging", "pulsar-azure-westus2-staging", "pulsar-aws-useast2-staging" ] } ---- --- -==== . Verify your `pulsar-admin` can view the replicated clusters for your namespace. + @@ -297,6 +287,6 @@ Result:: include::partial$georeplication-monitoring.adoc[] -== What's next? +== See also -For more on multiregion georeplication, including region awareness and rack awareness, see the https://pulsar.apache.org/docs/concepts-replication/[Pulsar docs]. +For more on multiregion georeplication, including region awareness and rack awareness, see the https://pulsar.apache.org/docs/concepts-replication/[Pulsar documentation]. diff --git a/modules/operations/pages/astream-limits.adoc b/modules/operations/pages/astream-limits.adoc index df78444..ad6ec68 100644 --- a/modules/operations/pages/astream-limits.adoc +++ b/modules/operations/pages/astream-limits.adoc @@ -30,15 +30,18 @@ Guardrails are initially provisioned in the default settings by {product_name} a |5MB (default) |Max throughput per topic for consumer -|5000 messages/s, 25MB/s + +|5000 messages per second, 25MB per second + Max throughput for a *non-partitioned topic* or a *single partition of a partitioned topic*. |Max throughput per topic for producer -|5000 messages/s, 25MB/s + +|5000 messages per second, 25MB per second + *Multiple producers* to a *non-partitioned topic* or a *single partition of a partitioned topic* can produce a *combined* 5000 messages per second maximum. |Max throughput per subscription -|5000 messages/s, 25MB/s + +|5000 messages per second, 25MB per second + *Multiple subscriptions* from a *non-partitioned topic* or a *single partition of a partitioned topic* can dispatch a *combined* 5000 messages per second maximum. |Auto topic creation @@ -73,7 +76,7 @@ Functions and connector resources are set in the control plane per pod resource The default settings are: * CPU: 0.25 core -* RAM: 500 MB +* RAM: 500MB [NOTE] ==== @@ -141,7 +144,7 @@ These namespace policies are initially provisioned in the default settings by {p |Yes |Backlog size -|5 GB, minimum setting is -1, which disables backlog size quota enforcement +|5GB, minimum setting is -1, which disables backlog size quota enforcement |Yes |Backlog Quota Retention Policy @@ -172,7 +175,7 @@ These namespace policies are initially provisioned in the default settings by {p [NOTE] ==== -The total characters of tenant name + namespace name + function name cannot exceed 54 characters. +The total characters of tenant name, namespace name, and function name cannot exceed 54 characters. This is a Kubernetes restriction based on a pod label's maximum size of 63 characters. You can read more about Kubernetes pod naming restrictions https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set[here]. ==== diff --git a/modules/operations/pages/astream-scrape-metrics.adoc b/modules/operations/pages/astream-scrape-metrics.adoc index 4321594..bb99411 100644 --- a/modules/operations/pages/astream-scrape-metrics.adoc +++ b/modules/operations/pages/astream-scrape-metrics.adoc @@ -4,9 +4,9 @@ Prometheus collects system metrics by scraping targets at intervals. These metri This doc will show you how to scrape an {product_name} tenant with Prometheus. -*Prerequisites:* + +== Prerequisites -* Astra Streaming tenant + +* Astra Streaming tenant * Docker installed locally == Get configuration file from {product_name} @@ -33,9 +33,10 @@ scrape_configs: - targets: [https://prometheus-aws-useast2.dev.streaming.datastax.com/pulsarmetrics/tenant-1] ---- + -This example `prometheus.yml` will scrape `+https://prometheus-aws-useast2.dev.streaming.datastax.com/pulsarmetrics/tenant-1+` every 60 seconds with the supplied Pulsar token. + -The `job_name` is added as a label to any timeseries scraped with this configuration. +This example `prometheus.yml` will scrape `\https://prometheus-aws-useast2.dev.streaming.datastax.com/pulsarmetrics/tenant-1` every 60 seconds with the supplied Pulsar token. + +The `job_name` is added as a label to any timeseries scraped with this configuration. + . Copy and paste the configuration code, or download it as a `.yml` file (it will be called `prometheus.yml`). == Build Prometheus with custom yml file @@ -43,6 +44,7 @@ The `job_name` is added as a label to any timeseries scraped with this configura Prometheus runs with a `prometheus.yml` file found either locally or in a Docker container. For this example, we'll tell Docker to run Prometheus from our downloaded `prometheus.yml` file. . Pull the Prometheus Docker image with `docker pull prom/prometheus`. + . Bind-mount your modified `prometheus.yml` file by running the Prometheus Docker container with a modified path in the `-v` argument. + [source,docker] @@ -69,8 +71,11 @@ If you encounter a `mounts denied` permissions error, ensure your local director == Scrape with Prometheus . Open your Prometheus dashboard at `localhost:9090`. In the *Status -> Targets* window, you should see the endpoint targeted in `static_configs` in an *UP* state. + . Navigate to the *Graph* window. Enter `pulsar_in_messages_total` in the *Expression* field and select *Execute*. Prometheus will now display total incoming Pulsar messages to your {product_name} cluster. + . Produce a few messages in your tenant with the Pulsar CLI or the {product_name} Websocket. + . Your Prometheus graph displays the number of incoming Pulsar messages with each 60 second scrape. + image::astream-prometheus-graph.png[Scraping {product_name} with Prometheus] @@ -79,8 +84,9 @@ You're scraping your {product_name} tenant with Prometheus! == Content encoding -{product_name} supports content encoding with either `gzip` or `deflate`. + -With the example from above still running, use a `curl` request to decompress your Prometheus scrape data. +{product_name} supports content encoding with either `gzip` or `deflate`. + +With the example from above still running, use a `curl` request to decompress your Prometheus scrape data: [tabs] ==== @@ -103,7 +109,7 @@ include::example$curl_gzip.sh[] -- ==== -*Deflate* or *Gzip* will extract your scraped metrics in a format like below: +*Deflate* or *Gzip* will extract your scraped metrics in a format such as the following: [source,console] ---- @@ -113,12 +119,7 @@ pulsar_topics_count{app="pulsar",cluster="pulsar-aws-useast1",component="broker" == Metrics exposed by {product_name} -Here's a list of Prometheus metrics exposed by {product_name}. - -[NOTE] -==== -Cluster operational metrics are *not* exposed to individual cluster tenants. A tenant can only access its own metrics on the *broker* or *function worker* pods. -==== +The following Prometheus metrics are exposed by {product_name}: * https://pulsar.apache.org/docs/reference-metrics/#namespace-metrics[Namespace metrics] * https://pulsar.apache.org/docs/reference-metrics/#topic-metrics[Topic metrics] @@ -127,9 +128,14 @@ Cluster operational metrics are *not* exposed to individual cluster tenants. A t * https://pulsar.apache.org/docs/reference-metrics/#pulsar-functions[Pulsar Functions metrics] * https://pulsar.apache.org/docs/reference-metrics/#connectors[Connector metrics] -== What's next? +[NOTE] +==== +Cluster operational metrics are *not* exposed to individual cluster tenants. A tenant can only access its own metrics on the *broker* or *function worker* pods. +==== + +== See also -* For more on monitoring Astra Streaming with Prometheus and Grafana, see xref:monitoring/index.adoc[]. -* Follow our xref:getting-started:index.adoc[getting started guide] to get started with Astra now. -* For more on Prometheus, see the https://prometheus.io/docs/introduction/overview/[Prometheus docs]. +* xref:monitoring/index.adoc[] +* xref:getting-started:index.adoc[] +* https://prometheus.io/docs/introduction/overview/[Prometheus documentation] diff --git a/modules/operations/pages/astream-token-gen.adoc b/modules/operations/pages/astream-token-gen.adoc index 8364547..bc2f1e9 100644 --- a/modules/operations/pages/astream-token-gen.adoc +++ b/modules/operations/pages/astream-token-gen.adoc @@ -101,9 +101,9 @@ The API results will show the UUID for each role id. + . In the command-line interface associated with your environment, paste the following environment variable copied from token generation: + -[source,shell] +[source,shell,subs="+quotes"] ---- -export ASTRA_DB_APPLICATION_TOKEN=<> +export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** ---- [#astra-token-ui] @@ -123,9 +123,9 @@ After you navigate away from the page, you won't be able to download your _Clien + . In the command-line interface associated with your environment, paste the following environment variable copied from token generation: + -[source,shell] +[source,shell,subs="+quotes"] ---- -export ASTRA_DB_APPLICATION_TOKEN=<> +export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** ---- + . You can now execute DevOps API commands from your terminal to your database. @@ -157,12 +157,12 @@ Download your Pulsar connection info as detailed https://docs.datastax.com/en/as In the command-line interface associated with your environment, paste the following environment variables copied for {product_name}: -[source,shell] +[source,shell,subs="+quotes"] ---- -export TENANT= -export INPUT_TOPIC= +export TENANT=**TENANT_NAME** +export INPUT_TOPIC=**INPUT_TOPIC_NAME** export NAMESPACE=default -export BEARER_TOKEN= +export BEARER_TOKEN=**PULSAR_TOKEN** ---- You can now execute Pulsar admin commands from your terminal to your database. @@ -237,6 +237,6 @@ curl --request GET \ ---- |=== -== What's next? +== See also -Use your new token to start streaming with the xref:getting-started:index.adoc[]. +* *xref:getting-started:index.adoc[] diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index 72e43a5..e9f4acb 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -155,7 +155,7 @@ For example, if the average message backlog increase rate exceeds a threshold, a The actual threshold values for these metrics is highly dependent on each application's workload and requirements, but the values should be relatively large positive numbers, e.g. several hundreds or several thousands. Otherwise, they may trigger too many false alarms. -== What's next? +== See also * For a list of exposed endpoints for Astra Streaming metrics, see xref:monitoring/metrics.adoc[]. * To scrape Astra Streaming metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index 616359f..7dd6001 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -95,7 +95,7 @@ One common issue that we see when integrating Astra Streaming metrics into an ex To fix this issue, go to the {astra_ui} and create a new JWT token (preferably with no expiration date). Get the new token value and repeat the above procedure. For more, see xref:astream-token-gen.adoc[]. -== What's next? +== See also * For a list of exposed endpoints for Astra Streaming metrics, see xref:monitoring/metrics.adoc[]. * To scrape Astra Streaming metrics into New Relic, see xref:monitoring/new-relic.adoc[]. diff --git a/modules/operations/pages/monitoring/metrics.adoc b/modules/operations/pages/monitoring/metrics.adoc index 2f3b67e..1fbbfdc 100644 --- a/modules/operations/pages/monitoring/metrics.adoc +++ b/modules/operations/pages/monitoring/metrics.adoc @@ -14,7 +14,7 @@ Dashboards are available for scraping metrics at Pulsar's tenant, namespace, and * xref:monitoring/namespace-dashboard.adoc[Namespace Dashboard] * xref:monitoring/topic-dashboard.adoc[Topic Dashboard] -== What's next? +== See also * To scrape Astra Streaming metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. * To scrape Astra Streaming metrics into New Relic, see xref:monitoring/new-relic.adoc[]. \ No newline at end of file diff --git a/modules/operations/pages/monitoring/namespace-dashboard.adoc b/modules/operations/pages/monitoring/namespace-dashboard.adoc index 00957d1..3f181c7 100644 --- a/modules/operations/pages/monitoring/namespace-dashboard.adoc +++ b/modules/operations/pages/monitoring/namespace-dashboard.adoc @@ -49,6 +49,6 @@ Geo-replication displays the time series metrics chart summarized at the namespa |Total (outgoing) message replication backlog from the namespace divided by remote clusters |=== -== What's next? +== See also Dashboards are available for scraping metrics at Pulsar's xref:monitoring/overview-dashboard.adoc[tenant], xref:monitoring/namespace-dashboard.adoc[namespace], and xref:monitoring/topic-dashboard.adoc[topic] levels. \ No newline at end of file diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index 0e4cc05..ed2e1ac 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -60,7 +60,7 @@ The top section of this configuration is for scraping Astra Streaming metrics (a .Pulsar message backlog metrics in New Relic image::pulsar-namespace-metrics.png[Metrics,align="center"] -== What's next? +== See also * For a list of exposed endpoints for Astra Streaming metrics, see xref:monitoring/metrics.adoc[]. * To scrape Astra Streaming metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. diff --git a/modules/operations/pages/monitoring/overview-dashboard.adoc b/modules/operations/pages/monitoring/overview-dashboard.adoc index 21ec7bf..0d56c3b 100644 --- a/modules/operations/pages/monitoring/overview-dashboard.adoc +++ b/modules/operations/pages/monitoring/overview-dashboard.adoc @@ -48,8 +48,6 @@ Messaging displays the time series metrics chart summarized at the tenant level. |Top 10 topics of the tenant by message storage size |=== -== What's next? - -Dashboards are available for scraping metrics at Pulsar's xref:monitoring/overview-dashboard.adoc[tenant], xref:monitoring/namespace-dashboard.adoc[namespace], and xref:monitoring/topic-dashboard.adoc[topic] levels. - +== See also +Dashboards are available for scraping metrics at Pulsar's xref:monitoring/overview-dashboard.adoc[tenant], xref:monitoring/namespace-dashboard.adoc[namespace], and xref:monitoring/topic-dashboard.adoc[topic] levels. \ No newline at end of file diff --git a/modules/operations/pages/monitoring/topic-dashboard.adoc b/modules/operations/pages/monitoring/topic-dashboard.adoc index 53f3b63..69e96db 100644 --- a/modules/operations/pages/monitoring/topic-dashboard.adoc +++ b/modules/operations/pages/monitoring/topic-dashboard.adoc @@ -71,6 +71,6 @@ Georeplication displays the time series metrics chart summarized at the topic le |=== -== What's next? +== See also Dashboards are available for scraping metrics at Pulsar's xref:monitoring/overview-dashboard.adoc[tenant], xref:monitoring/namespace-dashboard.adoc[namespace], and xref:monitoring/topic-dashboard.adoc[topic] levels. \ No newline at end of file diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index a5972b5..1072532 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -27,8 +27,7 @@ A *dedicated environment* is your own private Pulsar cluster with the additional Sign in to Astra just as you would with a *Pay As You Go* streaming account. When you create new tenants, additional options are available for deploying to your private cluster(s). There are less limits in *dedicated environments* than in *Pay As You Go* - it's your cluster, after all. -Finally, billing for a dedicated cluster is unique to each customer. + -mailto:streaming@datastax.com[Contact the team] to learn more. +Finally, billing for a dedicated cluster is unique to each customer. TIP: In a *Pay As You Go* environment, you can create tenants in any of the xref:astream-regions.adoc[supported regions], while *dedicated environments* are open to almost any public cloud region. ==== diff --git a/modules/ragstack/pages/index.adoc b/modules/ragstack/pages/index.adoc index 7146671..880a552 100644 --- a/modules/ragstack/pages/index.adoc +++ b/modules/ragstack/pages/index.adoc @@ -24,6 +24,6 @@ DataStax has been busy helping our customers through the pains of RAG implementa * Advanced RAG techniques - use advanced patterns like Chain of Thought and Multi-Query RAG * Future-proof - as new techniques are discovered, {ragstack} offers enterprise users an upgrade path to always be on the cutting edge of AI. -== What's next? +== See also -xref:quickstart.adoc[] \ No newline at end of file +* xref:quickstart.adoc[] \ No newline at end of file diff --git a/modules/ragstack/pages/quickstart.adoc b/modules/ragstack/pages/quickstart.adoc index 751ebcf..bdf9f43 100644 --- a/modules/ragstack/pages/quickstart.adoc +++ b/modules/ragstack/pages/quickstart.adoc @@ -378,6 +378,7 @@ If you're unsure of the profile name, use `ragstack profiles list`, then `ragsta ---- To update these values, use `ragstack profiles update astra-ragstack-tenant --command-option="value"`. + [cols="2,3", options="header"] |=== | Command Option | Description @@ -389,6 +390,7 @@ To update these values, use `ragstack profiles update astra-ragstack-tenant --co |=== If you get lost along the way, here are the default profile values: + [source,console] ---- webServiceUrl: "http://localhost:8090" @@ -399,6 +401,7 @@ token: null Issue a curl call to your {ragstack} tenant to find the connection values for your tenant. The `X-DataStax-Current-Org` value is the client-id associated with the Astra token, and can be found in the Astra UI. + [tabs] ==== curl:: @@ -434,6 +437,7 @@ Ensure the values returned from the curl call match the values in your {ragstack In the {ragstack} CLI, run the following command to open a gateway connection to your Astra Streaming tenant. This command will connect to your tenant and consume from the output-topic and produce to the input-topic. + [source,shell] ---- ragstack gateway chat sample-app -cg consume-output -pg produce-input -p sessionId=$(uuidgen) @@ -443,7 +447,8 @@ In Astra Streaming, confirm that your application is connected to your tenant. Select the Websocket tab of your {ragstack}-enabled tenant, and choose to consume from output-topic and to produce to input-topic. If the Websocket tab is not visible, you may need to refresh the page or try opening it in Incognito mode. Send a message to your application, and confirm that it is received by the Astra websocket: -[source,shell] + +[source,console] ---- ragstack gateway chat sample-app -cg consume-output -pg produce-input -p sessionId=$(uuidgen) Connected to wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway//v1/consume/ragstack-tenant/sample-app/consume-output?param:sessionId=F85E4665-BE00-4513-A5C5-E59B42646490&option:position=latest @@ -460,12 +465,13 @@ image::websocket-chat.png[Websocket chat] Your gateway connection is confirmed, and you can send messages to your application. This sample-app also produces messages to the consume-history gateway to provide more context to the AI model. To consume from this gateway, run the following command: + [tabs] ==== {ragstack} CLI:: + -- -[source,shell] +[source,console] ---- ragstack gateway consume sample-app consume-history -p sessionId=F85E4665-BE00-4513-A5C5-E59B42646490 ---- @@ -482,9 +488,6 @@ Connected to wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gatew -- ==== -== What's next? - -{ragstack} is built with the LangStream framework, which is a set of tools for building Generative AI streaming applications. - -For more, see https://github.com/LangStream/langstream[GitHub]. +== See also +{ragstack} is built with the https://github.com/LangStream/langstream[LangStream framework], which is a set of tools for building Generative AI streaming applications. \ No newline at end of file From 4d9d31e02ccf3111b174edcf56624ad1aeee0899 Mon Sep 17 00:00:00 2001 From: April M Date: Mon, 14 Oct 2024 16:06:18 -0700 Subject: [PATCH 02/18] tables --- .../ROOT/pages/astream-org-permissions.adoc | 51 +++++------ .../astream-subscriptions-exclusive.adoc | 2 +- .../pages/astream-subscriptions-failover.adoc | 2 +- .../astream-subscriptions-keyshared.adoc | 2 +- .../pages/astream-subscriptions-shared.adoc | 2 +- .../partials/georeplication-monitoring.adoc | 39 -------- modules/developing/pages/astream-cdc.adoc | 5 +- modules/developing/pages/astream-rabbit.adoc | 39 ++++---- .../pages/clients/csharp-produce-consume.adoc | 2 +- .../pages/clients/golang-produce-consume.adoc | 2 +- modules/developing/pages/clients/index.adoc | 35 ++------ .../pages/clients/java-produce-consume.adoc | 2 +- .../pages/clients/nodejs-produce-consume.adoc | 2 +- .../pages/clients/python-produce-consume.adoc | 2 +- .../pages/configure-pulsar-env.adoc | 13 ++- .../pages/produce-consume-astra-portal.adoc | 39 +++----- .../partials/client-variables-table.adoc | 51 ++++++----- modules/getting-started/pages/index.adoc | 23 ++--- .../pages/astream-georeplication.adoc | 2 +- modules/operations/pages/astream-limits.adoc | 30 +++---- modules/operations/pages/astream-regions.adoc | 36 ++++---- .../operations/pages/astream-token-gen.adoc | 36 ++++---- .../operations/pages/monitoring/index.adoc | 9 +- .../pages/monitoring/namespace-dashboard.adoc | 64 ++++++-------- .../pages/monitoring/overview-dashboard.adoc | 69 +++++++-------- .../pages/monitoring/topic-dashboard.adoc | 88 ++++++++----------- .../pages/private-connectivity.adoc | 13 ++- .../partials/code-examples-text.adoc | 25 +++--- .../partials/georeplication-monitoring.adoc | 32 ++++--- .../partials/subscription-prereq.adoc | 22 ----- modules/ragstack/pages/quickstart.adoc | 24 +++-- 31 files changed, 309 insertions(+), 454 deletions(-) delete mode 100644 modules/ROOT/partials/georeplication-monitoring.adoc delete mode 100644 modules/operations/partials/subscription-prereq.adoc diff --git a/modules/ROOT/pages/astream-org-permissions.adoc b/modules/ROOT/pages/astream-org-permissions.adoc index 9821ae9..4aa1929 100644 --- a/modules/ROOT/pages/astream-org-permissions.adoc +++ b/modules/ROOT/pages/astream-org-permissions.adoc @@ -3,93 +3,88 @@ Default and xref:astream-custom-roles.adoc[custom roles] allow admins to manage unique permissions for users based on your organization and database requirements. -You can manage roles using the {astra_ui} or the https://docs.datastax.com/en/astra/docs/manage/devops/devops-roles.html[DevOps API]. +You can manage roles using the {astra_ui} or the {astra_db} DevOps API. +For more information, see https://docs.datastax.com/en/astra/docs/user-permissions.html[Astra DB User Permissions]. == {product_name} Organization permissions -[cols=3*,options=header] +[cols="1,1,1"] |=== -|Console name -|Description -|DevOps API parameter +|Console name |Description |DevOps API parameter |Read Audits |Enables read and download audits. -|org-audits-read +|`org-audits-read` |Write IP Access List |Create or modify an access list using the DevOps API or the Astra console. -|accesslist-write +|`accesslist-write` |Delete Custom Role |Delete of custom role. -|org-role-delete +|`org-role-delete` |Manage Streaming |Create a Streaming Service using the DevOps API or the Astra console. -|org-stream-manage +|`org-stream-manage` |Write Organization |Create new organizations or delete an existing organization. Hides manage org and org settings. -|org-write +|`org-write` |Read Billing |Enables links and access to billing details page. -|org-billing-read +|`org-billing-read` |Read IP Access List |Enables links and access to access list page. -|accesslist-read +|`accesslist-read` |Read User |Access to viewing users of an organization. -|org-user-read +|`org-user-read` |Read Organization |View organization in the Astra console. -|org-read +|`org-read` |Read Custom Role |See a custom role and its associated permissions. -|org-role-read +|`org-role-read` |Read External Auth |See security settings related to external authentication providers. -|org-external-auth-read +|`org-external-auth-read` |Read Token |Read token details. -|org-token-read +|`org-token-read` |Delete Custom Role |Delete of custom role. -|org-role-delete +|`org-role-delete` |Notification Write |Enable or disable notifications in organization notification settings. |org-notification-write |Write Billing |Enables links and ability to add or edit billing payment info. -|org-billing-write +|`org-billing-write` |Write User |Add, create, or remove a user using the DevOps API or the Astra console. -|org-user-write +|`org-user-write` |Write Custom Role |Create custom role. -|org-role-write +|`org-role-write` |Write External Auth |Update security settings related to external auth providers. -|org-external-auth-write +|`org-external-auth-write` |Write Token |Create application token. -|org-token-write +|`org-token-write` -|=== - -== Astra DB Permissions - -For documentation about Astra DB user permissions, see https://docs.datastax.com/en/astra/docs/user-permissions.html[Astra DB User Permissions]. \ No newline at end of file +|=== \ No newline at end of file diff --git a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc index ca5e85a..88c03d1 100644 --- a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc +++ b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc @@ -8,7 +8,7 @@ An _exclusive* subscription_ describes a basic publish-subscribe pattern where a This document explains how to use Pulsar's exclusive subscription model to manage your topic consumption. -include::partial$subscription-prereq.adoc[] +include::ROOT:partial$subscription-prereq.adoc[] [#example] == Exclusive subscription example diff --git a/modules/ROOT/pages/astream-subscriptions-failover.adoc b/modules/ROOT/pages/astream-subscriptions-failover.adoc index b855dfb..4c91db5 100644 --- a/modules/ROOT/pages/astream-subscriptions-failover.adoc +++ b/modules/ROOT/pages/astream-subscriptions-failover.adoc @@ -11,7 +11,7 @@ If the primary consumer disconnects, the standby consumers begin consuming the s This document explains how to use Pulsar's failover subscription model to manage your topic consumption. -include::partial$subscription-prereq.adoc[] +include::ROOT:partial$subscription-prereq.adoc[] [#example] == Failover subscription example diff --git a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc index ac9e64d..5038481 100644 --- a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc @@ -15,7 +15,7 @@ Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model trade This document explains how to use Pulsar's Key_Shared subscription model to manage your topic consumption. -include::partial$subscription-prereq.adoc[] +include::ROOT:partial$subscription-prereq.adoc[] [#example] == Key_Shared subscription example diff --git a/modules/ROOT/pages/astream-subscriptions-shared.adoc b/modules/ROOT/pages/astream-subscriptions-shared.adoc index 3e94de5..ec009e9 100644 --- a/modules/ROOT/pages/astream-subscriptions-shared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-shared.adoc @@ -10,7 +10,7 @@ More consumers in a shared subscription can increase your Pulsar deployment's ra This document explains how to use Pulsar's shared subscription model to manage your topic consumption. -include::partial$subscription-prereq.adoc[] +include::ROOT:partial$subscription-prereq.adoc[] [#example] == Shared subscription example diff --git a/modules/ROOT/partials/georeplication-monitoring.adoc b/modules/ROOT/partials/georeplication-monitoring.adoc deleted file mode 100644 index ac7962a..0000000 --- a/modules/ROOT/partials/georeplication-monitoring.adoc +++ /dev/null @@ -1,39 +0,0 @@ -[cols=3*,options=header] -|=== -|*Name* -|*Type* -|*Description* - -|pulsar_replication_rate_in -|Gauge -|The total message rate of the topic replicating from remote cluster (messages/second). - -|pulsar_replication_rate_out -|Gauge -|The total message rate of the topic replicating to remote cluster (messages/second). - -|pulsar_replication_throughput_in -|Gauge -|The total throughput of the topic replicating from remote cluster (bytes/second). - -|pulsar_replication_throughput_out -|Gauge -|The total throughput of the topic replicating to remote cluster (bytes/second). - -|pulsar_replication_backlog -|Gauge -|The total backlog of the topic replicating to remote cluster (messages). - -|pulsar_replication_rate_expired -|Gauge -|Total rate of messages expired (messages/second) - -|pulsar_replication_connected_count -|Gauge -|The count of replication subscribers up and running to replicate to remote clusters. - -|pulsar_replication_delay_in_seconds -|Gauge -|Time in seconds from the time a message was produced to the time when it is about to be replicated. - -|=== diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index 7e9247c..9f2b819 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -22,10 +22,9 @@ See https://www.datastax.com/pricing/astra-streaming[{astra_stream} pricing] and The following data types and corresponding AVRO or logical types are supported for CDC for {db-serverless} databases: -[cols=2] +[cols="1,1"] |=== -| Data type -| AVRO type +| Data type | AVRO type | ascii | string diff --git a/modules/developing/pages/astream-rabbit.adoc b/modules/developing/pages/astream-rabbit.adoc index 64b318b..4468537 100644 --- a/modules/developing/pages/astream-rabbit.adoc +++ b/modules/developing/pages/astream-rabbit.adoc @@ -123,40 +123,37 @@ You should see new topics called `amq.default.__queuename` and `amq.default_rout == RabbitMQ exchanges and Pulsar topics -{starlight_rabbitmq} maps the RabbitMQ concept of exchanges to the Pulsar concept of topics. This table shows how those concepts are mapped and used: +{starlight_rabbitmq} maps RabbitMQ _exchanges_ to Pulsar _topics_, as described in the following table: -[cols=4*,options=header] +[cols="1,1,1,1"] |=== -|*Exchange* -|*Routing key* -|*Pulsar topic name* -|*Code example* +|Exchange |Routing key |Pulsar topic name |Usage example -|amp.direct +|`amp.direct` |used -|amq.direct.__{routing key} -|channel.basic_publish(exchange='amp.direct', +|`amq.direct.__{routing key}` +|`channel.basic_publish(exchange='amp.direct',` -|amp.default or empty string +|`amp.default` or empty string |used -|amq.default.__{routing key} -|channel.basic_publish(exchange=", +|`amq.default.__{routing key}` +|`channel.basic_publish(exchange="),` -|amp.match +|`amp.match` |not used -|amp.match -|channel.basic_publish(exchange=amp.match, +|`amp.match` +|`channel.basic_publish(exchange=amp.match),` -|amp.fanout +|`amp.fanout` |not used -|amp.fanout -|channel.basic_publish(exchange='amp.fanout', +|`amp.fanout` +|`channel.basic_publish(exchange='amp.fanout'),` -|headers +|`headers` |not used |Name of the header -|channel.exchange_declare(exchange='header_logs', exchange_type='headers') -channel.basic_publish(exchange='header_logs'), +|`channel.exchange_declare(exchange='header_logs', exchange_type='headers') +channel.basic_publish(exchange='header_logs'),` |=== diff --git a/modules/developing/pages/clients/csharp-produce-consume.adoc b/modules/developing/pages/clients/csharp-produce-consume.adoc index 5869249..fec7a0a 100644 --- a/modules/developing/pages/clients/csharp-produce-consume.adoc +++ b/modules/developing/pages/clients/csharp-produce-consume.adoc @@ -41,7 +41,7 @@ include::{astra-streaming-examples-repo}/csharp/simple-producer-consumer/SimpleP There are a few values that you'll need to fill in: -include::partial$client-variables-table.adoc[] +include::developing:partial$client-variables-table.adoc[] == The producer diff --git a/modules/developing/pages/clients/golang-produce-consume.adoc b/modules/developing/pages/clients/golang-produce-consume.adoc index 1e555b1..ca9300a 100644 --- a/modules/developing/pages/clients/golang-produce-consume.adoc +++ b/modules/developing/pages/clients/golang-produce-consume.adoc @@ -43,7 +43,7 @@ This is NOT a complete code snippet: it imports the required packages, establish Notice there are a few variables that are waiting for replacement values. You can find those values here: -include::partial$client-variables-table.adoc[] +include::developing:partial$client-variables-table.adoc[] == Create a producer diff --git a/modules/developing/pages/clients/index.adoc b/modules/developing/pages/clients/index.adoc index a0bb2ef..265155a 100644 --- a/modules/developing/pages/clients/index.adoc +++ b/modules/developing/pages/clients/index.adoc @@ -7,30 +7,11 @@ To connect to your service, use the open-source client APIs provided by the Apache Pulsar project. Astra Streaming is running Pulsar version {pulsar_version}. You should use this API version or higher. -Below is a list of popular Pulsar client runtimes. -Visit each guide to create a simple message producer and consumer in your Astra Streaming tenant. - -== Pulsar Clients - -[cols="^1,^1,^1,^1,^1,^1", grid=none,frame=none] -|=== -| xref:clients/csharp-produce-consume.adoc[image:csharp-icon.png[]] - -xref:clients/csharp-produce-consume.adoc[C#] -| xref:clients/golang-produce-consume.adoc[image:golang-icon.png[]] - -xref:clients/golang-produce-consume.adoc[Golang] -| xref:clients/java-produce-consume.adoc[image:java-icon.png[]] - -xref:clients/java-produce-consume.adoc[Java] -| xref:clients/nodejs-produce-consume.adoc[image:node-icon.png[]] - -xref:clients/nodejs-produce-consume.adoc[Node.js] -| xref:clients/python-produce-consume.adoc[image:python-icon.png[]] - -xref:clients/python-produce-consume.adoc[Python] - -| xref:clients/spring-produce-consume.adoc[image:spring-boot-icon.png[]] - -xref:clients/spring-produce-consume.adoc[Spring Boot] -|=== \ No newline at end of file +Popular Pulsar clients include the following: + +* xref:clients/csharp-produce-consume.adoc[image:csharp-icon.png[] C#] +* xref:clients/golang-produce-consume.adoc[image:golang-icon.png[] Golang] +* xref:clients/java-produce-consume.adoc[image:java-icon.png[] Java] +* xref:clients/nodejs-produce-consume.adoc[image:node-icon.png[] Node.js] +* xref:clients/python-produce-consume.adoc[image:python-icon.png[] Python] +* xref:clients/spring-produce-consume.adoc[image:spring-boot-icon.png[] Spring Boot] \ No newline at end of file diff --git a/modules/developing/pages/clients/java-produce-consume.adoc b/modules/developing/pages/clients/java-produce-consume.adoc index c573d7d..04d75df 100644 --- a/modules/developing/pages/clients/java-produce-consume.adoc +++ b/modules/developing/pages/clients/java-produce-consume.adoc @@ -86,7 +86,7 @@ include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimplePro Notice there are a few variables waiting for replacement values. You can find those values here: -include::partial$client-variables-table.adoc[] +include::developing:partial$client-variables-table.adoc[] == Create a producer diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index 5d9e957..5b7c5f6 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -83,7 +83,7 @@ include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleP Notice there are a few variables waiting for replacement values. You can find those values here: -include::partial$client-variables-table.adoc[] +include::developing:partial$client-variables-table.adoc[] == Create a producer diff --git a/modules/developing/pages/clients/python-produce-consume.adoc b/modules/developing/pages/clients/python-produce-consume.adoc index 918f577..b87e579 100644 --- a/modules/developing/pages/clients/python-produce-consume.adoc +++ b/modules/developing/pages/clients/python-produce-consume.adoc @@ -43,7 +43,7 @@ include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleP Notice there are a few variables waiting for replacement values. You can find those values here: -include::partial$client-variables-table.adoc[] +include::developing:partial$client-variables-table.adoc[] == Create a producer diff --git a/modules/developing/pages/configure-pulsar-env.adoc b/modules/developing/pages/configure-pulsar-env.adoc index 21f9b18..10c9598 100644 --- a/modules/developing/pages/configure-pulsar-env.adoc +++ b/modules/developing/pages/configure-pulsar-env.adoc @@ -52,15 +52,11 @@ Astra Portal:: -- . Navigate to the "Connect" tab in the Astra Streaming portal. + -|=== -a|image:connect-tab.png[Connect tab in Astra Streaming] -|=== +image:connect-tab.png[Connect tab in Astra Streaming] . Locate the "Download client.conf" button and click to download the conf file. + -|=== -a|image:download-client.png[Download pulsar client conf in Astra Streaming] -|=== +image:download-client.png[Download pulsar client conf in Astra Streaming] . Save the file in the "/conf" folder of the Pulsar folder. This will overwrite the default client.conf already in the /conf folder. @@ -69,13 +65,14 @@ This will overwrite the default client.conf already in the /conf folder. With your Astra Streaming tenant's configuration in place, you can use any of the binaries to interact with a Pulsar cluster. +[cols="1,3"] |=== |Binary |Uses -| ./bin/pulsar-admin +| `./bin/pulsar-admin` | Administrative commands to manage namespaces, topics, functions, connectors, etc. -| ./bin/pulsar-client +| `./bin/pulsar-client` | Interactive commands for producing and consuming messages. |=== diff --git a/modules/developing/pages/produce-consume-astra-portal.adoc b/modules/developing/pages/produce-consume-astra-portal.adoc index a49e1b1..e526bdb 100644 --- a/modules/developing/pages/produce-consume-astra-portal.adoc +++ b/modules/developing/pages/produce-consume-astra-portal.adoc @@ -9,44 +9,29 @@ The following steps will use the "Try Me" feature of the Astra Portal to interac == Select the producer and consumer topics -Navigate to the "Try Me" tab +. Navigate to the "Try Me" tab. ++ +image:try-me-tab.png[Try me tab in Astra Streaming] -|=== -a|image:try-me-tab.png[Try me tab in Astra Streaming] -|=== - -Choose the appropriate namespace and topic. +. Choose the appropriate namespace and topic. In this example, the name for both the producer and consumer is the same. Leave the rest of the settings as default. - -[width=70%] -|=== -a|image:config-try-me.png[Config try me in Astra Streaming] -|=== ++ +image:config-try-me.png[Config try me in Astra Streaming] == Connect and send a message -Click the "Connect" button to open a websocket connection in your browser with this topic. - -Type the message "Hi there" in the *Send message* box and click the "Send" button. +. Click *Connect* to open a websocket connection in your browser with this topic. -[width=80%] -|=== -a|image:test-message-input.png[Send message in Astra Streaming] -|=== +. In *Send message*, type a message, and then click *Send*. ++ +image:test-message-input.png[Send message in Astra Streaming] -A message will be produced (ie: sent) to your selected topic and the consumer will consume (ie: retrieve) the message. +A message is produced (sent) to your selected topic, and then the consumer consumes (retrieves) the message. The result is a chat style write and read. -[width=80%] -|=== -a|image:try-me-test-message.png[Test message in Astra Streaming] -|=== - -{emoji-tada} Congratulations! You have sent and received your first message. - -Now it's time to take your skills further {emoji-rocket}. +image:try-me-test-message.png[Test message in Astra Streaming] == See also diff --git a/modules/developing/partials/client-variables-table.adoc b/modules/developing/partials/client-variables-table.adoc index 7e41d72..6fc9af2 100644 --- a/modules/developing/partials/client-variables-table.adoc +++ b/modules/developing/partials/client-variables-table.adoc @@ -1,25 +1,30 @@ -[cols="^1,2"] +[cols="1,1,3"] |=== -.^|serviceUrl -|This is the URL for connecting to the Pulsar cluster. - -In the Astra Portal, navigate to the "Connect" tab of your streaming tenant. In the "Details" area you will find the "Broker Service URL". - -.^|pulsarToken -|This is the token used for Pulsar cluster authentications. - -In the Astra Portal, navigate to the "Settings" tab of your streaming tenant. Select "Create Token". A new token will be generated and available to copy. - -.^|tenantName -|The name of your streaming tenant. - -In the Astra Portal, navigate to your streaming tenant. In the "Details" area you will find the "Name". - -.^|namespace -|Within your streaming tenant, this is the segmented area for certain topics. - -In the Astra Portal, navigate to the "Namespace And Topics" tab of your streaming tenant to see a list of namespaces. - -.^|topicName -|Expanding the above chosen namespace will list the topics within. This value is just the topic name (not the "Full Name"). +|Parameter |Definition |Where to find the value + +|`serviceUrl` +|The URL to connect to the Pulsar cluster +|In the {astra_ui} navigation menu, click *Streaming*, select your streaming tenant, and then click the *Connect* tab. +In the *Details* section, get the *Broker Service URL*. + +|`pulsarToken` +|The token for Pulsar cluster authentication +|In the {astra_ui} navigation menu, click *Streaming*, select your streaming tenant, and then click the *Settings* tab. +Click *Create Token* to generate a Pulsar token. + +|`tenantName` +|The name of your streaming tenant +|In the {astra_ui} navigation menu, click *Streaming*, select your streaming tenant. +In the *Details* section, get the *Name*. + +|`namespace` +|The segmented area for certain topics in your streaming tenant +|In the {astra_ui} navigation menu, click *Streaming*, select your streaming tenant, and then click the *Namespace and Topics* tab. +Choose the target namespace from the list of namespaces. + +|`topicName` +|Topic name (not the full name) +|In the {astra_ui} navigation menu, click *Streaming*, select your streaming tenant, and then click the *Namespace and Topics* tab. +Expand the target namespace in the list of namespaces to view the names of the topics within. +Do _not_ use the *Full Name*. |=== \ No newline at end of file diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index 1ba149b..34695e7 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -263,23 +263,10 @@ include::{astra-streaming-examples-repo}/curl/create-topic.sh[] -- ==== -== Putting your topic to work +== Next steps -Now that you have a topic created, it's time to produce and consume messages in your new message topic. -There are several different ways to accomplish this. -Choose the right one for your needs (they all end up doing the same thing). +Your new topic is ready to produce and consume messages. +There are several different ways to do this: -[cols=3*,frame=none,grid=none] -|=== -a|xref:developing:produce-consume-astra-portal.adoc[Astra Portal] - -[.small]##Using Astra Streaming's "Try Me" feature in the UI## - -a|xref:developing:produce-consume-pulsar-client.adoc[Pulsar Client] - -[.small]##Use the cli provided within Pulsar to interact with the topic## - -a|xref:developing:clients/index.adoc[Runtime Clients] - -[.small]##Create a client application that interacts with Pulsar (C#, Java, Python, etc)## -|=== \ No newline at end of file +* xref:developing:produce-consume-astra-portal.adoc[Astra Portal]: Use Astra Streaming's "Try Me" feature in the {astra_ui}.* xref:developing:produce-consume-pulsar-client.adoc[Pulsar Client]: Use the Pulsar CLI to interact with the topic. +* xref:developing:clients/index.adoc[Runtime Clients]: Create a client application that interacts with Pulsar. \ No newline at end of file diff --git a/modules/operations/pages/astream-georeplication.adoc b/modules/operations/pages/astream-georeplication.adoc index 0f93521..2c16b76 100644 --- a/modules/operations/pages/astream-georeplication.adoc +++ b/modules/operations/pages/astream-georeplication.adoc @@ -285,7 +285,7 @@ Result:: {product_name} exposes the following topic-level replication metrics, which can be viewed in the **Overview** tab of the Namespaces and Topics page. -include::partial$georeplication-monitoring.adoc[] +include::operations:partial$georeplication-monitoring.adoc[] == See also diff --git a/modules/operations/pages/astream-limits.adoc b/modules/operations/pages/astream-limits.adoc index ad6ec68..1fbf00f 100644 --- a/modules/operations/pages/astream-limits.adoc +++ b/modules/operations/pages/astream-limits.adoc @@ -10,12 +10,12 @@ The below limits are for Pay As You Go clusters. These limits may be changed on == {product_name} guardrails -Guardrails are initially provisioned in the default settings by {product_name} and cannot be changed directly by users. If you need assistance, open {support_url}[a support ticket]. +Guardrails are initially provisioned in the default settings by {product_name}. +You can't change these guardrails. -[cols=2*] +[cols="1,1"] |=== -|*Guardrail* -|*Limit* +|Guardrail |Limit |Number of tenants per organization* |10 @@ -66,9 +66,8 @@ Max throughput for a *non-partitioned topic* or a *single partition of a partiti |Not set |=== -_*These guardrails cannot be changed._ - -_**Max message size can be changed on dedicated clusters, but not serverless clusters. If you need assistance, open {support_url}[a support ticket]._ +Max message size can be changed for Classic databases, but not Serverless databases. +If you need assistance, contact {support_url}[{company} Support]. == Function and connector resource limits @@ -125,11 +124,9 @@ For more on `pulsar-admin`, see the Apache Pulsar https://pulsar.apache.org/docs These namespace policies are initially provisioned in the default settings by {product_name} and _can_ be changed by users. -[cols=3*] +[cols="1,1,1"] |=== -|*Policy* -|*Default Setting* -|*Exposed by UI?* +|Policy |Default Setting |Available in the {astra_ui} |Max retention time |Disabled/no data retention by default @@ -184,11 +181,9 @@ You can read more about Kubernetes pod naming restrictions https://kubernetes.io These topic and namespace actions are initially provisioned in the default settings by {product_name} and can be performed by users. -[cols=3*] +[cols="1,1,1"] |=== -|*Allowed Action* -|*Default Setting* -|*Exposed by UI?* +|Allowed Action |Default Setting |Available in the {astra_ui} |Terminate topic |Enabled @@ -228,10 +223,9 @@ These topic and namespace actions are initially provisioned in the default setti Message throughput, rate, and message max size can be customized on dedicated clusters. If you need assistance, open {support_url}[a support ticket]. -[cols=2*] +[cols="1,1"] |=== -|*Guardrail* -|*Limit* +|Guardrail |Limit |Number of tenants per organization |No limit diff --git a/modules/operations/pages/astream-regions.adoc b/modules/operations/pages/astream-regions.adoc index d1dffef..b881aed 100644 --- a/modules/operations/pages/astream-regions.adoc +++ b/modules/operations/pages/astream-regions.adoc @@ -1,15 +1,20 @@ = {product_name} Regions :page-tag: astra-streaming,admin,manage,pulsar -When creating a tenant, select a region for your tenant. Choose a region that is geographically close to your users to optimize performance. +When creating a tenant, select a region for your tenant. +Choose a region that is geographically close to your users to optimize performance. -Here are the regions that {product_name} supports: +{product_name} supports AWS, Microsoft Azure, and Google Cloud regions. +These regions are also supported by CDC for {astra_db}. +{product_name} CDC can only be used in a region that supports both {product_name} and {astra_db}. + +ElasticSearch and Snowflake can be in different regions than {product_name}. == AWS -[cols=2*,options=header] + +[cols="1,1"] |=== -|Region -|Location +|Region |Location |`ap-south-1` |Mumbai @@ -33,11 +38,11 @@ Here are the regions that {product_name} supports: |Oregon |=== -== Azure -[cols=2*,options=header] +== Microsoft Azure + +[cols="1,1"] |=== -|Region -|Location +|Region |Location |`australiaeast` |New South Wales @@ -56,10 +61,10 @@ Here are the regions that {product_name} supports: |=== == Google Cloud -[cols=2*,options=header] + +[cols="1,1"] |=== -|Region -|Location +|Region |Location |`australia-southeast1` |Sydney, Australia @@ -76,9 +81,4 @@ Here are the regions that {product_name} supports: |`us-east4` |Ashburn, Virginia -|=== - -[NOTE] -==== -These regions are also supported by CDC for Astra DB. {product_name} CDC can only be used in a region that supports both {product_name} and Astra DB. ElasticSearch and Snowflake can be in different regions than {product_name}. -==== \ No newline at end of file +|=== \ No newline at end of file diff --git a/modules/operations/pages/astream-token-gen.adoc b/modules/operations/pages/astream-token-gen.adoc index bc2f1e9..49ec89c 100644 --- a/modules/operations/pages/astream-token-gen.adoc +++ b/modules/operations/pages/astream-token-gen.adoc @@ -187,17 +187,13 @@ Use the DevOps API for org-wide Astra scope. Users, tenants, billing, and usage Some cases can use `pulsar-admin` **or** the DevOps API - we want the tools to be complementary, not restrictive, so do what works best for you! -This section should help you choose which tool to use, and which token is required. +This section should help you choose which tool to use, and which token is required: -[cols="2,2",options=header] -|=== -|*Use case* -|*Token* +=== Track monthly usage -|Track monthly usage -|Astra token +Use an {astra_db} application token to track monthly usage. +For example: -2+a|Example [source,shell] ---- curl --request GET \ @@ -206,28 +202,31 @@ curl --request GET \ --header 'Authorization: Bearer ' ---- -|Monitor a topic's health -|Pulsar token +=== Monitor a topic's health + +Use a Pulsar token to monitor a topic's health. +For example: -2+a|Example [source,shell] ---- bin/pulsar-admin topics stats ---- -|Monitor a connector's health -|Pulsar token +=== Monitor a connector's health + +Use a Pulsar token to monitor a connector's health. +For example: -2+a|Example [source,shell] ---- bin/pulsar-admin sinks status ---- -|Billing report by tenant -|Astra token +=== Billing report by tenant + +Use an {astra_db} application token to get tenant billing reports. +For example: -2+a|Example [source,shell] ---- curl --request GET \ @@ -235,8 +234,7 @@ curl --request GET \ --header 'Accept: application/json' \ --header 'Authorization: Bearer ' ---- -|=== == See also -* *xref:getting-started:index.adoc[] +* *xref:getting-started:index.adoc[] \ No newline at end of file diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index e9f4acb..94a51db 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -136,7 +136,8 @@ topk by(topic) (10, sum(pulsar_msg_backlog{namespace=~"$namespace", topic !~ ".* == Metrics to be alerted Most of the exposed Astra Streaming metrics are for informational purposes only and in most cases the metrics values are just reflecting the application workload characteristics. For example, message rate or throughput are common examples of such metrics. -There are, however, several metrics that need special attention when we see an increasing number of their values. Among the exposed Astra Streaming metrics, these metrics are: +There are, however, several metrics that need special attention when we see an increasing number of their values. Among the exposed Astra Streaming metrics, these metrics are as follows: + .Metrics for alerting [%header,format=csv,cols="2,2,1,3"] |=== @@ -157,7 +158,7 @@ Otherwise, they may trigger too many false alarms. == See also -* For a list of exposed endpoints for Astra Streaming metrics, see xref:monitoring/metrics.adoc[]. -* To scrape Astra Streaming metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. -* To scrape Astra Streaming metrics into New Relic, see xref:monitoring/new-relic.adoc[]. +* xref:monitoring/metrics.adoc[] +* xref:monitoring/integration.adoc[] +* xref:monitoring/new-relic.adoc[] diff --git a/modules/operations/pages/monitoring/namespace-dashboard.adoc b/modules/operations/pages/monitoring/namespace-dashboard.adoc index 3f181c7..206579c 100644 --- a/modules/operations/pages/monitoring/namespace-dashboard.adoc +++ b/modules/operations/pages/monitoring/namespace-dashboard.adoc @@ -1,53 +1,45 @@ = Namespace dashboard -The https://github.com/datastax/astra-streaming-examples/blob/master/grafana-dashboards/as-namespace.json[namespace dashboard] aggregates metrics at the namespace level. In the variable section, you can choose a cluster, tenant, and namespace. +The https://github.com/datastax/astra-streaming-examples/blob/master/grafana-dashboards/as-namespace.json[namespace dashboard] aggregates metrics at the namespace level. +In the variable section, you can choose a cluster, tenant, and namespace. == Namespace overview + Namespace overview displays the aggregated total metrics values summarized at the namespace level. -.Namespace overview -[cols=1*] -|=== -|Total number of topics -|Total number of producers -|Total number of consumers -|Total number of subscriptions -|Total message backlog -|Total message replication backlog -|Total message storage size -|Total message size offloaded to a tiered storage -|Total hourly incoming message number -|Total hourly incoming message average size -|=== +* Total number of topics +* Total number of producers +* Total number of consumers +* Total number of subscriptions +* Total message backlog +* Total message replication backlog +* Total message storage size +* Total message size offloaded to a tiered storage +* Total hourly incoming message number +* Total hourly incoming message average size == Messaging + Messaging displays the time series metrics chart summarized at the namespace level with an additional level of detail (topics). -.Messaging -[cols=1*] -|=== -|Total incoming message rate (msg/s) of the namespace divided by topics -|Total outgoing message rate (msg/s) of the namespace divided by topics -|Total incoming message throughput (byte/s) of the namespace divided by topics -|Total outgoing message throughput (byte/s) of the namespace divided by topics -|Total message backlog of the namespace divided by topics -|Total unacknowledged messages of the namespace divided by topics -|Total message drop rate of the namespace divided by topics -|Total Producer/Consumer/Subscription count of the namespace divided by topics -|=== +* Total incoming message rate (msg/s) of the namespace divided by topics +* Total outgoing message rate (msg/s) of the namespace divided by topics +* Total incoming message throughput (byte/s) of the namespace divided by topics +* Total outgoing message throughput (byte/s) of the namespace divided by topics +* Total message backlog of the namespace divided by topics +* Total unacknowledged messages of the namespace divided by topics +* Total message drop rate of the namespace divided by topics +* Total Producer/Consumer/Subscription count of the namespace divided by topics == Georeplication + Geo-replication displays the time series metrics chart summarized at the namespace level with an additional level of detail (remote clusters). -.Georeplication -[cols=1*] -|=== -|Total incoming replication rate (msg/s) to the namespace divided by remote clusters -|Total outgoing replication rate (msg/s) from the namespace divided by remote clusters -|Total incoming replication throughput (byte/s) to the namespace divided by remote clusters -|Total outgoing replication throughput (byte/s) from the namespace divided by remote clusters -|Total (outgoing) message replication backlog from the namespace divided by remote clusters -|=== +* Total incoming replication rate (msg/s) to the namespace divided by remote clusters +* Total outgoing replication rate (msg/s) from the namespace divided by remote clusters +* Total incoming replication throughput (byte/s) to the namespace divided by remote clusters +* Total outgoing replication throughput (byte/s) from the namespace divided by remote clusters +* Total (outgoing) message replication backlog from the namespace divided by remote clusters == See also diff --git a/modules/operations/pages/monitoring/overview-dashboard.adoc b/modules/operations/pages/monitoring/overview-dashboard.adoc index 0d56c3b..4f9a37b 100644 --- a/modules/operations/pages/monitoring/overview-dashboard.adoc +++ b/modules/operations/pages/monitoring/overview-dashboard.adoc @@ -5,48 +5,41 @@ The https://github.com/datastax/astra-streaming-examples/blob/master/grafana-das == Tenant overview Overview displays the aggregated total metrics values summarized at the tenant level. The following metrics are included: -.Tenant overview -[cols=1*] -|=== -|Total number of namespaces -|Total number of topics -|Total number of producers -|Total number of consumers -|Total number of subscriptions -|Total message storage size (logical) - before replication -|Total message storage size - after replication -|Total message size offloaded to a tiered storage -|Total message backlog -|Total message replication backlog -|Total hourly incoming message number -|Total hourly incoming message average size -|=== +* Total number of namespaces +* Total number of topics +* Total number of producers +* Total number of consumers +* Total number of subscriptions +* Total message storage size (logical) - before replication +* Total message storage size - after replication +* Total message size offloaded to a tiered storage +* Total message backlog +* Total message replication backlog +* Total hourly incoming message number +* Total hourly incoming message average size == Messaging + Messaging displays the time series metrics chart summarized at the tenant level. The following metrics are included: -.Messaging -[cols=1*] -|=== -|Total incoming message rate (msg/s) of the tenant divided by namespaces -|Total outgoing message rate (msg/s) of the tenant divided by namespaces -|Total incoming message throughput (byte/s) of the tenant divided by namespaces -|Total outgoing message throughput (byte/s) of the tenant divided by namespaces -|Total message backlog of the tenant divided by namespaces -|Total message storage size of the tenant divided by namespaces -|Total Message replication backlog rate (msg/s) divided of the tenant divided by remote clusters -|Total Producer/Consumer/Subscription count of the tenant -|Total unacknowledged messages of the tenant divided by namespaces -|Total message drop rate of the tenant divided by namespaces -|Total incoming message replication rate (msg/s) of the tenant divided by remote clusters -|Total outgoing message replication rate (msg/s) of the tenant divided by remote clusters -|Total incoming message replication throughput (byte/s) of the tenant divided by remote clusters -|Total outgoing message replication throughput (byte/s) of the tenant divided by remote clusters -|Top 10 topics of the tenant by message backlog -|Top 10 topics of the tenant by message replication backlog -|Top 10 topics of the tenant by unacknowledged message -|Top 10 topics of the tenant by message storage size -|=== +* Total incoming message rate (msg/s) of the tenant divided by namespaces +* Total outgoing message rate (msg/s) of the tenant divided by namespaces +* Total incoming message throughput (byte/s) of the tenant divided by namespaces +* Total outgoing message throughput (byte/s) of the tenant divided by namespaces +* Total message backlog of the tenant divided by namespaces +* Total message storage size of the tenant divided by namespaces +* Total Message replication backlog rate (msg/s) divided of the tenant divided by remote clusters +* Total Producer/Consumer/Subscription count of the tenant +* Total unacknowledged messages of the tenant divided by namespaces +* Total message drop rate of the tenant divided by namespaces +* Total incoming message replication rate (msg/s) of the tenant divided by remote clusters +* Total outgoing message replication rate (msg/s) of the tenant divided by remote clusters +* Total incoming message replication throughput (byte/s) of the tenant divided by remote clusters +* Total outgoing message replication throughput (byte/s) of the tenant divided by remote clusters +* Top 10 topics of the tenant by message backlog +* Top 10 topics of the tenant by message replication backlog +* Top 10 topics of the tenant by unacknowledged message +* Top 10 topics of the tenant by message storage size == See also diff --git a/modules/operations/pages/monitoring/topic-dashboard.adoc b/modules/operations/pages/monitoring/topic-dashboard.adoc index 69e96db..2d87aee 100644 --- a/modules/operations/pages/monitoring/topic-dashboard.adoc +++ b/modules/operations/pages/monitoring/topic-dashboard.adoc @@ -4,72 +4,58 @@ The https://github.com/datastax/astra-streaming-examples/blob/master/grafana-das This dashboard analyzes metrics at the parent topic level, not at the partition level. == Topic overview + Overview displays the aggregated total metrics values summarized at the topic level. -.Topic overview -[cols=1*] -|=== -|Total number of producers -|Total number of consumers -|Total number of subscriptions -|Total message backlog -|Total message replication backlog -|Total message storage size - after replication -|Total message size offloaded to a tiered storage -|Total hourly incoming message number -|Total hourly incoming message average size -|=== +* Total number of producers +* Total number of consumers +* Total number of subscriptions +* Total message backlog +* Total message replication backlog +* Total message storage size - after replication +* Total message size offloaded to a tiered storage +* Total hourly incoming message number +* Total hourly incoming message average size == Messaging + Messaging displays the time series metrics chart summarized at the topic level with an additional level of detail (partitions). -.Messaging -[cols=1*] -|=== -|Total incoming message rate (msg/s) of the topic divided by partitions -|Total outgoing message rate (msg/s) of the topic divided by partitions -|Total incoming message throughput (bytes/s) of the topic divided by partitions -|Total outgoing message throughput (bytes/s) of the topic divided by partitions -|Total message backlog of the topic divided by partitions -|Total unacknowledged messages of the topic divided by partitions -|=== +* Total incoming message rate (msg/s) of the topic divided by partitions +* Total outgoing message rate (msg/s) of the topic divided by partitions +* Total incoming message throughput (bytes/s) of the topic divided by partitions +* Total outgoing message throughput (bytes/s) of the topic divided by partitions +* Total message backlog of the topic divided by partitions +* Total unacknowledged messages of the topic divided by partitions === Subscription + Subscription displays the time series metrics chart summarized at the subscription level with an additional level of detail (by individual subscriptions). -.Subscription -[cols=1*] -|=== -|Total subscription message backlog divided by individual subscriptions -|Total subscription message backlog with no delay divided by individual subscriptions -|Total subscription unacknowledged messages divided by individual subscriptions -|Total subscription delayed messages divided by individual subscriptions -|Total subscription message dispatch rate (msg/s) divided by individual subscriptions -|Total subscription message throughput rate (byte/s) divided by individual subscriptions -|Total subscription message acknowledgement rate (msg/s) divided by individual subscriptions -|Total subscription message redelivery rate (msg/s) divided by individual subscriptions -|Total subscription message expired rate (msg/s) divided by individual subscriptions -|Total subscription message dropped rate (msg/s) divided by individual subscriptions -|Total subscription messages processed by EntryFilter, divided by individual subscriptions -|Total subscription messages accepted by EntryFilter, divided by individual subscriptions -|Total subscription messages rejected by EntryFilter, divided by individual subscriptions -|Total subscription messages rescheduled by EntryFilter, divided by individual subscriptions -|=== +* Total subscription message backlog divided by individual subscriptions +* Total subscription message backlog with no delay divided by individual subscriptions +* Total subscription unacknowledged messages divided by individual subscriptions +* Total subscription delayed messages divided by individual subscriptions +* Total subscription message dispatch rate (msg/s) divided by individual subscriptions +* Total subscription message throughput rate (byte/s) divided by individual subscriptions +* Total subscription message acknowledgement rate (msg/s) divided by individual subscriptions +* Total subscription message redelivery rate (msg/s) divided by individual subscriptions +* Total subscription message expired rate (msg/s) divided by individual subscriptions +* Total subscription message dropped rate (msg/s) divided by individual subscriptions +* Total subscription messages processed by EntryFilter, divided by individual subscriptions +* Total subscription messages accepted by EntryFilter, divided by individual subscriptions +* Total subscription messages rejected by EntryFilter, divided by individual subscriptions +* Total subscription messages rescheduled by EntryFilter, divided by individual subscriptions == Georeplication Georeplication displays the time series metrics chart summarized at the topic level with an additional level of detail (remote clusters). -.Georeplication -[cols=1*] -|=== -|Incoming replication rate (msg/s) to the topic divided by remote clusters -|Outgoing replication rate (msg/s) from the topic divided by remote clusters -|Incoming replication throughput (byte/s) to the topic divided by remote clusters -|Outgoing replication throughput (byte/s) from the topic divided by remote clusters -|Total (outgoing) message replication backlog from the topic divided by remote clusters -|=== - +* Incoming replication rate (msg/s) to the topic divided by remote clusters +* Outgoing replication rate (msg/s) from the topic divided by remote clusters +* Incoming replication throughput (byte/s) to the topic divided by remote clusters +* Outgoing replication throughput (byte/s) from the topic divided by remote clusters +* Total (outgoing) message replication backlog from the topic divided by remote clusters == See also diff --git a/modules/operations/pages/private-connectivity.adoc b/modules/operations/pages/private-connectivity.adoc index e384785..e92b50e 100644 --- a/modules/operations/pages/private-connectivity.adoc +++ b/modules/operations/pages/private-connectivity.adoc @@ -19,10 +19,9 @@ The private link service pattern is the same across cloud providers, but the hos [#inbound] .Inbound private link service endpoints -[cols=2*,options=header] +[cols="1,3"] |=== -|Service -|Endpoint pattern +|Service |Endpoint pattern |Pulsar Messaging |`pulsar-azure-eastus.private.streaming.datastax.com:6651` @@ -39,6 +38,7 @@ The private link service pattern is the same across cloud providers, but the hos [#outbound] == Outbound traffic + {product_name} also supports private outbound traffic (from {product_name} to your private endpoint) on a case-by-case basis. The outbound traffic pattern creates a private endpoint in {product_name} that connects to your private link service. We open a port on the tenant's firewall (firewalls are per tenant) so connectors and functions (running in a dedicated namespace on our cluster) can connect to your private network. @@ -46,16 +46,15 @@ The outbound traffic pattern creates a private endpoint in {product_name} that c To open an outbound private endpoint, open {support_url}[a support ticket] and include the <> required for your cloud provider. == Cloud provider credentials + For more on connecting to your cloud provider, see your cloud provider's documentation. Each cloud provider will require different credentials to connect to the private endpoint. [#credentials] .Cloud providers -[cols=3*,options=header] +[cols="1,1,3"] |=== -|Cloud provider -|Credentials required -|Documentation +|Cloud provider |Credentials required |Documentation |AWS |AWS account number(s) diff --git a/modules/operations/partials/code-examples-text.adoc b/modules/operations/partials/code-examples-text.adoc index 24780f0..6de3ca3 100644 --- a/modules/operations/partials/code-examples-text.adoc +++ b/modules/operations/partials/code-examples-text.adoc @@ -1,17 +1,16 @@ {product_name} is powered by http://pulsar.apache.org/[Apache Pulsar]. To connect to your service, use the open-source client APIs provided by the Apache Pulsar project. -{product_name} is running Pulsar version {pulsar_version}. You should use this API version or higher. +{product_name} is running Pulsar version {pulsar_version}. +You should use this API version or higher. -**Choose the language that you would like to use:** -[cols="1,1,1,1"] -|=== -| xref:astream-java-eg.adoc[image:java-icon.png[]] -| xref:astream-python-eg.adoc[image:python-icon.png[]] -| xref:astream-golang-eg.adoc[image:golang-icon.png[]] -| xref:astream-nodejs-eg.adoc[image:node-icon.png[]] -| xref:astream-java-eg.adoc[Java] -| xref:astream-python-eg.adoc[Python] -| xref:astream-golang-eg.adoc[Golang] -| xref:astream-nodejs-eg.adoc[Node.js] -|=== +Choose your preferred language: + +* xref:astream-java-eg.adoc[image:java-icon.png[]] +* xref:astream-python-eg.adoc[image:python-icon.png[]] +* xref:astream-golang-eg.adoc[image:golang-icon.png[]] +* xref:astream-nodejs-eg.adoc[image:node-icon.png[]] +* xref:astream-java-eg.adoc[Java] +* xref:astream-python-eg.adoc[Python] +* xref:astream-golang-eg.adoc[Golang] +* xref:astream-nodejs-eg.adoc[Node.js] diff --git a/modules/operations/partials/georeplication-monitoring.adoc b/modules/operations/partials/georeplication-monitoring.adoc index ac7962a..beead81 100644 --- a/modules/operations/partials/georeplication-monitoring.adoc +++ b/modules/operations/partials/georeplication-monitoring.adoc @@ -1,38 +1,36 @@ -[cols=3*,options=header] +[cols="1,1,3"] |=== -|*Name* -|*Type* -|*Description* +|Name |Type |Description -|pulsar_replication_rate_in +|`pulsar_replication_rate_in` |Gauge -|The total message rate of the topic replicating from remote cluster (messages/second). +|The total message rate of the topic replicating from remote cluster (messages per second). -|pulsar_replication_rate_out +|`pulsar_replication_rate_out` |Gauge -|The total message rate of the topic replicating to remote cluster (messages/second). +|The total message rate of the topic replicating to remote cluster (messages per second). -|pulsar_replication_throughput_in +|`pulsar_replication_throughput_in` |Gauge -|The total throughput of the topic replicating from remote cluster (bytes/second). +|The total throughput of the topic replicating from remote cluster (bytes per second). -|pulsar_replication_throughput_out +|`pulsar_replication_throughput_out` |Gauge -|The total throughput of the topic replicating to remote cluster (bytes/second). +|The total throughput of the topic replicating to remote cluster (bytes per second). -|pulsar_replication_backlog +|`pulsar_replication_backlog` |Gauge |The total backlog of the topic replicating to remote cluster (messages). -|pulsar_replication_rate_expired +|`pulsar_replication_rate_expired` |Gauge -|Total rate of messages expired (messages/second) +|Total rate of messages expired (messages per second) -|pulsar_replication_connected_count +|`pulsar_replication_connected_count` |Gauge |The count of replication subscribers up and running to replicate to remote clusters. -|pulsar_replication_delay_in_seconds +|`pulsar_replication_delay_in_seconds` |Gauge |Time in seconds from the time a message was produced to the time when it is about to be replicated. diff --git a/modules/operations/partials/subscription-prereq.adoc b/modules/operations/partials/subscription-prereq.adoc deleted file mode 100644 index cbd73de..0000000 --- a/modules/operations/partials/subscription-prereq.adoc +++ /dev/null @@ -1,22 +0,0 @@ -== Prerequisites - -To run this example, you'll need: - -* https://maven.apache.org/install.html[Apache Maven] - -* https://openjdk.java.net/install/[Java OpenJDK 11] - -* A configured Astra Streaming instance with at least *one streaming tenant* and *one topic*. See the https://docs.datastax.com/en/astra-streaming/docs/astream-quick-start.html[Astra Streaming quick start] for instructions. - -* A local clone of the https://github.com/datastax/pulsar-subscription-example[DataStax Pulsar Subscription Example repository] - -* Modify the `src/main/resources/application.properties` in the `pulsar-subscription-example` repo to connect to your Astra Streaming cluster, as below: -+ -[source,bash] ----- -service_url={broker-service-url} -namespace=default -tenant_name=my-tenant -authentication_token={astra-auth-token} -topic_name=my-topic ----- \ No newline at end of file diff --git a/modules/ragstack/pages/quickstart.adoc b/modules/ragstack/pages/quickstart.adoc index bdf9f43..cf68428 100644 --- a/modules/ragstack/pages/quickstart.adoc +++ b/modules/ragstack/pages/quickstart.adoc @@ -379,14 +379,24 @@ If you're unsure of the profile name, use `ragstack profiles list`, then `ragsta To update these values, use `ragstack profiles update astra-ragstack-tenant --command-option="value"`. -[cols="2,3", options="header"] +[cols="1,1"] |=== -| Command Option | Description -| --set-current | Set this profile as current -| --web-service-url| webServiceUrl of the profile -| --api-gateway-url| apiGatewayUrl of the profile -| --tenant | tenant of the profile -| --token | token of the profile +| Command Option | Description + +| `--set-current` +| Set this profile as current + +| `--web-service-url` +| webServiceUrl of the profile + +| `--api-gateway-url` +| apiGatewayUrl of the profile + +| `--tenant` +| tenant of the profile + +| `--token` +| token of the profile |=== If you get lost along the way, here are the default profile values: From f5a95ded4dcad68574156237a259cdf1e7373a46 Mon Sep 17 00:00:00 2001 From: April M Date: Mon, 14 Oct 2024 16:45:41 -0700 Subject: [PATCH 03/18] admonitions --- modules/ROOT/pages/astream-custom-roles.adoc | 19 ++-- .../astream-subscriptions-exclusive.adoc | 4 +- modules/apis/pages/api-operations.adoc | 71 +++++++-------- modules/developing/pages/astream-cdc.adoc | 23 +++-- .../developing/pages/astream-functions.adoc | 28 ++---- modules/developing/pages/astream-kafka.adoc | 7 +- modules/developing/pages/astream-rabbit.adoc | 21 ++--- .../pages/clients/nodejs-produce-consume.adoc | 10 +-- .../pages/configure-pulsar-env.adoc | 4 +- .../pages/gpt-schema-translator.adoc | 14 ++- modules/developing/pages/using-curl.adoc | 90 +++++++++---------- modules/getting-started/pages/index.adoc | 11 ++- modules/operations/pages/astream-limits.adoc | 24 ++--- .../pages/astream-scrape-metrics.adoc | 12 ++- .../pages/monitoring/integration.adoc | 15 ++-- .../operations/pages/monitoring/metrics.adoc | 10 +-- .../pages/monitoring/new-relic.adoc | 25 +++--- modules/operations/pages/onboarding-faq.adoc | 11 ++- modules/ragstack/pages/quickstart.adoc | 16 ++-- 19 files changed, 180 insertions(+), 235 deletions(-) diff --git a/modules/ROOT/pages/astream-custom-roles.adoc b/modules/ROOT/pages/astream-custom-roles.adoc index d3840e2..a291e88 100644 --- a/modules/ROOT/pages/astream-custom-roles.adoc +++ b/modules/ROOT/pages/astream-custom-roles.adoc @@ -1,6 +1,7 @@ = Manage custom roles :page-tag: astra-streaming,security,secure,pulsar + Within *Settings* > *Users*, you can see the permissions for a specific role by hovering over the number in the *Roles* column of the table. This will show the permissions granted to the role. image::ROOT:astream-roles.png[] @@ -9,24 +10,27 @@ If the default roles don't meet your requirements, you can use custom roles that == Create custom role -[NOTE] -==== -You can also create custom roles using the DevOps API. -==== +In the {astra_ui} or {astra_db} DevOps API, you can create custom roles. . From your Organization page, select *Role Management*. + . In the main dropdown, select the organization for which you want to add your custom role. + . From *Settings* page, select *Roles*. + . Select *Add Custom Role*. + . Enter the name you want to use for your custom role. This name should help you easily identify when you want to assign this role to users. + . Select the Organization, Keyspace, Table, and API permissions you want to assign to your custom role. + -[NOTE] +[IMPORTANT] ==== If you want users with this role to be able to see the {product_name} user interface, make sure you select _Manage Streaming_ permissions. ==== -+ + . If you want to apply your selected permissions to specific databases or keyspaces, toggle the switch to not apply the permissions to all databases in an organization. Then select the specific databases or keyspaces to which you want to apply the permissions. + . Once you have selected your permissions, select *Create Role*. To see your custom roles, select *Role Management* within your Organization. You can now invite users using your new custom role. @@ -34,8 +38,11 @@ To see your custom roles, select *Role Management* within your Organization. You == Edit user roles . From *Settings* page, select *Roles*. + . Select *Edit Role* from the overflow menu for the custom role you want to update. + . When editing the role, you can edit the name, permissions, database, and keyspace. + . Once you have updated your permissions, select *Edit Role*. Your updated custom role will show up in *Roles* within your Organization. diff --git a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc index 88c03d1..295a9ee 100644 --- a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc +++ b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc @@ -29,9 +29,9 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- -[NOTE] +[IMPORTANT] ==== -Pulsar creates an exclusive subscription by default when no `subscriptionType` is declared. +Pulsar creates an exclusive subscription by default if you don't delcare a `subscriptionType`. ==== . Open the `pulsar-subscription-example` repo in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. diff --git a/modules/apis/pages/api-operations.adoc b/modules/apis/pages/api-operations.adoc index beab7fa..eaec42f 100644 --- a/modules/apis/pages/api-operations.adoc +++ b/modules/apis/pages/api-operations.adoc @@ -19,8 +19,8 @@ As noted in the guide above, save or download the new Astra Streaming Token valu == Prerequisites -Export the following environmental variables to your path. - +* Export the following environmental variables to your path: ++ [source,bash] ---- export PULSAR_TOKEN="" @@ -37,13 +37,11 @@ export FUNCTION="" export TOKENID="" ---- -[NOTE] -==== -“python3 -mjson.tool" in the examples below is only used to format the output into JSON. -It is NOT required to execute the API requests. -==== +* (Optional) The following examples use `python3 -mjson.tool` to format the output into JSON. +This is not required to execute the API requests. == List tenants with details + [tabs] ==== Curl:: @@ -181,6 +179,8 @@ curl --fail --location --request POST 'https://api.astra.datastax.com/v2/streami Result:: + -- +The output includes the "pulsarToken" which is the JWT token for this Pulsar instance. + [source,console] ---- { @@ -212,12 +212,8 @@ Result:: -- ==== -[NOTE] -==== -The output includes the "pulsarToken" which is the JWT token for this Pulsar instance. -==== - == Delete a tenant + [tabs] ==== Curl:: @@ -463,10 +459,9 @@ Output: No reply means successful. ==== === Set AutoTopicCreation True/False on Namespace -[NOTE] -==== + Input parameter “topicType" should be either “non-partitioned" or “partitioned". -==== + [tabs] ==== Curl:: @@ -1068,10 +1063,9 @@ Result:: ==== === Create a Subscription for a Topic (Replicated) -[NOTE] -==== + "Replicated=true" can be set to “false" for non replicated subscriptions. -==== + [tabs] ==== Curl:: @@ -1198,10 +1192,9 @@ Result:: ==== === Create Geo-Replication between Namespaces -[NOTE] -==== + The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of Astra Streaming sections of this guide. -==== + [tabs] ==== Curl:: @@ -1230,10 +1223,9 @@ Output: No reply means successful. ==== === Delete Geo-Replication between Namespaces -[NOTE] -==== + The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of Astra Streaming sections of this guide. -==== + [tabs] ==== Curl:: @@ -1552,10 +1544,9 @@ Output: No reply means successful. == Astra Streaming JWT Token DevOps APIs === List Existing Tokens IDs Get a list of Token IDs for your Cluster. With the TokenID, you can then lookup and obtain the Pulsar JWT Token string. The TokenIDs are also listed in the Astra UI for that Tenant and Cluster. -[NOTE] -==== -Note - Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. -==== + +Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. + [tabs] ==== Curl:: @@ -1610,10 +1601,9 @@ eyJhbGciOiJSUzI1NiIsI . . . === Create a JWT Token Create a new Pulsar JWT Token. The new JWT Token will also be visible in the Astra UI for that Tenant and Cluster. -[NOTE] -==== + Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. -==== + [tabs] ==== Curl:: @@ -1637,11 +1627,10 @@ eyJhbGciOiJSUzI1NiIsI . . . ==== === Delete a JWT Token -[NOTE] -==== + Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. List of "TOKENID" can be obtained from List Existing Tokens IDs. -==== + [tabs] ==== Curl:: @@ -2039,13 +2028,16 @@ Output: No reply means successful. In the example above, a configuration file is provided as input to CURL. The file is named "mynetty-source-config.json", which has the following context for the built-in “netty" source connector in Astra Streaming. + [source,bash] ---- Output: No reply means successful. ---- -[NOTE] + +[TIP] ==== -The CURL parameter “@" indicates an input file. When executing the CURL command, ensure the input file is accessible, and in the proper directory for reading. +The curl parameter `@` indicates an input file. +When executing the curl command, ensure the input file is accessible and in the proper directory for reading. ==== === Delete a Source Connector @@ -2093,6 +2085,7 @@ Output: No reply means successful. ==== In the example above, a configuration file is provided as input to CURL. The file is named mykafka-sink-config.json which has the following context for the built-in “kafka" source connector in Astra Streaming. + [source,bash] ---- { @@ -2121,10 +2114,10 @@ In the example above, a configuration file is provided as input to CURL. The fi "inputs": [ "persistent://testcreate/ns0/mytopic3" ] ---- -[NOTE] +[TIP] ==== -The CURL parameter “@" indicates an input file. -When executing the CURL command, ensure the input file is accessible, and in the proper directory for reading. +The curl parameter `@` indicates an input file. +When executing the curl command, ensure the input file is accessible and in the proper directory for reading. ==== === Delete a Sink Connector diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index 9f2b819..eab0f18 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -3,7 +3,10 @@ [IMPORTANT] ==== -CDC connectors are only available for {db-serverless} deployments. +* CDC connectors are only available for {db-serverless} deployments. + +* Enabling CDC for {db-serverless} databases increases costs based on your {astra_stream} usage. +See https://www.datastax.com/pricing/astra-streaming[{astra_stream} pricing] and https://www.datastax.com/products/datastax-astra/cdc-for-astra-db[CDC metering rates]. ==== CDC for Astra DB automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data into {product_name} where it can be processed by client applications or sent to downstream systems. @@ -12,12 +15,6 @@ CDC for Astra DB automatically captures changes in real time, de-duplicates the This doc will show you how to create a CDC connector for your Astra DB deployment and send change data to an Elasticsearch sink. -[NOTE] -==== -Enabling CDC for {db-serverless} databases increases costs based on your {astra_stream} usage. -See https://www.datastax.com/pricing/astra-streaming[{astra_stream} pricing] and https://www.datastax.com/products/datastax-astra/cdc-for-astra-db[CDC metering rates]. -==== - == Supported data structures The following data types and corresponding AVRO or logical types are supported for CDC for {db-serverless} databases: @@ -168,18 +165,18 @@ You need the following items to complete this procedure: [[create-tenant]] == Create a streaming tenant -. Log into the {link-astra-portal}. -At the bottom of the Welcome page, select *View Streaming*. +. In the {link-astra-portal} navigation menu, click *Streaming*. + . Select *Create Tenant*. + . Enter a name for your new streaming tenant. + . Select a provider and region. -. Select *Create Tenant*. + -[NOTE] -==== {astra_stream} CDC can only be used in a region that supports both {astra_stream} and {db-serverless} databases. See xref:operations:astream-regions.adoc[] for more information. -==== + +. Select *Create Tenant*. [[create-table]] == Create a table diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index fc4725b..bdea7fa 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -7,17 +7,11 @@ Functions run inside {product_name} and are therefore serverless. You write the Functions are implemented using Apache Pulsar(R) functions. See https://pulsar.apache.org/docs/en/functions-overview/[Pulsar Functions overview] for more information about Pulsar functions. -[NOTE] +[TIP] ==== -Custom functions can only be created by *qualified organizations*. -A *qualified organization* is an organization on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan with a payment method on file. -Upgrade your organization to a qualified organization by: +Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise*] plan with a payment method on file. -* Enrolling in the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan in the {astra_ui} with a payment method. For more, see https://docs.datastax.com/en/astra-serverless/docs/plan/plan-options.html[Plan Options]. -* Contacting our sales team to see how we can help. -* Opening {support_url}[a support ticket]. - -Unqualified orgs can still use xref:streaming-learning:functions:index.adoc[transform functions]. +Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. ==== == Deploy Python functions in a ZIP file @@ -255,9 +249,11 @@ userConfig: + This file will be passed to the pulsar-admin create function command. + -[NOTE] +[IMPORTANT] ==== -Astra Streaming requires the `inputs` topic to have a message schema defined before deploying the function. Otherwise, deployment errors may occur. Use the {astra_ui} to define the message schema for a topic. +Astra Streaming requires the `inputs` topic to have a message schema defined before deploying the function. +Otherwise, deployment errors may occur. +Use the {astra_ui} to define the message schema for a topic. ==== + . Use pulsar-admin to deploy your new JAR to Astra Streaming or Pulsar. @@ -360,15 +356,9 @@ $ ./pulsar-admin functions create \ + You will see "Created Successfully!" if the function is set up and ready to accept messages. + -[NOTE] +[TIP] ==== -If you receive a 402 error with "Reason: only qualified organizations can create functions", this means your organization needs to be upgraded to a https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan with a payment method. -A *qualified organization* is an organization on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan with a payment method on file. -Upgrade your organization to a qualified organization by: - -* Enrolling in the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan in the {astra_ui} with a payment method. For more, see https://docs.datastax.com/en/astra-serverless/docs/plan/plan-options.html[Plan Options]. -* Contacting our sales team to see how we can help. -* Opening {support_url}[a support ticket]. +If you receive a 402 error with "Reason: only qualified organizations can create functions", this means your organization needs to be upgraded to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise*] plan with a payment method on file. ==== . Use `./pulsar-admin functions list --tenant ` to list the functions in your tenant and confirm your new function was created. diff --git a/modules/developing/pages/astream-kafka.adoc b/modules/developing/pages/astream-kafka.adoc index c3bf86f..0498098 100644 --- a/modules/developing/pages/astream-kafka.adoc +++ b/modules/developing/pages/astream-kafka.adoc @@ -20,9 +20,9 @@ This document will help you get started producing and consuming Kafka messages o . When the popup appears, confirm you want to enable Kafka on your tenant. + -[NOTE] +[IMPORTANT] ==== -You will not be able to remove the Kafka namespaces created on your tenant with this step. +You can't remove the Kafka namespaces created on your tenant with this step. You must remove the tenant itself to remove these namespaces. ==== @@ -55,10 +55,7 @@ You're now ready to connect Kafka and Pulsar. + image::astream-create-kafka-topic.png[Create Kafka Topic] + -[NOTE] -==== This example uses tools included with the https://kafka.apache.org/downloads[Apache Kafka tarball]. -==== . Move the `ssl.properties` file you downloaded to your `Kafka_2.13-3.1.0/config` folder. These values are required for SSL encryption. For this example, the values are: + diff --git a/modules/developing/pages/astream-rabbit.adoc b/modules/developing/pages/astream-rabbit.adoc index 4468537..a8576f1 100644 --- a/modules/developing/pages/astream-rabbit.adoc +++ b/modules/developing/pages/astream-rabbit.adoc @@ -3,18 +3,10 @@ {starlight_rabbitmq} brings native RabbitMQ(R) protocol support to Apache Pulsar(R), enabling migration of existing RabbitMQ applications and services to Pulsar without modifying the code. RabbitMQ applications can now leverage Pulsar’s powerful features, such as: -* *Consistent metadata store* -+ -{starlight_rabbitmq} uses Apache ZooKeeper(TM), so existing Zookeeper configuration stores can store {starlight_rabbitmq} metadata. -* *Security and authentication* -+ -{starlight_rabbitmq} connects to brokers that have TLS, authentication, and/or authorization enabled, because it uses the same `AuthenticationService` as Pulsar. -* *Clustering* -+ -Launch multiple stateless {starlight_rabbitmq} instances simultaneously for scalability and high availability. -* *Multi-tenancy* -+ -{starlight_rabbitmq} offers support for multi-tenancy, mapping an AMQP virtual host to a Pulsar tenant and namespace. +* *Consistent metadata store*: {starlight_rabbitmq} uses Apache ZooKeeper(TM), so existing Zookeeper configuration stores can store {starlight_rabbitmq} metadata. +* *Security and authentication*: {starlight_rabbitmq} connects to brokers that have TLS, authentication, and/or authorization enabled, because it uses the same `AuthenticationService` as Pulsar. +* *Clustering*: Launch multiple stateless {starlight_rabbitmq} instances simultaneously for scalability and high availability. +* *Multi-tenancy*: {starlight_rabbitmq} offers support for multi-tenancy, mapping an AMQP virtual host to a Pulsar tenant and namespace. By integrating two popular event streaming ecosystems, {starlight_rabbitmq} unlocks new use cases and reduces barriers for users adopting Pulsar. Leverage advantages from each ecosystem and build a truly unified event streaming platform with {starlight_rabbitmq} to accelerate the development of real-time applications and services. @@ -26,9 +18,10 @@ Get started producing and consuming RabbitMQ messages on a Pulsar cluster. . When the popup appears, confirm you want to enable RabbitMQ on your tenant. + -[NOTE] +[IMPORTANT] ==== -You will not be able to remove the RabbitMQ namespace created on your tenant with this step. +You can't remove the RabbitMQ namespace created on your tenant with this step. +You must remove the tenant itself to remove this namespace. ==== . Select *Enable RabbitMQ*. diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index 5b7c5f6..f5cad08 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -19,16 +19,10 @@ Visit our https://github.com/datastax/astra-streaming-examples[examples repo] to == Setup environment -Before we get started with the app, a little pre-work needs to be done. - +Install the C++ Pulsar library dependency. The Node.js Pulsar client npm package depends on the C++ Pulsar library. -[NOTE] -==== -Pulsar Node client versions 1.8 and greater do not require installation of the C++ Pulsar library dependency. -==== - -Install the C++ Pulsar library dependency. +Pulsar Node client versions 1.8 and later do not require installation of the C++ Pulsar library dependency. [tabs] ==== diff --git a/modules/developing/pages/configure-pulsar-env.adoc b/modules/developing/pages/configure-pulsar-env.adoc index 10c9598..bd1f06b 100644 --- a/modules/developing/pages/configure-pulsar-env.adoc +++ b/modules/developing/pages/configure-pulsar-env.adoc @@ -81,15 +81,17 @@ With your Astra Streaming tenant's configuration in place, you can use any of th With the Pulsar folder in place and the correct client configuration saved, the next step is to validate everything. Run each command to validate binary conf. -TIP: For a full reference of all commands within the CLI, see the https://pulsar.apache.org/docs/reference-cli-tools/[Pulsar's CLI docs]. +For a full reference of all commands within the CLI, see the https://pulsar.apache.org/docs/reference-cli-tools/[Pulsar's CLI docs]. List all tenants: + [source,shell,subs="attributes+"] ---- ./bin/pulsar-admin tenants list ---- Produce a message: + [source,shell,subs="attributes+"] ---- ./bin/pulsar-client produce /default/ --messages "Hi there" --num-produce 1 diff --git a/modules/developing/pages/gpt-schema-translator.adoc b/modules/developing/pages/gpt-schema-translator.adoc index 5d28de9..b1c4ef3 100644 --- a/modules/developing/pages/gpt-schema-translator.adoc +++ b/modules/developing/pages/gpt-schema-translator.adoc @@ -1,13 +1,9 @@ = {gpt-schema-translator} -[NOTE] -==== -The {gpt-schema-translator} is currently available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[Astra DB Sink Connector], with support for additional connectors to come. -==== +Systems within streaming pipelines typically represent schema and data types differently. +This requires schemas within a pipeline to be mapped to each other, a process which is complicated, tedious, and error-prone. +For example, to send data from a CDC-enabled Cassandra (C*) table to a Pulsar topic, you must define a schema mapping between the C* table and the Pulsar topic, represented below: -== Overview - -Systems within streaming pipelines typically represent schema and data types differently. This requires schemas within a pipeline to be mapped to each other, a process which is complicated, tedious, and error-prone. For example, to send data from a CDC-enabled Cassandra (C*) table to a Pulsar topic, you must define a schema mapping between the C* table and the Pulsar topic, represented below. [tabs] ==== Cassandra table schema:: @@ -98,13 +94,15 @@ The {gpt-schema-translator} uses generative AI to automatically generate schema == Usage -To use the {gpt-schema-translator}, start by creating an AstraDB sink connector as you usually would. If you're unsure about this, see the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[Astra DB Sink Connector documentation]. +The {gpt-schema-translator} is available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[Astra DB Sink Connector]. +To use the {gpt-schema-translator}, create an Astra DB sink connector. In this example, we'll continue with the two schemas from above: one JSON schema for the Pulsar topic, and one CQL schema for the Astra DB table. The {gpt-schema-translator} will generate a mapping between the two schemas, which can be used by the Astra DB sink connector to write data from the Pulsar topic to the Astra DB table. image::two-schemas.png[Schema mapping,320,240] To generate a schema mapping, click the "Generate Mapping With GPT" button. This will generate a schema mapping. + [tabs] ==== Cassandra table schema:: diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index b5a5708..85c52c6 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -8,89 +8,79 @@ When you create a tenant in Astra Streaming, all supporting APIs are enabled. To interact with your Astra Streaming tenant you will need two pieces of information: a token and the service URL. This guide will show you how to gather that information and test your connection. -TIP: The https://pulsar.apache.org/docs/2.10.x/reference-rest-api-overview/[Pulsar documentation] has a full reference for each API supported in a cluster. +[TIP] +==== +The https://pulsar.apache.org/docs/2.10.x/reference-rest-api-overview/[Pulsar documentation] has a full reference for each API supported in a cluster. +==== This reference will be helpful as you look to automate certain actions in your Astra Streaming tenant. -== Finding your tenant's web service URL +== Find your tenant's web service URL -First, you need to know where to send your curl request. -This is known as the "Web Service URL". +You send HTTP requests to your tenant's _Web Service URL_: -[NOTE] -==== -Pulsar has other connection URLs, and it's easy to confuse them. + -The easiest way to tell the difference is the protocol being used in the address: +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -**The Web Service URL starts with "http(s)", while the Broker Service URL starts with "pulsar(+ssl)".** -==== +. Click the *Connect* tab. ++ +image:connect-tab.png[Connect tab in Astra Streaming] -After you have signed in to your Astra account, navigate to the "Connect" tab in any of your streaming tenants. +. In the *Details* section, locate the *Tenant Details*. +Here you can find the essential information you need to communicate with your Pulsar tenant. ++ +image:tenant-details.png[Tenant details in Astra Streaming] -|=== -a|image:connect-tab.png[Connect tab in Astra Streaming] -|=== +. Copy the *Web Service URL*. -Choose the "Details" area of the page and find the section labeled "Tenant Details". -This is a listing of all essential info you will need to communicate with your Pulsar tenant. +[TIP] +==== +The *Web Service URL* is different from a Pulsar *Broker Service URL*. -|=== -a|image:tenant-details.png[Tenant details in Astra Streaming] -|=== +Web Service URLs start with `http`. +Broker Service URLs start with `pulsar(+ssl)`. +==== -Locate the value for the "Web Service URL" and copy the address to a safe place. +== Retrieve your Pulsar token in Astra Streaming -== Retrieving your Pulsar token in Astra Streaming +You need a Pulsar token to authenticate requests. -Next, you need an authentication token. -Most APIs use *tokens* to authenticate a request, and Pulsar follows this same pattern. +An xref:operations:astream-token-gen.adoc[{astra_db} application token] is _not_ the same as a Pulsar token. -[NOTE] -==== -Don't confuse your Pulsar token with your Astra token - the concept is the same, but it's a different authentication. xref:operations:astream-token-gen.adoc[More details are here]. -==== +. To create a new Pulsar token in your Astra Streaming tenant, navigate to the "Settings" tab in the Astra portal. ++ +image:settings-tab.png[Settings tab in Astra Streaming] -To create a new Pulsar token in your Astra Streaming tenant, navigate to the "Settings" tab in the Astra portal. -|=== -a|image:settings-tab.png[Settings tab in Astra Streaming] -|=== - -Click "Create Token" and choose the time to expire. +. Click "Create Token" and choose the time to expire. Be smart about this choice - "Never Expire" isn't always the best option. A window will appear with the newly generated token - it's a long string of letters and numbers. ++ +image:copy-token.png[Copy token in Astra Streaming] -|=== -a|image:copy-token.png[Copy token in Astra Streaming] -|=== - -Click the clipboard icon to copy the token to your clipboard, and paste the token in a safe place. +. Click the clipboard icon to copy the token to your clipboard, and paste the token in a safe place. This is the only time you will be able to copy it. -== Example curl call - -You now have the two necessary ingredients to make RESTful calls to your Astra Streaming tenant. Let's put them to use! - -Set the required variables in a terminal window. +== Make a request +. Set your environment variables: ++ [source,shell,subs="attributes+"] ---- PULSAR_TOKEN="" WEB_SERVICE_URL="" ---- -Run the following curl command to list all the built-in sink connectors. - +. Run a curl command. +The following example lists built-in sink connectors. ++ [source,shell,subs="attributes+"] ---- curl -sS --fail --location --request GET \ -H "Authorization: $PULSAR_TOKEN" \ "$WEB_SERVICE_URL/admin/v3/sinks/builtinsinks" ---- - -The output should be a very long JSON-formatted string containing details about all the sink connectors waiting for you to have fun with {emoji-smile}. - -TIP: If your curl command doesn't go so smoothly, add `-v` to your call. + -The `-v` is for verbose, and prints more detail about what problem has occurred. ++ +The default response is a single JSON string. +You can use modifications like ` | jq .` to format the output for easier reading. == See also diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index 34695e7..91a2647 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -103,16 +103,15 @@ include::{astra-streaming-examples-repo}/astra-cli/create-tenant.sh[] A namespace exists within a tenant. A namespace is a logical grouping of message topics. + +{astra_stream} automatically create a `default` namespaces when you create a tenant. +You can use the default namespace or create new ones. + Tenants usually have many namespaces. What is contained within namespace is limited only by your imagination. It could be an environment (dev, stage, prod) or by application (catalog, cart, user) or whatever logical grouping makes sense to you. -Learn more about namespaces in the https://pulsar.apache.org/docs/concepts-messaging/#namespaces[Pulsar docs]. -[NOTE] -==== -Astra Streaming automatically created a namespace named "default" when you created your tenant. -If you would like to use the default namespace instead of creating a new namespace, that's perfectly fine. -==== +Learn more about namespaces in the https://pulsar.apache.org/docs/concepts-messaging/#namespaces[Pulsar docs]. [tabs] ==== diff --git a/modules/operations/pages/astream-limits.adoc b/modules/operations/pages/astream-limits.adoc index 1fbf00f..6af3399 100644 --- a/modules/operations/pages/astream-limits.adoc +++ b/modules/operations/pages/astream-limits.adoc @@ -3,10 +3,8 @@ DataStax {product_name} includes guardrails and limits to ensure good practices, foster availability, and promote optimal configurations for your databases. -[NOTE] -==== -The below limits are for Pay As You Go clusters. These limits may be changed on <> if necessary. {support_url}[Contact Support] to change the limits on a dedicated cluster. -==== +The below limits are for Pay As You Go clusters. +These limits can be different on <>. == {product_name} guardrails @@ -77,18 +75,11 @@ The default settings are: * CPU: 0.25 core * RAM: 500MB -[NOTE] -==== -Custom functions can only be created by *qualified organizations*. -A *qualified organization* is an organization on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan with a payment method on file. -Upgrade your organization to a qualified organization by: +=== Custom functions -* Enrolling in the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan in the {astra_ui} with a payment method. For more, see https://docs.datastax.com/en/astra-serverless/docs/plan/plan-options.html[Plan Options]. -* Contacting our sales team to see how we can help. -* Opening {support_url}[a support ticket]. +Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* plan] with a payment method on file. -Unqualified orgs can still use xref:streaming-learning:functions:index.adoc[transform functions]. -==== +Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. == Non-changeable configurations @@ -170,12 +161,11 @@ These namespace policies are initially provisioned in the default settings by {p |=== -[NOTE] -==== +=== Pod label limit + The total characters of tenant name, namespace name, and function name cannot exceed 54 characters. This is a Kubernetes restriction based on a pod label's maximum size of 63 characters. You can read more about Kubernetes pod naming restrictions https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set[here]. -==== == {product_name} topic and namespace actions diff --git a/modules/operations/pages/astream-scrape-metrics.adoc b/modules/operations/pages/astream-scrape-metrics.adoc index bb99411..e0dba40 100644 --- a/modules/operations/pages/astream-scrape-metrics.adoc +++ b/modules/operations/pages/astream-scrape-metrics.adoc @@ -63,14 +63,14 @@ ts=2022-05-10T20:40:30.877Z caller=main.go:1199 level=info msg="Completed loadin ts=2022-05-10T20:40:30.877Z caller=main.go:930 level=info msg="Server is ready to receive web requests." ---- + -[NOTE] +[TIP] ==== -If you encounter a `mounts denied` permissions error, ensure your local directory is shared with Docker in File Sharing -> Resources. +If you get a `mounts denied` permissions error, in Docker, go to *File Sharing > Resources*, and then make sure your local directory is shared with Docker. ==== == Scrape with Prometheus -. Open your Prometheus dashboard at `localhost:9090`. In the *Status -> Targets* window, you should see the endpoint targeted in `static_configs` in an *UP* state. +. Open your Prometheus dashboard at `localhost:9090`. In the *Status > Targets* window, you should see the endpoint targeted in `static_configs` in an *UP* state. . Navigate to the *Graph* window. Enter `pulsar_in_messages_total` in the *Expression* field and select *Execute*. Prometheus will now display total incoming Pulsar messages to your {product_name} cluster. @@ -128,10 +128,8 @@ The following Prometheus metrics are exposed by {product_name}: * https://pulsar.apache.org/docs/reference-metrics/#pulsar-functions[Pulsar Functions metrics] * https://pulsar.apache.org/docs/reference-metrics/#connectors[Connector metrics] -[NOTE] -==== -Cluster operational metrics are *not* exposed to individual cluster tenants. A tenant can only access its own metrics on the *broker* or *function worker* pods. -==== +Cluster operational metrics are *not* exposed to individual cluster tenants. +A tenant can only access its own metrics on the *broker* or *function worker* pods. == See also diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index 7dd6001..bd2726d 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -1,10 +1,5 @@ = External Prometheus and Grafana Integration -[NOTE] -==== -This article is a continuation of xref:monitoring/index.adoc[]. Please read that article first to understand the fundamentals of what resources are being used. -==== - Astra Streaming exposes some of the Pulsar metrics through Prometheus endpoints. The Prometheus configuration information to scrape Astra Streaming metrics into an external Prometheus server is found in the Astra streaming UI. This document will show you how to set up a Prometheus server in a Kubernetes cluster and configure it to scrape Astra streaming metrics. @@ -30,9 +25,10 @@ For more on downloading the Prometheus configuration from the Astra Streaming UI - targets: ['prometheus-gcp-uscentral1.streaming.datastax.com'] ---- + -[NOTE] +[IMPORTANT] ==== -DO NOT add other Prometheus configurations like scrape_interval or evaluation_interval yet. They will be added in later steps when configuring the Helm chart. +Do _not_ add other Prometheus configurations, like `scrape_interval` or `evaluation_interval` yet. +These are added later when configuring the Helm chart. ==== . Create a K8s secret named astra-msgenrich with the above configuration file and apply it. @@ -97,6 +93,7 @@ To fix this issue, go to the {astra_ui} and create a new JWT token (preferably w == See also -* For a list of exposed endpoints for Astra Streaming metrics, see xref:monitoring/metrics.adoc[]. -* To scrape Astra Streaming metrics into New Relic, see xref:monitoring/new-relic.adoc[]. +* xref:monitoring/index.adoc[] +* xref:monitoring/metrics.adoc[] +* xref:monitoring/new-relic.adoc[] diff --git a/modules/operations/pages/monitoring/metrics.adoc b/modules/operations/pages/monitoring/metrics.adoc index 1fbbfdc..9408543 100644 --- a/modules/operations/pages/monitoring/metrics.adoc +++ b/modules/operations/pages/monitoring/metrics.adoc @@ -1,10 +1,5 @@ = Grafana dashboards for Astra Streaming metrics -[NOTE] -==== -This article is a continuation of xref:monitoring/index.adoc[]. Please read that article first to understand the fundamentals of what resources are being used. -==== - DataStax has built Grafana dashboards around core message processing for the exposed Astra Streaming metrics. The dashboards can be found https://github.com/datastax/astra-streaming-examples/tree/master/grafana-dashboards[on GitHub]. @@ -16,5 +11,6 @@ Dashboards are available for scraping metrics at Pulsar's tenant, namespace, and == See also -* To scrape Astra Streaming metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. -* To scrape Astra Streaming metrics into New Relic, see xref:monitoring/new-relic.adoc[]. \ No newline at end of file +* xref:monitoring/index.adoc[] +* xref:monitoring/integration.adoc[] +* xref:monitoring/new-relic.adoc[] \ No newline at end of file diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index ed2e1ac..126b2b0 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -1,25 +1,26 @@ = New Relic Integration -[NOTE] -==== -This article is a continuation of xref:monitoring/index.adoc[]. Please read that article first to understand the fundamentals of what resources are being used. -==== +There are three ways to integrate external Prometheus data into New Relic: -According to the https://docs.newrelic.com/[New Relic documentation], there are 3 different ways to integrate external Prometheus data into New Relic: +* https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#Agent[Prometheus Agent for Kubernetes] +* https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#OpenMetrics[Prometheus OpenMetrics integration for Docker] +* https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] -* Option 1: https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#Agent[Prometheus Agent for Kubernetes] -* Option 2: https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#OpenMetrics[Prometheus OpenMetrics integration for Docker] -* Option 3: https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] +Only the third option is relevant to Astra Streaming. -Options 1 and 2 are not relevant for Astra Streaming, so option 3 is the only possible solution. -Option 3 would require modifying the configuration of the Prometheus server which Astra Streaming relies on, but due to the managed-service nature of Astra Streaming, this is not possible. Instead, an extra Prometheus server can be installed (per the instructions in xref:monitoring/integration.adoc[]) to act as a bridge to forward scraped Astra Streaming metrics to New Relic. The diagram below shows this idea: +Typically, this option requires modifying the configuration of the Prometheus server that Astra Streaming relies on. +However, this isn't possible because {astra_stream} is a managed service. + +Instead, you can xref:monitoring/integration.adoc[install an extra Prometheus server] to act as a bridge to forward scraped Astra Streaming metrics to New Relic. .External Prometheus server for New Relic integration image::monitoring-map.png[Map,align="center"] == Prerequisites -* https://docs.newrelic.com/[Set up a New Relic account] -* When creating a New Relic account, it will generate a license key. Save this key to a local file for usage in later steps. + +* Review xref:monitoring/index.adoc[]. +* https://docs.newrelic.com/[Set up a New Relic account.] +* Save your new relic license key locally. == Configure New Relic diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index 1072532..34de10b 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -29,7 +29,7 @@ When you create new tenants, additional options are available for deploying to y There are less limits in *dedicated environments* than in *Pay As You Go* - it's your cluster, after all. Finally, billing for a dedicated cluster is unique to each customer. -TIP: In a *Pay As You Go* environment, you can create tenants in any of the xref:astream-regions.adoc[supported regions], while *dedicated environments* are open to almost any public cloud region. +In a *Pay As You Go* environment, you can create tenants in any of the xref:astream-regions.adoc[supported regions], while *dedicated environments* are open to almost any public cloud region. ==== .Why does DataStax call it "serverless"? @@ -69,7 +69,7 @@ With a dedicated cluster you have the option to connect over the (public) intern As a *Pay As You Go* customer, the Astra platform offers single sign-on through your GitHub account and your Google account. Astra also offers custom SSO integration as a premium option. mailto:streaming@datastax.com[Email the team] for more information. -NOTE: To integrate a custom SSO provider, you will need a non-default Astra Organization. +To integrate a custom SSO provider, you will need a non-default Astra Organization. Refer to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/configuring-sso.html[Astra Serverless SSO documentation] or mailto:streaming@datastax.com[email the team] for more information. ==== @@ -117,9 +117,12 @@ For more on geo-replication, see xref:astream-georeplication.adoc[]. .Can I migrate data from my existing Pulsar cluster to Astra Streaming? [%collapsible] ==== -Unless you are starting a project from scratch, you likely have message data that needs to be brought over to your Astra Streaming tenant(s). The Streaming Team has quite a bit of experience with this and can help you find the right way to migrate. mailto:streaming@datastax.com[Email the team] for more information. +Unless you are starting a project from scratch, you likely have message data that needs to be brought over to your Astra Streaming tenants. +The Streaming Team has quite a bit of experience with this and can help you find the right way to migrate. mailto:streaming@datastax.com[Email the team] for more information. -TIP: Did you know every tenant in Astra Streaming comes with custom ports for Kafka and RabbitMQ workloads? We also offer a fully compatible JMS implementation for your Java workloads! xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more here]. +Every tenant in Astra Streaming comes with custom ports for Kafka and RabbitMQ workloads. +{company} also offers a fully-compatible JMS implementation for your Java workloads. +xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] ==== == Application Development diff --git a/modules/ragstack/pages/quickstart.adoc b/modules/ragstack/pages/quickstart.adoc index cf68428..f463ab2 100644 --- a/modules/ragstack/pages/quickstart.adoc +++ b/modules/ragstack/pages/quickstart.adoc @@ -3,11 +3,13 @@ The {ragstack} project combines the intelligence of large language models with the agility of stream processing to create powerful Generative AI applications. This guide will help you build and deploy a {ragstack} application to Astra Streaming. + [IMPORTANT] ==== *Private preview feature* {ragstack} is available only in a *private preview*. This feature is not intended for production use, has not been certified for production workloads, and might contain bugs and other functional issues. There is no guarantee that a preview feature will ever become generally available.If you are interested in participating in the private preview, contact us at mailto:Astra-PM@datastax.com[Astra-PM@datastax.com,RAGSstack private preview,I am interested in the RAGStack private preview.]. We will contact you with more information. ==== + == Install {ragstack} CLI Install the {ragstack} CLI: @@ -67,11 +69,10 @@ token: AstraCS:... ---- Your tenant is now connected to the {ragstack} CLI. -[NOTE] -==== + You can also establish a connection by including the configuration values from the Astra Streaming Connect tab in your {ragstack} application's instance.yaml file. -See << instance >> for an example. -==== +See <> for an example. + == Build a {ragstack} Application Build a {ragstack} application by creating YAML files to describe the application. @@ -164,6 +165,7 @@ secrets: ---- You can either replace the values in secrets.yaml with the actual values, use a `.env` file, or export the secrets as below: + [source,shell] ---- export OPEN_AI_URL=https://company-openai-dev.openai.azure.com/ @@ -179,10 +181,8 @@ export ASTRA_CLIENT_ID=xxxx export ASTRA_TOKEN=AstraCS:... export GOOGLE_CLIENT_ID=xxxx.apps.googleusercontent.com ---- -[NOTE] -==== -For more on creating a Google client ID, see https://developers.google.com/identity/protocols/oauth2#serviceaccount[Google Service Account]. -==== + +For more information about creating a Google client ID, see https://developers.google.com/identity/protocols/oauth2#serviceaccount[Google Service Account]. Pipeline.yaml contains the chain of agents that makes up your program, and the input and output topics that they communicate with. For more on building pipelines, see https://docs.langstream.ai/building-applications/pipelines[Pipelines]. From 3bc95fb861867c329d6d33d0f6847b0b887745f2 Mon Sep 17 00:00:00 2001 From: April M Date: Mon, 14 Oct 2024 17:21:57 -0700 Subject: [PATCH 04/18] tabs --- modules/apis/pages/api-operations.adoc | 944 ++++-------------- modules/developing/pages/astra-cli.adoc | 30 +- .../developing/pages/astream-functions.adoc | 15 +- .../pages/clients/nodejs-produce-consume.adoc | 9 +- .../pages/clients/spring-produce-consume.adoc | 39 +- .../pages/configure-pulsar-env.adoc | 7 - .../pages/gpt-schema-translator.adoc | 8 +- modules/getting-started/pages/index.adoc | 116 +-- .../pages/astream-georeplication.adoc | 28 +- .../pages/astream-scrape-metrics.adoc | 4 +- .../operations/pages/astream-token-gen.adoc | 47 +- modules/ragstack/pages/quickstart.adoc | 100 +- 12 files changed, 340 insertions(+), 1007 deletions(-) diff --git a/modules/apis/pages/api-operations.adoc b/modules/apis/pages/api-operations.adoc index eaec42f..85bb8b4 100644 --- a/modules/apis/pages/api-operations.adoc +++ b/modules/apis/pages/api-operations.adoc @@ -42,20 +42,14 @@ This is not required to execute the API requests. == List tenants with details -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/tenants' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ @@ -107,24 +101,18 @@ Result:: } ] ---- --- ==== == List Astra Streaming cloud providers -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/providers' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -147,15 +135,12 @@ Result:: ] } ---- --- ==== == Create DevOps API -[tabs] -==== -Curl:: -+ --- + +Create a tenant: + [source,curl] ---- curl --location --request POST 'https://api.astra.datastax.com/v2/streaming/tenants' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --data-raw '{ @@ -167,18 +152,16 @@ curl --location --request POST 'https://api.astra.datastax.com/v2/streaming/tena ---- -- -With file input:: -+ --- -[source,bash] +Create a tenant with file input: + +[source,curl] ---- curl --fail --location --request POST 'https://api.astra.datastax.com/v2/streaming/tenants' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --data "@mytenant-config.json" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== The output includes the "pulsarToken" which is the JWT token for this Pulsar instance. [source,console] @@ -209,51 +192,31 @@ The output includes the "pulsarToken" which is the JWT token for this Pulsar ins "pulsarInstance": "" } ---- --- ==== == Delete a tenant -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --location --request DELETE 'https://api.astra.datastax.com/v2/streaming/tenants/{tenant}/clusters/{cluster}' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. == Namespace DevOps APIs For managing Astra Streaming Namespaces, we use the native https://pulsar.apache.org/admin-rest-api/[Pulsar REST APIs]. These are documented on the Apache Pulsar Docs for REST API. === List Existing Namespaces -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --location --request GET “https://$WEB_SERVER_URL/admin/v2/namespaces/$TENANT" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ @@ -261,68 +224,43 @@ Result:: "mytenant/mynamespace" ] ---- --- ==== === Create a Namespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request PUT --header "Authorization: Bearer $PULSAR_TOKEN" "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE" ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- Output: No reply means successful. ---- --- ==== === Delete a Namespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request DELETE --header "Authorization: Bearer $PULSAR_TOKEN" "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Get Namespace Message Retention -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/retention" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -330,15 +268,10 @@ Result:: "retentionSizeInMB": 0 } ---- --- ==== === Set Namespace Message Retention -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --location "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/retention" --header 'Content-Type: application/json' --header "Authorization: Bearer $PULSAR_TOKEN" --data '{ @@ -346,33 +279,19 @@ curl --location "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/retenti "retentionSizeInMB": 102 }' ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Get Namespace BacklogQuota -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/backlogQuotaMap" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -383,15 +302,10 @@ Result:: "policy": "producer_exception" } ---- --- ==== === Set Namespace BacklogQuota Settings -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/backlogQuota" --header "Authorization: Bearer $PULSAR_TOKEN" --header 'Content-Type: application/json' --data '{ @@ -401,72 +315,31 @@ curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces "policy": "producer_exception" }' ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Get Namespace Message TTL -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/messageTTL" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- -[source,console] ----- -Output - Return raw number, like: -3600 ----- --- -==== +The response is a number, such as `3600`. === Set Namespace Message TTL -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/messageTTL" --header "Authorization: Bearer $PULSAR_TOKEN" --header 'Content-Type: application/json' --data 3600 ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Set AutoTopicCreation True/False on Namespace Input parameter “topicType" should be either “non-partitioned" or “partitioned". -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl -sS --fail --location --request POST --header "Authorization: Bearer $PULSAR_TOKEN" "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/autoTopicCreation" --header 'Content-Type: application/json' --data '{ @@ -474,124 +347,53 @@ curl -sS --fail --location --request POST --header "Authorization: Bearer $PULSA "topicType": "non-partitioned" }' ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Get Namespace MaxConsumersPerTopic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/maxConsumersPerTopic" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- -[source,console] ----- -Output - Return raw number, like: -50 ----- --- -==== +The response is a number, such as `50`. === Set Namespace MaxConsumersPerTopic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/maxConsumersPerTopic" --header "Authorization: Bearer $PULSAR_TOKEN" --header 'Content-Type: application/json' --data 100 ---- --- - -Result:: -+ --- -[source,console] ----- -Output - 409 Forbidden (Contact Astra Streaming Support to increase Max) ----- --- -==== === Get Namespace MaxTopicPerNamespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/maxTopicsPerNamespace" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- -[source,console] ----- -Output - Return raw number, like: -50 ----- --- -==== +The response is a number. === Set Namespace MaxTopicPerNamespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/maxTopicsPerNamespace" --header 'Content-Type: application/json' --header "Authorization: Bearer $PULSAR_TOKEN" --data 1000 ---- --- -Result:: -+ --- -[source,console] ----- -Output - Return raw number, like: -50 ----- --- -==== +The response is a number. == Topics DevOps APIs -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ @@ -605,91 +407,46 @@ Result:: "persistent://testtenant/ns0/tp1-partition-3" ] ---- --- ==== === Create Non-partitioned Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request PUT "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE /$TOPIC" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Create Partitioned Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request PUT "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/partitions" --header "Authorization: Bearer $PULSAR_TOKEN" --header "Content-Type: application/json" --data $NUM_OF_PARTITIONS ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Delete a Persistent Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request DELETE"$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/partitions" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Get InternalStats of Non-Partitioned Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/internalStats" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -721,25 +478,20 @@ Result:: "offloaded": false, "underReplicated": false } +} ---- --- ==== === Get Stats of All Topics -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/stats/topics/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -856,25 +608,23 @@ Result:: "backlogStorageByteSize": 1586, "msgBacklogNumber": 4, "updatedAt": "2023-04-25T16:00:24.252452735Z" + ...TRUNCATED FOR READABILITY... + } + ...TRUNCATED FOR READABILITY... +} ---- --- ==== === Get Stats of Partitioned Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/partitioned-stats" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -980,25 +730,21 @@ Result:: } } } + ...TRUNCATED FOR READABILITY... +} ---- --- ==== === Get Stats of Non-partition Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/stats" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1033,25 +779,21 @@ Result:: "lastCompactionFailedTimestamp": 0, "lastCompactionDurationTimeInMills": 0 } + ...TRUNCATED FOR READABILITY... +} ---- --- ==== === Get List of Subscriptions for a Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/subscriptions" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ @@ -1059,95 +801,49 @@ Result:: "subscript2" ] ---- --- ==== === Create a Subscription for a Topic (Replicated) "Replicated=true" can be set to “false" for non replicated subscriptions. -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl -sS --fail --location --request PUT "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/subscription/$SUBSCRIPTION?replicated=true" --header "Authorization: Bearer $PULSAR_TOKEN" --header "Content-Type: application/json" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Delete a Subscription for a Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request DELETE"$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/subscription/$SUBSCRIPTION" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Clear a Subscription for a Topic -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/subscription/$SUBSCRIPTION/skip_all" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. == Geo-Replication DevOps APIs === Get Status of Geo-Replication -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --location --fail --request GET "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1187,19 +883,16 @@ Result:: "pulsarInstance": "prod0", "regionZone": "" } + ...TRUNCATED FOR READABILITY... + } +} ---- --- ==== === Create Geo-Replication between Namespaces The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of Astra Streaming sections of this guide. -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --location --fail --request POST "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" --header "Content-Type: application/json" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --data-raw '{ @@ -1210,27 +903,13 @@ curl --location --fail --request POST "https://api.astra.datastax.com/v2/streami "originCluster": "pulsar-aws-useast2" }' ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Delete Geo-Replication between Namespaces The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of Astra Streaming sections of this guide. -[tabs] -==== -Curl:: -+ --- [source,bash] ---- curl --location --fail --request DELETE "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" \ @@ -1244,59 +923,39 @@ curl --location --fail --request DELETE "https://api.astra.datastax.com/v2/strea "originCluster": "pulsar-aws-useast2" }' ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. == Functions DevOps APIs === List Existing Functions in a Namespace -[tabs] -==== -Curl:: -+ --- + [source,bash] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ "testfunction1" ] ---- --- ==== === Get Status of a Function -[tabs] -==== -Curl:: -+ --- + [source,bash] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE/$FUNCTION/status" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1321,26 +980,20 @@ Result:: } } ] - +} ---- --- ==== === Get Stats of a Function -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE/$FUNCTION/stats" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1378,26 +1031,20 @@ Result:: } } ] +} ---- --- ==== === Get Function Details -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE/$FUNCTION" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1471,96 +1118,53 @@ Result:: "maxPendingAsyncRequests": null, "exposePulsarAdminClientEnabled": null, "subscriptionPosition": "Latest" +} ---- --- ==== === Start a Function -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE/$FUNCTION/start" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Stop a Function -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE/$FUNCTION/stop" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Restart a Function -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENANT/$NAMESPACE/$FUNCTION/restart" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. == Astra Streaming JWT Token DevOps APIs + === List Existing Tokens IDs + Get a list of Token IDs for your Cluster. With the TokenID, you can then lookup and obtain the Pulsar JWT Token string. The TokenIDs are also listed in the Astra UI for that Tenant and Cluster. Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --location --request GET "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ @@ -1572,58 +1176,45 @@ Result:: } ] ---- --- ==== === List Token String by ID -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens/$TOKENID" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- Output: Raw string JWT token eyJhbGciOiJSUzI1NiIsI . . . ---- --- ==== === Create a JWT Token + Create a new Pulsar JWT Token. The new JWT Token will also be visible in the Astra UI for that Tenant and Cluster. Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --fail --location --request POST "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- Output: new raw string JWT token eyJhbGciOiJSUzI1NiIsI . . . ---- --- ==== === Delete a JWT Token @@ -1631,93 +1222,63 @@ eyJhbGciOiJSUzI1NiIsI . . . Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. List of "TOKENID" can be obtained from List Existing Tokens IDs. -[tabs] -==== -Curl:: -+ --- [source,curl] ---- curl --fail --location --request DELETE "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. == Pulsar IO Connectors DevOps APIs + Pulsar Sources and Sinks share a similar API structure for most methods. As such, this guide will show both Source and Sink CURL examples together. === List Existing Sources in a Namespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ "mysource1" ] ---- --- ==== === List Existing Sinks in a Namespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- [ "mysink1" ] ---- --- ==== === Get Status of a Source -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SOURCE/status" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1741,25 +1302,20 @@ Result:: } } ] +} ---- --- ==== === Status of a Sink in a Namespace -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SINK/status" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1783,25 +1339,20 @@ Result:: } } ] +} ---- --- ==== === Get Source Connector Details -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SOURCE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1838,25 +1389,20 @@ Result:: "serdeClassName": null, "tenant": "testcreate", "topicName": "persistent://testcreate/ns0/t1" +} ---- --- ==== === Get Sink Details -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$NAMESPACE/$SINK" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -1908,132 +1454,58 @@ Result:: "transformFunction": null, "transformFunctionClassName": null, "transformFunctionConfig": null +} ---- --- ==== === Start a Source Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SOURCE/start" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- - -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Start a Sink -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$NAMESPACE/$SINK/start" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- - -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Stop a Source Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SOURCE/stop" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- - -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Stop a Sink Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$NAMESPACE/$SINK/stop" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- - -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Create a Source Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SOURCE" --header "Authorization: Bearer $PULSAR_TOKEN" --form "sourceConfig=@mynetty-source-config.json;type=application/json" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. In the example above, a configuration file is provided as input to CURL. The file is named "mynetty-source-config.json", which has the following context for the built-in “netty" source connector in Astra Streaming. -[source,bash] ----- -Output: No reply means successful. ----- - [TIP] ==== The curl parameter `@` indicates an input file. @@ -2041,52 +1513,26 @@ When executing the curl command, ensure the input file is accessible and in the ==== === Delete a Source Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request DELETE "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/$NAMESPACE/$SOURCE" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. === Create a Sink Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$NAMESPACE/$SINK" --header "Authorization: Bearer $PULSAR_TOKEN" --form "sinkConfig=@mykafka-sink-config.json;type=application/json" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. In the example above, a configuration file is provided as input to CURL. The file is named mykafka-sink-config.json which has the following context for the built-in “kafka" source connector in Astra Streaming. -[source,bash] +[source,json] ---- { "tenant": "testcreate", @@ -2112,6 +1558,7 @@ In the example above, a configuration file is provided as input to CURL. The fi "topic": "mykafka-topic" }, "inputs": [ "persistent://testcreate/ns0/mytopic3" ] +} ---- [TIP] @@ -2121,23 +1568,10 @@ When executing the curl command, ensure the input file is accessible and in the ==== === Delete a Sink Connector -[tabs] -==== -Curl:: -+ --- + [source,curl] ---- curl --fail --location --request DELETE "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$NAMESPACE/$SINK" --header "Authorization: Bearer $PULSAR_TOKEN" ---- --- -Result:: -+ --- -[source,console] ----- -Output: No reply means successful. ----- --- -==== +No response indicates success. \ No newline at end of file diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index 99327d9..a4f6cc5 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -20,22 +20,16 @@ The advantage for you: {astra_cli} makes it possible to submit commands instead Here are two quick {astra_cli} command examples to demonstrate how Astra CLI manages resources across {astra_db} databases and {astra_stream} tenants from your local terminal. -Let's create an {astra_db} database named `demo` from the command line: - -[tabs] -==== -Astra CLI:: +. Create an {astra_db} database named `demo` from the command line: + --- [source,bash,subs="attributes+"] ---- astra db create demo -k ks2 --if-not-exist --wait ---- --- - -Result:: + --- +.Result +[%collapsible] +==== [source,bash,subs="attributes+"] ---- [INFO] Database 'demo' does not exist. Creating database 'demo' with keyspace 'ks2' @@ -44,30 +38,22 @@ Result:: [INFO] Database 'demo' has status 'ACTIVE' (took 103513 millis) [OK] Database 'demo' is ready. ---- --- ==== -Now let's create a Pulsar tenant: - -[tabs] -==== -Astra CLI:: +Create a Pulsar tenant: + --- [source,bash,subs="attributes+"] ---- astra streaming create new-tenant-from-cli ---- --- - -Result:: + --- +.Result +[%collapsible] +==== [source,bash,subs="attributes+"] ---- [OK] Tenant 'new-tenant-from-cli' has being created. ---- --- ==== == See also diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index bdea7fa..aa514fc 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -199,22 +199,16 @@ An example pom.xml file is shown below: ---- ==== -. Package the JAR file with Maven. +. Package the JAR file with Maven: + -[tabs] -==== -Maven:: -+ --- [source,bash] ---- mvn package ---- --- - -Result:: + --- +.Result +[%collapsible] +==== [source,bash] ---- [INFO] ------------------------------------------------------------------------ @@ -224,7 +218,6 @@ Result:: [INFO] Finished at: 2023-05-16T16:19:05-04:00 [INFO] ------------------------------------------------------------------------ ---- --- ==== . Create a deployment configuration file with contents similar to the following `func-create-config.yaml` example: diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index f5cad08..ce048a5 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -25,8 +25,8 @@ The Node.js Pulsar client npm package depends on the C++ Pulsar library. Pulsar Node client versions 1.8 and later do not require installation of the C++ Pulsar library dependency. [tabs] -==== -Ubuntu-based deb:: +====== +Ubuntu-based Debian:: + -- [source,shell] @@ -34,7 +34,8 @@ Ubuntu-based deb:: include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/create-project.sh[tag=prep-env] ---- -- -Centos/RHEL based rpm:: + +Centos/RHEL-based rpm:: + -- [source,shell] @@ -48,7 +49,7 @@ sudo rpm -i ./apache-pulsar-client-devel-2.10.2-1.x86_64.rpm sudo ldconfig ---- -- -==== +====== == Create a project diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index 833f980..8ef79a5 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -59,23 +59,18 @@ You could instead modify the values in the "application.properties" file in the include::{astra-streaming-examples-repo}/java/spring-boot/SpringBoot/src/main/resources/application.properties[] ---- ==== + . Change directory to the project root and run the following command to compile the project: + -[tabs] -==== -Maven:: -+ --- [source,bash] ---- mvn clean compile ---- --- - -Result:: + --- -[source,bash] +.Result +[%collapsible] +==== +[source,console] ---- [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS @@ -84,33 +79,27 @@ Result:: [INFO] Finished at: 2023-05-09T15:36:33-04:00 [INFO] ------------------------------------------------------------------------ ---- --- ==== + -. Maven has compiled your Java application with the Pulsar client dependency. -You can now run the project and send a message to your {product_name} cluster. -+ -[tabs] -==== -Maven:: +Maven has compiled your Java application with the Pulsar client dependency. + +. Run the project and send a message to your {product_name} cluster: + --- [source,bash] ---- mvn spring-boot:run ---- --- - -Result:: + --- -[source,bash] +.Result +[%collapsible] +==== +[source,console] ---- Message received: Hello World ---- --- ==== -. Remember to check in your {product_name} instance to see the message you sent to the newly created subscription "my-subscription". + +Remember to check in your {product_name} instance to see the message you sent to the newly created subscription, "my-subscription". == See also diff --git a/modules/developing/pages/configure-pulsar-env.adoc b/modules/developing/pages/configure-pulsar-env.adoc index bd1f06b..66f32f0 100644 --- a/modules/developing/pages/configure-pulsar-env.adoc +++ b/modules/developing/pages/configure-pulsar-env.adoc @@ -45,11 +45,6 @@ The executables in bin use the configurations in conf to run commands. Each tenant you create in Astra Streaming comes with its own custom configuration (for SSO, endpoints, etc). You will need to download the tenant configuration from Astra Streaming and overwrite the `./conf/client.conf` file. -[tabs] -==== -Astra Portal:: -+ --- . Navigate to the "Connect" tab in the Astra Streaming portal. + image:connect-tab.png[Connect tab in Astra Streaming] @@ -60,8 +55,6 @@ image:download-client.png[Download pulsar client conf in Astra Streaming] . Save the file in the "/conf" folder of the Pulsar folder. This will overwrite the default client.conf already in the /conf folder. --- -==== With your Astra Streaming tenant's configuration in place, you can use any of the binaries to interact with a Pulsar cluster. diff --git a/modules/developing/pages/gpt-schema-translator.adoc b/modules/developing/pages/gpt-schema-translator.adoc index b1c4ef3..3f47198 100644 --- a/modules/developing/pages/gpt-schema-translator.adoc +++ b/modules/developing/pages/gpt-schema-translator.adoc @@ -5,7 +5,7 @@ This requires schemas within a pipeline to be mapped to each other, a process wh For example, to send data from a CDC-enabled Cassandra (C*) table to a Pulsar topic, you must define a schema mapping between the C* table and the Pulsar topic, represented below: [tabs] -==== +====== Cassandra table schema:: + -- @@ -81,7 +81,7 @@ Pulsar JSON schema:: } ---- -- -==== +====== As you add more complex data types like maps and nested data structures, schema management becomes more difficult. Schema management is tedious work that requires understanding rules across various domains and translating between them, but it is trivial work for the GPT-4 AI model. @@ -104,7 +104,7 @@ image::two-schemas.png[Schema mapping,320,240] To generate a schema mapping, click the "Generate Mapping With GPT" button. This will generate a schema mapping. [tabs] -==== +====== Cassandra table schema:: + -- @@ -189,7 +189,7 @@ Generated schema mapping:: id=key, file1=value.file1, file2=value.file2, file3=value.file3 ---- -- -==== +====== Great! Now, once your connector is created, messages will flow smoothly between the two different schemas. Check in your {astra_ui} logs to see the data flowing into your table with no pesky error messages. diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index 91a2647..9184226 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -43,46 +43,41 @@ You'll use this tenant to create namespaces, topics, functions, and pretty much The only difference between the tabs is how your tenant is created - they all have the same result. [tabs] -==== -Astra Portal:: +====== +{astra_ui}:: + -- -. Once signed in, click the "Create a Stream" button on the portal home page. -+ -|=== -a|image:home-quick-access.png[Create a stream in Astra Streaming] -|=== - -. Name your Streaming Tenant something memorable like "my-stream-". -Replace `` with random letters or numbers - all tenant names in Astra Streaming must be unique. -Choose your preferred cloud provider and region. -For your first example tenant, the region doesn't really matter (it's all free). -+ -|=== -a|image:create-a-stream.png[Create a new tenant in Astra Streaming] -|=== +. In the {astra_ui} navigation menu, click *Streaming*. + +. Click *Create a Tenant*. -. Click the "Create Tenant" button. +. Name your Streaming Tenant something memorable like `my-stream-**RANDOM_UUID**`. +All tenant names in Astra Streaming must be unique. + +. Choose your preferred cloud provider and region. +For your first example tenant, the region doesn't really matter. ++ +image:create-a-stream.png[Create a new tenant in Astra Streaming] -. You will be directed to the quickstart page for your new tenant. +. Click *Create Tenant*. +You are directed to the quickstart page for your new tenant. + -|=== -a|image:new-tenant-quickstart.png[New tenant quickstart in Astra Streaming] -|=== +image:new-tenant-quickstart.png[New tenant quickstart in Astra Streaming] -- -Astra CLI:: +{astra_cli}:: + -- {astra_cli} is a set of commands for creating and managing all Astra resources. -For more, see the https://docs.datastax.com/en/astra-cli/docs/0.2/[documentation]. +For more information, see the https://docs.datastax.com/en/astra-cli/docs/0.2/[documentation]. -. Set the required variables, replacing with a few random numbers or letters. -Optionally you can choose a different cloud provider and region. Use `astra streaming list-regions` to see values. +. Set the required variables, replacing `**RANDOM_UUID**` with a few random numbers or letters. +Optionally, you can choose a different cloud provider and region. +Use `astra streaming list-regions` to see available values. + [source,shell] ---- -TENANT="my-stream-" +TENANT="my-stream-**RANDOM_UUID**" CLOUD_PROVIDER_STREAMING="gcp" CLOUD_REGION_STREAMING="uscentral-1" ---- @@ -93,11 +88,10 @@ CLOUD_REGION_STREAMING="uscentral-1" ---- include::{astra-streaming-examples-repo}/astra-cli/create-tenant.sh[] ---- - -. Within the newly created tenant, you're ready to create a namespace. - -- -==== +====== + +Next, create a namespace in your new tenant. == A namespace to hold topics @@ -114,36 +108,27 @@ It could be an environment (dev, stage, prod) or by application (catalog, cart, Learn more about namespaces in the https://pulsar.apache.org/docs/concepts-messaging/#namespaces[Pulsar docs]. [tabs] -==== -Astra Portal:: +====== +{astra_ui}:: + -- . Navigate to the "Namespace And Topics" tab. + -|=== -a|image:namespace-tab.png[Namespace tab in Astra Streaming] -|=== +image:namespace-tab.png[Namespace tab in Astra Streaming] . Click the "Create Namespace" button, and give your namespace a super original name like "my-namespace". + -[width=75%] -|=== -a|image:create-namespace.png[Create namespace in Astra Streaming] -|=== +image:create-namespace.png[Create namespace in Astra Streaming] . Click "Create" to create the namespace. + -|=== -a|image:namespace-listing.png[Namespaces in Astra Streaming] -|=== +image:namespace-listing.png[Namespaces in Astra Streaming] -- Pulsar Admin:: + -- -Not sure about this? Learn more about xref:developing:configure-pulsar-env.adoc[configuring your local environment for Astra Streaming]. - -. Set the required variables. +. Set the required variables: + [source,shell] ---- @@ -151,7 +136,7 @@ NAMESPACE="my-namespace" # TENANT="my-stream-" # set previously ---- -. Run the following script to create a new namespace. +. Run the following script to create a new namespace: + [source,shell] ---- @@ -159,12 +144,10 @@ include::{astra-streaming-examples-repo}/pulsar-admin/create-namespace.sh[] ---- -- -Curl:: +curl:: + -- -Not sure about this? Learn more about interacting with Astra Streaming through curl xref:developing:using-curl.adoc[here]. - -. Set the required variables. +. Set the required variables: + [source,shell] ---- @@ -174,14 +157,14 @@ NAMESPACE="my-namespace" # TENANT="my-stream-" # set previously ---- -. Run the following script to create a new namespace. +. Run the following script to create a new namespace: + [source,shell] ---- include::{astra-streaming-examples-repo}/curl/create-namespace.sh[] ---- -- -==== +====== == A topic to organize messages @@ -194,8 +177,8 @@ In Pulsar, topic addresses look like a URL (ie: persistent://tenant/namespace/to Learn more about topics in the https://pulsar.apache.org/docs/concepts-messaging/#topics[Pulsar docs]. [tabs] -==== -Astra Portal:: +====== +{astra_ui}:: + -- . In the *Namespace And Topics* tab, locate the namespace created above and click its *Add Topic* button. @@ -203,24 +186,19 @@ Astra Portal:: It must start with a lowercase letter, be alphanumeric, and can contain hyphens. . Leave the choice of persistence and partitioning alone for now - those can be a part of https://pulsar.apache.org/docs/concepts-messaging/#partitioned-topics[future learning]. + -[width=75%] -|=== -a|image:add-topic.png[Add topic in Astra Streaming] -|=== +image:add-topic.png[Add topic in Astra Streaming] . Click the *Add Topic* button to create the topic within the namespace. + -|=== -a|image:topic-listing.png[Topic listing in Astra Streaming] -|=== +image:topic-listing.png[Topic listing in Astra Streaming] -- Pulsar Admin:: + -- -Not sure about this? Learn more about connecting to Astra Streaming with the pulsar-admin CLI xref:developing:configure-pulsar-env.adoc[here]. +To learn more about connecting to Astra Streaming with the pulsar-admin CLI, see xref:developing:configure-pulsar-env.adoc[]. -. Set the required variables. +. Set the required variables: + [source,shell] ---- @@ -229,7 +207,7 @@ TOPIC="my-topic" # TENANT="my-stream-" # set previously ---- -. Run the following script to create a new topic. +. Run the following script to create a new topic: + [source,shell] ---- @@ -237,12 +215,12 @@ include::{astra-streaming-examples-repo}/pulsar-admin/create-topic.sh[] ---- -- -Curl:: +curl:: + -- -Not sure about this? Learn more about interacting with Astra Streaming through curl xref:developing:using-curl.adoc[here]. +To learn more about interacting with Astra Streaming through HTTP, see xref:developing:using-curl.adoc[]. -. Set the required variables. +. Set the required variables: + [source,shell] ---- @@ -253,14 +231,14 @@ TOPIC="my-topic" # TENANT="my-stream-" # set previously ---- -. Run the following script to create a new topic. +. Run the following script to create a new topic: + [source,shell] ---- include::{astra-streaming-examples-repo}/curl/create-topic.sh[] ---- -- -==== +====== == Next steps diff --git a/modules/operations/pages/astream-georeplication.adoc b/modules/operations/pages/astream-georeplication.adoc index 2c16b76..927c49a 100644 --- a/modules/operations/pages/astream-georeplication.adoc +++ b/modules/operations/pages/astream-georeplication.adoc @@ -90,28 +90,21 @@ In the result, the `allowedClusters` are the clusters the tenant can be replicat } ---- -. Verify your `pulsar-admin` can view the replicated clusters for your namespace. +. Verify your `pulsar-admin` can view the replicated clusters for your namespace: + -[tabs] -==== -Command:: -+ --- [source,bash] ---- bin/pulsar-admin namespaces get-clusters / ---- --- + -Result:: -+ --- +.Result +[%collapsible] +==== [source,json] ---- pulsar-aws-useast1-staging pulsar-aws-useast2-staging ---- --- ==== . Create a Pulsar consumer with a subscription to the `//` topic: @@ -175,22 +168,15 @@ Consumer consumer = pulsarClient.newConsumer(Schema.STRING) .subscribe(); ---- -. Check topic stats. `isReplicated` is now `true` for this subscription. +. Check topic stats: + -[tabs] -==== -Command:: -+ --- [source,bash] ---- bin/pulsar-admin topics stats persistent://// ---- --- + -Result:: +`isReplicated` is now `true` for this subscription: + --- [source,json] ---- { @@ -277,8 +263,6 @@ Result:: } } ---- --- -==== [#monitor] == Monitor replicated clusters diff --git a/modules/operations/pages/astream-scrape-metrics.adoc b/modules/operations/pages/astream-scrape-metrics.adoc index e0dba40..510748f 100644 --- a/modules/operations/pages/astream-scrape-metrics.adoc +++ b/modules/operations/pages/astream-scrape-metrics.adoc @@ -89,7 +89,7 @@ You're scraping your {product_name} tenant with Prometheus! With the example from above still running, use a `curl` request to decompress your Prometheus scrape data: [tabs] -==== +====== Deflate:: + -- @@ -107,7 +107,7 @@ Gzip:: include::example$curl_gzip.sh[] ---- -- -==== +====== *Deflate* or *Gzip* will extract your scraped metrics in a format such as the following: diff --git a/modules/operations/pages/astream-token-gen.adoc b/modules/operations/pages/astream-token-gen.adoc index 49ec89c..14ffbe2 100644 --- a/modules/operations/pages/astream-token-gen.adoc +++ b/modules/operations/pages/astream-token-gen.adoc @@ -27,11 +27,6 @@ The Astra token can be generated with the <> or the <' ---- --- -+ -Result:: + --- +.Response +[%collapsible] +==== [source,json] ---- {"clients":[ @@ -55,16 +49,10 @@ Result:: "generatedOn":"2021-04-28T18:49:11.323Z"} ]} ---- --- ==== . Create an application token for a specific client: + -[tabs] -==== -Curl:: -+ --- [source,shell] ---- curl --request POST \ @@ -73,11 +61,10 @@ curl --request POST \ --header 'Authorization: Bearer ' \ --data '{"roles": [""]}' ---- --- + -Result:: -+ --- +.Response +[%collapsible] +==== [source,json] ---- { @@ -89,7 +76,6 @@ Result:: "generatedOn":"2021-04-30T19:38:26.147847107Z" } ---- --- ==== + [TIP] @@ -98,7 +84,7 @@ For the `roleId`, provide the relevant role's `id` UUID value from a prior `GET` https://docs.datastax.com/en/astra-serverless/docs/manage/devops/devops-roles.html#_creating_a_new_role[Getting existing roles in your organization]. The API results will show the UUID for each role id. ==== -+ + . In the command-line interface associated with your environment, paste the following environment variable copied from token generation: + [source,shell,subs="+quotes"] @@ -110,47 +96,50 @@ export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** === Generate Astra token in Astra Portal . From any page in {astra_ui}, select the *Organizations* dropdown. + . In the main dropdown, select *Organization Settings*. + . From your Organization page, select *Token Management*. + . Select the role you want to attach to your token. The permissions for your selected role will be displayed. + . Select *Generate Token*. {product_name} will generate your token and display the _Client ID_, _Client Secret_, and _Token_. + . Download your _Client ID_, _Client Secret_, and _Token_. + [IMPORTANT] ==== After you navigate away from the page, you won't be able to download your _Client ID_, _Client Secret_, and _Token_ again. ==== -+ + . In the command-line interface associated with your environment, paste the following environment variable copied from token generation: + [source,shell,subs="+quotes"] ---- export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** ---- -+ -. You can now execute DevOps API commands from your terminal to your database. + +You can now execute DevOps API commands from your terminal to your database. === Delete Astra token If you need to limit access to your database, you can delete a token. . Select the overflow menu for the token you want to delete. + . Select *Delete* to delete that token. + . If necessary, generate a new token for the same user role. [#pulsar-token] == Generate Pulsar token To generate, copy, or delete Astra Streaming Pulsar tokens within your streaming tenant, visit the **Token Management** section of your streaming tenant's **Settings** page. - -Select **Create Token** to generate a Pulsar token for this streaming tenant. +Select **Create Token** to generate a Pulsar token for this streaming tenant, and then copy the Pulsar token and store it securely. Token duration ranges from 7 days to never expiring. - If you choose a token with an expiration, ensure you replace your token with a new, valid Pulsar token before the expiration date. -Select the **clipboard** icon to copy a Pulsar token to your clipboard. - === Set environment variables Download your Pulsar connection info as detailed https://docs.datastax.com/en/astra-streaming/docs/astream-quick-start.html#download-connect-info[here]. diff --git a/modules/ragstack/pages/quickstart.adoc b/modules/ragstack/pages/quickstart.adoc index f463ab2..eff89f5 100644 --- a/modules/ragstack/pages/quickstart.adoc +++ b/modules/ragstack/pages/quickstart.adoc @@ -34,32 +34,27 @@ Under the hood, this is enabling the xref:starlight-for-kafka:ROOT:index.adoc[St == Connect {ragstack} to your tenant -Select the *Generate Configuration Command* button to generate a CLI configuration file for your tenant. +. Select the *Generate Configuration Command* button to generate a CLI configuration file for your tenant. -Run the generated command in your local environment to connect your tenant to the {ragstack} CLI. -[tabs] -==== -{ragstack} CLI:: +. Run the generated command in your local environment to connect your tenant to the {ragstack} CLI: + --- [source,shell] ---- ragstack profiles import astra-rs-tenant --inline 'base64:...' --set-current -u ---- --- - -Result:: + --- +.Result +[%collapsible] +==== [source,console] ---- profile astra-rs-tenant created profile astra-rs-tenant set as current ---- --- ==== -The configuration values will look something like this. +The configuration values will look something like this: + [source,console] ---- tenant: rs-tenant @@ -186,6 +181,7 @@ For more information about creating a Google client ID, see https://developers.g Pipeline.yaml contains the chain of agents that makes up your program, and the input and output topics that they communicate with. For more on building pipelines, see https://docs.langstream.ai/building-applications/pipelines[Pipelines]. + [source,yaml] ---- topics: @@ -228,6 +224,7 @@ pipeline: Gateways.yaml contains API gateways for communicating with your application. For more on gateways and authentication, see https://docs.langstream.ai/building-applications/api-gateways[API Gateways]. + [source,yaml] ---- gateways: @@ -302,6 +299,7 @@ gateways: Configuration.yaml contains additional configuration and resources for your application. A configuration.yaml file can be downloaded from the Connect tab of your Astra Streaming tenant (under AstraDB). For more on configuration, see https://docs.langstream.ai/building-applications/configuration[Configuration]. + [source,yaml] ---- configuration: @@ -318,24 +316,19 @@ Remember to save all your yaml files. == Deploy the {ragstack} application on Astra -To deploy the application, run the following commands from the root of your application folder. +. To deploy the application, run the following commands from the root of your application folder. The first command deploys the application from the YAML files you created above, and the second command gets the status of the application. For more on {ragstack} CLI commands, see https://docs.langstream.ai/installation/langstream-cli[{ragstack} CLI]. -[tabs] -==== -{ragstack} CLI:: + --- [source,shell] ---- ragstack apps deploy sample-app -app ./application -i ./instance.yaml -s ./secrets.yaml ragstack apps get sample-app ---- --- - -Result:: + --- +.Result +[%collapsible] +==== [source,console] ---- packaging app: /Users/mendon.kissling/sample-app/./application @@ -345,26 +338,31 @@ application sample-app deployed ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS sample-app kafka kubernetes DEPLOYED 1/1 1/1 ---- --- ==== -Ensure your app is running - a Kubernetes pod should be deployed with your application, and STATUS will change to DEPLOYED. - -Your application should be listed in your {ragstack} tenant: +. Ensure your app is running: ++ +* A Kubernetes pod should be deployed with your application, and the `STATUS` will change to `DEPLOYED`. +* Your application should be listed in your {ragstack} tenant: ++ image::app-deployed.png[App deployed] -You should see a map of your application in the {ragstack} UI: - +* You should see a map of your application in the {ragstack} UI: ++ image::app-map.png[App map] -Hmm, this application has an Error. To get logs, use `ragstack apps logs `. +[TIP] +==== +To get logs, run `ragstack apps logs **APP_NAME**`. +==== == {ragstack} CLI connection values -If you're running into issues, ensure the values in your CLI profile match the values in your Astra Streaming tenant. +If you encounter an error or problem, make sure the values in your CLI profile match the values in your Astra Streaming tenant. -If you're unsure of the profile name, use `ragstack profiles list`, then `ragstack profiles get -o=json` to display the current values. +If you're unsure of the profile name, run `ragstack profiles list`, and then run `ragstack profiles get **PROFILE_NAME** -o=json`. +For example: [source,json] ---- @@ -377,7 +375,7 @@ If you're unsure of the profile name, use `ragstack profiles list`, then `ragsta } ---- -To update these values, use `ragstack profiles update astra-ragstack-tenant --command-option="value"`. +To update values for the above profile, run `ragstack profiles update astra-ragstack-tenant --**OPTION**="**VALUE**"`: [cols="1,1"] |=== @@ -399,24 +397,21 @@ To update these values, use `ragstack profiles update astra-ragstack-tenant --co | token of the profile |=== -If you get lost along the way, here are the default profile values: +The default profile values are as follows: -[source,console] +[source,json] ---- -webServiceUrl: "http://localhost:8090" -apiGatewayUrl: "ws://localhost:8091" -tenant: "default" -token: null +{ + "webServiceUrl": "http://localhost:8090", + "apiGatewayUrl": "ws://localhost:8091", + "tenant": "default", + "token": null +} ---- Issue a curl call to your {ragstack} tenant to find the connection values for your tenant. The `X-DataStax-Current-Org` value is the client-id associated with the Astra token, and can be found in the Astra UI. -[tabs] -==== -curl:: -+ --- [source,curl] ---- curl --location --request POST 'https://pulsar-gcp-useast1.api.streaming.datastax.com/langstream/ragstack-tenant' \ @@ -424,11 +419,10 @@ curl --location --request POST 'https://pulsar-gcp-useast1.api.streaming.datasta --header 'X-DataStax-Pulsar-Cluster: pulsar-gcp-useast1' \ --header 'Authorization: Bearer AstraCS:' ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- { @@ -438,7 +432,6 @@ Result:: "tenant":"astra-ragstack-tenant", "token":"{astra token}"}% ---- --- ==== Ensure the values returned from the curl call match the values in your {ragstack} CLI profile. @@ -476,26 +469,19 @@ Your gateway connection is confirmed, and you can send messages to your applicat This sample-app also produces messages to the consume-history gateway to provide more context to the AI model. To consume from this gateway, run the following command: -[tabs] -==== -{ragstack} CLI:: -+ --- [source,console] ---- ragstack gateway consume sample-app consume-history -p sessionId=F85E4665-BE00-4513-A5C5-E59B42646490 ---- --- -Result:: -+ --- +.Result +[%collapsible] +==== [source,console] ---- Connected to wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway//v1/consume/ragstack-tenant/sample-app/consume-history?param:sessionId=F85E4665-BE00-4513-A5C5-E59B42646490 {"record":{"key":null,"value":"Hi K8s, it's me, Astra.","headers":{}},"offset":"eyJvZmZzZXRzIjp7IjAiOiIxIn19"} ---- --- ==== == See also From 92415584eca9ef98519d220a4adc95d784e86954 Mon Sep 17 00:00:00 2001 From: April M Date: Tue, 15 Oct 2024 06:56:46 -0700 Subject: [PATCH 05/18] attributes --- antora.yml | 2 +- modules/ROOT/pages/astream-custom-roles.adoc | 2 +- modules/ROOT/pages/astream-faq.adoc | 44 +++--- .../ROOT/pages/astream-org-permissions.adoc | 12 +- modules/ROOT/pages/index.adoc | 32 ++-- .../ROOT/partials/subscription-prereq.adoc | 8 +- modules/apis/pages/api-operations.adoc | 37 ++--- modules/apis/pages/index.adoc | 16 +- modules/developing/nav.adoc | 2 + modules/developing/pages/astra-cli.adoc | 12 +- modules/developing/pages/astream-cdc.adoc | 26 ++-- .../developing/pages/astream-functions.adoc | 34 ++--- modules/developing/pages/astream-kafka.adoc | 6 +- modules/developing/pages/astream-rabbit.adoc | 6 +- .../pages/clients/csharp-produce-consume.adoc | 2 +- .../pages/clients/golang-produce-consume.adoc | 2 +- modules/developing/pages/clients/index.adoc | 8 +- .../pages/clients/java-produce-consume.adoc | 4 +- .../pages/clients/nodejs-produce-consume.adoc | 2 +- .../pages/clients/python-produce-consume.adoc | 2 +- .../pages/clients/spring-produce-consume.adoc | 10 +- .../pages/configure-pulsar-env.adoc | 24 +-- .../pages/gpt-schema-translator.adoc | 20 +-- .../pages/produce-consume-astra-portal.adoc | 18 +-- .../pages/produce-consume-pulsar-client.adoc | 6 +- modules/developing/pages/using-curl.adoc | 24 +-- modules/getting-started/pages/index.adoc | 55 +++---- .../pages/astream-georeplication.adoc | 18 +-- modules/operations/pages/astream-limits.adoc | 24 +-- modules/operations/pages/astream-pricing.adoc | 4 +- modules/operations/pages/astream-regions.adoc | 8 +- .../pages/astream-release-notes.adoc | 29 ++-- .../pages/astream-scrape-metrics.adoc | 24 +-- .../operations/pages/astream-token-gen.adoc | 34 ++--- .../operations/pages/monitoring/index.adoc | 51 ++++--- .../pages/monitoring/integration.adoc | 20 ++- .../operations/pages/monitoring/metrics.adoc | 7 +- .../pages/monitoring/new-relic.adoc | 44 ++++-- .../pages/monitoring/stream-audit-logs.adoc | 18 +-- modules/operations/pages/onboarding-faq.adoc | 143 ++++++++---------- .../pages/private-connectivity.adoc | 14 +- .../partials/code-examples-text.adoc | 4 +- modules/ragstack/pages/index.adoc | 4 +- modules/ragstack/pages/quickstart.adoc | 54 ++++--- 44 files changed, 470 insertions(+), 446 deletions(-) diff --git a/antora.yml b/antora.yml index 9cc59b4..ce87b52 100644 --- a/antora.yml +++ b/antora.yml @@ -14,7 +14,7 @@ asciidoc: attributes: pulsar_version: '2.10' #DO NOT INCLUDE PATCH VERSION .. pulsar_full_version: '2.10.1' - product_name: 'Astra Streaming' + product: 'Astra Streaming' kafka_for_astra: 'Starlight for Kafka' starlight_rabbitmq: 'Starlight for RabbitMQ' gpt-schema-translator: 'GPT schema translator' diff --git a/modules/ROOT/pages/astream-custom-roles.adoc b/modules/ROOT/pages/astream-custom-roles.adoc index a291e88..2a02cf4 100644 --- a/modules/ROOT/pages/astream-custom-roles.adoc +++ b/modules/ROOT/pages/astream-custom-roles.adoc @@ -26,7 +26,7 @@ In the {astra_ui} or {astra_db} DevOps API, you can create custom roles. + [IMPORTANT] ==== -If you want users with this role to be able to see the {product_name} user interface, make sure you select _Manage Streaming_ permissions. +If you want users with this role to be able to see the {product} user interface, make sure you select _Manage Streaming_ permissions. ==== . If you want to apply your selected permissions to specific databases or keyspaces, toggle the switch to not apply the permissions to all databases in an organization. Then select the specific databases or keyspaces to which you want to apply the permissions. diff --git a/modules/ROOT/pages/astream-faq.adoc b/modules/ROOT/pages/astream-faq.adoc index 94aa50b..c373531 100644 --- a/modules/ROOT/pages/astream-faq.adoc +++ b/modules/ROOT/pages/astream-faq.adoc @@ -1,49 +1,53 @@ -= {product_name} FAQs += {product} FAQs :navtitle: FAQs :page-tag: astra-streaming,dev,admin,planner,plan,pulsar -== How do I sign up for the {product_name}? +== How do I sign up for the {product}? -Follow our simple xref:getting-started:index.adoc[getting started guide] to sign up for Astra and get your first streaming tenant created. +Follow our simple xref:getting-started:index.adoc[getting started guide] to sign up for {astra_db} and get your first streaming tenant created. -== How is {product_name} priced? -{product_name} offers customers a *Pay As You Go*, consumption based pricing model that allows customers to use {product_name} with a cost model that scales as they grow. +== How is {product} priced? -Customers can opt to customize their deployment to meet specific requirements which will adjust their pricing up or down depending on their specific requirement. These customizations options include items such as: +{product} offers three subscription plans, as well as pricing for *Dedicated Clusters* plans. +For more information, see https://www.datastax.com/products/astra-streaming/pricing[{product} Pricing]. + +Additional customizations can impact billed charges, such as the following: * Message retention duration * Maximum message retention storage * Number of tenants * Region of tenant -== Why did DataStax opt to base {product_name} on Apache Pulsar? -See our https://www.datastax.com/blog/four-reasons-why-apache-pulsar-essential-modern-data-stack[blog post] that explains why we are excited about Apache Pulsar and why we decided it was the best technology to base {product_name} on. +== Why is {product} based on Apache Pulsar? + +See our https://www.datastax.com/blog/four-reasons-why-apache-pulsar-essential-modern-data-stack[blog post] that explains why we are excited about Apache Pulsar and why we decided it was the best technology to base {product} on. + +== What will happen to Kesque? -== What is DataStax plan for Kesque? -{product_name} is based heavily on technology originally created as part of Kesque. With the launch of {product_name} we will begin the process of shutting down the Kesque service and migrating customers to the new {product_name} platform. +{product} is based heavily on technology originally created as part of Kesque. With the launch of {product} we will begin the process of shutting down the Kesque service and migrating customers to the new {product} platform. -== How can I get started with {product_name}? -To get started with {product_name}, you can create a free account at https://astra.datastax.com and create your first streaming instance immediately. No credit card required. +== How can I get started with {product}? +To get started with {product}, you can create a free account at https://astra.datastax.com and create your first streaming instance immediately. No credit card required. -== Who are the target customers for {product_name}? -{product_name} has been architected to satisfy the most stringent enterprise requirements around availability, scale and latency. {product_name} was built to handle mission critical use cases for Fortune 100 companies across BFSI, Telecommunications, Technology, Retail, Oil & Gas and Healthcare. +== Who are the target customers for {product}? +{product} has been architected to satisfy the most stringent enterprise requirements around availability, scale and latency. {product} was built to handle mission critical use cases for Fortune 100 companies across BFSI, Telecommunications, Technology, Retail, Oil & Gas and Healthcare. -The pricing model also makes {product_name} accessible to mid market and small/medium business customers who need event stream processing capabilities to run core parts of their business. +The pricing model also makes {product} accessible to mid market and small/medium business customers who need event stream processing capabilities to run core parts of their business. -And finally {product_name} offers a user friendly interface and free tier to satisfy the needs of individual developers and technologists who want to learn more about Apache Pulsar and data streaming in general. +And finally {product} offers a user friendly interface and free tier to satisfy the needs of individual developers and technologists who want to learn more about Apache Pulsar and data streaming in general. -== What is CDC for Astra DB? +== What is CDC for {astra_db}? -Change Data Capture (CDC) for Astra DB uses a Pulsar IO source connector that processes changes from the Cassandra Change Agent via a Pulsar topic. For more, see https://docs.datastax.com/en/astra/docs/astream-cdc.html[CDC for Astra DB]. +Change Data Capture (CDC) for {astra_db} uses a Pulsar IO source connector that processes changes from the Cassandra Change Agent via a Pulsar topic. For more, see https://docs.datastax.com/en/astra/docs/astream-cdc.html[CDC for {astra_db}]. == What are tenants, namespaces, topics, and sinks? *Tenants* are an isolated administrative unit for which an authorization scheme can be set and a set of clusters can be defined. Each tenant can have multiple *namespaces*, a logical container for creating and managing a hierarchy of topics. A *topic* is a named channel for transmitting messages from producers to consumers. -A *sink* feeds data from {product_name} to an external system, such as Cassandra or Elastic Search. +A *sink* feeds data from {product} to an external system, such as Cassandra or Elastic Search. == See also -* xref:getting-started:index.adoc[Getting started with Astra Streaming] +* xref:getting-started:index.adoc[] * Browse the xref:apis:index.adoc[] diff --git a/modules/ROOT/pages/astream-org-permissions.adoc b/modules/ROOT/pages/astream-org-permissions.adoc index 4aa1929..cd72125 100644 --- a/modules/ROOT/pages/astream-org-permissions.adoc +++ b/modules/ROOT/pages/astream-org-permissions.adoc @@ -4,9 +4,9 @@ Default and xref:astream-custom-roles.adoc[custom roles] allow admins to manage unique permissions for users based on your organization and database requirements. You can manage roles using the {astra_ui} or the {astra_db} DevOps API. -For more information, see https://docs.datastax.com/en/astra/docs/user-permissions.html[Astra DB User Permissions]. +For more information, see https://docs.datastax.com/en/astra/docs/user-permissions.html[{astra_db} User Permissions]. -== {product_name} Organization permissions +== {product} Organization permissions [cols="1,1,1"] |=== @@ -17,7 +17,7 @@ For more information, see https://docs.datastax.com/en/astra/docs/user-permissio |`org-audits-read` |Write IP Access List -|Create or modify an access list using the DevOps API or the Astra console. +|Create or modify an access list using the DevOps API or the {astra_ui}. |`accesslist-write` |Delete Custom Role @@ -25,7 +25,7 @@ For more information, see https://docs.datastax.com/en/astra/docs/user-permissio |`org-role-delete` |Manage Streaming -|Create a Streaming Service using the DevOps API or the Astra console. +|Create an {product} Service using the DevOps API or the {astra_ui}. |`org-stream-manage` |Write Organization @@ -45,7 +45,7 @@ For more information, see https://docs.datastax.com/en/astra/docs/user-permissio |`org-user-read` |Read Organization -|View organization in the Astra console. +|View organization in the {astra_ui}. |`org-read` |Read Custom Role @@ -72,7 +72,7 @@ For more information, see https://docs.datastax.com/en/astra/docs/user-permissio |`org-billing-write` |Write User -|Add, create, or remove a user using the DevOps API or the Astra console. +|Add, create, or remove a user using the DevOps API or the {astra_ui}. |`org-user-write` |Write Custom Role diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index 0335a1e..12eb61b 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -1,45 +1,45 @@ -= What is DataStax {product_name}? -:navtitle: Astra Streaming += Introduction to {product}? +:navtitle: Intro to {product} :page-tag: astra-streaming,planner,admin,dev,pulsar -{product_name} is a cloud native data streaming and event stream processing +{product} is a cloud native data streaming and event stream processing service tightly integrated into the {astra_ui} and powered by Apache Pulsar(TM). -Using {product_name}, customers can quickly create Pulsar instances, +Using {product}, customers can quickly create Pulsar instances, manage their clusters, scale across cloud regions, and manage Pulsar resources such as topics, connectors, functions and subscriptions. -{product_name} takes advantage of the core capabilities built into Astra such +{product} takes advantage of the core capabilities built into {astra_db} such as SSO, IAM and billing. -Existing Astra customers can augment their existing database capabilities +Existing {astra_db} customers can augment their existing database capabilities with pub/sub and streaming to address a wider range of use cases right away. -With {product_name}, customers now have powerful capabilities to help drive key +With {product}, customers now have powerful capabilities to help drive key business and technical outcomes including: *Real Time Processing*:: Legacy batch processing jobs result in stale data that can slow down an entire business. -With {product_name}, customers can capture event data and data changes in real-time, +With {product}, customers can capture event data and data changes in real-time, process that data and take action to create a more responsive, nimble organization. *Data Science & Machine Learning*:: Today, every business is a data business. -{product_name} allows event data to be stored as a persistent event log which +{product} allows event data to be stored as a persistent event log which can be retained indefinitely and played back to refine ML data models or offloaded to a data lake or other storage for further analysis in the future. -*Modernized Event Driven Architectures*:: {product_name} provides a unified +*Modernized Event Driven Architectures*:: {product} provides a unified messaging platform that addresses streaming, pub/sub and queuing use cases with low latency and at massive scale. Organizations that are struggling to make -their legacy messaging technologies keep up will find {product_name} to be a +their legacy messaging technologies keep up will find {product} to be a frictionless path to modernization. *Faster Access to Data*:: No one has patience for slow loading web pages or mobile apps. -By combining {product_name} with Astra DB, customers can create read-optimized -views of data that can be quickly read from Astra DB and ensure that data is -always up to date by leveraging the event stream processing capabilities of {product_name}. +By combining {product} with {astra_db}, customers can create read-optimized +views of data that can be quickly read from {astra_db} and ensure that data is +always up to date by leveraging the event stream processing capabilities of {product}. -== {product_name} limits +== {product} limits -For details about about the {product_name} limits, see xref:operations:astream-limits.adoc[{product_name} Limits]. +For details about about the {product} limits, see xref:operations:astream-limits.adoc[{product} Limits]. == See also diff --git a/modules/ROOT/partials/subscription-prereq.adoc b/modules/ROOT/partials/subscription-prereq.adoc index cbd73de..5087a1a 100644 --- a/modules/ROOT/partials/subscription-prereq.adoc +++ b/modules/ROOT/partials/subscription-prereq.adoc @@ -1,16 +1,16 @@ == Prerequisites -To run this example, you'll need: +This example requires the following: * https://maven.apache.org/install.html[Apache Maven] * https://openjdk.java.net/install/[Java OpenJDK 11] -* A configured Astra Streaming instance with at least *one streaming tenant* and *one topic*. See the https://docs.datastax.com/en/astra-streaming/docs/astream-quick-start.html[Astra Streaming quick start] for instructions. +* {product} access with at least one streaming tenant and one topic -* A local clone of the https://github.com/datastax/pulsar-subscription-example[DataStax Pulsar Subscription Example repository] +* A local clone of the https://github.com/datastax/pulsar-subscription-example[{company} Pulsar Subscription Example repository] -* Modify the `src/main/resources/application.properties` in the `pulsar-subscription-example` repo to connect to your Astra Streaming cluster, as below: +* In the `pulsar-subscription-example` repo, navigate to `src/main/resources`, and then edit `application.properties` to connect to your {product} cluster: + [source,bash] ---- diff --git a/modules/apis/pages/api-operations.adoc b/modules/apis/pages/api-operations.adoc index 85bb8b4..2ea982d 100644 --- a/modules/apis/pages/api-operations.adoc +++ b/modules/apis/pages/api-operations.adoc @@ -1,24 +1,15 @@ -= API Operations -:navtitle: API operations += API operations -This Astra Streaming workbook is a comprehensive guide that provides detailed examples and practices for managing the Astra Streaming platform using the DevOps APIs. It provides details on the most commonly used APIs for managing Astra Streaming and Pulsar instances. These details include required parameters and the expected output from the API. The workbook is designed to fill the gap between detailed API reference docs and HowTo guides. The result is to help customers in operating and managing Astra Streaming and provide guidance on how to use DevOps API to automate many common tasks. +This {product} workbook is a comprehensive guide that provides detailed examples and practices for managing the {product} platform using the DevOps APIs. It provides details on the most commonly used APIs for managing {product} and Pulsar instances. These details include required parameters and the expected output from the API. The workbook is designed to fill the gap between detailed API reference docs and HowTo guides. The result is to help customers in operating and managing {product} and provide guidance on how to use DevOps API to automate many common tasks. The workbook covers a wide range of topics, including provisioning of resources, monitoring, and troubleshooting. It provides instructions for various operations, such as creating a new tenant, namespace, topics, geo-replication, and access tokens, to setting up monitoring and alerting, and troubleshooting common issues. -Overall, this Astra Streaming Workbook is a valuable resource for customers who want to leverage the benefits of Astra Streaming and manage their streaming environment effectively with DevOps APIs. By following the best practices and guidelines outlined in the workbook, customers can ensure that their streaming applications are secure, performant, and reliable. - -== Prerequisites - -To get started using Astra Streaming DevOps APIs, you will need an active Astra Streaming account. You can setup this account by following the guide below: -Create an Astra Streaming Account - -Once an account has been established, you will need to set up a Role and Access Token for Streaming. See the guide below on how to setup these items: -Generate Astra Streaming Admin Token - -As noted in the guide above, save or download the new Astra Streaming Token values. They will be needed in nearly all DevOps API calls, especially the “Token" value. +Overall, this {product} Workbook is a valuable resource for customers who want to leverage the benefits of {product} and manage their streaming environment effectively with DevOps APIs. By following the best practices and guidelines outlined in the workbook, customers can ensure that their streaming applications are secure, performant, and reliable. == Prerequisites +* An active {astra_db} account +* An application token and Pulsar token for {product} * Export the following environmental variables to your path: + [source,bash] @@ -103,7 +94,7 @@ curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/tena ---- ==== -== List Astra Streaming cloud providers +== List {product} cloud providers [source,curl] ---- @@ -205,7 +196,7 @@ No response indicates success. == Namespace DevOps APIs -For managing Astra Streaming Namespaces, we use the native https://pulsar.apache.org/admin-rest-api/[Pulsar REST APIs]. These are documented on the Apache Pulsar Docs for REST API. +For managing {product} Namespaces, we use the native https://pulsar.apache.org/admin-rest-api/[Pulsar REST APIs]. These are documented on the Apache Pulsar Docs for REST API. === List Existing Namespaces @@ -891,7 +882,7 @@ curl --location --fail --request GET "https://api.astra.datastax.com/v2/streamin === Create Geo-Replication between Namespaces -The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of Astra Streaming sections of this guide. +The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of {product} sections of this guide. [source,curl] ---- @@ -908,7 +899,7 @@ No response indicates success. === Delete Geo-Replication between Namespaces -The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of Astra Streaming sections of this guide. +The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of {product} sections of this guide. [source,bash] ---- @@ -1149,11 +1140,11 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENA No response indicates success. -== Astra Streaming JWT Token DevOps APIs +== {product} JWT Token DevOps APIs === List Existing Tokens IDs -Get a list of Token IDs for your Cluster. With the TokenID, you can then lookup and obtain the Pulsar JWT Token string. The TokenIDs are also listed in the Astra UI for that Tenant and Cluster. +Get a list of Token IDs for your Cluster. With the TokenID, you can then lookup and obtain the Pulsar JWT Token string. The TokenIDs are also listed in the {astra_ui} for that Tenant and Cluster. Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. @@ -1198,7 +1189,7 @@ eyJhbGciOiJSUzI1NiIsI . . . === Create a JWT Token Create a new Pulsar JWT Token. -The new JWT Token will also be visible in the Astra UI for that Tenant and Cluster. +The new JWT Token will also be visible in the {astra_ui} for that Tenant and Cluster. Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. @@ -1504,7 +1495,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sources/$TENANT No response indicates success. In the example above, a configuration file is provided as input to CURL. -The file is named "mynetty-source-config.json", which has the following context for the built-in “netty" source connector in Astra Streaming. +The file is named "mynetty-source-config.json", which has the following context for the built-in “netty" source connector in {product}. [TIP] ==== @@ -1530,7 +1521,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$ No response indicates success. -In the example above, a configuration file is provided as input to CURL. The file is named mykafka-sink-config.json which has the following context for the built-in “kafka" source connector in Astra Streaming. +In the example above, a configuration file is provided as input to CURL. The file is named mykafka-sink-config.json which has the following context for the built-in “kafka" source connector in {product}. [source,json] ---- diff --git a/modules/apis/pages/index.adoc b/modules/apis/pages/index.adoc index f0d13b2..7a40973 100644 --- a/modules/apis/pages/index.adoc +++ b/modules/apis/pages/index.adoc @@ -1,28 +1,28 @@ = API References :navtitle: API references overview -:description: Astra provides APIs to manage both DB and Streaming instances +:description: Learn about {product} APIs :page-tag: astra-streaming,dev,develop,pulsar You use two APIs to manage Pulsar tenants and their resources. -== {astra_stream} DevOps API +== {product} DevOps API -Use the xref:astra-streaming:apis:attachment$devops.html[{astra_stream} DevOps API] to manage higher level objects associated with your account, such as the change data capture (CDC) settings, Pulsar tenants, geo-replications, Pulsar stats, and Pulsar tokens. +Use the xref:astra-streaming:apis:attachment$devops.html[{product} DevOps API] to manage higher level objects associated with your account, such as the change data capture (CDC) settings, Pulsar tenants, geo-replications, Pulsar stats, and Pulsar tokens. This API uses an {astra_db} application token for authentication. -== {astra_stream} Pulsar Admin API +== {product} Pulsar Admin API -Use the xref:astra-streaming:apis:attachment$pulsar-admin.html[{astra_stream} Pulsar Admin API] to manage specific resources within a specific tenant, such as namespaces, topics, and subscriptions. +Use the xref:astra-streaming:apis:attachment$pulsar-admin.html[{product} Pulsar Admin API] to manage specific resources within a specific tenant, such as namespaces, topics, and subscriptions. This API uses your Pulsar token for authentication. === OSS Pulsar Admin API -The https://pulsar.apache.org/admin-rest-api[open source Pulsar project's Pulsar Admin API] isn't the same as the {astra_stream} Pulsar Admin API. +The https://pulsar.apache.org/admin-rest-api[open source Pulsar project's Pulsar Admin API] isn't the same as the {product} Pulsar Admin API. In OSS Pulsar you manage instances, the clusters within each instance, the tenants in the cluster, and so on. -In {astra_stream}, clusters are a managed service. +In {product}, clusters are a managed service. You manage only the tenants and resources within those tenants. -Some OSS Pulsar Admin API endpoints aren't supported in the {astra_stream} Pulsar Admin API because they don't apply to the {astra_stream} managed service. \ No newline at end of file +Some OSS Pulsar Admin API endpoints aren't supported in the {product} Pulsar Admin API because they don't apply to the {product} managed service. \ No newline at end of file diff --git a/modules/developing/nav.adoc b/modules/developing/nav.adoc index 8db27ac..47cb819 100644 --- a/modules/developing/nav.adoc +++ b/modules/developing/nav.adoc @@ -1,3 +1,5 @@ +* xref:ROOT:index.adoc[] + .Developing * xref:gpt-schema-translator.adoc[] * xref:configure-pulsar-env.adoc[] diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index a4f6cc5..9230655 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -1,24 +1,24 @@ = {astra_cli} -The {company} Astra Command-Line Interface ({astra_cli}) is a set of commands for creating and managing Astra resources. -{astra_cli} commands are available for {astra_db} and {astra_stream}. +The {astra_cli} is a set of commands for creating and managing {astra_db} resources. +{astra_cli} commands are available for {astra_db} and {product}. They're designed to get you working quickly, with an emphasis on automation. -{astra_cli} provides a one-stop shop for managing your Astra resources through scripts or commands in your local terminal. +{astra_cli} provides a one-stop shop for managing your {astra_db} resources through scripts or commands in your local terminal. The wide variety of capabilities include: -* Creation and management of {astra_db} and {astra_stream} artifacts +* Creation and management of {astra_db} and {product} artifacts * Querying & data loading * Organization and user management * Security and token configuration The advantage for you: {astra_cli} makes it possible to submit commands instead of *or in addition to* using {astra_ui} and {company} API calls. -{astra_cli} features are provided especially for operators, Site Reliability Engineers (SREs), and developers who want the option of using commands when working with {astra_db} databases and {astra_stream} tenants. +{astra_cli} features are provided especially for operators, Site Reliability Engineers (SREs), and developers who want the option of using commands when working with {astra_db} databases and {product} tenants. == Two quick examples -Here are two quick {astra_cli} command examples to demonstrate how Astra CLI manages resources across {astra_db} databases and {astra_stream} tenants from your local terminal. +Here are two quick {astra_cli} command examples to demonstrate how {astra_cli} manages resources across {astra_db} databases and {product} tenants from your local terminal. . Create an {astra_db} database named `demo` from the command line: + diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index eab0f18..c042b36 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -1,19 +1,19 @@ = Create a Change Data Capture (CDC) connector -:description: CDC for Astra DB automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data +:description: CDC for {astra_db} automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data [IMPORTANT] ==== * CDC connectors are only available for {db-serverless} deployments. -* Enabling CDC for {db-serverless} databases increases costs based on your {astra_stream} usage. -See https://www.datastax.com/pricing/astra-streaming[{astra_stream} pricing] and https://www.datastax.com/products/datastax-astra/cdc-for-astra-db[CDC metering rates]. +* Enabling CDC for {db-serverless} databases increases costs based on your {product} usage. +See https://www.datastax.com/pricing/astra-streaming[{product} pricing] and https://www.datastax.com/products/datastax-astra/cdc-for-astra-db[CDC metering rates]. ==== -CDC for Astra DB automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data into {product_name} where it can be processed by client applications or sent to downstream systems. +CDC for {astra_db} automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data into {product} where it can be processed by client applications or sent to downstream systems. -{product_name} processes data changes via a Pulsar topic. By design, the Change Data Capture (CDC) component is simple, with a 1:1 correspondence between the table and a single Pulsar topic. +{product} processes data changes via a Pulsar topic. By design, the Change Data Capture (CDC) component is simple, with a 1:1 correspondence between the table and a single Pulsar topic. -This doc will show you how to create a CDC connector for your Astra DB deployment and send change data to an Elasticsearch sink. +This doc will show you how to create a CDC connector for your {astra_db} deployment and send change data to an Elasticsearch sink. == Supported data structures @@ -156,7 +156,7 @@ CDC for {db-serverless} databases has the following limitations: You need the following items to complete this procedure: -* An active {url-astra}[Astra account^]. +* An active {url-astra}[{astra_db} account^]. * An https://docs.datastax.com/en/astra-db-serverless/databases/create-database.html#create-a-serverless-non-vector-database[{db-serverless} database] created in the {link-astra-portal}. * An https://docs.datastax.com/en/astra-db-serverless/databases/manage-keyspaces.html[keyspace] created in the {link-astra-portal}. * An active https://cloud.elastic.co/login[Elasticsearch] account. @@ -173,7 +173,7 @@ You need the following items to complete this procedure: . Select a provider and region. + -{astra_stream} CDC can only be used in a region that supports both {astra_stream} and {db-serverless} databases. +{product} CDC can only be used in a region that supports both {product} and {db-serverless} databases. See xref:operations:astream-regions.adoc[] for more information. . Select *Create Tenant*. @@ -224,7 +224,7 @@ Complete the following steps after you have created a <>. @@ -302,7 +302,7 @@ curl -X POST "*ELASTIC_URL*/*INDEX_NAME*/_search?pretty" -H "Authorization: ApiKey '*API_KEY*'" ---- + -. A JSON response with your changes to the index is returned, confirming that {astra_stream} is sending your CDC changes to your Elasticsearch sink. +. A JSON response with your changes to the index is returned, confirming that {product} is sending your CDC changes to your Elasticsearch sink. + [source,json,subs="verbatim,quotes"] ---- @@ -476,7 +476,7 @@ Result: == Resources -For more on {astra_stream}, see: +For more on {product}, see: -* https://docs.datastax.com/en/streaming/astra-streaming/astream-faq.html[{astra_stream} FAQs] -* https://docs.datastax.com/en/streaming/astra-streaming/developing/clients/index.html[Pulsar clients with {astra_stream}] \ No newline at end of file +* https://docs.datastax.com/en/streaming/astra-streaming/astream-faq.html[{product} FAQs] +* https://docs.datastax.com/en/streaming/astra-streaming/developing/clients/index.html[Pulsar clients with {product}] \ No newline at end of file diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index aa514fc..177a1b6 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -1,23 +1,23 @@ -= {product_name} Functions += {product} Functions :page-tag: astra-streaming,dev,develop,pulsar,java,python Functions are lightweight compute processes that enable you to process each message received on a topic. You can apply custom logic to that message, transforming or enriching it, and then output it to a different topic. -Functions run inside {product_name} and are therefore serverless. You write the code for your function in Java, Python, or Go, then upload the code. It will be automatically run for each message published to the specified input topic. +Functions run inside {product} and are therefore serverless. You write the code for your function in Java, Python, or Go, then upload the code. It will be automatically run for each message published to the specified input topic. Functions are implemented using Apache Pulsar(R) functions. See https://pulsar.apache.org/docs/en/functions-overview/[Pulsar Functions overview] for more information about Pulsar functions. [TIP] ==== -Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise*] plan with a payment method on file. +Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. ==== == Deploy Python functions in a ZIP file -Astra Streaming supports Python-based Pulsar Functions. -These functions can be packaged in a ZIP file and deployed to {product_name} or Pulsar. The same ZIP file can be deployed to either environment. +{product} supports Python-based Pulsar Functions. +These functions can be packaged in a ZIP file and deployed to {product} or Pulsar. The same ZIP file can be deployed to either environment. We'll create a ZIP file in the proper format, then use the pulsar-admin command to deploy the ZIP. We'll pass a create function YAML configuration file as a parameter to pulsar-admin, which defines the Pulsar Function options and parameters. @@ -108,7 +108,7 @@ userConfig: + This file will be passed to the pulsar-admin create function command. -. Use pulsar-admin to deploy the Python ZIP to Astra Streaming or Pulsar. +. Use pulsar-admin to deploy the Python ZIP to {product} or Pulsar. The command below assumes you've properly configured the client.conf file for pulsar-admin commands against your Pulsar cluster. See the documentation xref:configure-pulsar-env.adoc[here] for more information. + [source,bash] @@ -116,7 +116,7 @@ The command below assumes you've properly configured the client.conf file for pu bin/pulsar-admin functions create --function-config-file ---- -. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product_name}. +. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product}. .. You can also use the pulsar-admin command to list your functions: + @@ -127,8 +127,8 @@ bin/pulsar-admin functions list --tenant --namespace == Deploy Java functions in a JAR file -Astra Streaming supports Java-based Pulsar Functions which are packaged in a JAR file. -The JAR can be deployed to Astra Streaming or Pulsar. The same JAR file can be deployed to either environment. +{product} supports Java-based Pulsar Functions which are packaged in a JAR file. +The JAR can be deployed to {product} or Pulsar. The same JAR file can be deployed to either environment. We'll create a JAR file using Maven, then use the pulsar-admin command to deploy the JAR. We'll pass a create function YAML configuration file as a parameter to pulsar-admin, which defines the Pulsar function options and parameters. @@ -244,12 +244,12 @@ This file will be passed to the pulsar-admin create function command. + [IMPORTANT] ==== -Astra Streaming requires the `inputs` topic to have a message schema defined before deploying the function. +{product} requires the `inputs` topic to have a message schema defined before deploying the function. Otherwise, deployment errors may occur. Use the {astra_ui} to define the message schema for a topic. ==== + -. Use pulsar-admin to deploy your new JAR to Astra Streaming or Pulsar. +. Use pulsar-admin to deploy your new JAR to {product} or Pulsar. The command below assumes you've properly configured the client.conf file for pulsar-admin commands against your Pulsar cluster. See the documentation xref:configure-pulsar-env.adoc[here] for more information. + [source,bash] @@ -257,7 +257,7 @@ The command below assumes you've properly configured the client.conf file for pu bin/pulsar-admin functions create --function-config-file ---- -. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product_name}. +. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product}. .. You can also use the pulsar-admin command to list your functions: + @@ -266,9 +266,9 @@ bin/pulsar-admin functions create --function-config-file --namespace ---- -== Add functions in {product_name} dashboard +== Add functions in {product} dashboard -Add functions in the Functions tab of the Astra Streaming dashboard. +Add functions in the Functions tab of the {product} dashboard. . Select *Create Function* to get started. . Choose your function name and namespace. @@ -277,7 +277,7 @@ image::astream-name-function.png[Function and Namespace] . Select the file you want to pull the function from and which function you want to use within that file. -Astra generates a list of acceptable classes. Python and Java functions are added a little differently from each other. +{astra_db} generates a list of acceptable classes. Python and Java functions are added a little differently from each other. Python functions are added by loading a Python file (.py) or a zipped Python file (.zip). When adding Python files, the Class Name is specified as the name of the Python file without the extension plus the class you want to execute. @@ -351,7 +351,7 @@ You will see "Created Successfully!" if the function is set up and ready to acce + [TIP] ==== -If you receive a 402 error with "Reason: only qualified organizations can create functions", this means your organization needs to be upgraded to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise*] plan with a payment method on file. +If you receive a 402 error with "Reason: only qualified organizations can create functions", this means your organization needs to be upgraded to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. ==== . Use `./pulsar-admin functions list --tenant ` to list the functions in your tenant and confirm your new function was created. @@ -439,4 +439,4 @@ video::OCqxcNK0HEo[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445p == Next -Learn more about developing functions for {product_name} and Pulsar https://pulsar.apache.org/docs/en/functions-develop/[here]. \ No newline at end of file +Learn more about developing functions for {product} and Pulsar https://pulsar.apache.org/docs/en/functions-develop/[here]. \ No newline at end of file diff --git a/modules/developing/pages/astream-kafka.adoc b/modules/developing/pages/astream-kafka.adoc index 0498098..ae4c216 100644 --- a/modules/developing/pages/astream-kafka.adoc +++ b/modules/developing/pages/astream-kafka.adoc @@ -16,7 +16,7 @@ This document will help you get started producing and consuming Kafka messages o :page-tag: starlight-kafka,quickstart,install,admin,dev,pulsar,kafka -. To start connecting {kafka_for_astra}, select *Kafka* in the {product_name} *Connect* tab. +. To start connecting {kafka_for_astra}, select *Kafka* in the {product} *Connect* tab. . When the popup appears, confirm you want to enable Kafka on your tenant. + @@ -28,7 +28,7 @@ You must remove the tenant itself to remove these namespaces. . Select *Enable Kafka*. + -Three new namespaces are created in your {product_name} tenant: +Three new namespaces are created in your {product} tenant: + * `kafka` for producing and consuming messages * `+__kafka+` for functionality @@ -85,7 +85,7 @@ $ bin/kafka-console-consumer --bootstrap-server kafka-aws-useast2.streaming.data hello pulsar ---- -. Send as many messages as you'd like, then return to your `kafka` namespace dashboard in {product_name} and monitor your activity. +. Send as many messages as you'd like, then return to your `kafka` namespace dashboard in {product} and monitor your activity. Your Kafka messages are being produced and consumed in a Pulsar cluster! diff --git a/modules/developing/pages/astream-rabbit.adoc b/modules/developing/pages/astream-rabbit.adoc index a8576f1..3b3b7db 100644 --- a/modules/developing/pages/astream-rabbit.adoc +++ b/modules/developing/pages/astream-rabbit.adoc @@ -14,7 +14,7 @@ Get started producing and consuming RabbitMQ messages on a Pulsar cluster. == {starlight_rabbitmq} Quickstart -. To start connecting {starlight_rabbitmq}, select *RabbitMQ* in the {product_name} *Connect* tab. +. To start connecting {starlight_rabbitmq}, select *RabbitMQ* in the {product} *Connect* tab. . When the popup appears, confirm you want to enable RabbitMQ on your tenant. + @@ -26,7 +26,7 @@ You must remove the tenant itself to remove this namespace. . Select *Enable RabbitMQ*. + -A new `rabbitmq` namespace is created in your {product_name} tenant for RabbitMQ functionality. +A new `rabbitmq` namespace is created in your {product} tenant for RabbitMQ functionality. + A new configuration file is generated in the *Connect* tab that looks like this: + @@ -110,7 +110,7 @@ started a channel sent one ---- -. Navigate to your `rabbitmq` namespace dashboard in {product_name} and monitor your activity. +. Navigate to your `rabbitmq` namespace dashboard in {product} and monitor your activity. You should see new topics called `amq.default.__queuename` and `amq.default_routingkey` that were created by the Python script above, and an increasing amount of traffic and messages. Your RabbitMQ messages are being published to a Pulsar topic. diff --git a/modules/developing/pages/clients/csharp-produce-consume.adoc b/modules/developing/pages/clients/csharp-produce-consume.adoc index fec7a0a..ff27da7 100644 --- a/modules/developing/pages/clients/csharp-produce-consume.adoc +++ b/modules/developing/pages/clients/csharp-produce-consume.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the C# pulsar client on Astra Streaming += Producing and consuming messages using the C# pulsar client on {product} :navtitle: C# :description: This is a guide to create a simple producer and consumer using Pulsar's C# client. diff --git a/modules/developing/pages/clients/golang-produce-consume.adoc b/modules/developing/pages/clients/golang-produce-consume.adoc index ca9300a..77995ca 100644 --- a/modules/developing/pages/clients/golang-produce-consume.adoc +++ b/modules/developing/pages/clients/golang-produce-consume.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the Golang pulsar client on Astra Streaming += Producing and consuming messages using the Golang pulsar client on {product} :navtitle: Golang :description: This is a guide to create a simple producer and consumer using Pulsar's golang client. :page-tag: astra-streaming,dev,develop,pulsar,go diff --git a/modules/developing/pages/clients/index.adoc b/modules/developing/pages/clients/index.adoc index 265155a..0a978a3 100644 --- a/modules/developing/pages/clients/index.adoc +++ b/modules/developing/pages/clients/index.adoc @@ -1,11 +1,11 @@ -= Producing and consuming messages on Astra Streaming with Pulsar clients += Producing and consuming messages on {product} with Pulsar clients :navtitle: Pulsar clients -:description: Use any of the Pulsar Clients to interact with your Astra Streaming topics. +:description: Use any of the Pulsar Clients to interact with your {product} topics. -{product_name} is powered by http://pulsar.apache.org/[Apache Pulsar]. +{product} is powered by http://pulsar.apache.org/[Apache Pulsar]. To connect to your service, use the open-source client APIs provided by the Apache Pulsar project. -Astra Streaming is running Pulsar version {pulsar_version}. You should use this API version or higher. +{product} is running Pulsar version {pulsar_version}. You should use this API version or higher. Popular Pulsar clients include the following: diff --git a/modules/developing/pages/clients/java-produce-consume.adoc b/modules/developing/pages/clients/java-produce-consume.adoc index 04d75df..9705b02 100644 --- a/modules/developing/pages/clients/java-produce-consume.adoc +++ b/modules/developing/pages/clients/java-produce-consume.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the Java pulsar client with Astra Streaming += Producing and consuming messages using the Java pulsar client with {product} :navtitle: Java :description: This is a guide to create a simple producer and consumer using Pulsar's java client. :page-tag: astra-streaming,dev,develop,pulsar,java @@ -74,7 +74,7 @@ You may also need to specify the versions on pom.xml as well. Let's set up the main Java class and have some fun! Open the "src/main/java/org/example/App.java" file in your favorite text editor or IDE. -Clear the contents of the file and add the following to bring in needed dependencies, create a client instance, and configure it to use your Astra Streaming tenant. +Clear the contents of the file and add the following to bring in needed dependencies, create a client instance, and configure it to use your {product} tenant. [source,java] ---- diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index ce048a5..d65f2c8 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the Node.js Pulsar client on Astra Streaming += Producing and consuming messages using the Node.js Pulsar client on {product} :navtitle: Node.js :description: This is a guide to create a simple producer and consumer using Pulsar's Node.js client. :page-tag: astra-streaming,connect,dev,develop,nodejs,pulsar diff --git a/modules/developing/pages/clients/python-produce-consume.adoc b/modules/developing/pages/clients/python-produce-consume.adoc index b87e579..70f415b 100644 --- a/modules/developing/pages/clients/python-produce-consume.adoc +++ b/modules/developing/pages/clients/python-produce-consume.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the python pulsar client on Astra Streaming += Producing and consuming messages using the python pulsar client on {product} :navtitle: Python :description: This is a guide to create a simple producer and consumer using Pulsar's python client. :page-tag: astra-streaming,dev,develop,python,pulsar diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index 8ef79a5..34645fc 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the Java pulsar client with Astra Streaming and Spring += Producing and consuming messages using the Java pulsar client with {product} and Spring :navtitle: Spring :description: This is a guide to create a simple producer and consumer using Pulsar's java client with Spring. :page-tag: astra-streaming,dev,develop,pulsar,java @@ -38,7 +38,7 @@ include::{astra-streaming-examples-repo}/java/spring-boot/SpringBoot/src/main/ja ---- ==== -. Replace the values in your Java application with values from the Connect tab of your {product_name} instance. +. Replace the values in your Java application with values from the Connect tab of your {product} instance. + [source,java] ---- @@ -49,7 +49,7 @@ include::{astra-streaming-examples-repo}/java/spring-boot/SpringBoot/src/main/ja private static final String topicName = "clue-sensors"; ---- + -You could instead modify the values in the "application.properties" file in the /resources folder, which is a better practice for production applications. The Connect tab in Astra Streaming has a link to download a properties file with the values your application needs to connect to your {product_name} instance. +You could instead modify the values in the "application.properties" file in the /resources folder, which is a better practice for production applications. The Connect tab in {product} has a link to download a properties file with the values your application needs to connect to your {product} instance. + .Spring application properties [%collapsible] @@ -83,7 +83,7 @@ mvn clean compile + Maven has compiled your Java application with the Pulsar client dependency. -. Run the project and send a message to your {product_name} cluster: +. Run the project and send a message to your {product} cluster: + [source,bash] ---- @@ -99,7 +99,7 @@ Message received: Hello World ---- ==== -Remember to check in your {product_name} instance to see the message you sent to the newly created subscription, "my-subscription". +Remember to check in your {product} instance to see the message you sent to the newly created subscription, "my-subscription". == See also diff --git a/modules/developing/pages/configure-pulsar-env.adoc b/modules/developing/pages/configure-pulsar-env.adoc index 66f32f0..56965a8 100644 --- a/modules/developing/pages/configure-pulsar-env.adoc +++ b/modules/developing/pages/configure-pulsar-env.adoc @@ -1,8 +1,8 @@ -= Configuring your local environment for Astra Streaming -:navtitle: Using Pulsar binaries with Astra Streaming -:description: This guide will provide the necessary steps to download a compatible Pulsar artifact and configure the binaries for use with Astra Streaming. += Configuring your local environment for {product} +:navtitle: Using Pulsar binaries with {product} +:description: This guide will provide the necessary steps to download a compatible Pulsar artifact and configure the binaries for use with {product}. -Did we mention that Astra Streaming is running Apache Pulsar(TM)? +Did we mention that {product} is running Apache Pulsar(TM)? All the benefits you enjoy with the open source Pulsar project can also be used on our platform. This guide details the necessary steps to getting set up and validating the connection. @@ -12,7 +12,7 @@ This guide details the necessary steps to getting set up and validating the conn Pulsar is distributed as a single artifact. The Pulsar https://pulsar.apache.org/download/[download page] offers the latest version, as well as older releases. -Astra Streaming is currently compatible with Pulsar {pulsar_version}. +{product} is currently compatible with Pulsar {pulsar_version}. Locate the latest patch version matching the major.minor version and download the binaries. [source,shell,subs="attributes+"] @@ -37,26 +37,26 @@ tar xvfz apache-pulsar-$PULSAR_VERSION-bin.tar.gz You will see most Pulsar commands prefixed with "./bin". That means you have `cd apache-pulsar-$PULSAR_VERSION` and are running commands from there. -== Configuring your binaries for Astra Streaming +== Configuring your binaries for {product} The Pulsar folder contains quite a few files and folders, but two most important are `./conf` and `./bin`. The executables in bin use the configurations in conf to run commands. -Each tenant you create in Astra Streaming comes with its own custom configuration (for SSO, endpoints, etc). -You will need to download the tenant configuration from Astra Streaming and overwrite the `./conf/client.conf` file. +Each tenant you create in {product} comes with its own custom configuration (for SSO, endpoints, etc). +You will need to download the tenant configuration from {product} and overwrite the `./conf/client.conf` file. -. Navigate to the "Connect" tab in the Astra Streaming portal. +. Navigate to the "Connect" tab in the {product} portal. + -image:connect-tab.png[Connect tab in Astra Streaming] +image:connect-tab.png[Connect tab in {product}] . Locate the "Download client.conf" button and click to download the conf file. + -image:download-client.png[Download pulsar client conf in Astra Streaming] +image:download-client.png[Download pulsar client conf in {product}] . Save the file in the "/conf" folder of the Pulsar folder. This will overwrite the default client.conf already in the /conf folder. -With your Astra Streaming tenant's configuration in place, you can use any of the binaries to interact with a Pulsar cluster. +With your {product} tenant's configuration in place, you can use any of the binaries to interact with a Pulsar cluster. [cols="1,3"] |=== diff --git a/modules/developing/pages/gpt-schema-translator.adoc b/modules/developing/pages/gpt-schema-translator.adoc index 3f47198..fc1531b 100644 --- a/modules/developing/pages/gpt-schema-translator.adoc +++ b/modules/developing/pages/gpt-schema-translator.adoc @@ -85,19 +85,19 @@ Pulsar JSON schema:: As you add more complex data types like maps and nested data structures, schema management becomes more difficult. Schema management is tedious work that requires understanding rules across various domains and translating between them, but it is trivial work for the GPT-4 AI model. -The {gpt-schema-translator} uses generative AI to automatically generate schema mappings between Astra Streaming (Pulsar topics) and the Astra DB sink connector (Cassandra tables). +The {gpt-schema-translator} uses generative AI to automatically generate schema mappings between {product} (Pulsar topics) and the {astra_db} sink connector (Cassandra tables). == Prerequisites -* An Astra Streaming cluster with a topic created. For more information, see the xref:getting-started:index.adoc[Getting Started] documentation. -* An Astra database with a keyspace and table created. For more, see the https://docs.datastax.com/en/astra-serverless/docs/[Astra DB documentation]. +* An {product} cluster with a tenant and topic. For more information, see the xref:getting-started:index.adoc[Getting Started] documentation. +* An {astra_db} database with a keyspace and table. For more, see the https://docs.datastax.com/en/astra-serverless/docs/[{astra_db} documentation]. == Usage -The {gpt-schema-translator} is available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[Astra DB Sink Connector]. -To use the {gpt-schema-translator}, create an Astra DB sink connector. +The {gpt-schema-translator} is available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[{astra_db} Sink Connector]. +To use the {gpt-schema-translator}, create an {astra_db} sink connector. -In this example, we'll continue with the two schemas from above: one JSON schema for the Pulsar topic, and one CQL schema for the Astra DB table. The {gpt-schema-translator} will generate a mapping between the two schemas, which can be used by the Astra DB sink connector to write data from the Pulsar topic to the Astra DB table. +In this example, we'll continue with the two schemas from above: one JSON schema for the Pulsar topic, and one CQL schema for the {astra_db} table. The {gpt-schema-translator} will generate a mapping between the two schemas, which can be used by the {astra_db} sink connector to write data from the Pulsar topic to the {astra_db} table. image::two-schemas.png[Schema mapping,320,240] @@ -196,9 +196,9 @@ Great! Now, once your connector is created, messages will flow smoothly between [#pulsar-topic-to-cql-table] == Pulsar topic with an AVRO schema to Cassandra table This example will produce a mapping between a Pulsar Topic in AVRO schema and a Cassandra table schema. -Avro schema definitions are JSON records, so this example isn't radically different from the first, but this time, we'll use the DataGenerator source connector to generate data for the Pulsar topic, the Astra DB sink connector to write data to the Cassandra table, and the {gpt-schema-translator} to generate a schema mapping between the two as the messages are processed. +Avro schema definitions are JSON records, so this example isn't radically different from the first, but this time, we'll use the DataGenerator source connector to generate data for the Pulsar topic, the {astra_db} sink connector to write data to the Cassandra table, and the {gpt-schema-translator} to generate a schema mapping between the two as the messages are processed. -* For more on creating the AstraDB sink connector, see the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[Astra DB Sink Connector documentation]. +* For more on creating the {astra_db} sink connector, see the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[{astra_db} Sink Connector documentation]. * For more on creating the DataGenerator source connector, see the xref:streaming-learning:pulsar-io:connectors/sources/data-generator.adoc[DataGenerator Connector documentation]. The DataGenerator source connector will generate data for the Pulsar topic using the following schema: @@ -438,7 +438,7 @@ The DataGenerator source connector will generate data for the Pulsar topic using ---- ==== -The Cassandra table for the AstraDB sink has the following schema: +The Cassandra table for the {astra_db} sink has the following schema: .CQL schema [%collapsible] @@ -482,7 +482,7 @@ When a topic schema is available to the {gpt-schema-translator}, the button prom image::create-schema-mapping.png[Schema mapping,320,240] -GPT examines the schemas and generates a mapping. The mapping is displayed in the UI, and can be copied to the clipboard. +GPT examines the schemas and generates a mapping. The mapping is displayed in the {astra_ui}, and can be copied to the clipboard. [source,] ---- passportnumber=value.passportNumber, age=value.age, firstname=value.firstName, lastname=value.lastName diff --git a/modules/developing/pages/produce-consume-astra-portal.adoc b/modules/developing/pages/produce-consume-astra-portal.adoc index e526bdb..fa0be50 100644 --- a/modules/developing/pages/produce-consume-astra-portal.adoc +++ b/modules/developing/pages/produce-consume-astra-portal.adoc @@ -1,23 +1,23 @@ -= Producing and consuming messages using the Astra Portal -:navtitle: Astra Portal -:description: Use this guide to create and consume a topic message using the Astra Streaming Portal. += Producing and consuming messages using the {astra_ui} +:navtitle: {astra_ui} +:description: Use this guide to create and consume a topic message using the {astra_ui}. -This guide assumes you have completed the xref:getting-started:index.adoc[Astra Streaming quickstart]. +This guide assumes you have completed the xref:getting-started:index.adoc[{product} quickstart]. Now, it's time to produce and consume messages in your new streaming tenant. -The following steps will use the "Try Me" feature of the Astra Portal to interact with an existing topic. +The following steps will use the "Try Me" feature of the {astra_ui} to interact with an existing topic. == Select the producer and consumer topics . Navigate to the "Try Me" tab. + -image:try-me-tab.png[Try me tab in Astra Streaming] +image:try-me-tab.png[Try me tab in {product}] . Choose the appropriate namespace and topic. In this example, the name for both the producer and consumer is the same. Leave the rest of the settings as default. + -image:config-try-me.png[Config try me in Astra Streaming] +image:config-try-me.png[Config try me in {product}] == Connect and send a message @@ -25,13 +25,13 @@ image:config-try-me.png[Config try me in Astra Streaming] . In *Send message*, type a message, and then click *Send*. + -image:test-message-input.png[Send message in Astra Streaming] +image:test-message-input.png[Send message in {product}] A message is produced (sent) to your selected topic, and then the consumer consumes (retrieves) the message. The result is a chat style write and read. -image:try-me-test-message.png[Test message in Astra Streaming] +image:try-me-test-message.png[Test message in {product}] == See also diff --git a/modules/developing/pages/produce-consume-pulsar-client.adoc b/modules/developing/pages/produce-consume-pulsar-client.adoc index 12b8152..0a887d2 100644 --- a/modules/developing/pages/produce-consume-pulsar-client.adoc +++ b/modules/developing/pages/produce-consume-pulsar-client.adoc @@ -1,8 +1,8 @@ -= Producing and consuming messages with the Pulsar Client on Astra Streaming += Producing and consuming messages with the Pulsar Client on {product} :navtitle: Pulsar CLI -:description: Use this guide to interact with your Astra Streaming tenant via the pulsar-client cli. +:description: Use this guide to interact with your {product} tenant via the pulsar-client cli. -This guide assumes you have completed the xref:getting-started:index.adoc[Astra Streaming quickstart] and you have xref:developing:configure-pulsar-env.adoc[set up your Pulsar binaries] to work with your tenant. +This guide assumes you have completed the xref:getting-started:index.adoc[{product} quickstart] and you have xref:developing:configure-pulsar-env.adoc[set up your Pulsar binaries] to work with your tenant. Now it's time to produce and consume messages in your new streaming tenant. diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index 85c52c6..e66928e 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -1,11 +1,11 @@ -= Restful requests using curl with Astra Streaming -:navtitle: Using curl with Astra Streaming -:description: This guide will provide the necessary steps to configure curl calls for use with Astra Streaming. += Restful requests using curl with {product} +:navtitle: Using curl with {product} +:description: This guide will provide the necessary steps to configure curl calls for use with {product}. Pulsar offers a RESTful API for administrative tasks. -When you create a tenant in Astra Streaming, all supporting APIs are enabled. +When you create a tenant in {product}, all supporting APIs are enabled. -To interact with your Astra Streaming tenant you will need two pieces of information: a token and the service URL. +To interact with your {product} tenant you will need two pieces of information: a token and the service URL. This guide will show you how to gather that information and test your connection. [TIP] @@ -13,7 +13,7 @@ This guide will show you how to gather that information and test your connection The https://pulsar.apache.org/docs/2.10.x/reference-rest-api-overview/[Pulsar documentation] has a full reference for each API supported in a cluster. ==== -This reference will be helpful as you look to automate certain actions in your Astra Streaming tenant. +This reference will be helpful as you look to automate certain actions in your {product} tenant. == Find your tenant's web service URL @@ -23,12 +23,12 @@ You send HTTP requests to your tenant's _Web Service URL_: . Click the *Connect* tab. + -image:connect-tab.png[Connect tab in Astra Streaming] +image:connect-tab.png[Connect tab in {product}] . In the *Details* section, locate the *Tenant Details*. Here you can find the essential information you need to communicate with your Pulsar tenant. + -image:tenant-details.png[Tenant details in Astra Streaming] +image:tenant-details.png[Tenant details in {product}] . Copy the *Web Service URL*. @@ -40,21 +40,21 @@ Web Service URLs start with `http`. Broker Service URLs start with `pulsar(+ssl)`. ==== -== Retrieve your Pulsar token in Astra Streaming +== Retrieve your Pulsar token in {product} You need a Pulsar token to authenticate requests. An xref:operations:astream-token-gen.adoc[{astra_db} application token] is _not_ the same as a Pulsar token. -. To create a new Pulsar token in your Astra Streaming tenant, navigate to the "Settings" tab in the Astra portal. +. To create a new Pulsar token in your {product} tenant, navigate to the "Settings" tab in the {astra_ui}. + -image:settings-tab.png[Settings tab in Astra Streaming] +image:settings-tab.png[Settings tab in {product}] . Click "Create Token" and choose the time to expire. Be smart about this choice - "Never Expire" isn't always the best option. A window will appear with the newly generated token - it's a long string of letters and numbers. + -image:copy-token.png[Copy token in Astra Streaming] +image:copy-token.png[Copy token in {product}] . Click the clipboard icon to copy the token to your clipboard, and paste the token in a safe place. This is the only time you will be able to copy it. diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index 9184226..28a88ab 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -1,14 +1,14 @@ -= Getting started with Astra Streaming += Getting started with {product} :navtitle: Get started :page-tag: astra-streaming,planner,quickstart,pulsar -Astra Streaming is a cloud native data streaming and event stream processing +{product} is a cloud native data streaming and event stream processing service tightly integrated into the {astra_ui} and powered by Apache Pulsar(TM). -Using Astra Streaming, customers can quickly create Pulsar instances, +Using {product}, customers can quickly create Pulsar instances, manage their clusters, scale across cloud regions, and manage Pulsar resources such as topics, connectors, functions and subscriptions. -Follow this guide to create a new account in DataStax Astra, along with a Streaming Tenant running Apache Pulsar. +Follow this guide to create a new account in {astra_db}, along with a streaming tenant running Apache Pulsar. == Prerequisites @@ -16,29 +16,29 @@ You will need the following to complete this guide: * A working email address -== Create your Astra account +== Create your {astra_db} account -. Navigate to the {streaming_signup_url}[Astra portal sign in page^]. +. Navigate to the {streaming_signup_url}[{astra_ui} sign in page^]. + -image:astream-login.png[Login to Astra] +image:astream-login.png[Login to {astra_db}] . Click the "Sign Up" link (toward the bottom). . Provide your information along with a valid email. + -image:astream-create-account.png[Create Astra account] +image:astream-create-account.png[Create {astra_db} account] -. You should receive an email from DataStax verifying your address - click the button within. +. You should receive an email from {company} verifying your address - click the button within. -. If you aren't automatically signed in, visit the {login_url}[Astra sign in page^] and use your new account creds. +. If you aren't automatically signed in, visit the {login_url}[{astra_db} sign in page^] and use your new account creds. == Your first streaming tenant -Think of a tenant in Astra Streaming as your safe space to work. -It is a portion of DataStax's managed Apache Pulsar that is only yours. +Think of a tenant in {product} as your safe space to work. +It is a portion of {company}'s managed Apache Pulsar that is only yours. Learn more about the concept of tenancy in the https://pulsar.apache.org/docs/concepts-multi-tenancy/[Pulsar docs]. -The steps in the below tabs will guide you through creating a Streaming Tenant. +The steps in the below tabs will guide you through creating a streaming tenant. You'll use this tenant to create namespaces, topics, functions, and pretty much everything else. The only difference between the tabs is how your tenant is created - they all have the same result. @@ -51,24 +51,24 @@ The only difference between the tabs is how your tenant is created - they all ha . Click *Create a Tenant*. -. Name your Streaming Tenant something memorable like `my-stream-**RANDOM_UUID**`. -All tenant names in Astra Streaming must be unique. +. Name your streaming tenant something memorable like `my-stream-**RANDOM_UUID**`. +All tenant names in {product} must be unique. . Choose your preferred cloud provider and region. For your first example tenant, the region doesn't really matter. + -image:create-a-stream.png[Create a new tenant in Astra Streaming] +image:create-a-stream.png[Create a new tenant in {product}] . Click *Create Tenant*. You are directed to the quickstart page for your new tenant. + -image:new-tenant-quickstart.png[New tenant quickstart in Astra Streaming] +image:new-tenant-quickstart.png[New tenant quickstart in {product}] -- {astra_cli}:: + -- -{astra_cli} is a set of commands for creating and managing all Astra resources. +{astra_cli} is a set of commands for creating and managing all {astra_db} resources. For more information, see the https://docs.datastax.com/en/astra-cli/docs/0.2/[documentation]. . Set the required variables, replacing `**RANDOM_UUID**` with a few random numbers or letters. @@ -98,7 +98,7 @@ Next, create a namespace in your new tenant. A namespace exists within a tenant. A namespace is a logical grouping of message topics. -{astra_stream} automatically create a `default` namespaces when you create a tenant. +{product} automatically create a `default` namespaces when you create a tenant. You can use the default namespace or create new ones. Tenants usually have many namespaces. @@ -114,15 +114,15 @@ Learn more about namespaces in the https://pulsar.apache.org/docs/concepts-messa -- . Navigate to the "Namespace And Topics" tab. + -image:namespace-tab.png[Namespace tab in Astra Streaming] +image:namespace-tab.png[Namespace tab in {product}] . Click the "Create Namespace" button, and give your namespace a super original name like "my-namespace". + -image:create-namespace.png[Create namespace in Astra Streaming] +image:create-namespace.png[Create namespace in {product}] . Click "Create" to create the namespace. + -image:namespace-listing.png[Namespaces in Astra Streaming] +image:namespace-listing.png[Namespaces in {product}] -- Pulsar Admin:: @@ -186,17 +186,17 @@ Learn more about topics in the https://pulsar.apache.org/docs/concepts-messaging It must start with a lowercase letter, be alphanumeric, and can contain hyphens. . Leave the choice of persistence and partitioning alone for now - those can be a part of https://pulsar.apache.org/docs/concepts-messaging/#partitioned-topics[future learning]. + -image:add-topic.png[Add topic in Astra Streaming] +image:add-topic.png[Add topic in {product}] . Click the *Add Topic* button to create the topic within the namespace. + -image:topic-listing.png[Topic listing in Astra Streaming] +image:topic-listing.png[Topic listing in {product}] -- Pulsar Admin:: + -- -To learn more about connecting to Astra Streaming with the pulsar-admin CLI, see xref:developing:configure-pulsar-env.adoc[]. +To learn more about connecting to {product} with the pulsar-admin CLI, see xref:developing:configure-pulsar-env.adoc[]. . Set the required variables: + @@ -218,7 +218,7 @@ include::{astra-streaming-examples-repo}/pulsar-admin/create-topic.sh[] curl:: + -- -To learn more about interacting with Astra Streaming through HTTP, see xref:developing:using-curl.adoc[]. +To learn more about interacting with {product} through HTTP, see xref:developing:using-curl.adoc[]. . Set the required variables: + @@ -245,5 +245,6 @@ include::{astra-streaming-examples-repo}/curl/create-topic.sh[] Your new topic is ready to produce and consume messages. There are several different ways to do this: -* xref:developing:produce-consume-astra-portal.adoc[Astra Portal]: Use Astra Streaming's "Try Me" feature in the {astra_ui}.* xref:developing:produce-consume-pulsar-client.adoc[Pulsar Client]: Use the Pulsar CLI to interact with the topic. +* xref:developing:produce-consume-astra-portal.adoc[{astra_ui}]: Use {product}'s "Try Me" feature in the {astra_ui}. +* xref:developing:produce-consume-pulsar-client.adoc[Pulsar Client]: Use the Pulsar CLI to interact with the topic. * xref:developing:clients/index.adoc[Runtime Clients]: Create a client application that interacts with Pulsar. \ No newline at end of file diff --git a/modules/operations/pages/astream-georeplication.adoc b/modules/operations/pages/astream-georeplication.adoc index 927c49a..8565fa3 100644 --- a/modules/operations/pages/astream-georeplication.adoc +++ b/modules/operations/pages/astream-georeplication.adoc @@ -15,21 +15,21 @@ This doc provides: * xref:astream-georeplication.adoc#replicated-subscriptions[] * xref:astream-georeplication.adoc#monitor[] -If you're already familiar with Pulsar's messaging replication and asynchronous geo-replication, skip ahead to xref:astream-georeplication.adoc#astra-ui[enabling geo-replication in Astra Streaming]. +If you're already familiar with Pulsar's messaging replication and asynchronous geo-replication, skip ahead to xref:astream-georeplication.adoc#astra-ui[enabling geo-replication in {product}]. [#overview] == Overview of message replication in Pulsar In Pulsar, cross-cluster message replication can be implemented with *synchronous* or *asynchronous* message replication, and with or without a global configuration store in ZooKeeper. -{product_name} only supports *asynchronous* geo-replication without a global configuration store. This approach offers better performance and lower latency. +{product} only supports *asynchronous* geo-replication without a global configuration store. This approach offers better performance and lower latency. In asynchronous geo-replication, each region has its own local Pulsar cluster. Messages published in a cluster of one region are automatically replicated asynchronously to remote clusters in other regions. This is achieved through Pulsar’s built-in geo-replication capability. [#async] == Asynchronous geo-replication overview -{product_name} only supports *asynchronous* geo-replication without a global configuration store. +{product} only supports *asynchronous* geo-replication without a global configuration store. An *asynchronous* geo-replication cluster consists of two or more independent Pulsar clusters running in different regions. @@ -39,18 +39,18 @@ When messages are produced on a Pulsar topic, they are first persisted to the lo The message producer doesn't wait for confirmation from multiple Pulsar clusters. The producer receives a response immediately after the nearest cluster successfully persists the data. The data is then asynchronously replicated to the other Pulsar clusters in the background. -To set up asynchronous geo-replication in {product_name}, see xref:astream-georeplication.adoc#astra-ui[]. +To set up asynchronous geo-replication in {product}, see xref:astream-georeplication.adoc#astra-ui[]. [#astra-ui] -== Enable geo-replication in Astra Streaming +== Enable geo-replication in {product} Asynchronous geo-replication is enabled on a per-tenant basis and managed at the namespace level. This means you can enable asynchronous geo-replication on topics where it is needed, while controlling which datasets are replicated by namespace. -To enable geo-replication in {product_name}, follow these steps: +To enable geo-replication in {product}, follow these steps: -. In your {product_name} tenant, select **Namespaces and Topics** to list your tenant's current namespaces and topics. +. In your {product} tenant, select **Namespaces and Topics** to list your tenant's current namespaces and topics. . Select **Modify namespace** in the namespace you want to replicate *from*. @@ -147,7 +147,7 @@ key:[null], properties:[], content:hello-from-pulsar key:[null], properties:[], content:hello-from-pulsar ---- -. Navigate to the **Namespaces and Topics** tab in your geo-replicated {product_name} clusters. + +. Navigate to the **Namespaces and Topics** tab in your geo-replicated {product} clusters. + `persistent:////` should be visible and showing traffic across all regions. [#replicated-subscriptions] @@ -267,7 +267,7 @@ bin/pulsar-admin topics stats persistent://// [#monitor] == Monitor replicated clusters -{product_name} exposes the following topic-level replication metrics, which can be viewed in the **Overview** tab of the Namespaces and Topics page. +{product} exposes the following topic-level replication metrics, which can be viewed in the **Overview** tab of the Namespaces and Topics page. include::operations:partial$georeplication-monitoring.adoc[] diff --git a/modules/operations/pages/astream-limits.adoc b/modules/operations/pages/astream-limits.adoc index 6af3399..5d5952b 100644 --- a/modules/operations/pages/astream-limits.adoc +++ b/modules/operations/pages/astream-limits.adoc @@ -1,14 +1,14 @@ -= {product_name} Limits += {product} limits :page-tag: astra-streaming,admin,manage,pulsar -DataStax {product_name} includes guardrails and limits to ensure good practices, foster availability, and promote optimal configurations for your databases. +{product} includes guardrails and limits to ensure good practices, foster availability, and promote optimal configurations for your databases. -The below limits are for Pay As You Go clusters. -These limits can be different on <>. +The below limits are for *Pay As You Go* {product} plans. +These limits can be different on the <>. -== {product_name} guardrails +== {product} guardrails -Guardrails are initially provisioned in the default settings by {product_name}. +Guardrails are initially provisioned in the default settings by {product}. You can't change these guardrails. [cols="1,1"] @@ -77,7 +77,7 @@ The default settings are: === Custom functions -Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* plan] with a payment method on file. +Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. @@ -97,7 +97,7 @@ Organizations on the *Free* plan can use xref:streaming-learning:functions:index == Pulsar-admin CLI limits -The following `pulsar-admin` commands don't work with {product_name}, +The following `pulsar-admin` commands don't work with {product}, either because they're not applicable in a cloud environment or they would cause issues with privacy or data integrity: @@ -111,9 +111,9 @@ issues with privacy or data integrity: For more on `pulsar-admin`, see the Apache Pulsar https://pulsar.apache.org/docs/pulsar-admin/[documentation]. -== {product_name} namespace policies +== {product} namespace policies -These namespace policies are initially provisioned in the default settings by {product_name} and _can_ be changed by users. +These namespace policies are initially provisioned in the default settings by {product} and _can_ be changed by users. [cols="1,1,1"] |=== @@ -167,9 +167,9 @@ The total characters of tenant name, namespace name, and function name cannot ex This is a Kubernetes restriction based on a pod label's maximum size of 63 characters. You can read more about Kubernetes pod naming restrictions https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set[here]. -== {product_name} topic and namespace actions +== {product} topic and namespace actions -These topic and namespace actions are initially provisioned in the default settings by {product_name} and can be performed by users. +These topic and namespace actions are initially provisioned in the default settings by {product} and can be performed by users. [cols="1,1,1"] |=== diff --git a/modules/operations/pages/astream-pricing.adoc b/modules/operations/pages/astream-pricing.adoc index 9f4ef57..a4e2239 100644 --- a/modules/operations/pages/astream-pricing.adoc +++ b/modules/operations/pages/astream-pricing.adoc @@ -1,5 +1,5 @@ -= {product_name} Pricing += {product} Pricing :page-tag: astra-streaming,planner,plan,pulsar -For information on *pricing* for {product_name}, see https://www.datastax.com/products/astra-streaming/pricing[Astra Streaming pricing]. +For information on pricing for {product}, see https://www.datastax.com/products/astra-streaming/pricing[{product} pricing]. diff --git a/modules/operations/pages/astream-regions.adoc b/modules/operations/pages/astream-regions.adoc index b881aed..730f5f8 100644 --- a/modules/operations/pages/astream-regions.adoc +++ b/modules/operations/pages/astream-regions.adoc @@ -1,14 +1,14 @@ -= {product_name} Regions += {product} Regions :page-tag: astra-streaming,admin,manage,pulsar When creating a tenant, select a region for your tenant. Choose a region that is geographically close to your users to optimize performance. -{product_name} supports AWS, Microsoft Azure, and Google Cloud regions. +{product} supports AWS, Microsoft Azure, and Google Cloud regions. These regions are also supported by CDC for {astra_db}. -{product_name} CDC can only be used in a region that supports both {product_name} and {astra_db}. +{product} CDC can only be used in a region that supports both {product} and {astra_db}. -ElasticSearch and Snowflake can be in different regions than {product_name}. +ElasticSearch and Snowflake can be in different regions than {product}. == AWS diff --git a/modules/operations/pages/astream-release-notes.adoc b/modules/operations/pages/astream-release-notes.adoc index 3603d7f..453f5e8 100644 --- a/modules/operations/pages/astream-release-notes.adoc +++ b/modules/operations/pages/astream-release-notes.adoc @@ -1,15 +1,15 @@ -= {product_name} release notes += {product} release notes :page-tag: astra-streaming,admin,dev,pulsar :navtitle: Release notes :page-toclevels: 1 -:description: This page summarizes significant changes and updates to {product_name}. +:description: This page summarizes significant changes and updates to {product}. :new: New features and enhancements :security: Security updates :fix: Fixed issues :dep: Deprecated features :boilerplate: This release includes the following changes and updates: -This page summarizes significant changes and updates to {product_name}. +This page summarizes significant changes and updates to {product}. == June 20, 2023 @@ -25,7 +25,7 @@ This page summarizes significant changes and updates to {product_name}. === {new} -* The xref:developing:gpt-schema-translator.adoc[GPT schema translator] is now available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[Astra DB Sink Connector]. +* The xref:developing:gpt-schema-translator.adoc[GPT schema translator] is now available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[{astra_db} Sink Connector]. == June 1, 2023 @@ -54,8 +54,8 @@ This page summarizes significant changes and updates to {product_name}. === {new} * Added support for https://spring.io/[Spring Boot]. -You can now use this Java framework to build your {product_name} applications. -For more information, see xref:developing:clients/spring-produce-consume.adoc[Spring Boot for {product_name}]. +You can now use this Java framework to build your {product} applications. +For more information, see xref:developing:clients/spring-produce-consume.adoc[Spring Boot for {product}]. == April 17, 2023 @@ -63,9 +63,8 @@ For more information, see xref:developing:clients/spring-produce-consume.adoc[Sp === {new} -* Custom functions can now only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[Pay As You Go] plan with a payment method on file. -+ -Organizations on the Free plan can still use xref:streaming-learning:functions:index.adoc[transform functions]. +* Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. +Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. == February 27, 2023 @@ -98,7 +97,7 @@ Organizations on the Free plan can still use xref:streaming-learning:functions:i [discrete] ==== {new} -* {product_name} now supports xref:streaming-learning:functions:index.adoc[transform functions]. +* {product} now supports xref:streaming-learning:functions:index.adoc[transform functions]. [discrete] === November 16, 2022 @@ -108,7 +107,7 @@ Organizations on the Free plan can still use xref:streaming-learning:functions:i [discrete] ==== {new} -* {product_name} now supports xref:operations:astream-georeplication.adoc[geo-replication]. +* {product} now supports xref:operations:astream-georeplication.adoc[geo-replication]. [discrete] === June 7, 2022 @@ -118,7 +117,7 @@ Organizations on the Free plan can still use xref:streaming-learning:functions:i [discrete] ==== {new} -* {product_name} now supports xref:operations:astream-scrape-metrics.adoc[scraping metrics with Prometheus]. +* {product} now supports xref:operations:astream-scrape-metrics.adoc[scraping metrics with Prometheus]. [discrete] === April 28, 2022 @@ -148,7 +147,7 @@ Organizations on the Free plan can still use xref:streaming-learning:functions:i [discrete] ==== {new} -* xref:developing:astream-cdc.adoc[CDC for Astra DB] is now available, which automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data into {product_name} where it can be processed by client applications or sent to downstream systems. +* xref:developing:astream-cdc.adoc[CDC for {astra_db}] is now available, which automatically captures changes in real time, de-duplicates the changes, and streams the clean set of changed data into {product} where it can be processed by client applications or sent to downstream systems. [discrete] === January 31, 2022 @@ -158,7 +157,7 @@ Organizations on the Free plan can still use xref:streaming-learning:functions:i [discrete] ==== {new} -* {product_name} is now generally available. +* {product} is now generally available. * Added support for the Google Cloud `us-central1` (Council Bluffs, Iowa) region. * xref:operations:astream-token-gen.adoc[Pulsar tokens] simplify connecting to your streaming instances. * Enabled xref:operations:astream-pricing.adoc[billing]. @@ -177,5 +176,5 @@ Organizations on the Free plan can still use xref:streaming-learning:functions:i * Security upgrade to Log4J 2.17.0 to mitigate CVE-2021-45105. -* The public preview of {product_name} brings the ability to quickly create Apache Pulsar(TM) instances, manage your clusters, scale across cloud regions, and manage Pulsar resources such as topics, connectors, functions and subscriptions. +* The public preview of {product} brings the ability to quickly create Apache Pulsar(TM) instances, manage your clusters, scale across cloud regions, and manage Pulsar resources such as topics, connectors, functions and subscriptions. ==== diff --git a/modules/operations/pages/astream-scrape-metrics.adoc b/modules/operations/pages/astream-scrape-metrics.adoc index 510748f..32e89f4 100644 --- a/modules/operations/pages/astream-scrape-metrics.adoc +++ b/modules/operations/pages/astream-scrape-metrics.adoc @@ -1,17 +1,17 @@ -= Scrape {product_name} metrics with Prometheus += Scrape {product} metrics with Prometheus Prometheus collects system metrics by scraping targets at intervals. These metrics are used to monitor deployments, generate alerts, and diagnose problems. -This doc will show you how to scrape an {product_name} tenant with Prometheus. +This doc will show you how to scrape an {product} tenant with Prometheus. == Prerequisites -* Astra Streaming tenant +* {product} tenant * Docker installed locally -== Get configuration file from {product_name} +== Get configuration file from {product} -. To start connecting your {product_name} tenant with Prometheus, select *Prometheus* in the {product_name} *Connect* tab. +. To start connecting your {product} tenant with Prometheus, select *Prometheus* in the {product} *Connect* tab. . A new configuration file will be generated in the *Connect* tab that looks like this: + @@ -72,19 +72,19 @@ If you get a `mounts denied` permissions error, in Docker, go to *File Sharing > . Open your Prometheus dashboard at `localhost:9090`. In the *Status > Targets* window, you should see the endpoint targeted in `static_configs` in an *UP* state. -. Navigate to the *Graph* window. Enter `pulsar_in_messages_total` in the *Expression* field and select *Execute*. Prometheus will now display total incoming Pulsar messages to your {product_name} cluster. +. Navigate to the *Graph* window. Enter `pulsar_in_messages_total` in the *Expression* field and select *Execute*. Prometheus will now display total incoming Pulsar messages to your {product} cluster. -. Produce a few messages in your tenant with the Pulsar CLI or the {product_name} Websocket. +. Produce a few messages in your tenant with the Pulsar CLI or the {product} Websocket. . Your Prometheus graph displays the number of incoming Pulsar messages with each 60 second scrape. + -image::astream-prometheus-graph.png[Scraping {product_name} with Prometheus] +image::astream-prometheus-graph.png[Scraping {product} with Prometheus] -You're scraping your {product_name} tenant with Prometheus! +You're scraping your {product} tenant with Prometheus! == Content encoding -{product_name} supports content encoding with either `gzip` or `deflate`. +{product} supports content encoding with either `gzip` or `deflate`. With the example from above still running, use a `curl` request to decompress your Prometheus scrape data: @@ -117,9 +117,9 @@ include::example$curl_gzip.sh[] pulsar_topics_count{app="pulsar",cluster="pulsar-aws-useast1",component="broker",controller_revision_hash="pulsar-aws-useast1-broker-7444bf6f64",instance="192.168.2.120:8080",job="broker",kubernetes_namespace="pulsar",kubernetes_pod_name="pulsar-aws-useast1-broker-1",namespace="mk-tenant/default",release="astraproduction-aws-useast1-pulsar",statefulset_kubernetes_io_pod_name="pulsar-aws-useast1-broker-1",prometheus="pulsar/astraproduction-aws-useast-prometheus",prometheus_replica="prometheus-astraproduction-aws-useast-prometheus-0"} 1 1654550685678 ---- -== Metrics exposed by {product_name} +== Metrics exposed by {product} -The following Prometheus metrics are exposed by {product_name}: +The following Prometheus metrics are exposed by {product}: * https://pulsar.apache.org/docs/reference-metrics/#namespace-metrics[Namespace metrics] * https://pulsar.apache.org/docs/reference-metrics/#topic-metrics[Topic metrics] diff --git a/modules/operations/pages/astream-token-gen.adoc b/modules/operations/pages/astream-token-gen.adoc index 14ffbe2..8a905d4 100644 --- a/modules/operations/pages/astream-token-gen.adoc +++ b/modules/operations/pages/astream-token-gen.adoc @@ -1,25 +1,25 @@ = Manage Tokens -There are *two* different tokens within Astra - the *Astra token* and the *Astra Streaming Pulsar token*. +There are *two* different tokens within {astra_db} - the *{astra_db} application token* and the *{product} Pulsar token*. -The <> is used for authentication within {astra_ui} and the xref:apis:index.adoc[DevOps API]. +The <> is used for authentication within {astra_ui} and the xref:apis:index.adoc[DevOps API]. The <> is a native Pulsar JSON Web Token (JWT) token that controls authentication to the Pulsar cluster. == What's the difference? -The Astra token is an access token. It is used to authenticate your service account in the DevOps API and {astra_ui}. +The {astra_db} token is an access token. It is used to authenticate your service account in the DevOps API and {astra_ui}. -The Astra Streaming Pulsar token is a JWT token. Astra forwards the token on to the Pulsar cluster, which verifies if the role in allowed. +The {product} Pulsar token is a JWT token. {astra_db} forwards the token on to the Pulsar cluster, which verifies if the role in allowed. -In general, actions related to your Astra Org (tenant management, members, org billing, usage metrics, etc.) use your Astra Token, and actions specific to a Pulsar tenant (message namespaces, topics, message metrics, etc.) use a Pulsar JWT token. +In general, actions related to your {astra_db} Org (tenant management, members, org billing, usage metrics, etc.) use your {astra_db} Token, and actions specific to a Pulsar tenant (message namespaces, topics, message metrics, etc.) use a Pulsar JWT token. For more, see https://docs.datastax.com/en/streaming/astra-streaming/operations/onboarding-faq.html#secure-sign-on-roles-and-permissions[SSO Roles and Permissions]. [#astra-token] -== Generate Astra token +== Generate {astra_db} token -The Astra token can be generated with the <> or the <>. +The {astra_db} token can be generated in the <> or the <>. === DevOps API @@ -69,10 +69,10 @@ curl --request POST \ ---- { "clientId":"zjCEYwRGWocLfQJHBNQxvorr", - "secret":"SLR.cllL1YzfJDnl+YhUv5DMKlx8HaeMFTKjIJ4I6YdKB7w-K7U_+j-a9daWbbcp0uugXW,hb.3J2S0PPqDNhT6+oUiPYYaI+,xuwm2O97.ZpHcYvCsnlrTyl8w1pH-0", + "secret":"SLR.cllL1Yz...", "orgId":"dccb8c32-cc2a-4bea-bd95-47ab8eb20510", "roles":["21ef3576-0197-415a-b167-d510af12ecf0"], - "token":"AstraCS:zjCEYwRGWocLfQJHBNQxvorr:8709074baaf63e746cc5de52891e3a5ca88c73ae1fb7336652e9b59b9e69eff2", + "token":"AstraCS:...", "generatedOn":"2021-04-30T19:38:26.147847107Z" } ---- @@ -93,7 +93,7 @@ export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** ---- [#astra-token-ui] -=== Generate Astra token in Astra Portal +=== Generate an {astra_db} application token . From any page in {astra_ui}, select the *Organizations* dropdown. @@ -103,7 +103,7 @@ export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** . Select the role you want to attach to your token. The permissions for your selected role will be displayed. -. Select *Generate Token*. {product_name} will generate your token and display the _Client ID_, _Client Secret_, and _Token_. +. Select *Generate Token*. {product} will generate your token and display the _Client ID_, _Client Secret_, and _Token_. . Download your _Client ID_, _Client Secret_, and _Token_. + @@ -121,7 +121,7 @@ export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** You can now execute DevOps API commands from your terminal to your database. -=== Delete Astra token +=== Delete {astra_db} token If you need to limit access to your database, you can delete a token. @@ -134,7 +134,7 @@ If you need to limit access to your database, you can delete a token. [#pulsar-token] == Generate Pulsar token -To generate, copy, or delete Astra Streaming Pulsar tokens within your streaming tenant, visit the **Token Management** section of your streaming tenant's **Settings** page. +To generate, copy, or delete {product} Pulsar tokens within your streaming tenant, visit the **Token Management** section of your streaming tenant's **Settings** page. Select **Create Token** to generate a Pulsar token for this streaming tenant, and then copy the Pulsar token and store it securely. Token duration ranges from 7 days to never expiring. @@ -144,7 +144,7 @@ If you choose a token with an expiration, ensure you replace your token with a n Download your Pulsar connection info as detailed https://docs.datastax.com/en/astra-streaming/docs/astream-quick-start.html#download-connect-info[here]. -In the command-line interface associated with your environment, paste the following environment variables copied for {product_name}: +In the command-line interface associated with your environment, paste the following environment variables copied for {product}: [source,shell,subs="+quotes"] ---- @@ -160,19 +160,19 @@ You can now execute Pulsar admin commands from your terminal to your database. Select the **trashcan** icon to delete a Pulsar token. -Ensure you update your application with a new, valid Pulsar token before deletion. Applications using the deleted Pulsar token will no longer be able to connect to {product_name}. +Ensure you update your application with a new, valid Pulsar token before deletion. Applications using the deleted Pulsar token will no longer be able to connect to {product}. For more on JSON Web Tokens, see the https://jwt.io/introduction/[JWT documentation]. == Which token should I use? -The line between AstraDB and {product_name} tokens can be a little unclear. +The line between {astra_db} and {product} tokens can be a little unclear. Think of `pulsar-admin` and the DevOps API as complementary tools with different scopes. Use `pulsar-admin` for interacting with your Pulsar clusters. Topics, namespaces, tenants, and their metrics fall under this scope. -Use the DevOps API for org-wide Astra scope. Users, tenants, billing, and usage metrics fall under this scope. +Use the DevOps API for org-wide {astra_db} scope. Users, tenants, billing, and usage metrics fall under this scope. Some cases can use `pulsar-admin` **or** the DevOps API - we want the tools to be complementary, not restrictive, so do what works best for you! diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index 94a51db..b794dc5 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -1,9 +1,9 @@ -= Monitoring Streaming Tenants += Monitoring streaming tenants :navtitle: Monitoring overview -Because Astra Streaming is a software-as-a-service product, not all Apache Pulsar metrics (https://pulsar.apache.org/docs/reference-metrics/[Pulsar Metrics Reference]) are exposed for external integration purposes. At a high level, Astra Streaming only exposes metrics that are related to namespaces. Other metrics that are not directly namespace related are not exposed externally, such as the Bookkeeper ledger and journal metrics and Zookeeper metrics. +Because {product} is a software-as-a-service product, not all Apache Pulsar metrics (https://pulsar.apache.org/docs/reference-metrics/[Pulsar Metrics Reference]) are exposed for external integration purposes. At a high level, {product} only exposes metrics that are related to namespaces. Other metrics that are not directly namespace related are not exposed externally, such as the Bookkeeper ledger and journal metrics and Zookeeper metrics. -In the following sections, we'll explore each of the Astra Streaming metrics categories that are available for external integration, and recommended metrics for external integration. +In the following sections, we'll explore each of the {product} metrics categories that are available for external integration, and recommended metrics for external integration. == Pulsar raw metrics @@ -13,12 +13,13 @@ For a complete Pulsar metrics reference, see: * https://pulsar.apache.org/docs/reference-metrics/#topic-metrics[Topic metrics] -For a complete Astra Streaming metrics reference, see xref:monitoring/metrics.adoc[]. +For a complete {product} metrics reference, see xref:monitoring/metrics.adoc[]. -== {product_name} metrics +== {product} metrics === Namespace and topic metrics -Astra Streaming exposes both namespace and topic level metrics. + +{product} exposes both namespace and topic level metrics. Namespace metrics can always be inferred from corresponding topic metrics via metrics aggregation. The following table lists recommended namespace and/or topic metrics as a starting point. @@ -29,6 +30,7 @@ include::example$namespace-topic-metrics.csv[] |=== === Replication Metrics + When geo-replication is enabled for a particular namespace, a subset of namespace metrics is available specifically for geo-replication purposes. Below is the list of recommended geo-replication metrics as a starting point. [%header,format=csv,cols="2,1,1,3"] @@ -37,6 +39,7 @@ include::example$replication-metrics.csv[] |=== === Subscription metrics + The following table gives the list of recommended subscription metrics as a starting point. [%header,format=csv,cols="2,1,3"] @@ -46,7 +49,7 @@ include::example$subscription-metrics.csv[] === Function Metrics -The following table gives the list of recommended function metrics as a starting point. This is only relevant when Pulsar functions are deployed in Astra Streaming. +The following table gives the list of recommended function metrics as a starting point. This is only relevant when Pulsar functions are deployed in {product}. [%header,format=csv,cols="2,1,3"] |=== @@ -54,7 +57,8 @@ include::example$function-metrics.csv[] |=== === Source connector metrics -The following table gives the list of recommended source connector metrics as a starting point. This is only relevant when Pulsar source connectors are deployed in Astra Streaming. + +The following table gives the list of recommended source connector metrics as a starting point. This is only relevant when Pulsar source connectors are deployed in {product}. [%header,format=csv,cols="2,1,3"] |=== @@ -62,17 +66,18 @@ include::example$source-connector-metrics.csv[] |=== === Sink connector metrics -The following table gives the list of recommended source connector metrics as a starting point. This is only relevant when Pulsar sink connectors are deployed in Astra Streaming. + +The following table gives the list of recommended source connector metrics as a starting point. This is only relevant when Pulsar sink connectors are deployed in {product}. [%header,format=csv,cols="2,1,3"] |=== include::example$sink-connector-metrics.csv[] |=== -== Aggregate Astra Streaming Metrics +== Aggregate {product} Metrics -Each externally exposed raw Astra Streaming metric is reported at a very low level, at each individual server instance (the `exported_instance` label) and each topic partition (the `topic` label). The same raw metrics could come from multiple server instances. From a {product_name} user's perspective, the direct monitoring of raw metrics is not really useful. Raw metrics need to be aggregated first - for example, by averaging or summing the raw metrics over a period of time. +Each externally exposed raw {product} metric is reported at a very low level, at each individual server instance (the `exported_instance` label) and each topic partition (the `topic` label). The same raw metrics could come from multiple server instances. From a {product} user's perspective, the direct monitoring of raw metrics is not really useful. Raw metrics need to be aggregated first - for example, by averaging or summing the raw metrics over a period of time. -Below is an example of a raw metric for the Pulsar message backlog (pulsar_msg_backlog) scraped from an Astra Streaming cluster located in the GCP US Central region: +Below is an example of a raw metric for the Pulsar message backlog (pulsar_msg_backlog) scraped from an {product} cluster located in the GCP US Central region: .Show raw metric for Pulsar message backlog: [%collapsible] @@ -82,18 +87,23 @@ pulsar_msg_backlog{app="pulsar", cluster="pulsar-gcp-uscentral1", component="bro .... ==== -To make raw metrics like this useful for end users, we recommend the following guidelines when aggregating raw metrics: +{company} recommends the following guidelines for aggregating raw metrics: + +* Aggregate metrics at the parent topic level, at minimum, instead of at the partition level. +In Pulsar, end user applications only deal with messages at the parent topic level; however, internally, Pulsar handles message processing at the partition level. -. Aggregate metrics to at least the parent topic level, instead of at the partition level. In Pulsar, end user applications only deal with messages at the parent topic level (but internally, Pulsar is handling message processing at the partition level). -. Exclude reported metrics that are associated with Astra Streaming's system namespaces and topics. These namespaces and topics normally have a name starting with ++__++ (two underscores). For example, when Pulsar's Kafka protocol handler is enabled (via S4K integration), a system namespace ++__kafka++ is created with one system topic within called ++__transaction_producer_state++. -Do NOT aggregate metrics with the Astra Streaming *Pay As You Go* option, since one cluster may be shared among multiple organizations. For more, see xref:astream-limits.adoc[Cluster Limits]. +* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores (++__++). +For example, enabling Pulsar's Kafka protocol handler through S4K integration, creates a system namespace called ++__kafka++ that has one system topic called ++__transaction_producer_state++. + +* Do _not_ aggregate metrics if you are on the *Pay As You Go* {product} plan because one cluster can be shared among multiple organizations. +For more information, see xref:astream-limits.adoc[]. === PromQL query patterns Prometheus provides a powerful but easy-to-use query language called PromQL for selecting and aggregating time series data in real time. PromQL syntax is beyond this document's scope, but the https://prometheus.io/docs/prometheus/latest/querying/basics/[Prometheus documentation] is a great place to start. -In the rest of this section, we'll recommend some PromQL query patterns for aggregating raw Astra Streaming metrics. -These examples use one Astra Streaming raw metric, pulsar_msg_backlog, as an example for illustrative purposes. +In the rest of this section, we'll recommend some PromQL query patterns for aggregating raw {product} metrics. +These examples use one {product} raw metric, pulsar_msg_backlog, as an example for illustrative purposes. We aggregate messages at the parent topic level or above, and exclude system topics per our recommendations above. We filter out system messages with the pattern +{topic !~ ".*__.*"}+. This PromQL pattern filters out messages when their topic labels do not include +__+. @@ -134,9 +144,10 @@ topk by(topic) (10, sum(pulsar_msg_backlog{namespace=~"$namespace", topic !~ ".* ---- == Metrics to be alerted -Most of the exposed Astra Streaming metrics are for informational purposes only and in most cases the metrics values are just reflecting the application workload characteristics. For example, message rate or throughput are common examples of such metrics. -There are, however, several metrics that need special attention when we see an increasing number of their values. Among the exposed Astra Streaming metrics, these metrics are as follows: +Most of the exposed {product} metrics are for informational purposes only and in most cases the metrics values are just reflecting the application workload characteristics. For example, message rate or throughput are common examples of such metrics. + +There are, however, several metrics that need special attention when we see an increasing number of their values. Among the exposed {product} metrics, these metrics are as follows: .Metrics for alerting [%header,format=csv,cols="2,2,1,3"] diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index bd2726d..49aace8 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -1,18 +1,21 @@ = External Prometheus and Grafana Integration -Astra Streaming exposes some of the Pulsar metrics through Prometheus endpoints. -The Prometheus configuration information to scrape Astra Streaming metrics into an external Prometheus server is found in the Astra streaming UI. -This document will show you how to set up a Prometheus server in a Kubernetes cluster and configure it to scrape Astra streaming metrics. +{product} exposes some of the Pulsar metrics through Prometheus endpoints. +The Prometheus configuration information to scrape {product} metrics into an external Prometheus server is found in the {astra_ui}. + +This page explains how to set up a Prometheus server in a Kubernetes cluster and configure it to scrape {product} metrics. == Prerequisites -* A Kubernetes cluster. + +* An {astra_db} account +* A Kubernetes cluster == Configure and deploy Prometheus In this example, we use the https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack[Prometheus Community Kubernetes Helm Charts] to set up a Prometheus server and a Grafana server in a K8s cluster. -. Create a configuration yaml file (e.g. astra-msgenrich.yml) using the Astra Streaming Prometheus configuration from the UI. -For more on downloading the Prometheus configuration from the Astra Streaming UI, see https://docs.datastax.com/en/streaming/astra-streaming/operations/astream-scrape-metrics.html[Scrape metrics from Astra Streaming]. +. Create a configuration yaml file (e.g. astra-msgenrich.yml) using the {product} Prometheus configuration from the {astra_ui}. +For more on downloading the Prometheus configuration from the {astra_ui}, see https://docs.datastax.com/en/streaming/astra-streaming/operations/astream-scrape-metrics.html[Scrape metrics from {product}]. + [source,yaml] ---- @@ -80,14 +83,15 @@ kubectl rollout status daemonset \ == Verify the integration -The Astra Streaming metrics should now be integrated with an external Prometheus server. Double check by visiting the external Prometheus server UI. +The {product} metrics should now be integrated with an external Prometheus server. Double check by visiting the external Prometheus server UI. The status of the additional scrape job 'astra-pulsar-metrics-msgenrich' should be in the UP state. If not, there are issues in the previous configuration procedures. image::scrape-status.png[] === 401 Unauthorized Issue -One common issue that we see when integrating Astra Streaming metrics into an external Prometheus server is the additional scrape job returning a 401 Unauthorized error. This is most likely because the JWT token we used in the previous steps has expired. + +One common issue that we see when integrating {product} metrics into an external Prometheus server is the additional scrape job returning a 401 Unauthorized error. This is most likely because the JWT token we used in the previous steps has expired. To fix this issue, go to the {astra_ui} and create a new JWT token (preferably with no expiration date). Get the new token value and repeat the above procedure. For more, see xref:astream-token-gen.adoc[]. diff --git a/modules/operations/pages/monitoring/metrics.adoc b/modules/operations/pages/monitoring/metrics.adoc index 9408543..3c660ac 100644 --- a/modules/operations/pages/monitoring/metrics.adoc +++ b/modules/operations/pages/monitoring/metrics.adoc @@ -1,7 +1,8 @@ -= Grafana dashboards for Astra Streaming metrics += Grafana dashboards for {product} metrics -DataStax has built Grafana dashboards around core message processing for the exposed Astra Streaming metrics. -The dashboards can be found https://github.com/datastax/astra-streaming-examples/tree/master/grafana-dashboards[on GitHub]. +{company} offers Grafana dashboards for core message processing for exposed {product} metrics. + +You can get the dashboards from the https://github.com/datastax/astra-streaming-examples/tree/master/grafana-dashboards[{product} Examples repository]. Dashboards are available for scraping metrics at Pulsar's tenant, namespace, and topic levels: diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index 126b2b0..e1469e7 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -6,12 +6,12 @@ There are three ways to integrate external Prometheus data into New Relic: * https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#OpenMetrics[Prometheus OpenMetrics integration for Docker] * https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] -Only the third option is relevant to Astra Streaming. +Only the third option is relevant to {product}. -Typically, this option requires modifying the configuration of the Prometheus server that Astra Streaming relies on. -However, this isn't possible because {astra_stream} is a managed service. +Typically, this option requires modifying the configuration of the Prometheus server that {product} relies on. +However, this isn't possible because {product} is a managed service. -Instead, you can xref:monitoring/integration.adoc[install an extra Prometheus server] to act as a bridge to forward scraped Astra Streaming metrics to New Relic. +Instead, you can xref:monitoring/integration.adoc[install an extra Prometheus server] to act as a bridge to forward scraped {product} metrics to New Relic. .External Prometheus server for New Relic integration image::monitoring-map.png[Map,align="center"] @@ -24,17 +24,24 @@ image::monitoring-map.png[Map,align="center"] == Configure New Relic -. Log in the New Relic UI with your account. -. Click “Add Data” and select “Prometheus Remote Write Integration” under the “Open source monitoring” category. -. In the new window, supply the name of your local Prometheus server (e.g. prometheus-docker-desktop) and click “Generate URL”. This generates the URL endpoints required for configuring “remote_write integration” on your local Prometheus server. -. Configure and restart your local Prometheus server. In this example, since the local Prometheus server is installed in a local docker-desktop K8s cluster, the installation and configuration method is K8s-oriented. +. In your New Relic account, click *Add Data*. + +. Under *Open source monitoring*, select *Prometheus Remote Write Integration*. + +. Enter the name of your local Prometheus server, such as `prometheus-docker-desktop`, and then click *Generate URL*. +This generates the endpoint URLs required to configure a `remote_write integration` on your local Prometheus server. + +. Configure and restart your local Prometheus server. +For this example, the local Prometheus server is installed in a local Docker Desktop Kubernetes cluster; therefore, the installation and configuration method are related to Kubernetes. + .. Create a K8s secret that corresponds to the New Relic license key that you received when setting up the account: + [source,bash] ---- kubectl create secret generic nr-license-key --from-literal=value= ---- -.. Modify the Prometheus configuration in the kube-prometheus-stack Helm chart file (e.g. custom-values.yaml). + +.. Modify the Prometheus configuration in the kube-prometheus-stack Helm chart file, such as `custom-values.yaml`: + [source,yaml] ---- @@ -52,17 +59,22 @@ prometheus: authorization: credentials: key: value - name: nr-license-key + name: nr-license-key ---- -The top section of this configuration is for scraping Astra Streaming metrics (as described in xref:monitoring/integration.adoc[]) and the bottom section is for sending local Prometheus metrics to New Relic via remote_write. The remote_write URL and authorization settings are described above. ++ +The first lines of this configuration are for scraping {product} metrics (as described in xref:monitoring/integration.adoc[]). +The `remoteWrite` section is for sending local Prometheus metrics to New Relic through `remote_write`. + . Restart your local Prometheus server. -. Return to the New Relic UI. If everything is set up correctly, you should see Astra Streaming metrics in New Relic UI. Below is a screenshot from New Relic data browsing UI in which we can see the Pulsar message backlog metrics are shown on the UI and aggregated by different Pulsar namespaces. -.Pulsar message backlog metrics in New Relic +. In your New Relic account, confirm the configuration. +If everything is set up correctly, you should see {product} metrics in New Relic. +In the following screenshot, the New Relic *Data Browsing* page has Pulsar message backlog metrics. + image::pulsar-namespace-metrics.png[Metrics,align="center"] == See also -* For a list of exposed endpoints for Astra Streaming metrics, see xref:monitoring/metrics.adoc[]. -* To scrape Astra Streaming metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. -* To scrape Astra Streaming metrics into New Relic, see xref:monitoring/new-relic.adoc[]. \ No newline at end of file +* For a list of exposed endpoints for {product} metrics, see xref:monitoring/metrics.adoc[]. +* To scrape {product} metrics in Kubernetes with an external Prometheus server, see xref:monitoring/integration.adoc[]. +* To scrape {product} metrics into New Relic, see xref:monitoring/new-relic.adoc[]. \ No newline at end of file diff --git a/modules/operations/pages/monitoring/stream-audit-logs.adoc b/modules/operations/pages/monitoring/stream-audit-logs.adoc index c0fa85e..ebf056a 100644 --- a/modules/operations/pages/monitoring/stream-audit-logs.adoc +++ b/modules/operations/pages/monitoring/stream-audit-logs.adoc @@ -1,19 +1,19 @@ -= Stream {astra_db} audit logs += Stream {astra_db} audit logs -Stream your xref:astra-db-serverless:administration:view-account-audit-log.adoc[{astra_db} audit logs] through {product_name} to an external system. +Stream your xref:astra-db-serverless:administration:view-account-audit-log.adoc[{astra_db} audit logs] through {product} to an external system. To enable audit log streaming, you must do one of the following: - * Provide the **Full Name** of your {product_name} topic and the streaming tenant's `client.conf` file to {support_url}[{company} Support] or your account representative. - * POST your configuration to the xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/configureTelemetry[Astra DevOps API telemetry endpoint]. + * Provide the **Full Name** of your {product} topic and the streaming tenant's `client.conf` file to {support_url}[{company} Support] or your account representative. + * POST your configuration to the xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/configureTelemetry[{astra_db} DevOps API telemetry endpoint]. -== Create an {product_name} topic for audit logs +== Create an {product} topic for audit logs Audit log streaming requires a streaming tenant in the AWS `us-east-2` region. -You can create a new tenant with the xref:astra-streaming:getting-started:index.adoc[{product_name} quickstart] or use an existing {product_name} tenant. +You can create a new tenant with the xref:astra-streaming:getting-started:index.adoc[{product} quickstart] or use an existing {product} tenant. . In the {link-astra-portal}, go to **Streaming**. -. Select an existing tenant or xref:astra-streaming:getting-started:index.adoc#your-first-streaming-tenant[Create a Streaming Tenant] in AWS `us-east-2`. +. Select an existing tenant or xref:astra-streaming:getting-started:index.adoc#your-first-streaming-tenant[Create a streaming tenant] in AWS `us-east-2`. . Add a xref:astra-streaming:getting-started:index.adoc#add-a-namespace-to-hold-topics[namespace] and xref:astra-streaming:getting-started:index.adoc#a-topic-to-organize-messages[topic] to the tenant. . On the *Namespace and Topics* page, click the new topic, and then copy the topic's **Full Name**, such as `persistent://aws-us-east-2-mk/*NAMESPACE_NAME*/*TOPIC_NAME*`. . If necessary, create additional audit log topics, and then record the **Full Name** for each topic. @@ -23,12 +23,12 @@ You can use topics to organize audit logs by event type or other criteria. . To finalize the configuration, do one of the following: + * Send your topic's full name and the `client.conf` file to {support_url}[{company} Support] or your account representative, and then {company} will complete the setup. -* <> +* <> [#use-the-devops-api] == Configure audit log streaming with the DevOps API -You can use the xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/configureTelemetry[Astra DevOps API telemetry endpoint] to configure audit log streaming instead of providing the configuration details to {company} Support. +You can use the xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/configureTelemetry[{astra_db} DevOps API telemetry endpoint] to configure audit log streaming instead of providing the configuration details to {company} Support. . In the {link-astra-portal}, create an application token with the **Organization Administrator** role, if you don't already have one. diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index 34de10b..a359d3e 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -1,38 +1,35 @@ -= Astra Streaming Enrollment FAQ += {product} enrollment FAQ :navtitle: Enrollment FAQ -:description: These are the most common questions we receive about getting started with Astra Streaming. +:description: These are the most common questions we receive about getting started with {product}. :page-tag: astra-streaming,onboarding,Orientation -When considering Astra Streaming for your production workloads, you might have questions about what options are available for connecting and consuming your serverless clusters. -This page answers the most common questions we receive about enrollment, and we hope that this FAQ will help you navigate the enrollment process smoothly and successfully. +When considering {product} for your production workloads, you might have questions about what options are available for connecting and consuming your serverless clusters. +This page answers the most common questions we receive about enrollment. -If you have additional questions or concerns that are not addressed here, please contact us! -The Streaming team is available to assist you in any way we can. +== {product} Environments -== Astra Streaming Environments - -.What are the different ways I can use Astra Streaming? +.What are the different ways I can use {product}? [%collapsible] ==== -Astra Streaming offers two types of serverless environments: *Pay As You Go* and *dedicated*. -If your Astra organization is on the *Free* plan, you use the *Pay As You Go* streaming option. +{product} offers three subscription plans, as well as pricing for *Dedicated Clusters* plans. +For more information, see https://www.datastax.com/products/astra-streaming/pricing[{product} Pricing]. + +If your {astra_db} organization is on the *Free* plan, you use the *Pay As You Go* streaming plan. When you provide a payment method, you only pay for resources used when messages are produced and consumed. -Therefore, you _pay as you go_. -In *Pay As You Go* streaming environments, your data and interaction with Pulsar are secured over a (public) internet connection, and there are limitations to how many resources you can create. -* For more on *Pay As You Go* streaming pricing, see https://www.datastax.com/products/astra-streaming/pricing[Astra Streaming Pricing]. -* For more on *Pay As You Go* streaming limits, see xref:astream-limits.adoc[Astra Streaming Limits]. +In *Pay As You Go* streaming environments, your data and interaction with Pulsar are secured over a public internet connection, and there are limitations to how many resources you can create. +For more information, see xref:astream-limits.adoc[]. -A *dedicated environment* is your own private Pulsar cluster with the additional benefits of Astra Streaming. -Sign in to Astra just as you would with a *Pay As You Go* streaming account. -When you create new tenants, additional options are available for deploying to your private cluster(s). -There are less limits in *dedicated environments* than in *Pay As You Go* - it's your cluster, after all. -Finally, billing for a dedicated cluster is unique to each customer. +For *Dedicated Clusters* plans, you have your own private Pulsar cluster with the additional benefits of {product}. +You sign in to {astra_db} as you would on any other plan. +However, when you create new tenants, you have additional options to deploy to your private clusters. +*Dedicated Clusters* plans have fewer limits than *Pay As You Go* plans, and billing for dedicated clusters is unique to each customer. -In a *Pay As You Go* environment, you can create tenants in any of the xref:astream-regions.adoc[supported regions], while *dedicated environments* are open to almost any public cloud region. +Finally, with *Pay As You Go* plans, you can create tenants in any of the xref:astream-regions.adoc[supported regions]. +*Dedicated Clusters* are open to almost any public cloud region. ==== -.Why does DataStax call it "serverless"? +.Why does {company} call it "Serverless"? [%collapsible] ==== Running a production grade Pulsar cluster that can handle at-scale workloads is not a trivial task. It requires many (virtual) machines to be configured in a very particular way. @@ -42,59 +39,53 @@ In traditional cloud environments, you would pay hourly for every machine whethe Serverless removes those operational burdens, and you pay only for the resources you actually use. ==== -== Cluster Connections +== Cluster connections -.Can a dedicated cluster have a private connection? -[%collapsible] -==== -All connections in Astra Streaming are guarded by AuthN, AuthZ, and secure (TLS) communications. -With a dedicated cluster you have the option to connect over the (public) internet or establish a private connection. To learn more about private connections, refer to your cloud provider's private link documentation: +On a *Dedicated Clusters* plan, all connections in {product} are guarded by AuthN, AuthZ, and secure (TLS) communications. +You can connect over the public internet or establish a private connection. +To learn more about private connections, refer to your cloud provider's private link documentation: * https://aws.amazon.com/privatelink/[AWS PrivateLink] * https://learn.microsoft.com/en-us/azure/private-link/private-link-overview[Azure Private Link] * https://cloud.google.com/vpc/docs/private-service-connect[GCP Private Service Connect] -==== -.Can a Pay As You Go streaming account have a private connection to the Pulsar cluster? -[%collapsible] -==== -*Pay As You Go* streaming accounts use a shared Pulsar cluster. Without dedicated cloud resources, a private link typically can't be established. mailto:streaming@datastax.com[Email the team] if you would like to explore this option. -==== +On a *Pay As You Go* plan, you use a shared Pulsar cluster. +Connections are secured over the public internet, but private links typically can't be established. -== Secure Sign-on, Roles, and Permissions +== User access -.Can I use my own single sign-on with Astra Streaming? +.Can I use my own single sign-on with {product}? [%collapsible] ==== -As a *Pay As You Go* customer, the Astra platform offers single sign-on through your GitHub account and your Google account. -Astra also offers custom SSO integration as a premium option. mailto:streaming@datastax.com[Email the team] for more information. - -To integrate a custom SSO provider, you will need a non-default Astra Organization. -Refer to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/configuring-sso.html[Astra Serverless SSO documentation] or mailto:streaming@datastax.com[email the team] for more information. +*Pay As You Go* and *Enterprise* organization can use SSO to sign in to the {astra_ui} +For more information, see https://docs.datastax.com/en/astra-serverless/docs/manage/org/configuring-sso.html[{astra_db} Serverless SSO documentation]. ==== -.How do Astra roles and permissions map to Pulsar roles and permissions? +.How do {astra_db} roles and permissions map to Pulsar roles and permissions? [%collapsible] ==== Pulsar has the concept of https://pulsar.apache.org/docs/security-authorization/[clients with role tokens]. Authentication in Pulsar is the process of verifying a provided (JWT) token, and authorization is the process of determining if the role claimed in that token is allowed to complete the requested action. -Astra Streaming uses the DataStax version of Apache Pulsar (called xref:luna-streaming::index.adoc[Luna Streaming]). -The https://github.com/datastax/pulsar[Luna project] is an open fork of the Pulsar project that maintains feature parity with OSS Pulsar. Astra Streaming, as a managed service, abstracts some features/options of Pulsar to ensure continuous, reliable service. +{product} uses the {company} version of Apache Pulsar (called xref:luna-streaming::index.adoc[Luna Streaming]). +The https://github.com/datastax/pulsar[Luna project] is an open fork of the Pulsar project that maintains feature parity with OSS Pulsar. {product}, as a managed service, abstracts some features/options of Pulsar to ensure continuous, reliable service. -Your *Pay As You Go* environment is an Astra Organization (Org) that has a tenant (or multiple tenants) on a shared Pulsar cluster. -Each of your tenants is secured by Pulsar AuthN & AuthZ models *and* the Astra Org AuthN and AuthZ. -The shared cluster is created and administered by Astra Streaming Admins. -Each tenant is assigned a custom role (and permission) limited to only that tenant. +On a *Pay As You Go* plan, your {astra_db} organization has one or more tenants on a shared Pulsar cluster. +Each of your tenants is secured by Pulsar AuthN & AuthZ models as well as your {astra_db} organization's AuthN and AuthZ. +The shared cluster is created and administered by {product} administrators. +Each tenant is assigned a custom role and permissions limited to that tenant only. All tokens created within a tenant are assigned roles similar to the assigning tenant. ==== -.What is the difference between the Astra token and the Astra Streaming token? Are they interchangeable? +.What is the difference between the {astra_db} application token and the Pulsar token? Are they interchangeable? [%collapsible] ==== -The Astra platform offers different layers of authentication based on the desired action. -In general, actions related to your Astra Org (members, org billing, usage metrics, etc.) use your Astra Token, and actions specific to a Pulsar tenant (message namespaces, topics, message metrics, etc.) use a Pulsar JWT token. +{astra_db} offers different layers of authentication based on the desired action. + +For actions related to your {astra_db} organization or the resources within, including users, roles, databases, and so on, you use an {astra_db} application token with the appropriate role. -If you would like to get a little deeper into exactly which token covers what action, see the following documentation: +For actions specific to a Pulsar tenant, such as message namespaces, topics, and message metrics, you use a Pulsar JWT token. + +For more information, see the following: * xref:astra-streaming:developing:astra-cli.adoc[] * xref:astra-streaming:developing:using-curl.adoc[] @@ -103,24 +94,28 @@ If you would like to get a little deeper into exactly which token covers what ac == Data Migration and Geo-replication -.What are the differences between dedicated and Pay As You Go geo-replication? +.What are the differences in geo-replication for Pay As You Go and Dedicated Clusters plan? [%collapsible] ==== -Geo-replication is available to both *Pay As You Go* and *dedicated serverless environments*. Both can replicate to other clusters, but there are some differences. +Geo-replication is available to *Pay As You Go* and *Dedicated Clusters* {product} plans. +Both can replicate to other clusters, but there are some differences. + +For *Pay As You Go* {product} plans, traffic between clusters is secured over the public internet, while dedicated clusters have the option for private communication. -In *Pay As You Go* streaming, traffic between clusters is secured over the (public) internet, while dedicated clusters have the option for private communication. *Pay As You Go* environments can replicate between any xref:astream-regions.adoc[supported region] of the same cloud provider. With dedicated clusters, you can use almost any region supported by your cloud provider, as well as across cloud providers. -mailto:streaming@datastax.com[Email the team] for more information. +*Pay As You Go* plans can replicate between any xref:astream-regions.adoc[supported region] of the same cloud provider. + +With *Dedicated Clusters*, you can use almost any region supported by your cloud provider, as well as across cloud providers. For more on geo-replication, see xref:astream-georeplication.adoc[]. ==== -.Can I migrate data from my existing Pulsar cluster to Astra Streaming? +.Can I migrate data from my existing Pulsar cluster to {product}? [%collapsible] ==== -Unless you are starting a project from scratch, you likely have message data that needs to be brought over to your Astra Streaming tenants. -The Streaming Team has quite a bit of experience with this and can help you find the right way to migrate. mailto:streaming@datastax.com[Email the team] for more information. +Unless you are starting a project from scratch, you likely have message data that needs to be brought over to your {product} tenants. +For migration assistance, contact {support_url}[{company} Support]. -Every tenant in Astra Streaming comes with custom ports for Kafka and RabbitMQ workloads. +Every tenant in {product} comes with custom ports for Kafka and RabbitMQ workloads. {company} also offers a fully-compatible JMS implementation for your Java workloads. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] ==== @@ -139,39 +134,35 @@ To support the hierarchy of development environments pattern, we recommend using This gives you the greatest flexibility to balance a separation of roles with consistent service configuration. All tokens created within a Tenant are limited to that Tenant. -For example, start with a tenant named “Dev” that development teams have access to (and create tokens from), then create other tenants named “Staging” and “Production”. +For example, start with a tenant named `Dev`` that development teams have access to (and create tokens from), then create other tenants named `Staging` and `Production`. Each Tenant has progressively less permissions to create tokens, but maintains parity between the three running environments. [discrete] ==== By Namespace -Alternatively, you might choose to separate development environments by namespace within your Astra Streaming tenant. -While this doesn’t offer as much flexibility as separation by tenant, it does offer a much simpler model to manage. +Alternatively, you might choose to separate development environments by namespace within your {product} tenant. +While this doesn't offer as much flexibility as separation by tenant, it does offer a much simpler model to manage. Also, note that in this scheme you cannot limit access by namespace. All tokens would have access to all namespaces. ==== -.Can we develop applications on open source Pulsar and then move to Astra Streaming? +.Can we develop applications on open source Pulsar and then move to {product}? [%collapsible] ==== -As mentioned previously, Astra Streaming is actively maintained to keep parity with the official https://pulsar.apache.org[Apache Pulsar project]. +As mentioned previously, {product} is actively maintained to keep parity with the official https://pulsar.apache.org[Apache Pulsar project]. The notable differences arise from accessibility and security. Naturally, you have less control in a managed, serverless cluster than you do in a cluster running in your own environment. -Beyond those differences, the effort to develop locally and then move to Astra Streaming should not be significant, but it is recommended to develop directly in Astra Streaming. -If you are trying to save costs, use the free tier of Astra Streaming and then “switch” when you are ready to stage your production services. +Beyond those differences, the effort to develop locally and then move to {product} should not be significant, but it is recommended to develop directly in {product}. +If you are trying to save costs, use the free tier of {product} and then switch when you are ready to stage your production services. ==== -.Can I use Astra Streaming with my existing Kafka or RabbitMQ applications? +.Can I use {product} with my existing Kafka or RabbitMQ applications? [%collapsible] ==== -Yes, Astra Streaming offers a fully compatible Kafka and RabbitMQ implementation. This means you can use your existing Kafka or RabbitMQ applications with Astra Streaming. You can also use the Astra Streaming Kafka or RabbitMQ implementation with your existing Pulsar applications. Astra Streaming comes with custom ports for Kafka and RabbitMQ workloads. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. +Yes, {product} offers a fully compatible Kafka and RabbitMQ implementation. This means you can use your existing Kafka or RabbitMQ applications with {product}. You can also use the {product} Kafka or RabbitMQ implementation with your existing Pulsar applications. {product} comes with custom ports for Kafka and RabbitMQ workloads. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. ==== -.Can I use Astra Streaming with my existing Java applications? +.Can I use {product} with my existing Java applications? [%collapsible] ==== -Yes, Astra Streaming offers a fully compatible JMS implementation. This means you can use your existing JMS applications with Astra Streaming. You can also use the Astra Streaming JMS implementation with your existing Pulsar applications. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. -==== - -== Additional Questions - -If you have additional questions about the Astra Platform, Astra Streaming, or any other DataStax product, please mailto:streaming@datastax.com[email the team]. We're happy to share best practices and ideas to help you get the most out of your Astra Streaming environment. \ No newline at end of file +Yes, {product} offers a fully compatible JMS implementation. This means you can use your existing JMS applications with {product}. You can also use the {product} JMS implementation with your existing Pulsar applications. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. +==== \ No newline at end of file diff --git a/modules/operations/pages/private-connectivity.adoc b/modules/operations/pages/private-connectivity.adoc index e92b50e..ced1fef 100644 --- a/modules/operations/pages/private-connectivity.adoc +++ b/modules/operations/pages/private-connectivity.adoc @@ -1,17 +1,17 @@ = Private connectivity -To better protect your streaming connections, connect {product_name} to a private link service for <> connectivity, or to a private endpoint for <> connectivity. +To better protect your streaming connections, connect {product} to a private link service for <> connectivity, or to a private endpoint for <> connectivity. -Private connections are only available within the same cloud provider and region as your {product_name} cluster. +Private connections are only available within the same cloud provider and region as your {product} cluster. To open a private link service or private endpoint, open {support_url}[a support ticket] and include the <> required for your cloud provider. == Inbound traffic -{product_name} supports inbound traffic (i.e. Your private endpoint → {product_name}). -The first inbound traffic pattern describes Pulsar, Kafka, and RabbitMQ messaging traffic, as well as Prometheus metrics traffic, flowing from a user's private endpoint to {product_name}. +{product} supports inbound traffic (i.e. Your private endpoint → {product}). +The first inbound traffic pattern describes Pulsar, Kafka, and RabbitMQ messaging traffic, as well as Prometheus metrics traffic, flowing from a user's private endpoint to {product}. -You create a connection to our private link service, and we route traffic to your {product_name} cluster. +You create a connection to our private link service, and we route traffic to your {product} cluster. If you have multiple tenants, they can have different VPCs. The different VPCs will have the same private FQDN with differing VNETs. The traffic on different private end point connections is isolated until it reaches our load balancer. @@ -39,9 +39,9 @@ The private link service pattern is the same across cloud providers, but the hos [#outbound] == Outbound traffic -{product_name} also supports private outbound traffic (from {product_name} to your private endpoint) on a case-by-case basis. +{product} also supports private outbound traffic (from {product} to your private endpoint) on a case-by-case basis. -The outbound traffic pattern creates a private endpoint in {product_name} that connects to your private link service. We open a port on the tenant's firewall (firewalls are per tenant) so connectors and functions (running in a dedicated namespace on our cluster) can connect to your private network. +The outbound traffic pattern creates a private endpoint in {product} that connects to your private link service. We open a port on the tenant's firewall (firewalls are per tenant) so connectors and functions (running in a dedicated namespace on our cluster) can connect to your private network. To open an outbound private endpoint, open {support_url}[a support ticket] and include the <> required for your cloud provider. diff --git a/modules/operations/partials/code-examples-text.adoc b/modules/operations/partials/code-examples-text.adoc index 6de3ca3..7a1b17d 100644 --- a/modules/operations/partials/code-examples-text.adoc +++ b/modules/operations/partials/code-examples-text.adoc @@ -1,7 +1,7 @@ -{product_name} is powered by http://pulsar.apache.org/[Apache Pulsar]. +{product} is powered by http://pulsar.apache.org/[Apache Pulsar]. To connect to your service, use the open-source client APIs provided by the Apache Pulsar project. -{product_name} is running Pulsar version {pulsar_version}. +{product} is running Pulsar version {pulsar_version}. You should use this API version or higher. Choose your preferred language: diff --git a/modules/ragstack/pages/index.adoc b/modules/ragstack/pages/index.adoc index 880a552..a68192b 100644 --- a/modules/ragstack/pages/index.adoc +++ b/modules/ragstack/pages/index.adoc @@ -10,7 +10,7 @@ DataStax has been busy helping our customers through the pains of RAG implementa * {ragstack} leverages the https://python.langchain.com/docs/get_started/introduction[LangChain] ecosystem and is fully compatible with https://docs.smith.langchain.com/[LangSmith] (for monitoring) and https://github.com/langchain-ai/langserve[LangServe] (for hosting). -* The https://docs.datastax.com/en/astra-serverless/docs/[AstraDB] vector database provides the best performance and scalability for RAG applications, in addition to being particularly well-suited to RAG workloads like question answering, semantic search, and semantic caching. +* The https://docs.datastax.com/en/astra-serverless/docs/[{astra_db}] vector database provides the best performance and scalability for RAG applications, in addition to being particularly well-suited to RAG workloads like question answering, semantic search, and semantic caching. * The https://langstream.ai[LangStream] package combines the best of event-based architectures with the latest Gen AI technologies. Develop robust Gen AI pipelines with just YAML files. Leverage Kafka for data flow, and Kubernetes for deployment and scaling. @@ -19,7 +19,7 @@ DataStax has been busy helping our customers through the pains of RAG implementa {ragstack} offers solutions for challenges facing developers building RAG applications. * Productivity - abstract over the RAG pattern's complexities to keep developers focused on business logic. -* Performance, scalability, cost - cache a large percentage of AI calls and leverage the inherent parallelism built into AstraDB +* Performance, scalability, cost - cache a large percentage of AI calls and leverage the inherent parallelism built into {astra_db} * Event-driven architectures - fresher data faster * Advanced RAG techniques - use advanced patterns like Chain of Thought and Multi-Query RAG * Future-proof - as new techniques are discovered, {ragstack} offers enterprise users an upgrade path to always be on the cutting edge of AI. diff --git a/modules/ragstack/pages/quickstart.adoc b/modules/ragstack/pages/quickstart.adoc index eff89f5..098ca01 100644 --- a/modules/ragstack/pages/quickstart.adoc +++ b/modules/ragstack/pages/quickstart.adoc @@ -2,12 +2,11 @@ The {ragstack} project combines the intelligence of large language models with the agility of stream processing to create powerful Generative AI applications. -This guide will help you build and deploy a {ragstack} application to Astra Streaming. +This guide will help you build and deploy a {ragstack} application to {product}. [IMPORTANT] ==== -*Private preview feature* -{ragstack} is available only in a *private preview*. This feature is not intended for production use, has not been certified for production workloads, and might contain bugs and other functional issues. There is no guarantee that a preview feature will ever become generally available.If you are interested in participating in the private preview, contact us at mailto:Astra-PM@datastax.com[Astra-PM@datastax.com,RAGSstack private preview,I am interested in the RAGStack private preview.]. We will contact you with more information. +{ragstack} is available only to qualified participants. This feature is not intended for production use, has not been certified for production workloads, and might contain bugs and other functional issues. There is no guarantee that a preview feature will ever become generally available. ==== == Install {ragstack} CLI @@ -20,9 +19,9 @@ brew install datastax/ragstack/ragstack For more on the {ragstack} CLI, see https://docs.langstream.ai/installation/langstream-cli[{ragstack} CLI]. -== Enable {ragstack} in Astra +== Enable {ragstack} in {astra_db} -xref:getting-started:index.adoc[Create an Astra Streaming tenant] in the GCP `us-east-1` region. +xref:getting-started:index.adoc[Create an {product} tenant] in the GCP `us-east-1` region. Your tenant will be created with a default namespace, which is a logical grouping of topics. @@ -65,7 +64,7 @@ token: AstraCS:... Your tenant is now connected to the {ragstack} CLI. -You can also establish a connection by including the configuration values from the Astra Streaming Connect tab in your {ragstack} application's instance.yaml file. +You can also establish a connection by including the configuration values from the {product} Connect tab in your {ragstack} application's instance.yaml file. See <> for an example. == Build a {ragstack} Application @@ -94,13 +93,17 @@ touch pipeline.yaml gateways.yaml configuration.yaml ---- The instance.yaml and secrets.yaml files cannot be in the "application" directory, because the application directory is passed as a zip at runtime. -Next, you will populate the YAML files to connect your application to your Astra Streaming tenant. +Next, you will populate the YAML files to connect your application to your {product} tenant. == Populate YAML files + [#instance] Instance.yaml declares the application's processing infrastructure, including where streaming and compute take place. + The secrets for tokens and passwords are stored in the secrets.yaml file, which you'll populate in the next step. -An instance.yaml file can be downloaded from the Connect tab of your Astra Streaming tenant. Paste it into your instance.yaml file to connect your application to your tenant. +An instance.yaml file can be downloaded from the Connect tab of your {product} tenant. Paste it into your instance.yaml file to connect your application to your tenant. + +.instance.yaml [source,yaml] ---- instance: @@ -121,21 +124,26 @@ instance: Secrets.yaml contains auth information for connecting to other services. Secret values can be modified directly in secrets.yaml, or you can pass your secrets as environment variables or in a .env file. The secrets.yaml resolves these environment variables. +..env [source,bash] ---- -export ASTRA_CLIENT_ID=... -export ASTRA_SECRET=... -export ASTRA_DATABASE=... -export ASTRA_TOKEN=... +export ASTRA_CLIENT_ID= +export ASTRA_SECRET= +export ASTRA_DATABASE= +export ASTRA_TOKEN= ---- When you go to production, you should create a dedicated secrets.yaml file for each environment. -The Astra client-id, token, and secret are found in the Astra UI. -The values for the Kafka bootstrap server are found in your Astra Streaming tenant or in the Starlight for Kafka ssl.properties file. + +You can get the `clientId`, `clientSecret`, and `token` by creating an {astra_db} application token in the {astra_ui}. +The values for the Kafka bootstrap server are found in your {product} tenant or in the Starlight for Kafka ssl.properties file. The Azure access key and URL are found in your Azure OpenAI deployment. -A secrets.yaml file can be downloaded from the Connect tab of your Astra Streaming tenant. + +A secrets.yaml file can be downloaded from the *Connect* tab of your {product} tenant. Paste it into your secrets.yaml file to authorize your application to your tenant. For more on finding values for secrets, see https://docs.langstream.ai/building-applications/secrets.html[Secrets]. + +.secrets.yaml [source,yaml] ---- secrets: @@ -297,7 +305,7 @@ gateways: ---- Configuration.yaml contains additional configuration and resources for your application. -A configuration.yaml file can be downloaded from the Connect tab of your Astra Streaming tenant (under AstraDB). +A configuration.yaml file can be downloaded from the Connect tab of your {product} tenant (under {astra_db}). For more on configuration, see https://docs.langstream.ai/building-applications/configuration[Configuration]. [source,yaml] @@ -314,7 +322,7 @@ configuration: Remember to save all your yaml files. -== Deploy the {ragstack} application on Astra +== Deploy the {ragstack} application on {astra_db} . To deploy the application, run the following commands from the root of your application folder. The first command deploys the application from the YAML files you created above, and the second command gets the status of the application. @@ -359,7 +367,7 @@ To get logs, run `ragstack apps logs **APP_NAME**`. == {ragstack} CLI connection values -If you encounter an error or problem, make sure the values in your CLI profile match the values in your Astra Streaming tenant. +If you encounter an error or problem, make sure the values in your CLI profile match the values in your {product} tenant. If you're unsure of the profile name, run `ragstack profiles list`, and then run `ragstack profiles get **PROFILE_NAME** -o=json`. For example: @@ -410,7 +418,7 @@ The default profile values are as follows: ---- Issue a curl call to your {ragstack} tenant to find the connection values for your tenant. -The `X-DataStax-Current-Org` value is the client-id associated with the Astra token, and can be found in the Astra UI. +The `X-DataStax-Current-Org` value is the client-id associated with the {astra_db} token, and can be found in the {astra_ui}. [source,curl] ---- @@ -436,9 +444,9 @@ curl --location --request POST 'https://pulsar-gcp-useast1.api.streaming.datasta Ensure the values returned from the curl call match the values in your {ragstack} CLI profile. -== Check connection to Astra +== Check connection to {astra_db} -In the {ragstack} CLI, run the following command to open a gateway connection to your Astra Streaming tenant. +In the {ragstack} CLI, run the following command to open a gateway connection to your {product} tenant. This command will connect to your tenant and consume from the output-topic and produce to the input-topic. [source,shell] @@ -446,10 +454,10 @@ This command will connect to your tenant and consume from the output-topic and p ragstack gateway chat sample-app -cg consume-output -pg produce-input -p sessionId=$(uuidgen) ---- -In Astra Streaming, confirm that your application is connected to your tenant. +In {product}, confirm that your application is connected to your tenant. Select the Websocket tab of your {ragstack}-enabled tenant, and choose to consume from output-topic and to produce to input-topic. If the Websocket tab is not visible, you may need to refresh the page or try opening it in Incognito mode. -Send a message to your application, and confirm that it is received by the Astra websocket: +Send a message to your application, and confirm that it is received by the {astra_db} websocket: [source,console] ---- From 5bd19eade1a249387b9f6c0b3b888dfb33270f22 Mon Sep 17 00:00:00 2001 From: April M Date: Tue, 15 Oct 2024 07:20:03 -0700 Subject: [PATCH 06/18] remove ragstack module --- .../pages/astream-subscriptions-shared.adoc | 45 +- modules/ROOT/pages/astream-subscriptions.adoc | 15 +- .../ROOT/partials/subscription-prereq.adoc | 7 +- modules/ragstack/images/app-deployed.png | Bin 52032 -> 0 bytes modules/ragstack/images/app-map.png | Bin 177856 -> 0 bytes modules/ragstack/images/enable.png | Bin 27399 -> 0 bytes modules/ragstack/images/websocket-chat.png | Bin 283208 -> 0 bytes modules/ragstack/nav.adoc | 3 - modules/ragstack/pages/index.adoc | 29 - modules/ragstack/pages/quickstart.adoc | 497 ------------------ 10 files changed, 39 insertions(+), 557 deletions(-) delete mode 100644 modules/ragstack/images/app-deployed.png delete mode 100644 modules/ragstack/images/app-map.png delete mode 100644 modules/ragstack/images/enable.png delete mode 100644 modules/ragstack/images/websocket-chat.png delete mode 100644 modules/ragstack/nav.adoc delete mode 100644 modules/ragstack/pages/index.adoc delete mode 100644 modules/ragstack/pages/quickstart.adoc diff --git a/modules/ROOT/pages/astream-subscriptions-shared.adoc b/modules/ROOT/pages/astream-subscriptions-shared.adoc index ec009e9..2e32535 100644 --- a/modules/ROOT/pages/astream-subscriptions-shared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-shared.adoc @@ -2,21 +2,22 @@ :navtitle: Shared :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. +_Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -A *shared* subscription allows *multiple consumers* to consume messages from a *single topic* in round-robin fashion. +A _shared subscription_ allows multiple consumers to consume messages from a single topic in round-robin fashion. +More consumers in a shared subscription can increase your Pulsar deployment's rate of message consumption. +However, there is a risk of losing message ordering guarantees and acknowledgement schemes. -More consumers in a shared subscription can increase your Pulsar deployment's rate of message consumption, but at the cost of losing message ordering guarantees and acknowledgement schemes. - -This document explains how to use Pulsar's shared subscription model to manage your topic consumption. +This page explains how you can use Pulsar's shared subscription model to manage your topic consumption. include::ROOT:partial$subscription-prereq.adoc[] [#example] == Shared subscription example -To try out a Pulsar shared subscription, add `.subscriptionType(SubscriptionType.Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`. +To try out a Pulsar shared subscription, add `.subscriptionType(SubscriptionType.Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`: +.SimplePulsarConsumer.java [source,java] ---- pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) @@ -31,19 +32,22 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- -. Open `pulsar-subscription-example` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. -The confirmation message and a cursor appear to indicate the consumer is ready. +. In the `pulsar-subscription-example` project, run `SimplePulsarConsumer.java` to begin consuming messages. ++ +The confirmation message and a cursor appear to indicate the consumer is ready: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.Configuration - Configuration has been loaded successfully ... [pulsar-client-io-1-1] INFO org.apache.pulsar.client.impl.ConsumerImpl - [persistent:////in][SimplePulsarConsumer] Subscribed to topic on -- consumer: 0 ---- -. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages. +. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 59819331 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 70129519 sent @@ -51,9 +55,10 @@ The confirmation message and a cursor appear to indicate the consumer is ready. [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 48206643 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 51277375 sent ---- - -. The consumer begins receiving messages. + +In the `SimplePulsarConsumer` terminal, the consumer begins receiving messages: ++ +.Result [source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":59819331,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -61,22 +66,26 @@ The confirmation message and a cursor appear to indicate the consumer is ready. [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":51277375,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. Open a new terminal window and run `SimplePulsarConsumer.java`. The new consumer subscribes to the topic and consumes a message. +. In a new terminal window, run another instance of `SimplePulsarConsumer.java`. ++ +The new consumer subscribes to the topic and consumes messages: + +.Result [source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":70129519,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":48206643,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. Open as many terminals as you'd like and run `SimplePulsarConsumer.java`. All the consumers subscribe to the topic and consume messages in a round-robin fashion. - +Because this test uses shared subscriptions, you can attach multiple consumers to the topic. If you run this test with xref:astream-subscriptions-exclusive.adoc[exclusive subscriptions], you can't attach more than once subscriber to the exclusive topic. -Since this test uses shared subscriptions, you can attach multiple consumers to the topic. + +To continue testing the shared subscription configuration, you can continue running new instances of `SimplePulsarConsumer.java` in new temrinal windows. +All the consumers subscribe to the topic and consume messages in a round-robin fashion. == Shared subscription video -Follow along with this video from our *Five Minutes About Pulsar* series to see shared subscriptions in action. +Follow along with this video from our *Five Minutes About Pulsar* series to see shared subscriptions in action: video::mmukXqGsauA[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] diff --git a/modules/ROOT/pages/astream-subscriptions.adoc b/modules/ROOT/pages/astream-subscriptions.adoc index 9b1c180..8e2fbaa 100644 --- a/modules/ROOT/pages/astream-subscriptions.adoc +++ b/modules/ROOT/pages/astream-subscriptions.adoc @@ -2,18 +2,19 @@ :navtitle: Pulsar subscriptions overview :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. +_Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. == Subscription metadata -Subscriptions are managed in the broker as a collection of metadata about a topic and its subscribed consumers. This metadata includes: +Subscriptions are managed in the broker as a collection of metadata about a topic and its subscribed consumers. +This metadata includes: -* Topic name - which topic the consumer wants data from -* Subscription name - a string representing a qualified name for the subscription -* Subscription type - which type of subscription is being used -* Subscription cursor - a representation of the consumer's current place in the subscribed topic log +* Topic name: The topic the consumer wants data from +* Subscription name: A string representing a qualified name for the subscription +* Subscription type: The type of subscription being used +* Subscription cursor: A representation of the consumer's current place in the subscribed topic log -For example, the Pulsar consumer below has a *shared* subscription starting at the *earliest* cursor position in `my-subscription` to `my-topic`: +For example, the Pulsar consumer below has a `Shared` subscription to `my-topic` that starts at the `Earliest` cursor position in `my-subscription`: [source,java] ---- diff --git a/modules/ROOT/partials/subscription-prereq.adoc b/modules/ROOT/partials/subscription-prereq.adoc index 5087a1a..2f7e616 100644 --- a/modules/ROOT/partials/subscription-prereq.adoc +++ b/modules/ROOT/partials/subscription-prereq.adoc @@ -12,11 +12,12 @@ This example requires the following: * In the `pulsar-subscription-example` repo, navigate to `src/main/resources`, and then edit `application.properties` to connect to your {product} cluster: + -[source,bash] +.application.properties +[source,plain,subs="+quotes"] ---- -service_url={broker-service-url} +service_url=**BROKER_SERVICE_URL** namespace=default tenant_name=my-tenant -authentication_token={astra-auth-token} +authentication_token=**ASTRA_DB_APPLICATION_TOKEN** topic_name=my-topic ---- \ No newline at end of file diff --git a/modules/ragstack/images/app-deployed.png b/modules/ragstack/images/app-deployed.png deleted file mode 100644 index fb366dddb0d47325812f5b058000a397ae80faa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52032 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
O9kZ@w`F9L6D>*0bF_m*!P_CcCPV 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

G@h;u_?G(@WV{G1!g$#(*+j2NyYBz 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 zⅆf1Xr+dH*=;+kWUt#MtH_mIA^ug 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 Lf0!rs)bsxUr3rGa diff --git a/modules/ragstack/images/enable.png b/modules/ragstack/images/enable.png deleted file mode 100644 index 4cf36df94cce6b817417e5000a7b110d370abcbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27399 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 XthydZu4WAO&TnOTb-5B5^9TO{hwY-U diff --git a/modules/ragstack/images/websocket-chat.png b/modules/ragstack/images/websocket-chat.png deleted file mode 100644 index ead48cd903ec9b67ad2f10e8b8a70c040f5ba396..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 283208 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�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�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�Cn&xk$$aelR#NRV>0WX&xs%_ENlr}W!(|X$L*F}^70d! zc&6L3xCs@6#4n+P_&$91L;;QYX6P7t4!Zw3nWKk=t&fN=+Fp;GdW)99biX0YbD^Ng z<-^Du39H%mJEp1qj<6xOv5bObm}AvAd{o{X|E=QwECUC|l7QWPxjDouq=g=%QQ<#N z`iMBixgq4smtDxqxNnNKb~z-Q=^%mU(t@@lh^z

$;EhH&|$zmdmUsIx|JJN|ll? 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 diff --git a/modules/ragstack/nav.adoc b/modules/ragstack/nav.adoc deleted file mode 100644 index 20523aa..0000000 --- a/modules/ragstack/nav.adoc +++ /dev/null @@ -1,3 +0,0 @@ -.RAGStack -* xref:index.adoc[] -* xref:quickstart.adoc[] diff --git a/modules/ragstack/pages/index.adoc b/modules/ragstack/pages/index.adoc deleted file mode 100644 index a68192b..0000000 --- a/modules/ragstack/pages/index.adoc +++ /dev/null @@ -1,29 +0,0 @@ -= {ragstack} - -{ragstack} is a curated stack of the best open-source software for easing implementation of the RAG pattern in production-ready applications. - -Instead of managing forks and installing dozens of open-source packages while wondering if you chose the right vector database solution, a single command (`brew install datastax/ragstack/ragstack`) unlocks all the open source packages required to build production-ready RAG applications. - -== Components - -DataStax has been busy helping our customers through the pains of RAG implementation, so the {ragstack} components we've selected have withstood production workloads and stringent testing by our engineering teams for performance, compatibility, and security. - -* {ragstack} leverages the https://python.langchain.com/docs/get_started/introduction[LangChain] ecosystem and is fully compatible with https://docs.smith.langchain.com/[LangSmith] (for monitoring) and https://github.com/langchain-ai/langserve[LangServe] (for hosting). - -* The https://docs.datastax.com/en/astra-serverless/docs/[{astra_db}] vector database provides the best performance and scalability for RAG applications, in addition to being particularly well-suited to RAG workloads like question answering, semantic search, and semantic caching. - -* The https://langstream.ai[LangStream] package combines the best of event-based architectures with the latest Gen AI technologies. Develop robust Gen AI pipelines with just YAML files. Leverage Kafka for data flow, and Kubernetes for deployment and scaling. - -== Why {ragstack}? - -{ragstack} offers solutions for challenges facing developers building RAG applications. - -* Productivity - abstract over the RAG pattern's complexities to keep developers focused on business logic. -* Performance, scalability, cost - cache a large percentage of AI calls and leverage the inherent parallelism built into {astra_db} -* Event-driven architectures - fresher data faster -* Advanced RAG techniques - use advanced patterns like Chain of Thought and Multi-Query RAG -* Future-proof - as new techniques are discovered, {ragstack} offers enterprise users an upgrade path to always be on the cutting edge of AI. - -== See also - -* xref:quickstart.adoc[] \ No newline at end of file diff --git a/modules/ragstack/pages/quickstart.adoc b/modules/ragstack/pages/quickstart.adoc deleted file mode 100644 index 098ca01..0000000 --- a/modules/ragstack/pages/quickstart.adoc +++ /dev/null @@ -1,497 +0,0 @@ -= Quickstart - -The {ragstack} project combines the intelligence of large language models with the agility of stream processing to create powerful Generative AI applications. - -This guide will help you build and deploy a {ragstack} application to {product}. - -[IMPORTANT] -==== -{ragstack} is available only to qualified participants. This feature is not intended for production use, has not been certified for production workloads, and might contain bugs and other functional issues. There is no guarantee that a preview feature will ever become generally available. -==== - -== Install {ragstack} CLI - -Install the {ragstack} CLI: -[source,bash] ----- -brew install datastax/ragstack/ragstack ----- - -For more on the {ragstack} CLI, see https://docs.langstream.ai/installation/langstream-cli[{ragstack} CLI]. - -== Enable {ragstack} in {astra_db} - -xref:getting-started:index.adoc[Create an {product} tenant] in the GCP `us-east-1` region. - -Your tenant will be created with a default namespace, which is a logical grouping of topics. - -Your tenant will be listed in the {ragstack} tab. Select *Enable* to enable {ragstack} for your tenant. - -image::enable.png[Enable {ragstack}] - -Under the hood, this is enabling the xref:starlight-for-kafka:ROOT:index.adoc[Starlight for Kafka] API for your tenant to connect to your Kafka cluster. - -== Connect {ragstack} to your tenant - -. Select the *Generate Configuration Command* button to generate a CLI configuration file for your tenant. - -. Run the generated command in your local environment to connect your tenant to the {ragstack} CLI: -+ -[source,shell] ----- -ragstack profiles import astra-rs-tenant --inline 'base64:...' --set-current -u ----- -+ -.Result -[%collapsible] -==== -[source,console] ----- -profile astra-rs-tenant created -profile astra-rs-tenant set as current ----- -==== - -The configuration values will look something like this: - -[source,console] ----- -tenant: rs-tenant -webServiceUrl: https://pulsar-gcp-useast1.api.streaming.datastax.com/ragstack -apiGatewayUrl: wss://lsgwy-gcp-useast1.streaming.datastax.com/ragstack-api-gateway/ -token: AstraCS:... ----- - -Your tenant is now connected to the {ragstack} CLI. - -You can also establish a connection by including the configuration values from the {product} Connect tab in your {ragstack} application's instance.yaml file. -See <> for an example. - -== Build a {ragstack} Application - -Build a {ragstack} application by creating YAML files to describe the application. -The application folder structure looks like this: - -[source,shell] ----- -|- project-folder - |- application - |- pipeline.yaml - |- gateways.yaml - |- configuration.yaml -|- secrets.yaml -|- instance.yaml ----- - -Here's a shortcut: -[source,shell] ----- -mkdir project-folder && cd project-folder -touch secrets.yaml instance.yaml -mkdir application && cd application -touch pipeline.yaml gateways.yaml configuration.yaml ----- - -The instance.yaml and secrets.yaml files cannot be in the "application" directory, because the application directory is passed as a zip at runtime. -Next, you will populate the YAML files to connect your application to your {product} tenant. - -== Populate YAML files - -[#instance] -Instance.yaml declares the application's processing infrastructure, including where streaming and compute take place. - -The secrets for tokens and passwords are stored in the secrets.yaml file, which you'll populate in the next step. -An instance.yaml file can be downloaded from the Connect tab of your {product} tenant. Paste it into your instance.yaml file to connect your application to your tenant. - -.instance.yaml -[source,yaml] ----- -instance: - streamingCluster: - type: "kafka" - configuration: - admin: - bootstrap.servers: "${ secrets.kafka.bootstrapServers }" - security.protocol: "${ secrets.kafka.securityProtocol }" - sasl.jaas.config: "org.apache.kafka.common.security.plain.PlainLoginModule required username='${ secrets.kafka.username }' password='${ secrets.kafka.password }';" - sasl.mechanism: PLAIN - session.timeout.ms: "45000" - - computeCluster: - type: "kubernetes" ----- - -Secrets.yaml contains auth information for connecting to other services. -Secret values can be modified directly in secrets.yaml, or you can pass your secrets as environment variables or in a .env file. The secrets.yaml resolves these environment variables. - -..env -[source,bash] ----- -export ASTRA_CLIENT_ID= -export ASTRA_SECRET= -export ASTRA_DATABASE= -export ASTRA_TOKEN= ----- - -When you go to production, you should create a dedicated secrets.yaml file for each environment. - -You can get the `clientId`, `clientSecret`, and `token` by creating an {astra_db} application token in the {astra_ui}. -The values for the Kafka bootstrap server are found in your {product} tenant or in the Starlight for Kafka ssl.properties file. -The Azure access key and URL are found in your Azure OpenAI deployment. - -A secrets.yaml file can be downloaded from the *Connect* tab of your {product} tenant. -Paste it into your secrets.yaml file to authorize your application to your tenant. -For more on finding values for secrets, see https://docs.langstream.ai/building-applications/secrets.html[Secrets]. - -.secrets.yaml -[source,yaml] ----- -secrets: - - id: astra - data: - clientId: ${ASTRA_CLIENT_ID:-} - secret: ${ASTRA_SECRET:-} - token: ${ASTRA_TOKEN:-} - database: ${ASTRA_DATABASE:-} - secureBundle: ${ASTRA_SECURE_BUNDLE:-} - environment: ${ASTRA_ENVIRONMENT:-PROD} - - id: open-ai - data: - access-key: "${OPEN_AI_ACCESS_KEY:-}" - url: "${OPEN_AI_URL:-}" - provider: "${OPEN_AI_PROVIDER:-azure}" - embeddings-model: "${OPEN_AI_EMBEDDINGS_MODEL:-text-embedding-ada-002}" - chat-completions-model: "${OPEN_AI_CHAT_COMPLETIONS_MODEL:-gpt-35-turbo}" - - id: google - data: - client-id: "${GOOGLE_CLIENT_ID:-}" ----- - -You can either replace the values in secrets.yaml with the actual values, use a `.env` file, or export the secrets as below: - -[source,shell] ----- -export OPEN_AI_URL=https://company-openai-dev.openai.azure.com/ -export OPEN_AI_ACCESS_KEY=your-openai-access-key -export OPEN_AI_EMBEDDINGS_MODEL=text-embedding-ada-002 -export OPEN_AI_CHAT_COMPLETIONS_MODEL=gpt-35-turbo -export OPEN_AI_PROVIDER=azure -export KAFKA_USERNAME=rs-tenant -export KAFKA_PASSWORD=eyRrr... -export KAFKA_BOOTSTRAP_SERVERS=kafka-gcp-useast1.streaming.datastax.com:9093 -export KAFKA_TENANT=rs-tenant -export ASTRA_CLIENT_ID=xxxx -export ASTRA_TOKEN=AstraCS:... -export GOOGLE_CLIENT_ID=xxxx.apps.googleusercontent.com ----- - -For more information about creating a Google client ID, see https://developers.google.com/identity/protocols/oauth2#serviceaccount[Google Service Account]. - -Pipeline.yaml contains the chain of agents that makes up your program, and the input and output topics that they communicate with. -For more on building pipelines, see https://docs.langstream.ai/building-applications/pipelines[Pipelines]. - -[source,yaml] ----- -topics: - - name: "input-topic" - creation-mode: create-if-not-exists - - name: "output-topic" - creation-mode: create-if-not-exists - - name: "history-topic" - creation-mode: create-if-not-exists -pipeline: - - name: "convert-to-json" - type: "document-to-json" - input: "input-topic" - configuration: - text-field: "question" - - name: "ai-chat-completions" - type: "ai-chat-completions" - output: "history-topic" - configuration: - model: "${secrets.open-ai.chat-completions-model}" # This needs to be set to the model deployment name, not the base name - # on the log-topic we add a field with the answer - completion-field: "value.answer" - # we are also logging the prompt we sent to the LLM - log-field: "value.prompt" - # here we configure the streaming behavior - # as soon as the LLM answers with a chunk we send it to the answers-topic - stream-to-topic: "output-topic" - # on the streaming answer we send the answer as whole message - # the 'value' syntax is used to refer to the whole value of the message - stream-response-completion-field: "value" - # we want to stream the answer as soon as we have 10 chunks - # in order to reduce latency for the first message the agent sends the first message - # with 1 chunk, then with 2 chunks....up to the min-chunks-per-message value - # eventually we want to send bigger messages to reduce the overhead of each message on the topic - min-chunks-per-message: 10 - messages: - - role: user - content: "You are a helpful assistant. Below you can find a question from the user. Please try to help them the best way you can.\n\n{{ value.question}}" ----- - -Gateways.yaml contains API gateways for communicating with your application. -For more on gateways and authentication, see https://docs.langstream.ai/building-applications/api-gateways[API Gateways]. - -[source,yaml] ----- -gateways: - - id: produce-input - type: produce - topic: input-topic - parameters: - - sessionId - produce-options: - headers: - - key: ragstack-client-session-id - value-from-parameters: sessionId - - - id: chat - type: chat - chat-options: - answers-topic: output-topic - questions-topic: input-topic - - - id: consume-output - type: consume - topic: output-topic - parameters: - - sessionId - consume-options: - filters: - headers: - - key: ragstack-client-session-id - value-from-parameters: sessionId - - - id: consume-history - type: consume - topic: history-topic - parameters: - - sessionId - consume-options: - filters: - headers: - - key: ragstack-client-session-id - value-from-parameters: sessionId - - - id: produce-input-auth - type: produce - topic: input-topic - parameters: - - sessionId - authentication: - provider: google - configuration: - clientId: "${secrets.google.client-id}" - produce-options: - headers: - - key: ragstack-client-user-id - value-from-authentication: subject - - - id: consume-output-auth - type: consume - topic: output-topic - parameters: - - sessionId - authentication: - provider: google - configuration: - clientId: "${secrets.google.client-id}" - consume-options: - filters: - headers: - - key: ragstack-client-user-id - value-from-authentication: subject ----- - -Configuration.yaml contains additional configuration and resources for your application. -A configuration.yaml file can be downloaded from the Connect tab of your {product} tenant (under {astra_db}). -For more on configuration, see https://docs.langstream.ai/building-applications/configuration[Configuration]. - -[source,yaml] ----- -configuration: - resources: - - type: "open-ai-configuration" - name: "OpenAI Azure configuration" - configuration: - url: "${secrets.open-ai.url}" - access-key: "${secrets.open-ai.access-key}" - provider: "${secrets.open-ai.provider}" ----- - -Remember to save all your yaml files. - -== Deploy the {ragstack} application on {astra_db} - -. To deploy the application, run the following commands from the root of your application folder. -The first command deploys the application from the YAML files you created above, and the second command gets the status of the application. -For more on {ragstack} CLI commands, see https://docs.langstream.ai/installation/langstream-cli[{ragstack} CLI]. -+ -[source,shell] ----- -ragstack apps deploy sample-app -app ./application -i ./instance.yaml -s ./secrets.yaml -ragstack apps get sample-app ----- -+ -.Result -[%collapsible] -==== -[source,console] ----- -packaging app: /Users/mendon.kissling/sample-app/./application -app packaged -deploying application: sample-app (1 KB) -application sample-app deployed -ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS -sample-app kafka kubernetes DEPLOYED 1/1 1/1 ----- -==== - -. Ensure your app is running: -+ -* A Kubernetes pod should be deployed with your application, and the `STATUS` will change to `DEPLOYED`. - -* Your application should be listed in your {ragstack} tenant: -+ -image::app-deployed.png[App deployed] - -* You should see a map of your application in the {ragstack} UI: -+ -image::app-map.png[App map] - -[TIP] -==== -To get logs, run `ragstack apps logs **APP_NAME**`. -==== - -== {ragstack} CLI connection values - -If you encounter an error or problem, make sure the values in your CLI profile match the values in your {product} tenant. - -If you're unsure of the profile name, run `ragstack profiles list`, and then run `ragstack profiles get **PROFILE_NAME** -o=json`. -For example: - -[source,json] ----- -{ - "webServiceUrl" : "https://pulsar-gcp-useast1.api.streaming.datastax.com/langstream", - "apiGatewayUrl" : "wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway/", - "tenant" : "ragstack-tenant", - "token" : "AstraCS:", - "name" : "astra-ragstack-tenant" -} ----- - -To update values for the above profile, run `ragstack profiles update astra-ragstack-tenant --**OPTION**="**VALUE**"`: - -[cols="1,1"] -|=== -| Command Option | Description - -| `--set-current` -| Set this profile as current - -| `--web-service-url` -| webServiceUrl of the profile - -| `--api-gateway-url` -| apiGatewayUrl of the profile - -| `--tenant` -| tenant of the profile - -| `--token` -| token of the profile -|=== - -The default profile values are as follows: - -[source,json] ----- -{ - "webServiceUrl": "http://localhost:8090", - "apiGatewayUrl": "ws://localhost:8091", - "tenant": "default", - "token": null -} ----- - -Issue a curl call to your {ragstack} tenant to find the connection values for your tenant. -The `X-DataStax-Current-Org` value is the client-id associated with the {astra_db} token, and can be found in the {astra_ui}. - -[source,curl] ----- -curl --location --request POST 'https://pulsar-gcp-useast1.api.streaming.datastax.com/langstream/ragstack-tenant' \ ---header 'X-DataStax-Current-Org:lzAiCLsTMKruZZZUxieNgYhe' \ ---header 'X-DataStax-Pulsar-Cluster: pulsar-gcp-useast1' \ ---header 'Authorization: Bearer AstraCS:' ----- - -.Result -[%collapsible] -==== -[source,console] ----- -{ - "status":true, - "webServiceUrl":"https://pulsar-gcp-useast1.api.streaming.datastax.com/langstream", - "apiGatewayUrl":"wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway/", - "tenant":"astra-ragstack-tenant", - "token":"{astra token}"}% ----- -==== - -Ensure the values returned from the curl call match the values in your {ragstack} CLI profile. - -== Check connection to {astra_db} - -In the {ragstack} CLI, run the following command to open a gateway connection to your {product} tenant. -This command will connect to your tenant and consume from the output-topic and produce to the input-topic. - -[source,shell] ----- -ragstack gateway chat sample-app -cg consume-output -pg produce-input -p sessionId=$(uuidgen) ----- - -In {product}, confirm that your application is connected to your tenant. -Select the Websocket tab of your {ragstack}-enabled tenant, and choose to consume from output-topic and to produce to input-topic. -If the Websocket tab is not visible, you may need to refresh the page or try opening it in Incognito mode. -Send a message to your application, and confirm that it is received by the {astra_db} websocket: - -[source,console] ----- -ragstack gateway chat sample-app -cg consume-output -pg produce-input -p sessionId=$(uuidgen) -Connected to wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway//v1/consume/ragstack-tenant/sample-app/consume-output?param:sessionId=F85E4665-BE00-4513-A5C5-E59B42646490&option:position=latest -Connected to wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway//v1/produce/ragstack-tenant/sample-app/produce-input?param:sessionId=F85E4665-BE00-4513-A5C5-E59B42646490 - -You: -> Hi Astra, it's me, K8s. How are you? -..✅ -... ----- - -image::websocket-chat.png[Websocket chat] - -Your gateway connection is confirmed, and you can send messages to your application. -This sample-app also produces messages to the consume-history gateway to provide more context to the AI model. -To consume from this gateway, run the following command: - -[source,console] ----- -ragstack gateway consume sample-app consume-history -p sessionId=F85E4665-BE00-4513-A5C5-E59B42646490 ----- - -.Result -[%collapsible] -==== -[source,console] ----- -Connected to wss://lsgwy-gcp-useast1.streaming.datastax.com/langstream-api-gateway//v1/consume/ragstack-tenant/sample-app/consume-history?param:sessionId=F85E4665-BE00-4513-A5C5-E59B42646490 -{"record":{"key":null,"value":"Hi K8s, it's me, Astra.","headers":{}},"offset":"eyJvZmZzZXRzIjp7IjAiOiIxIn19"} ----- -==== - -== See also - -{ragstack} is built with the https://github.com/LangStream/langstream[LangStream framework], which is a set of tools for building Generative AI streaming applications. \ No newline at end of file From 67b93ea7a2b58c70df3958760113a0357760276f Mon Sep 17 00:00:00 2001 From: April M Date: Tue, 15 Oct 2024 11:13:57 -0700 Subject: [PATCH 07/18] source blocks pt 1 --- .../astream-subscriptions-exclusive.adoc | 47 ++++---- .../pages/astream-subscriptions-failover.adoc | 40 ++++--- .../astream-subscriptions-keyshared.adoc | 80 +++++++------ modules/developing/pages/astream-kafka.adoc | 59 +++++----- modules/developing/pages/astream-rabbit.adoc | 68 ++++++----- modules/developing/pages/using-curl.adoc | 14 +-- .../pages/astream-scrape-metrics.adoc | 71 +++++++----- .../operations/pages/monitoring/index.adoc | 109 +++++++++++------- .../pages/monitoring/integration.adoc | 89 +++++++------- .../pages/monitoring/new-relic.adoc | 69 +++++------ .../pages/monitoring/stream-audit-logs.adoc | 39 ++++--- 11 files changed, 379 insertions(+), 306 deletions(-) diff --git a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc index 295a9ee..8313567 100644 --- a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc +++ b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc @@ -4,17 +4,19 @@ _Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -An _exclusive* subscription_ describes a basic publish-subscribe pattern where a single consumer subscribes to a single topic and consumes from it. +An _exclusive subscription_ describes a basic publish-subscribe (pub-sub) pattern where a single consumer subscribes to a single topic and consumes from it. -This document explains how to use Pulsar's exclusive subscription model to manage your topic consumption. +This page explains how to use Pulsar's exclusive subscription model to manage your topic consumption. include::ROOT:partial$subscription-prereq.adoc[] [#example] == Exclusive subscription example -This example uses the following `pulsarConsumer` object in `SimplePulsarConsumer.java`: - +. To configure a Pulsar exclusive subscription, define a `pulsarConsumer` object in `SimplePulsarConsumer.java`, as you would for other subscription types. +However, you don't need to declare a `subscriptionType`. +Whereas other subscription types required you to declare a specific `subscriptionType`, Pulsar creates an exclusive subscription by default if you don't declare a `subscriptionType`. ++ .SimplePulsarConsumer.java [source,java] ---- @@ -28,33 +30,34 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscriptionName("SimplePulsarConsumer") .subscribe(); ---- ++ +If you want to explicitly define an exclusive subscription, you can add `.subscriptionType(SubscriptionType.Exclusive)` to the consumer. -[IMPORTANT] -==== -Pulsar creates an exclusive subscription by default if you don't delcare a `subscriptionType`. -==== - -. Open the `pulsar-subscription-example` repo in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. -The confirmation message and a cursor appear to indicate the consumer is ready. +. In the `pulsar-subscription-example` project, run `SimplePulsarConsumer.java` to begin consuming messages. + -[source,bash] +The confirmation message and a cursor appear to indicate the consumer is ready: ++ +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.Configuration - Configuration has been loaded successfully ... [pulsar-client-io-1-1] INFO org.apache.pulsar.client.impl.ConsumerImpl - [persistent:////in][SimplePulsarConsumer] Subscribed to topic on -- consumer: 0 ---- -. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages. +. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 93573631 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 16931522 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 68306175 sent ---- - -. The consumer begins consuming the produced messages. + +In the `SimplePulsarConsumer` terminal, the consumer begins consuming the produced messages: ++ +.Result [source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":93573631,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -62,9 +65,12 @@ The confirmation message and a cursor appear to indicate the consumer is ready. [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":68306175,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. Open a new terminal window and try to run `SimplePulsarConsumer.java`. +. In a new terminal window, try to run another instance of `SimplePulsarConsumer.java`. ++ +The second consumer can't subscribe to the topic because the subscription is exclusive: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.Configuration - Configuration has been loaded successfully ... @@ -73,11 +79,6 @@ Exception in thread "main" java.lang.IllegalStateException: Cannot connect to pu at com.datastax.pulsar.SimplePulsarConsumer.main(SimplePulsarConsumer.java:53) Caused by: org.apache.pulsar.client.api.PulsarClientException$ConsumerBusyException: {"errorMsg":"Exclusive consumer is already connected","reqId":2964994443801550457, "remote":"", "local":"/192.168.0.95:55777"} ---- -+ -The second consumer can't subscribe to the topic because the subscription is exclusive. -+ -In the example above, the consumer didn't declare a subscription type, so Pulsar created an exclusive subscription by default. -To explicitly define an exclusive subscription, add `.subscriptionType(SubscriptionType.Exclusive)` to the consumer. == See also diff --git a/modules/ROOT/pages/astream-subscriptions-failover.adoc b/modules/ROOT/pages/astream-subscriptions-failover.adoc index 4c91db5..41c969e 100644 --- a/modules/ROOT/pages/astream-subscriptions-failover.adoc +++ b/modules/ROOT/pages/astream-subscriptions-failover.adoc @@ -2,22 +2,23 @@ :navtitle: Failover :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -_Subscriptions_*_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. +_Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -If an xref:astream-subscriptions-exclusive.adoc[exclusive] consumer fails, the topic backlog balloons with stale, unacknowledged messages. +If an xref:astream-subscriptions-exclusive.adoc[exclusive] consumer fails, the topic backlog accumulates stale, unacknowledged messages. Pulsar solves this problem with failover subscriptions. In _failover subscriptions_, Pulsar designates one primary consumer and multiple standby consumers. If the primary consumer disconnects, the standby consumers begin consuming the subsequent unacknowledged messages. -This document explains how to use Pulsar's failover subscription model to manage your topic consumption. +This page explains how to use Pulsar's failover subscription model to manage your topic consumption. include::ROOT:partial$subscription-prereq.adoc[] [#example] == Failover subscription example -To try out a Pulsar failover subscription, add `.subscriptionType(SubscriptionType.Failover)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`. +To try out a Pulsar failover subscription, add `.subscriptionType(SubscriptionType.Failover)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`: +.SimplePulsarConsumer.java [source,java] ---- pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) @@ -32,20 +33,22 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- -. Open `pulsar-subscription-example` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. -This is the primary consumer. -The confirmation message and a cursor appear to indicate the consumer is ready. +. In the `pulsar-subscription-example` project, run `SimplePulsarConsumer.java` to begin consuming messages as the primary consumer. + -[source,bash] +The confirmation message and a cursor appear to indicate the consumer is ready: ++ +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.Configuration - Configuration has been loaded successfully ... [pulsar-client-io-1-1] INFO org.apache.pulsar.client.impl.ConsumerImpl - [persistent:////in][SimplePulsarConsumer] Subscribed to topic on -- consumer: 0 ---- -. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages. +. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 50585599 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 98055337 sent @@ -54,9 +57,10 @@ The confirmation message and a cursor appear to indicate the consumer is ready. [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 73260535 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 42372149 sent ---- - -. The primary consumer begins consuming messages. + +In the `SimplePulsarConsumer` terminal, the primary consumer begins consuming messages: ++ +.Result [source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":50585599,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} @@ -64,21 +68,25 @@ The confirmation message and a cursor appear to indicate the consumer is ready. [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":36327100,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. Open a new terminal window and run `SimplePulsarConsumer.java`. This is the *backup* consumer. The backup consumer subscribes to the topic, but does not immediately begin consuming messages. +. In a new terminal window, run a new instance of `SimplePulsarConsumer.java` as a backup consumer. +The backup consumer subscribes to the topic, but does not immediately begin consuming messages. -. Return to the *primary* consumer and `Ctrl+C` to stop it. The *backup* consumer begins consuming right where the first consumer left off. +. In the primary `SimplePulsarConsumer` terminal, stop the process (`Ctrl+C`). +In the second `SimplePulsarConsumer` terminal, the backup consumer begins consuming messages where the first consumer left off: + +.Result [source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":73260535,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":42372149,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. You can repeat this process with as many primary and backup consumers as you want- the next message is delivered to the subscribed failover consumers. +You can configure as many backup consumers as you like. +To test them, you can progressively end each `SimplePulsarConsumer` process, and then check that the next backup consumer has begun receiving messages. === Failover subscription video -Follow along with this video from our *Five Minutes About Pulsar* series to see failover subscriptions in action. +Follow along with this video from our *Five Minutes About Pulsar* series to see failover subscriptions in action: video::ckB87OLs5eM[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] diff --git a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc index 5038481..50176eb 100644 --- a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc @@ -1,30 +1,28 @@ -= Key_Shared subscriptions in Pulsar += Key shared subscriptions in Pulsar :navtitle: Key_Shared :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar -*Subscriptions* in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. +_Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model trades an increased message processing rate for ordering guarantees. In a round-robin delivery, there's no way for the broker to know which messages are going to which consumer. +Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model enables increased message processing rate, but risks losing message ordering guarantees. +In a round-robin delivery, there's no way for the broker to know which messages are going to which consumer. -*Key_Shared* subscriptions allow multiple consumers to subscribe to a topic, but with additional metadata in the form of *keys* which link messages to specific consumers. +_Key shared subscriptions_ allow multiple consumers to subscribe to a topic, and provide additional metadata in the form of _keys_ that link messages to specific consumers. +Keys are generated with hashing that converts arbitrary values like `topic-name` or JSON blobs into fixed integer values, and then the hashed values are assigned to subscribed consumers in one of two ways: -*Keys* are generated with *hashing*, which converts arbitrary values like "topic-name" or JSON blobs into fixed integer values. These hashed values are then assigned to subscribed consumers in one of two ways: +* *Auto hash*: Uses _consistent hashing_ to balance range values across available consumers without requiring manual setup of hash ranges. +* *Sticky hash*: The client manually assigns consumer range values, and then all hashes within a configured range go to one consumer. -* *Auto hash* - uses *consistent hashing* to balance range values across available consumers, without requiring manual setup of hash ranges. -* *Sticky hash* - the client manually assigns consumer range values. All hashes within a configured range go to one consumer. - -This document explains how to use Pulsar's Key_Shared subscription model to manage your topic consumption. +This page explains how to use Pulsar's key shared subscription model to manage your topic consumption. include::ROOT:partial$subscription-prereq.adoc[] [#example] -== Key_Shared subscription example - -To try out a Pulsar Key_Shared subscription, add `.subscriptionType(SubscriptionType.Key_Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`. +== Key shared subscription example -You must also tell Pulsar what `keySharedPolicy` this subscription will use. -The example below uses the auto-hashing `keySharedPolicy`. +To try out a Pulsar key shared subscription, add `.subscriptionType(SubscriptionType.Key_Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`: +.SimplePulsarConsumer.java [source,java] ---- pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) @@ -40,16 +38,20 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- -Running multiple consumers with auto-hashing balances the messaging load across all available consumers. +=== keySharedPolicy -=== Manually set stickyHashRange +The `keySharedPolicy` defines the how hashed values are assigned to subscribed consumers. -You can manually set a hash range with `KeySharedPolicy.stickyHashRange()`. +The above example used `autoSplitHashRange`, which is an auto-hashing policy. +Running multiple consumers with auto-hashing balances the messaging load across all available consumers. + +If you want to manually set a hash range, use `KeySharedPolicy.stickyHashRange()`. -To test out sticky hashed Key_Shared subscriptions, you need to first import some additional classes. +==== Test sticky hashed key shared subscriptions -. Add the following classes to `SimplePulsarConsumer.java`: +. To test out sticky hashed key shared subscriptions, import the following additional classes: + +.SimplePulsarConsumer.java [source,java] ---- import org.apache.pulsar.client.api.Range; @@ -59,14 +61,16 @@ import org.apache.pulsar.client.api.SubscriptionType; . Add the following classes to `SimplePulsarProducer.java`: + +.SimplePulsarProducer.java [source,java] ---- import org.apache.pulsar.client.api.BatcherBuilder; import org.apache.pulsar.client.api.HashingScheme; ---- -. Modify the `pulsarProducer` in `SimplePulsarProducer.java` to use the `JavaStringHash` hashing scheme. +. In `SimplePulsarProducer.java`, modify the `pulsarProducer` to use the `JavaStringHash` hashing scheme: + +.SimplePulsarProducer.java [source,java] ---- pulsarProducer = pulsarClient @@ -80,45 +84,54 @@ pulsarProducer = pulsarClient .create(); ---- -. Modify the `pulsarConsumer` in `SimplePulsarConsumer.java` to use sticky hashing. This example sets all possible hashes (0-65535) on this subscription to this consumer. +. In `SimplePulsarConsumer.java`, modify the `pulsarConsumer` to use sticky hashing. +This example sets all possible hashes (`0-65535`) on this subscription to one consumer. + +.SimplePulsarConsumer.java [source,java] ---- .subscriptionType(SubscriptionType.Key_Shared) .keySharedPolicy(KeySharedPolicy.stickyHashRange().ranges(Range.of(0,65535))) ---- -. Open `pulsar-examples` in the IDE of your choice and run `SimplePulsarConsumer.java` to begin consuming messages. -The confirmation message and a cursor appear to indicate the consumer is ready. +. In the `pulsar-examples` project, run `SimplePulsarConsumer.java` to begin consuming messages. ++ +The confirmation message and a cursor appear to indicate the consumer is ready: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.Configuration - Configuration has been loaded successfully ... [pulsar-client-io-1-1] INFO org.apache.pulsar.client.impl.ConsumerImpl - [persistent:////in][SimplePulsarConsumer] Subscribed to topic on -- consumer: 0 ---- -. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages. +. In a new terminal window, run `SimplePulsarProducer.java` to begin producing messages: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 55794190 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 41791865 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 74840732 sent [main] INFO com.datastax.pulsar.SimplePulsarProducer - Message 57467766 sent ---- - -. The consumer begins receiving messages. + +In the `SimplePulsarConsumer` terminal, the consumer begins receiving messages: ++ +.Result [source,console] ---- [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":55794190,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} [main] INFO com.datastax.pulsar.SimplePulsarConsumer - Message received: {"show_id":41791865,"cast":"LeBron James, Anthony Davis, Kyrie Irving, Damian Lillard, Klay Thompson...","country":"United States","date_added":"July 16, 2021","description":"NBA superstar LeBron James teams up with Bugs Bunny and the rest of the Looney Tunes for this long-awaited sequel.","director":"Malcolm D. Lee","duration":"120 min","listed_in":"Animation, Adventure, Comedy","rating":"PG","release_year":2021,"title":"Space Jam: A New Legacy","type":"Movie"} ---- -. Open a new terminal window and try to run `SimplePulsarConsumer.java`. +. In a new terminal window, try to run a new instance of `SimplePulsarConsumer.java`. ++ +The new consumer can't subscribe to the topic because the `SimplePulsarConsumer` configuration reserved the entire hash range for the first consumer: + -[source,bash] +.Result +[source,console] ---- [main] INFO com.datastax.pulsar.Configuration - Configuration has been loaded successfully [main] INFO com.datastax.pulsar.SimplePulsarConsumer - SimplePulsarProducer has been stopped. @@ -130,11 +143,12 @@ Caused by: org.apache.pulsar.client.api.PulsarClientException$ConsumerAssignExce at com.datastax.pulsar.SimplePulsarConsumer.main(SimplePulsarConsumer.java:47) ---- -The new consumer can't subscribe to the topic because you reserved the entire hash range for the first consumer. +. To run multiple consumers with sticky hashing, modify the `SimplePulsarConsumer.java` configuration to split the hash range between consumers. +Then, you can launch multiple instances of `SimplePulsarConsumer.java` to consume messages from different hash ranges. -== Key_Shared subscription video +== Key shared subscription video -Follow along with this video from our *Five Minutes About Pulsar* series to see Key_Shared subscriptions in action. +Follow along with this video from our *Five Minutes About Pulsar* series to see key shared subscriptions in action: video::_49wlA53L_8[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] diff --git a/modules/developing/pages/astream-kafka.adoc b/modules/developing/pages/astream-kafka.adoc index ae4c216..f7c48d6 100644 --- a/modules/developing/pages/astream-kafka.adoc +++ b/modules/developing/pages/astream-kafka.adoc @@ -26,39 +26,41 @@ You can't remove the Kafka namespaces created on your tenant with this step. You must remove the tenant itself to remove these namespaces. ==== -. Select *Enable Kafka*. -+ -Three new namespaces are created in your {product} tenant: +. Select *Enable Kafka* to create a configuration file and the following three namespaces in your {product} tenant: + * `kafka` for producing and consuming messages * `+__kafka+` for functionality * `+__kafka_unlimited+` for storing metadata + +. Save the configuration to a `ssl.properties` file: + -A new configuration file will be generated in the *Connect* tab that looks like this: -+ +.ssl.properties +[source,plain,subs="+quotes"] ---- -username: +username: **TENANT_NAME** password: token:****** bootstrap.servers: kafka-aws-useast2.streaming.datastax.com:9093 schema.registry.url: https://kafka-aws-useast2.streaming.datastax.com:8081 security.protocol: SASL_SSL sasl.mechanism: PLAIN ---- ++ +The configuration details depend on your {product} tenant configuration. -. Copy and paste the code or download it as a config file (it will be called `ssl.properties`). - -You're now ready to connect Kafka and Pulsar. +== Connect Kafka and Pulsar -== Example: Hello Pulsar +This example uses tools included with the https://kafka.apache.org/downloads[Apache Kafka tarball]. -. Create a new topic in the newly created `kafka` namespace. For this example, we created `test-topic` within the `kafka` namespace on the `tenant-1` tenant. +. Create a new topic in your `kafka` namespace. +This example creates a topic named `test-topic` in the `kafka` namespace on a tenant named `tenant-1`. + image::astream-create-kafka-topic.png[Create Kafka Topic] -+ -This example uses tools included with the https://kafka.apache.org/downloads[Apache Kafka tarball]. -. Move the `ssl.properties` file you downloaded to your `Kafka_2.13-3.1.0/config` folder. These values are required for SSL encryption. For this example, the values are: +. Move your `ssl.properties` file to your `Kafka_2.13-3.1.0/config` folder. +These values are required for SSL encryption. +For this example, the values are as follows: + +[source,plain] ---- bootstrap.servers=kafka-aws-useast2.streaming.datastax.com:9093 security.protocol=SASL_SSL @@ -67,33 +69,38 @@ sasl.mechanism=PLAIN session.timeout.ms=45000 ---- -. Change directory to Kafka. -. Create a Kafka producer to produce messages on `tenant-1/kafka/test-topic`. +. In the `Kafka` directory, create a Kafka producer to produce messages on `tenant-1/kafka/test-topic`. + -Once the producer is ready, it accepts standard input from the user. +[source,shell] +---- +bin/kafka-console-producer --broker-list kafka-aws-useast2.streaming.datastax.com:9093 --topic tenant-1/kafka/test-topic --producer.config config/ssl.properties +---- + -[source,bash] +Once the producer is ready, it accepts standard input from the user: ++ +.Result +[source,console] ---- -$ bin/kafka-console-producer --broker-list kafka-aws-useast2.streaming.datastax.com:9093 --topic tenant-1/kafka/test-topic --producer.config config/ssl.properties >hello pulsar ---- + . In a new terminal window, create a Kafka consumer to consume messages from the beginning of `tenant-1/kafka/test-topic`: + -[source,bash] +[source,shell] ---- -$ bin/kafka-console-consumer --bootstrap-server kafka-aws-useast2.streaming.datastax.com:9093 --topic tenant-1/kafka/test-topic --consumer.config config/ssl.properties --from-beginning +bin/kafka-console-consumer --bootstrap-server kafka-aws-useast2.streaming.datastax.com:9093 --topic tenant-1/kafka/test-topic --consumer.config config/ssl.properties --from-beginning hello pulsar ---- -. Send as many messages as you'd like, then return to your `kafka` namespace dashboard in {product} and monitor your activity. - -Your Kafka messages are being produced and consumed in a Pulsar cluster! - +. Send a few messages, and then return to your `kafka` namespace dashboard in {product} to monitor your activity. ++ +Your Kafka messages are being produced and consumed in a Pulsar cluster: ++ image::astream-kafka-monitor.png[Monitor Kafka Activity] == Starlight for Kafka video -Follow along with this video from our *Five Minutes About Pulsar* series to migrate from Kafka to Pulsar. +Follow along with this video from our *Five Minutes About Pulsar* series to migrate from Kafka to Pulsar: video::Qy2ZlelLjXg[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] diff --git a/modules/developing/pages/astream-rabbit.adoc b/modules/developing/pages/astream-rabbit.adoc index 3b3b7db..8ef186d 100644 --- a/modules/developing/pages/astream-rabbit.adoc +++ b/modules/developing/pages/astream-rabbit.adoc @@ -24,37 +24,36 @@ You can't remove the RabbitMQ namespace created on your tenant with this step. You must remove the tenant itself to remove this namespace. ==== -. Select *Enable RabbitMQ*. -+ -A new `rabbitmq` namespace is created in your {product} tenant for RabbitMQ functionality. -+ -A new configuration file is generated in the *Connect* tab that looks like this: +. Select *Enable RabbitMQ* to create a `rabbitmq` namespace in your {product} tenant for RabbitMQ functionality, as well as a configuration file. + +. Save the configuration to a `rabbitmq.conf` file: + +.rabbitmq.conf +[source,plain,subs="+quotes"] ---- -username: +username: **TENANT_NAME** password: token:*** -host: rabbitmq-azure-us-west-2.dev.streaming.datastax.com +host: rabbitmq-azure-us-west-2.streaming.datastax.com port: 5671 virtual_host: azure/rabbitmq ---- ++ +The configuration details depend on your {product} tenant configuration. -. Copy and paste the code or download it as a configuration file (it will be called `rabbitmq.conf`). - -You're now ready to connect RabbitMQ and Pulsar. - -== Test connection +== Connect RabbitMQ and Pulsar -We'll use a Python script to create a connection between RabbitMQ and your Pulsar tenant, establish a message queue named `queuename`, print ten messages, and close the connection. +This example uses a Python script to create a connection between RabbitMQ and your Pulsar tenant, establish a message queue named `queuename`, print ten messages, and then close the connection. -. Create a file called `connect-test.py` and paste the below Python code into it: +. Create a `connect-test.py` file containing the following code: + -[source,python] +.connect-test.py +[source,python,subs="+quotes"] ---- import ssl import pika -virtual_host = "{your_tenant_name}/rabbitmq" -token = "{pulsar_token}" +virtual_host = "**VIRTUAL_HOST**" +token = "**TOKEN**" context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) context.verify_mode = ssl.CERT_NONE @@ -64,9 +63,9 @@ ssl_options = pika.SSLOptions(context) connection = pika.BlockingConnection(pika.ConnectionParameters( virtual_host=virtual_host, - host="{your_host_name}", + host="**HOST_NAME**", ssl_options=ssl_options, - port=5671, + port=**PORT**, credentials=pika.PlainCredentials("", token))) print("connection success") @@ -83,19 +82,27 @@ for x in range(10): connection.close() ---- ++ +Replace the following with values from your downloaded `rabbitmq.conf` file: ++ +* `**VIRTUAL_HOST**` +* `**TOKEN**` +* `**HOST_NAME**` +* `**PORT**` -. Replace the following values in `connect-test.py` with values from your downloaded `rabbitmq.conf` file: -* `virtual_host` -* `token` -* `host` -* `port` +. Save the `connect-test.py` file. -. Save `connect-test.py` with the new values. -. Run `connect-test.py` with `python3 connect-test.py`. It should return: +. Run `connect-test.py`: + -[source,bash] +[source,shell] ---- python3 connect-test.py +---- + +. Make sure the result is similar to the following: ++ +[source,console] +---- connection success started a channel sent one @@ -110,9 +117,10 @@ started a channel sent one ---- -. Navigate to your `rabbitmq` namespace dashboard in {product} and monitor your activity. - -You should see new topics called `amq.default.__queuename` and `amq.default_routingkey` that were created by the Python script above, and an increasing amount of traffic and messages. Your RabbitMQ messages are being published to a Pulsar topic. +. Navigate to your `rabbitmq` namespace dashboard in {product}, and then monitor your activity. ++ +If configured correctly, you should have new topics called `amq.default.__queuename` and `amq.default_routingkey` that were created by the Python script, as well as an increasing amount of traffic and messages. +Your RabbitMQ messages are being published to a Pulsar topic. == RabbitMQ exchanges and Pulsar topics diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index e66928e..02221cd 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -63,20 +63,20 @@ This is the only time you will be able to copy it. . Set your environment variables: + -[source,shell,subs="attributes+"] +[source,shell,subs="+quotes"] ---- -PULSAR_TOKEN="" -WEB_SERVICE_URL="" +PULSAR_TOKEN="**PULSAR_TOKEN**" +WEB_SERVICE_URL="**TENANT_WEB_SERVICE_URL**" ---- . Run a curl command. The following example lists built-in sink connectors. + -[source,shell,subs="attributes+"] +[source,curl] ---- -curl -sS --fail --location --request GET \ - -H "Authorization: $PULSAR_TOKEN" \ - "$WEB_SERVICE_URL/admin/v3/sinks/builtinsinks" +curl -sS --location -X GET "$WEB_SERVICE_URL/admin/v3/sinks/builtinsinks" \ +--header "Authorization: $PULSAR_TOKEN" \ +--header "Content-Type: application/json" ---- + The default response is a single JSON string. diff --git a/modules/operations/pages/astream-scrape-metrics.adoc b/modules/operations/pages/astream-scrape-metrics.adoc index 32e89f4..e028f53 100644 --- a/modules/operations/pages/astream-scrape-metrics.adoc +++ b/modules/operations/pages/astream-scrape-metrics.adoc @@ -9,13 +9,14 @@ This doc will show you how to scrape an {product} tenant with Prometheus. * {product} tenant * Docker installed locally -== Get configuration file from {product} +== Get the configuration file from {product} -. To start connecting your {product} tenant with Prometheus, select *Prometheus* in the {product} *Connect* tab. +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -. A new configuration file will be generated in the *Connect* tab that looks like this: +. On the *Connect* tab, click *Prometheus* to generate a new configuration file: + -[source,yaml] +.prometheus.yml +[source,yaml,subs="+quotes"] ---- global: scrape_interval: 60s @@ -25,68 +26,74 @@ scrape_configs: - job_name: "astra-pulsar-metrics" scheme: 'https' - metrics_path: '/pulsarmetrics/tenant-1' + metrics_path: '/pulsarmetrics/**TENANT_NAME**' authorization: - credentials: '' + credentials: '**PULSAR_TOKEN**' static_configs: - - targets: [https://prometheus-aws-useast2.dev.streaming.datastax.com/pulsarmetrics/tenant-1] + - targets: [https://prometheus-**PROVIDER**-**REGION**.streaming.datastax.com/pulsarmetrics/**TENANT_NAME**] ---- + -This example `prometheus.yml` will scrape `\https://prometheus-aws-useast2.dev.streaming.datastax.com/pulsarmetrics/tenant-1` every 60 seconds with the supplied Pulsar token. -+ +The default `prometheus.yml` scrapes the `target` tenant every 60 seconds, and it uses the specified `**PULSAR_TOKEN**` for authentication. The `job_name` is added as a label to any timeseries scraped with this configuration. -. Copy and paste the configuration code, or download it as a `.yml` file (it will be called `prometheus.yml`). +. Copy the configuration YAML or download the `prometheus.yml` file. -== Build Prometheus with custom yml file +== Build Prometheus -Prometheus runs with a `prometheus.yml` file found either locally or in a Docker container. For this example, we'll tell Docker to run Prometheus from our downloaded `prometheus.yml` file. +Prometheus runs with a `prometheus.yml` file available locally or in a Docker container. +This example uses Docker. -. Pull the Prometheus Docker image with `docker pull prom/prometheus`. +. Pull the Prometheus Docker image: ++ +[source,shell] +---- +docker pull prom/prometheus +---- -. Bind-mount your modified `prometheus.yml` file by running the Prometheus Docker container with a modified path in the `-v` argument. +. Bind-mount your modified `prometheus.yml` file by running the Prometheus Docker container with a modified path in the `-v` argument: + -[source,docker] +[source,shell] ---- docker run \ -p 9090:9090 \ - -v //prometheus.yml:/etc/prometheus/prometheus.yml \ + -v /**PATH_TO_PROMETHEUS_YAML**:/etc/prometheus/prometheus.yml \ prom/prometheus ---- -. If you receive output similar to below, you're ready to scrape with Prometheus. +. Make sure the response is similar to the following example. +This indicates that Prometheus is ready to scrape your {product} tenant: + -[source,docker] +[source,console] ---- ts=2022-05-10T20:40:30.877Z caller=main.go:1199 level=info msg="Completed loading of configuration file" filename=/etc/prometheus/prometheus.yml totalDuration=2.75025ms db_storage=584ns remote_storage=708ns web_handler=167ns query_engine=416ns scrape=262.125µs scrape_sd=12.208µs notify=667ns notify_sd=792ns rules=1.042µs tracing=2.959µs ts=2022-05-10T20:40:30.877Z caller=main.go:930 level=info msg="Server is ready to receive web requests." ---- + -[TIP] -==== -If you get a `mounts denied` permissions error, in Docker, go to *File Sharing > Resources*, and then make sure your local directory is shared with Docker. -==== +If you get a `mounts denied` permissions error, make sure your local directory is shared with Docker. +In Docker Desktop, click *File Sharing*, click *Resources*, and then make sure your local directory is listed. == Scrape with Prometheus -. Open your Prometheus dashboard at `localhost:9090`. In the *Status > Targets* window, you should see the endpoint targeted in `static_configs` in an *UP* state. +. Navigate to your Prometheus dashboard at `localhost:9090`. + +. Click *Status*, select *Targets*, and then make sure your `status_config.targets` endpoint's status is *UP*. -. Navigate to the *Graph* window. Enter `pulsar_in_messages_total` in the *Expression* field and select *Execute*. Prometheus will now display total incoming Pulsar messages to your {product} cluster. +. Navigate to `http://localhost:9090/graph`, and click the *Graph* tab. -. Produce a few messages in your tenant with the Pulsar CLI or the {product} Websocket. +. In the *Expression* field, enter `pulsar_in_messages_total`, and then click *Execute*. +Prometheus now shows the total incoming Pulsar messages for your {product} cluster. -. Your Prometheus graph displays the number of incoming Pulsar messages with each 60 second scrape. +. To test the integration, produce a few messages in your tenant with the Pulsar CLI or the {product} Websocket. +Make sure your Prometheus graph displays the number of incoming Pulsar messages with each 60 second scrape: + image::astream-prometheus-graph.png[Scraping {product} with Prometheus] -You're scraping your {product} tenant with Prometheus! - == Content encoding {product} supports content encoding with either `gzip` or `deflate`. -With the example from above still running, use a `curl` request to decompress your Prometheus scrape data: +With your Prometheus scrape container running, you can use curl commands to decompress your Prometheus scrape data: [tabs] ====== @@ -109,7 +116,7 @@ include::example$curl_gzip.sh[] -- ====== -*Deflate* or *Gzip* will extract your scraped metrics in a format such as the following: +Deflate or Gzip will extract your scraped metrics in a format such as the following: [source,console] ---- @@ -128,8 +135,8 @@ The following Prometheus metrics are exposed by {product}: * https://pulsar.apache.org/docs/reference-metrics/#pulsar-functions[Pulsar Functions metrics] * https://pulsar.apache.org/docs/reference-metrics/#connectors[Connector metrics] -Cluster operational metrics are *not* exposed to individual cluster tenants. -A tenant can only access its own metrics on the *broker* or *function worker* pods. +Cluster operational metrics are _not_ exposed to individual cluster tenants. +A tenant can only access its own metrics on the broker pod or function worker pod. == See also diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index b794dc5..a113da4 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -73,81 +73,100 @@ The following table gives the list of recommended source connector metrics as a include::example$sink-connector-metrics.csv[] |=== +[#aggregate-astra-streaming-metrics] == Aggregate {product} Metrics +[IMPORTANT] +==== +Do _not_ aggregate metrics if you are on the *Pay As You Go* {product} plan because one cluster can be shared among multiple organizations. +For more information, see xref:astream-limits.adoc[]. +==== + Each externally exposed raw {product} metric is reported at a very low level, at each individual server instance (the `exported_instance` label) and each topic partition (the `topic` label). The same raw metrics could come from multiple server instances. From a {product} user's perspective, the direct monitoring of raw metrics is not really useful. Raw metrics need to be aggregated first - for example, by averaging or summing the raw metrics over a period of time. -Below is an example of a raw metric for the Pulsar message backlog (pulsar_msg_backlog) scraped from an {product} cluster located in the GCP US Central region: +The following example shows some raw metrics for a Pulsar message backlog (`pulsar_msg_backlog`) scraped from an {product} cluster in the Google Cloud `us-central1` region: -.Show raw metric for Pulsar message backlog: -[%collapsible] -==== +[source,console] +---- .... -pulsar_msg_backlog{app="pulsar", cluster="pulsar-gcp-uscentral1", component="broker", controller_revision_hash="pulsar-gcp-uscentral1-broker-f", exported_instance=":", exported_job="broker", helm_release_name="astraproduction-gcp-pulsar-uscentral1", instance="prometheus-gcp-uscentral1.streaming.datastax.com:443", job="astra-pulsar-metrics-msgenrich", kubernetes_namespace="pulsar", kubernetes_pod_name="pulsar-gcp-uscentral1-broker-3", namespace="msgenrich/testns", prometheus="pulsar/astraproduction-gcp-pulsar-prometheus", prometheus_replica="prometheus-astraproduction-gcp-pulsar-prometheus-0", pulsar_cluster_dns="gcp-uscentral1.streaming.datastax.com", release="astraproduction-gcp-pulsar-uscentral1", statefulset_kubernetes_io_pod_name="pulsar-gcp-uscentral1-broker-3", topic="persistent://msgenrich/testns/raw-partition-0"} +pulsar_msg_backlog{app="pulsar", cluster="pulsar-gcp-uscentral1", component="broker", controller_revision_hash="pulsar-gcp-uscentral1-broker-f", exported_instance=":", exported_job="broker", helm_release_name="astraproduction-gcp-pulsar-uscentral1", instance="prometheus-gcp-uscentral1.streaming.datastax.com:443", job="astra-pulsar-metrics-demo", kubernetes_namespace="pulsar", kubernetes_pod_name="pulsar-gcp-uscentral1-broker-3", namespace="demo/testns", prometheus="pulsar/astraproduction-gcp-pulsar-prometheus", prometheus_replica="prometheus-astraproduction-gcp-pulsar-prometheus-0", pulsar_cluster_dns="gcp-uscentral1.streaming.datastax.com", release="astraproduction-gcp-pulsar-uscentral1", statefulset_kubernetes_io_pod_name="pulsar-gcp-uscentral1-broker-3", topic="persistent://demo/testns/raw-partition-0"} .... -==== +---- -{company} recommends the following guidelines for aggregating raw metrics: +To transform raw metrics into a usable state, {company} recommends the following: * Aggregate metrics at the parent topic level, at minimum, instead of at the partition level. In Pulsar, end user applications only deal with messages at the parent topic level; however, internally, Pulsar handles message processing at the partition level. -* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores (++__++). -For example, enabling Pulsar's Kafka protocol handler through S4K integration, creates a system namespace called ++__kafka++ that has one system topic called ++__transaction_producer_state++. - -* Do _not_ aggregate metrics if you are on the *Pay As You Go* {product} plan because one cluster can be shared among multiple organizations. -For more information, see xref:astream-limits.adoc[]. +* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores (`__`). +For example, enabling Pulsar's Kafka protocol handler through S4K integration, creates a system namespace called `__kafka` that has one system topic called `\__transaction_producer_state`. === PromQL query patterns -Prometheus provides a powerful but easy-to-use query language called PromQL for selecting and aggregating time series data in real time. PromQL syntax is beyond this document's scope, but the https://prometheus.io/docs/prometheus/latest/querying/basics/[Prometheus documentation] is a great place to start. +PromQL is Prometheus's simple and powerful query language that you can use to select and aggregate time series data in real time. +For more information, see the https://prometheus.io/docs/prometheus/latest/querying/basics/[PromQL documentation]. + +{company} recommends the following PromQL query patterns for aggregating raw {product} metrics. +These examples use the `pulsar_msg_backlog` raw metric to demonstrate the patterns. +Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher and they exclude system topics (with the PromQL statement `{topic !~ ".*__.*"}`). -In the rest of this section, we'll recommend some PromQL query patterns for aggregating raw {product} metrics. -These examples use one {product} raw metric, pulsar_msg_backlog, as an example for illustrative purposes. -We aggregate messages at the parent topic level or above, and exclude system topics per our recommendations above. -We filter out system messages with the pattern +{topic !~ ".*__.*"}+. -This PromQL pattern filters out messages when their topic labels do not include +__+. -This works because Pulsar system topics usually have ++__++ as the topic or namespace name prefix (e.g. persistent:///++__++kafka/__consumer_offsets_partition_0). -This pattern assumes that the user applications don't also have namespaces and topics with +__+ as part of the names, or they will be filtered as well. +//TODO: Check the styles of double underscore and query filters -Pattern 1: Get the total message backlog of a specific parent topic, excluding system topics. -"$ptopic" is a (Grafana dashboard) variable that represents a specific parent topic. -[source,psql] +.About the system topics filter +[%collapsible] +==== +The PromQL pattern `{topic !~ ".*__.*"}` filters out messages with topic labels that do not include two consecutive underscores. +This works because Pulsar system topics and namespaces are usually prefixed by two underscores, such as `persistent:///__kafka/__consumer_offsets_partition_0`. +However, this pattern assumes that your applications' namespace and topic names don't contain double underscores. +If they do, they are also filtered. +==== + +==== Get the total message backlog of a specific parent topic, excluding system topics + +`$ptopic` is a Grafana dashboard variable that represents a specific parent topic. + +[source,pgsql] ---- sum(pulsar_msg_backlog{topic=~$ptopic, topic !~ ".*__.*"}) ---- -Pattern 2: Get the total message backlog of a specific namespace, excluding system topics. -"$namespace" is a (Grafana dashboard) variable that represents a specific namespace. -[source,psql] +==== Get the total message backlog of a specific namespace, excluding system topics + +`$namespace` is a Grafana dashboard variable that represents a specific namespace. + +[source,pgsql] ---- sum(pulsar_msg_backlog{namespace=~"$namespace", topic !~ ".*__.*"}) ---- -Pattern 3: Get the total message backlog of a tenant, excluding system topics. -"$tenant" is a (Grafana dashboard) variable that represents a specific tenant. -[source,psql] +==== Get the total message backlog of a tenant, excluding system topics + +`$tenant` is a (Grafana dashboard) variable that represents a specific tenant. + +[source,pgsql] ---- sum(pulsar_msg_backlog{namespace=~"$tenant.+"", topic !~ ".*__.*"}) ---- -Pattern 4: Get the total message backlog of each topic within a specific namespace, excluding system topics. -[source,psql] +==== Get the total message backlog of each topic within a specific namespace, excluding system topics + +[source,pgsql] ---- sum by(topic) (pulsar_msg_backlog{namespace=~"$namespace", topic !~ ".*__.*"}) ---- -Pattern 5: Get the top 10 message backlog by topic within a specific namespace, excluding system topics. -[source,psql] +==== Get the top 10 message backlog by topic within a specific namespace, excluding system topics + +[source,pssql] ---- -topk by(topic) (10, sum(pulsar_msg_backlog{namespace=~"$namespace", topic !~ ".*__.*"}) +topk (10, sum by(topic) (pulsar_msg_backlog{namespace=~"$namespace", topic !~ ".*__.*"})) ---- -== Metrics to be alerted +== Metrics alerts -Most of the exposed {product} metrics are for informational purposes only and in most cases the metrics values are just reflecting the application workload characteristics. For example, message rate or throughput are common examples of such metrics. +Most of the exposed {product} metrics reflect generic application workload characteristics, such as message rate or throughput, and they are for informational purposes only. -There are, however, several metrics that need special attention when we see an increasing number of their values. Among the exposed {product} metrics, these metrics are as follows: +However, {company} recommends that you monitor the following metrics for unexpected increases: .Metrics for alerting [%header,format=csv,cols="2,2,1,3"] @@ -156,16 +175,18 @@ include::example$alert-metrics.csv[] |=== === Alerting rules -In a perfect world, these metrics should always stay at 0, but in reality, these metrics will increase when the application workload becomes heavier. -If your system is behaving correctly, these metrics should go down when the application workload drops. -A simple way to trigger an alert on these metrics is to set a threshold which triggers an alert when the metric exceeds it. However, this will probably cause false alarms during workload spikes. +In a perfect world, these metrics would always be `0`. +In reality, these metrics will increase when an application's workload increases, and then return to normal when the workload decreases. + +You can set an alert threshold to be notified when these metrics exceed normal capacity, but this can cause false alarms during expected workload spikes. -A better approach is calculating the metrics' increase rate over a period of time (e.g. 1 hour) and setting a threshold on the rate of increase. -For example, if the average message backlog increase rate exceeds a threshold, an alert is triggered. +Alternatively, you can calculate the metrics' increase rate over a period of time, such as one hour, and then set a threshold based on the rate of increase. +For example, if the average message backlog increase rate exceeds the given threshold, an alert is triggered. -The actual threshold values for these metrics is highly dependent on each application's workload and requirements, but the values should be relatively large positive numbers, e.g. several hundreds or several thousands. -Otherwise, they may trigger too many false alarms. +Thresholds for these metrics depends on your application's routine workloads and requirements. +Generally, these values are large positive numbers, ranging in the several hundreds or several thousands. +If your receive too many false alarms, adjust the alert threshold to a higher value. == See also diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index 49aace8..ecf7e0e 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -12,20 +12,33 @@ This page explains how to set up a Prometheus server in a Kubernetes cluster and == Configure and deploy Prometheus -In this example, we use the https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack[Prometheus Community Kubernetes Helm Charts] to set up a Prometheus server and a Grafana server in a K8s cluster. +This example uses the https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack[Prometheus Community Kubernetes Helm Charts] to set up a Prometheus server and a Grafana server in a Kubernetes cluster. -. Create a configuration yaml file (e.g. astra-msgenrich.yml) using the {product} Prometheus configuration from the {astra_ui}. -For more on downloading the Prometheus configuration from the {astra_ui}, see https://docs.datastax.com/en/streaming/astra-streaming/operations/astream-scrape-metrics.html[Scrape metrics from {product}]. +. Download the {product} Prometheus configuration from the {astra_ui}. +For more information, see https://docs.datastax.com/en/streaming/astra-streaming/operations/astream-scrape-metrics.html[Scrape metrics from {product}]. + +. Use the downloaded file to create a `config.yml`. ++ +This example uses a job named `astra-pulsar-metrics-demo`. +The values in your `config.yml` depend on your tenant configuration. + -[source,yaml] +.config.yml +[source,yaml,subs="+quotes"] ---- -- job_name: 'astra-pulsar-metrics-msgenrich' - scheme: 'https' - metrics_path: '/pulsarmetrics/msgenrich' - authorization: - credentials: - static_configs: - - targets: ['prometheus-gcp-uscentral1.streaming.datastax.com'] +global: + scrape_interval: 60s + evaluation_interval: 60s + +scrape_configs: + - job_name: "astra-pulsar-metrics-demo" + + scheme: 'https' + metrics_path: '/pulsarmetrics/**TENANT_NAME**' + authorization: + credentials: '**PULSAR_JWT_TOKEN**' + + static_configs: + - targets: [https://prometheus-**PROVIDER**-**REGION**.streaming.datastax.com/pulsarmetrics/**TENANT_NAME**] ---- + [IMPORTANT] @@ -34,20 +47,23 @@ Do _not_ add other Prometheus configurations, like `scrape_interval` or `evaluat These are added later when configuring the Helm chart. ==== -. Create a K8s secret named astra-msgenrich with the above configuration file and apply it. +. Create a Kubernetes secret with the above configuration file, and then apply it: + -[source,bash] +[source,bash,subs="+quotes"] ---- -kubectl create secret generic astra-msgenrich \ - --from-file=astra-msgenrich.yml \ - --dry-run=client -oyaml > k8s-additional-scrape-config-msgenrich.yaml -kubectl apply -f k8s-additional-scrape-config-msgenrich.yaml +kubectl create secret generic **SECRET_NAME** \ + --from-file=config.yml \ + --dry-run=client -oyaml > k8s-additional-scrape-config-demo.yaml +kubectl apply -f k8s-additional-scrape-config-demo.yaml ---- ++ +You can change the name of the YAML output file, if desired. -. Create a customized values file (e.g. custom-values.yaml) for the Prometheus Community Kubernetes Helm chart. -Pay close attention to the 'additionalScrapeConfigsSecret' section and ensure that the name and key match the K8s secret name and file name in the previous 2 steps. +. Create a customized values file (`custom-values.yml`) for the Prometheus Community Kubernetes Helm chart. +In the 'additionalScrapeConfigsSecret' section, make sure the `name` and `key` match the name if your Kubernetes secret and config file name, such as `astra-secret` and `config.yml`. + -[source,yaml] +.custom-values.yml +[source,yaml,subs="+quotes"] ---- prometheus: prometheusSpec: @@ -55,22 +71,22 @@ prometheus: evaluationInterval: 15s additionalScrapeConfigsSecret: enabled: true - name: astra-msgenrich - key: astra-msgenrich.yml + name: *SECRET_NAME* + key: *CONFIG_YAML* ---- -. Deploy the Helm chart. +. Deploy the Helm chart: + -[source,yaml] +[source,shell] ---- helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm upgrade --install kubeprom -f custom-values.yaml prometheus-community/kube-prometheus-stack ---- -[discrete] -== Optional -For Mac users (e.g. using docker-desktop as the underlying K8s cluster), run the following commands to ensure the deployment is successful. Otherwise, there might be issues to bring up the Prometheus node exporter daemon set. -[source,bash] +. For macOS users who run Docker Desktop as the underlying Kubernetes cluster, run the following commands to ensure the deployment is successful. +If you don't run this command, you might encounter a problem when initializing the Prometheus node exporter daemon set. ++ +[source,shell] ---- # Patch Prometheus node exporter daemon set kubectl patch ds kubeprom-prometheus-node-exporter --type "json" -p '[{"op": "remove", "path" : "/spec/template/spec/containers/0/volumeMounts/2/mountPropagation"}]' @@ -81,19 +97,14 @@ kubectl rollout status daemonset \ --timeout 60s ---- -== Verify the integration - -The {product} metrics should now be integrated with an external Prometheus server. Double check by visiting the external Prometheus server UI. - -The status of the additional scrape job 'astra-pulsar-metrics-msgenrich' should be in the UP state. If not, there are issues in the previous configuration procedures. - -image::scrape-status.png[] - -=== 401 Unauthorized Issue +. To confirm that {product} metrics are integrated with your external Prometheus server, go to your external Prometheus server UI. +Make sure the additional scrape job is in `UP` status. +If not, there are issues in the previous configuration procedures. -One common issue that we see when integrating {product} metrics into an external Prometheus server is the additional scrape job returning a 401 Unauthorized error. This is most likely because the JWT token we used in the previous steps has expired. +== Troubleshoot 401 Unauthorized errors -To fix this issue, go to the {astra_ui} and create a new JWT token (preferably with no expiration date). Get the new token value and repeat the above procedure. For more, see xref:astream-token-gen.adoc[]. +If the additional scrape job returns a 401 Unauthorized error, make sure your Pulsar JWT token isn't expired. +For more information, see xref:astream-token-gen.adoc[]. == See also diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index e1469e7..a3cc6e9 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -1,20 +1,6 @@ = New Relic Integration -There are three ways to integrate external Prometheus data into New Relic: - -* https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#Agent[Prometheus Agent for Kubernetes] -* https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#OpenMetrics[Prometheus OpenMetrics integration for Docker] -* https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] - -Only the third option is relevant to {product}. - -Typically, this option requires modifying the configuration of the Prometheus server that {product} relies on. -However, this isn't possible because {product} is a managed service. - -Instead, you can xref:monitoring/integration.adoc[install an extra Prometheus server] to act as a bridge to forward scraped {product} metrics to New Relic. - -.External Prometheus server for New Relic integration -image::monitoring-map.png[Map,align="center"] +While there are multiple ways to https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic[send external Prometheus data to New Relic], one the https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] option is relevant to {product}. == Prerequisites @@ -22,56 +8,59 @@ image::monitoring-map.png[Map,align="center"] * https://docs.newrelic.com/[Set up a New Relic account.] * Save your new relic license key locally. +== Prepare the extra Prometheus server + +* xref:monitoring/integration.adoc[Install an extra Prometheus server] to act as a bridge to forward scraped {product} metrics to New Relic. + +This is required because {product} is a managed service, and you can't modify the {product} Prometheus server as required by the New Relic Prometheus write integration. + == Configure New Relic +Configure the `remoteWrite` integration on your local Prometheus server. +For more information, see https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/install-configure-remote-write/set-your-prometheus-remote-write-integration/[Set up your Prometheus remote write integration]. + +This example uses a local Prometheus server installed in a local Docker Desktop Kubernetes cluster. +Your `remoteWrite` configuration depends on your Prometheus server setup. + . In your New Relic account, click *Add Data*. . Under *Open source monitoring*, select *Prometheus Remote Write Integration*. . Enter the name of your local Prometheus server, such as `prometheus-docker-desktop`, and then click *Generate URL*. -This generates the endpoint URLs required to configure a `remote_write integration` on your local Prometheus server. +This generates the endpoint URLs required to configure a `remote_write` integration on your local Prometheus server. -. Configure and restart your local Prometheus server. -For this example, the local Prometheus server is installed in a local Docker Desktop Kubernetes cluster; therefore, the installation and configuration method are related to Kubernetes. - -.. Create a K8s secret that corresponds to the New Relic license key that you received when setting up the account: +. Create a Kubernetes secret with your New Relic license key: + -[source,bash] +[source,shell,subs="+quotes"] ---- -kubectl create secret generic nr-license-key --from-literal=value= +kubectl create secret generic nr-license-key --from-literal=value=**LICENSE_KEY** ---- -.. Modify the Prometheus configuration in the kube-prometheus-stack Helm chart file, such as `custom-values.yaml`: +. In your extra Prometheus server's `custom-values.yaml` file, add the `remoteWrite` configuration to send local Prometheus metrics to New Relic through `remote_write`: + -[source,yaml] +.custom-values.yaml +[source,yaml,subs="+quotes"] ---- prometheus: prometheusSpec: scrapeInterval: 60s evaluationInterval: 15s additionalScrapeConfigsSecret: - enabled: true - name: astra-msgenrich - key: astra-msgenrich.yml + enabled: true + name: *SECRET_NAME* + key: *CONFIG_YAML* remoteWrite: - - url: https://metric-api.newrelic.com/prometheus/v1/write?prometheus_server=prometheus-docker-desktop - authorization: - credentials: - key: value - name: nr-license-key + - url: https://metric-api.newrelic.com/prometheus/v1/write?prometheus_server=**CLUSTER_NAME** + authorization: + credentials: + key: value + name: nr-license-key ---- -+ -The first lines of this configuration are for scraping {product} metrics (as described in xref:monitoring/integration.adoc[]). -The `remoteWrite` section is for sending local Prometheus metrics to New Relic through `remote_write`. . Restart your local Prometheus server. -. In your New Relic account, confirm the configuration. -If everything is set up correctly, you should see {product} metrics in New Relic. -In the following screenshot, the New Relic *Data Browsing* page has Pulsar message backlog metrics. - -image::pulsar-namespace-metrics.png[Metrics,align="center"] +. In your New Relic account, go to the *Remote Write Dashboard* to confirm that {product} metrics are visible in New Relic. == See also diff --git a/modules/operations/pages/monitoring/stream-audit-logs.adoc b/modules/operations/pages/monitoring/stream-audit-logs.adoc index ebf056a..10de31e 100644 --- a/modules/operations/pages/monitoring/stream-audit-logs.adoc +++ b/modules/operations/pages/monitoring/stream-audit-logs.adoc @@ -30,19 +30,16 @@ You can use topics to organize audit logs by event type or other criteria. You can use the xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/configureTelemetry[{astra_db} DevOps API telemetry endpoint] to configure audit log streaming instead of providing the configuration details to {company} Support. -. In the {link-astra-portal}, create an application token with the **Organization Administrator** role, if you don't already have one. +. In the {link-astra-portal}, create an application token with the **Organization Administrator** role. -. To create the audit log streaming configuration, send a POST request with your topic’s full name and the required values from the tenant's `client.conf` file. +. Create the audit log streaming configuration using the values from the tenant's `client.conf` file and your Pulsar configuration: + -The `auth_strategy` and other authentication details depend on your Pulsar configuration. -+ -[source,curl,subs="verbatim,quotes"] +[source,curl,subs="+quotes"] ---- -curl --request POST \ - --url 'https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs' \ - --header 'Accept: application/json' \ - --header 'Authorization: Bearer **APPLICATION_TOKEN**' \ - --data '{ +curl -sS -X POST "https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs" \ +--header "Authorization: Bearer **APPLICATION_TOKEN**" \ +--header "Accept: application/json" +--data '{ "pulsar": { "endpoint": "pulsar+**BROKER_SERVICE_URL**", "auth_strategy": "token", @@ -53,6 +50,14 @@ curl --request POST \ }' ---- + +Replace the following: ++ +* `**ORG_ID**`: Your {astra_db} organization ID. +* `**APPLICATION_TOKEN**`: Your {astra_db} application token. +* `**BROKER_SERVICE_URL**`: The {product} broker service URL, such as `ssl://pulsar-aws-useast2.streaming.datastax.com:6651`. +* `**TOPIC_FULL_NAME**`: The full name of the {product} topic where you want to stream audit logs. +* The `auth_strategy` and other authentication details depend on your Pulsar configuration. ++ .Response [%collapsible] ==== @@ -61,13 +66,14 @@ curl --request POST \ HTTP/1.1 202 Accepted ---- ==== -. To retrieve and verify the audit log streaming configuration, send a GET request: + +. Retrieve and verify the audit log streaming configuration: + -[source,curl,subs="verbatim,quotes"] +[source,curl,subs="+quotes"] ---- -curl --request GET \ - --url 'https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs' \ - --header 'Authorization: Bearer **APPLICATION_TOKEN**' +curl -sS -X GET "https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs" \ +--header "Authorization: Bearer **APPLICATION_TOKEN**" \ +--header "Accept: application/json" ---- + .Response @@ -87,5 +93,6 @@ curl --request GET \ ---- ==== -. To delete an audit log streaming configuration, xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/deleteTelemetryConfig[send a DELETE request]. +== Delete an audit log streaming configuration +To delete an audit log streaming configuration, xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Organization-Operations/operation/deleteTelemetryConfig[send a DELETE request]. \ No newline at end of file From 6243749cd8df75db0db970ef681d77512259491c Mon Sep 17 00:00:00 2001 From: April M Date: Tue, 15 Oct 2024 16:11:01 -0700 Subject: [PATCH 08/18] source blocks pt 2 --- modules/ROOT/nav.adoc | 18 +- modules/apis/nav.adoc | 3 +- modules/apis/pages/index.adoc | 8 +- modules/developing/nav.adoc | 16 +- modules/developing/pages/astra-cli.adoc | 65 ++---- modules/developing/pages/astream-cdc.adoc | 139 ++++++------ .../pages/clients/csharp-produce-consume.adoc | 90 ++++---- .../pages/clients/golang-produce-consume.adoc | 98 ++++----- .../pages/clients/java-produce-consume.adoc | 149 +++++++------ .../pages/clients/nodejs-produce-consume.adoc | 113 +++++----- .../pages/clients/python-produce-consume.adoc | 97 ++++----- .../pages/clients/spring-produce-consume.adoc | 41 ++-- .../pages/configure-pulsar-env.adoc | 105 ++++------ .../pages/gpt-schema-translator.adoc | 197 ++++++------------ .../pages/produce-consume-pulsar-client.adoc | 35 ++-- modules/developing/pages/using-curl.adoc | 66 +++--- modules/getting-started/pages/index.adoc | 48 +++-- modules/operations/nav.adoc | 35 ++-- .../pages/astream-georeplication.adoc | 55 ++--- 19 files changed, 608 insertions(+), 770 deletions(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 4e72403..44d3e9e 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -1,21 +1,5 @@ -.Guides and examples -* Manage permissions -** xref:astream-org-permissions.adoc[] -** xref:astream-custom-roles.adoc[] -* Pulsar subscriptions -** xref:astream-subscriptions.adoc[] -** xref:astream-subscriptions-exclusive.adoc[] -** xref:astream-subscriptions-shared.adoc[] -** xref:astream-subscriptions-failover.adoc[] -** xref:astream-subscriptions-keyshared.adoc[] - -// - -.IO connectors -* xref:streaming-learning:pulsar-io:connectors/index.adoc[IO Connectors] - .Frequently asked questions -* xref:astream-faq.adoc[] +* xref:ROOT:astream-faq.adoc[] * xref:operations:onboarding-faq.adoc[] .Release notes diff --git a/modules/apis/nav.adoc b/modules/apis/nav.adoc index d07ed70..e2445dc 100644 --- a/modules/apis/nav.adoc +++ b/modules/apis/nav.adoc @@ -1,3 +1,4 @@ .API references * xref:index.adoc[] -* xref:api-operations.adoc[] +* xref:developing:using-curl.adoc[] +* xref:api-operations.adoc[] \ No newline at end of file diff --git a/modules/apis/pages/index.adoc b/modules/apis/pages/index.adoc index 7a40973..e9e81e3 100644 --- a/modules/apis/pages/index.adoc +++ b/modules/apis/pages/index.adoc @@ -25,4 +25,10 @@ In OSS Pulsar you manage instances, the clusters within each instance, the tenan In {product}, clusters are a managed service. You manage only the tenants and resources within those tenants. -Some OSS Pulsar Admin API endpoints aren't supported in the {product} Pulsar Admin API because they don't apply to the {product} managed service. \ No newline at end of file +Some OSS Pulsar Admin API endpoints aren't supported in the {product} Pulsar Admin API because they don't apply to the {product} managed service. + +== See also + +* xref:apis:api-operations.adoc[] +* xref:developing:using-curl.adoc[] +* https://pulsar.apache.org/docs/reference-rest-api-overview/[Pulsar REST APIs] \ No newline at end of file diff --git a/modules/developing/nav.adoc b/modules/developing/nav.adoc index 47cb819..439c3da 100644 --- a/modules/developing/nav.adoc +++ b/modules/developing/nav.adoc @@ -1,14 +1,11 @@ * xref:ROOT:index.adoc[] -.Developing -* xref:gpt-schema-translator.adoc[] +.Develop * xref:configure-pulsar-env.adoc[] -* xref:using-curl.adoc[] -* xref:astra-cli.adoc[] * xref:astream-functions.adoc[] * xref:astream-kafka.adoc[] * xref:astream-rabbit.adoc[] -* Producing and consuming messages +* Produce and consume messages ** xref:produce-consume-astra-portal.adoc[] ** xref:produce-consume-pulsar-client.adoc[] ** Client applications @@ -19,4 +16,13 @@ *** xref:clients/golang-produce-consume.adoc[] *** xref:clients/nodejs-produce-consume.adoc[] *** xref:clients/spring-produce-consume.adoc[] +** Pulsar subscriptions +*** xref:ROOT:astream-subscriptions.adoc[] +*** xref:ROOT:astream-subscriptions-exclusive.adoc[] +*** xref:ROOT:astream-subscriptions-shared.adoc[] +*** xref:ROOT:astream-subscriptions-failover.adoc[] +*** xref:ROOT:astream-subscriptions-keyshared.adoc[] +* xref:gpt-schema-translator.adoc[] +* xref:astra-cli.adoc[] * xref:astream-cdc.adoc[] +* xref:streaming-learning:pulsar-io:connectors/index.adoc[IO Connectors] \ No newline at end of file diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index 9230655..f7ba9b0 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -1,62 +1,35 @@ -= {astra_cli} += {astra_cli} overview +:navtitle: {astra_cli} +:description: {astra_cli} provides a one-stop shop for managing your Astra resources through scripts or commands in your local terminal. -The {astra_cli} is a set of commands for creating and managing {astra_db} resources. -{astra_cli} commands are available for {astra_db} and {product}. -They're designed to get you working quickly, with an emphasis on automation. - -{astra_cli} provides a one-stop shop for managing your {astra_db} resources through scripts or commands in your local terminal. -The wide variety of capabilities include: - -* Creation and management of {astra_db} and {product} artifacts -* Querying & data loading -* Organization and user management -* Security and token configuration - -The advantage for you: {astra_cli} makes it possible to submit commands instead of *or in addition to* using {astra_ui} and {company} API calls. - -{astra_cli} features are provided especially for operators, Site Reliability Engineers (SREs), and developers who want the option of using commands when working with {astra_db} databases and {product} tenants. +The xref:astra-cli:ROOT:index.adoc[{astra_db} Command-Line Interface ({astra_cli})] is a set of commands you can use to create and manage {astra_db} and {product} resources: -== Two quick examples +* Creation and management of {astra_db} databases, {astra_stream} tenants, and their associated artifacts. +* Querying and data loading. +* Organization and user management. +* Security and token configuration. -Here are two quick {astra_cli} command examples to demonstrate how {astra_cli} manages resources across {astra_db} databases and {product} tenants from your local terminal. - -. Create an {astra_db} database named `demo` from the command line: -+ -[source,bash,subs="attributes+"] ----- -astra db create demo -k ks2 --if-not-exist --wait ----- -+ -.Result -[%collapsible] -==== -[source,bash,subs="attributes+"] ----- -[INFO] Database 'demo' does not exist. Creating database 'demo' with keyspace 'ks2' -[INFO] Database 'demo' and keyspace 'ks2' are being created. -[INFO] Database 'demo' has status 'PENDING' waiting to be 'ACTIVE' ... -[INFO] Database 'demo' has status 'ACTIVE' (took 103513 millis) -[OK] Database 'demo' is ready. ----- -==== +{astra_cli} commands are available for {astra_db} and {astra_stream}. +They're designed to get you working quickly, with an emphasis on automation. +For example, the following command create a Pulsar tenant: -Create a Pulsar tenant: -+ -[source,bash,subs="attributes+"] +[source,bash] ---- -astra streaming create new-tenant-from-cli +astra streaming create cli-tenant-demo ---- + .Result [%collapsible] ==== -[source,bash,subs="attributes+"] +[source,console] ---- -[OK] Tenant 'new-tenant-from-cli' has being created. +[OK] Tenant 'cli-tenant-demo' has been created. ---- ==== -== See also +You can use {astra_cli} instead of or in addition to the {astra_ui} and {astra_db} APIs. -* https://docs.datastax.com/en/astra-cli/docs/0.2/[{astra_cli} documentation] +For more information, see the following: +* xref:api-reference:databases.adoc[] +* https://www.datastax.com/blog/introducing-cassandra-astra-cli[Accelerate your Cassandra development and automation with the {astra-cli}] diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index c042b36..3332b5e 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -13,7 +13,7 @@ CDC for {astra_db} automatically captures changes in real time, de-duplicates th {product} processes data changes via a Pulsar topic. By design, the Change Data Capture (CDC) component is simple, with a 1:1 correspondence between the table and a single Pulsar topic. -This doc will show you how to create a CDC connector for your {astra_db} deployment and send change data to an Elasticsearch sink. +This guide explains how to connect your {astra_db} database to CDC and send change data to an Elasticsearch sink. == Supported data structures @@ -182,24 +182,28 @@ See xref:operations:astream-regions.adoc[] for more information. == Create a table . Select *Databases* from the main navigation. + . Select the name of the active database that you would like to use. + . Select the *CQL Console* tab. + . Create a table with a primary key column using the following command. Edit the command to add your *`KEYSPACE_NAME`* and choose a *`TABLE_NAME`*. + -[source,cql,subs="verbatim,quotes"] +[source,cql,subs="+quotes"] ---- CREATE TABLE IF NOT EXISTS *KEYSPACE_NAME*.*TABLE_NAME* (key text PRIMARY KEY, c1 text); ---- -+ + . Confirm that your table was created: + -[source,sql,subs="verbatim,quotes"] +[source,sql,subs="+quotes"] ---- select * from *KEYSPACE_NAME*.*TABLE_NAME*; ---- + -Result: -+ +.Result +[%collapsible] +==== [source,sql,subs="verbatim,quotes"] ---- key | c1 @@ -207,25 +211,26 @@ Result: (0 rows) ---- - -You have now created a table and confirmed that the table exists in your {db-serverless} database. +==== == Connect to CDC for {db-serverless} databases Complete the following steps after you have created a <> and a <>. -. Select *Databases* from the main navigation. -. Select the name of the active database that you would like to use. +. In the {astra_ui}, go to your database. + . Click the *CDC* tab. + . Click *Enable CDC*. -. Complete the fields to select a tenant, select a keyspace, and select the name of the table you created. + +. Select a tenant, keyspace, and table. + . Click *Enable CDC*. -Enabling CDC creates a new `astracdc` namespace with two new topics, `data-` and `log-`. +Enabling CDC creates a new `astracdc` namespace with two new topics: `data-` and `log-`. The `log-` topic consumes schema changes, processes them, and then writes clean data to the `data-` topic. -The `log-` topic is for CDC functionality and should not be used. +The `log-` topic is for mandatory CDC functionality and should not be used. The `data-` topic is used to consume CDC data in {product}. - For more information, see <>. == Connect Elasticsearch sink @@ -233,51 +238,59 @@ For more information, see < - org.apache.pulsar - pulsar-client - 2.10.2 + org.apache.pulsar + pulsar-client + 2.10.2 ---- -For this example we will be creating a single artifact. -Add the following build target in pom.xml. +. For this example, add the following build target in `pom.xml`. +This example creates a single artifact. ++ +.pom.xml [source,xml] ---- - - - maven-assembly-plugin - - - - org.example.App - - - - jar-with-dependencies - - - - + + + maven-assembly-plugin + + + + org.example.App + + + + jar-with-dependencies + + + + ---- -You may also need to specify the versions on pom.xml as well. - +. If necessary, specify the compiler versions in `pom.xml`: ++ +.pom.xml [source,xml] ---- - 11 - 11 + 11 + 11 ---- -== Add the client - -Let's set up the main Java class and have some fun! +== Write the script -Open the "src/main/java/org/example/App.java" file in your favorite text editor or IDE. -Clear the contents of the file and add the following to bring in needed dependencies, create a client instance, and configure it to use your {product} tenant. +. In your project, navigate to `src/main/java/org/example`, and then open `App.java`. +. Remove any existing content from the file, and then add the following code that imports dependencies, creates a client instance, and configures the instance to use your {product} tenant: ++ +.App.java [source,java] ---- include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=build-client] ---- ++ +This class is incomplete. +Your editor might show errors until you complete the next steps. -*This isn't a complete class*, so don't be alarmed if your editor shows errors. - -Notice there are a few variables waiting for replacement values. -You can find those values here: - +. Provide values for the following variables: ++ include::developing:partial$client-variables-table.adoc[] -== Create a producer - -Use the client to create a producer. - +. Use the client to create a producer. The producer builds on the client configuration for directions about what topic to produce messages to. - -Add this code to "App.java" to create a new producer. - ++ +.App.java [source,java] ---- include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=build-producer] ---- -Add this code to "App.java" to asynchronously send a single message to the broker and wait for acknowledgment. - +. Asynchronously send a single message to the broker and wait for acknowledgment: ++ +.App.java [source,java] ---- include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=produce-message] ---- -== Create a consumer - -Create a new consumer instance in "App.java". - -This code directs the consumer to watch a certain topic, names the subscription for watching topics, and begins the subscription. - -Add the following code to "App.java". - +. Create a new consumer instance. +This code directs the consumer to watch a certain topic, identifies the subscription for watching topics, and begins the subscription. ++ +.App.java [source,java] ---- include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=build-consumer] ---- - -With the consumer and subscription in place, we can receive the unacknowledged messages added by the producer earlier. - -Add the following to "App.java". - ++ +. Receive the messages added by the producer: ++ +.App.java [source,java] ---- include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=consumer-loop] ---- -Finally, add a little clean up and close out the Java class. - +. Clean up and close the class: ++ +.App.java [source,java] ---- include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=close-consumer] include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimpleProducerConsumer/src/main/java/org/example/App.java[tag=close-client] ---- -== Run the example - -Your Java class is ready for the big time! -Let's build the app and run. +== Run the script +. Build and run the app: ++ [source,shell] ---- mvn clean package assembly:single java -jar target/SimpleProducerConsumer-1.0-SNAPSHOT-jar-with-dependencies.jar ---- -You should see output similar to the following: +`Message received` output indicates the script succeeded: -[source,shell] +[source,console] ---- Message received: Hello World ---- == Next steps -Woo-hoo{emoji-tada}! You did it! You're on your way to messaging glory. Let's continue learning. - * xref:developing:configure-pulsar-env.adoc[] * xref:developing:astream-functions.adoc[] * xref:streaming-learning:pulsar-io:connectors/index.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index d65f2c8..461fc74 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -3,27 +3,22 @@ :description: This is a guide to create a simple producer and consumer using Pulsar's Node.js client. :page-tag: astra-streaming,connect,dev,develop,nodejs,pulsar -== Prerequisites +Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. -You will need the following prerequisites in place to complete this guide: +== Prerequisites * `sudo` permission to install dependencies -* Node.js version 10+ and npm 6+ -* A working Pulsar topic (get started xref:getting-started:index.adoc[here] if you don't have a topic) -* A basic text editor or IDE - -[TIP] -==== -Visit our https://github.com/datastax/astra-streaming-examples[examples repo] to see the complete source of this example. -==== +* Node.js version 10 or later +* npm 6 or later +* A Pulsar topic in {product} +* A text editor or IDE -== Setup environment - -Install the C++ Pulsar library dependency. -The Node.js Pulsar client npm package depends on the C++ Pulsar library. +== Set up the environment +. Install the C++ Pulsar library dependency required by the Node.js Pulsar client npm package. ++ Pulsar Node client versions 1.8 and later do not require installation of the C++ Pulsar library dependency. - ++ [tabs] ====== Ubuntu-based Debian:: @@ -53,104 +48,96 @@ sudo ldconfig == Create a project -With the environment dependencies set up, let's create a new Node.js project. - -Run the following script in a terminal. -If NPM asks for project values, use the defaults it suggests. +. In a terminal, run the following script to create a new Node.js project. +If NPM asks for project values, use the automatically suggested defaults. [source,shell] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/create-project.sh[tag=create-project] ---- -== Add the client - -A new "index.js" file will be created within the project folder. -Open that file in your editor of choice and add the following code. +== Write the script +. In your new project, open the `index.js` file, and then add the following code. ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=create-client] ---- ++ +This script is intentionally incomplete. +Your IDE might show errors until you complete the next steps. -*This isn't complete code (yet)*, so don't be alarmed if your editor shows errors. - -Notice there are a few variables waiting for replacement values. -You can find those values here: - +. Provide values for the following variables: ++ include::developing:partial$client-variables-table.adoc[] -== Create a producer - -Use the client to create a producer. - -While there are quite a few configuration options available for the producer, for now we'll just declare the topic where messages should go. - -Add the following code to "index.js". - +. Use the client to create a producer. ++ +There are many configuation options for producers. +For this example, declare the topic where messages should go. ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=create-producer] ---- -After the code above creates a producer, add this code to actually send the message and receive acknowledgment. - +. Send a message and receive acknowledgment: ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=produce-message] ---- -Finally, add a little clean-up. - +. Clean up: ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=cleanup-producer] ---- ++ +At this point, the script produces a message that waits to be consumed and acknowledged. -== Create a consumer - -If we ran the above example, we'd have a produced message waiting to be consumed and acknowledged. - -The code below creates a new consumer subscription, names the subscription, and declares what topic to watch. - -Add this code to "index.js". - +. Create a new consumer subscription, name the subscription, and declare the topic to watch: ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=create-consumer] ---- -We want this consumer to receive messages, write them to console, and acknowledge receipt with the broker. - -Add this code to "index.js". - +. Receive messages, write them to console, and acknowledge receipt with the broker: ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=consume-message] ---- -Finally, add a little clean-up. -No one likes loose ends, right?! - +. Clean up: ++ +.index.js [source,javascript] ---- include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=cleanup-consumer] ---- -== Run the example - -Alright, it's that time! Let's see if all that hard work will pay off. - -Return to the terminal and run the following command. +== Run the script +* In your Node.js project, run the script: ++ [source,shell] ---- node index.js ---- -You should see output similar to the following. +Output such as the following confirms that the script succeeded: -[source,shell] +[source,console] ---- sent message Hello World @@ -158,8 +145,6 @@ Hello World == Next steps -You did it{emoji-tada}! You're on your way to messaging glory. Let's continue learning. - * xref:developing:configure-pulsar-env.adoc[] * xref:developing:astream-functions.adoc[] * xref:streaming-learning:pulsar-io:connectors/index.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/clients/python-produce-consume.adoc b/modules/developing/pages/clients/python-produce-consume.adoc index 70f415b..ac78dda 100644 --- a/modules/developing/pages/clients/python-produce-consume.adoc +++ b/modules/developing/pages/clients/python-produce-consume.adoc @@ -3,118 +3,103 @@ :description: This is a guide to create a simple producer and consumer using Pulsar's python client. :page-tag: astra-streaming,dev,develop,python,pulsar -== Prerequisites - -You will need the following prerequisites in place to complete this guide: +Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. -* Linux Python versions 3.4 to 3.7 are supported -* MacOS version 3.7 is supported -* A working Pulsar topic (get started xref:getting-started:index.adoc[here] if you don't have a topic) -* A basic text editor or IDE +== Prerequisites -[TIP] -==== -Visit our https://github.com/datastax/astra-streaming-examples[examples repo] to see the complete source of this example. -==== +* A supported Python version: +** For Linux, versions 3.4 to 3.7 are supported +** For macOS, version 3.7 is supported +* A Pulsar topic in {product} +* A text editor or IDE == Create a project -Create a folder, change directory into it, and start a new Python project. +. Create a folder for a new Python project. -Install the Pulsar client library with pip. +. In your new directory, install the Pulsar client library with pip: ++ [source,shell] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/create-project.sh[] ---- -== Add the client - -Create a new Pulsar client instance with the topic URL and using token authentication. - -Open "index.py" in your favorite text editor or IDE and copy in the following code. +== Write the script +. Create an `index.py` file containing the following code. +This code creates a Pulsar client instance with the topic URL and token authentication. ++ +.index.py [source,python] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleProducerConsumer/index.py[tag=create-client] ---- ++ +This script is intentionally incomplete. +Your IDE might show errors until you complete the next steps. -*This isn't complete code (yet)*, so don't be alarmed if your editor shows errors. - -Notice there are a few variables waiting for replacement values. -You can find those values here: - +. Provide values for the following variables: ++ include::developing:partial$client-variables-table.adoc[] -== Create a producer - -Use the client to create a producer. - -Add the following code to "index.py". - +. Use the client to create a producer: ++ +.index.py [source,python] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleProducerConsumer/index.py[tag=create-producer] ---- -Once the code above creates a producer, add this code to "index.py" to actually send the message. - +. Send a message: ++ +.index.py [source,python] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleProducerConsumer/index.py[tag=produce-message] ---- -== Create a consumer - -Just like the producer, use the Python client instance to create a consumer subscription to the same topic we sent a message to. - -Add this code to "index.py". - +. Use the Python client instance to create a consumer subscription to the same topic that you sent a message to: ++ +.index.py [source,python] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleProducerConsumer/index.py[tag=create-consumer] ---- -And now for the good stuff! -Let's iterate through messages and write their data. - -Add this code to "index.py". - +. Iterate through messages and write their data: ++ +.index.py [source,python] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleProducerConsumer/index.py[tag=consume-message] ---- -Finally, add a little clean-up. - +. Clean up: ++ [source,python] ---- include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleProducerConsumer/index.py[tag=clean-up] ---- -== Run the example - -Here it comes: your greatest Python creation yet! +== Run the script -Return to the terminal where you created your Python project and run the following command. - -[source,python] +* In your Python project directory, run the script: ++ +[source,shell] ---- python3 index.py ---- -There will likely be a lot of logging in the output, but look closely and you should see the following message. +The output includes a lot of logs. +`Received message` confirms that the script succeeded: -[source,shell] +[source,console] ---- Received message 'Hello World' id='(422529,5,-1,0)' ---- -You did it{emoji-tada}! - == Next steps -You're one step closer to being a messaging ninja. Let's continue the learning with these guides. - * xref:developing:configure-pulsar-env.adoc[] * xref:developing:astream-functions.adoc[] * xref:streaming-learning:pulsar-io:connectors/index.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index 34645fc..af7a1dd 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -21,15 +21,18 @@ Visit our https://github.com/datastax/astra-streaming-examples[examples repo] to Spring Initializr is a great tool to quickly create a project with the dependencies you need. Let's use it to create a project with the Pulsar client dependency. -. Go to https://start.spring.io/[Spring Initializr,title=Spring Initializr] to initalize a new project. -. Select Maven, Java 17, a non-SNAPSHOT version of Spring Boot, and the Apache Pulsar dependency. +. Go to https://start.spring.io/[Spring Initializr] to initialize a new project. + +. Select Maven, Java 17, a non-SNAPSHOT version of Spring Boot, and the Apache Pulsar dependency: + image::spring-initializr.png[Spring Initializr] -. Select Generate Project and download the zip file. -. Unzip the file and open the project in your IDE. -In src/main/java/DemoApplication, you'll find the main method that will run your application, with the dependencies helpfully injected by Spring. This is where we'll add our code. + +. Click *Generate Project*, download the zip file, and then extract it. + +. Navigate to `src/main/java`, and then open the `DemoApplication.java` file. +This file contains the main method that will run your application with the dependencies specified. + -.Java application code +.DemoApplication.java [%collapsible] ==== [source,java] @@ -38,18 +41,20 @@ include::{astra-streaming-examples-repo}/java/spring-boot/SpringBoot/src/main/ja ---- ==== -. Replace the values in your Java application with values from the Connect tab of your {product} instance. +. Replace the following values in `DemoApplication.java` with values from your tenant's *Connect* tab in the {astra_ui}: + -[source,java] +.DemoApplication.java +[source,java,subs="+quotes"] ---- - private static final String serviceUrl = "pulsar+ssl://pulsar-aws-useast1.streaming.datastax.com:6651"; - private static final String pulsarToken = "ey..."; - private static final String tenantName = "homelab"; - private static final String namespace = "default"; - private static final String topicName = "clue-sensors"; + private static final String serviceUrl = "pulsar+ssl://pulsar-**PROVIDER**-**REGION**.streaming.datastax.com:**PORT**"; + private static final String pulsarToken = "**PULSAR_JTW_TOKEN**"; + private static final String tenantName = "**TENANT_NAME**"; + private static final String namespace = "**NAMESPACE_NAME**"; + private static final String topicName = "**TOPIC_NAME**"; ---- + -You could instead modify the values in the "application.properties" file in the /resources folder, which is a better practice for production applications. The Connect tab in {product} has a link to download a properties file with the values your application needs to connect to your {product} instance. +You can also modify the values in the `application.properties` file in the `/resources` directory, which is recommended for production applications. +On your tenant's *Connect* tab, you can download a properties file with the values your application needs to connect to your {product} instance. + .Spring application properties [%collapsible] @@ -60,7 +65,7 @@ include::{astra-streaming-examples-repo}/java/spring-boot/SpringBoot/src/main/re ---- ==== -. Change directory to the project root and run the following command to compile the project: +. From your project's root directory, compile the Java application with the Pulsar client dependency: + [source,bash] ---- @@ -80,10 +85,8 @@ mvn clean compile [INFO] ------------------------------------------------------------------------ ---- ==== -+ -Maven has compiled your Java application with the Pulsar client dependency. -. Run the project and send a message to your {product} cluster: +. Run the project to send a message to your {product} cluster: + [source,bash] ---- @@ -99,7 +102,7 @@ Message received: Hello World ---- ==== -Remember to check in your {product} instance to see the message you sent to the newly created subscription, "my-subscription". +. In the {astra_ui}, go to your tenant to verify that the message appears in the specified namespace and topic. == See also diff --git a/modules/developing/pages/configure-pulsar-env.adoc b/modules/developing/pages/configure-pulsar-env.adoc index 56965a8..955611d 100644 --- a/modules/developing/pages/configure-pulsar-env.adoc +++ b/modules/developing/pages/configure-pulsar-env.adoc @@ -1,93 +1,76 @@ -= Configuring your local environment for {product} -:navtitle: Using Pulsar binaries with {product} -:description: This guide will provide the necessary steps to download a compatible Pulsar artifact and configure the binaries for use with {product}. += Use Pulsar binaries with {product} +:navtitle: Configure the Pulsar CLI +:description: Download a compatible Pulsar artifact and configure the binaries for use with {product}. -Did we mention that {product} is running Apache Pulsar(TM)? +{product} runs Apache Pulsar(TM). +The benefits of OSS Pulsar are also available in {product}. -All the benefits you enjoy with the open source Pulsar project can also be used on our platform. -This guide details the necessary steps to getting set up and validating the connection. +To get started, you must download a compatible Pulsar artifact, and then configure the binaries for use with {product}. -== Choosing a compatible version +== Download a compatible artifact -Pulsar is distributed as a single artifact. -The Pulsar https://pulsar.apache.org/download/[download page] offers the latest version, as well as older releases. +{product} is compatible with Pulsar {pulsar_version}. -{product} is currently compatible with Pulsar {pulsar_version}. -Locate the latest patch version matching the major.minor version and download the binaries. - -[source,shell,subs="attributes+"] +. Download the binaries for the https://pulsar.apache.org/download/[latest {pulsar_version} patch version]: ++ +[source,shell,subs="+quotes,+attributes"] ---- -PULSAR_VERSION="{pulsar_version}.??" +PULSAR_VERSION="{pulsar_version}.**PATCH**" wget https://archive.apache.org/dist/pulsar/pulsar-$PULSAR_VERSION/apache-pulsar-$PULSAR_VERSION-bin.tar.gz ---- -== Setting up your Pulsar folder - -With the artifact downloaded, the next step is to extract its contents. - -The following script will extract the tarball into a directory named for the pulsar version. -This new directory is considered the "Pulsar folder". -Pulsar guides typically assume you are working within this directory. - -[source,shell,subs="attributes+"] +. Extract the downloaded artifact: ++ +[source,shell] ---- tar xvfz apache-pulsar-$PULSAR_VERSION-bin.tar.gz ---- - -You will see most Pulsar commands prefixed with "./bin". -That means you have `cd apache-pulsar-$PULSAR_VERSION` and are running commands from there. - -== Configuring your binaries for {product} - -The Pulsar folder contains quite a few files and folders, but two most important are `./conf` and `./bin`. - -The executables in bin use the configurations in conf to run commands. -Each tenant you create in {product} comes with its own custom configuration (for SSO, endpoints, etc). -You will need to download the tenant configuration from {product} and overwrite the `./conf/client.conf` file. - -. Navigate to the "Connect" tab in the {product} portal. -+ -image:connect-tab.png[Connect tab in {product}] - -. Locate the "Download client.conf" button and click to download the conf file. + -image:download-client.png[Download pulsar client conf in {product}] +The resulting directory is named `apache-pulsar-**PULSAR_VERSON**`. +This directory is known as the _Pulsar folder_. +Pulsar guides assume you are working within this directory. +Pulsar commands prefixed by `./bin` indicate the command is run from within the Pulsar folder. -. Save the file in the "/conf" folder of the Pulsar folder. -This will overwrite the default client.conf already in the /conf folder. +== Configure binaries for {product} -With your {product} tenant's configuration in place, you can use any of the binaries to interact with a Pulsar cluster. +There are several files and folders in the Pulsar folder. +The two most important folders are `/conf` and `/bin`. -[cols="1,3"] -|=== -|Binary |Uses +The executables in `/bin` use the configurations in `/conf` to run commands. -| `./bin/pulsar-admin` -| Administrative commands to manage namespaces, topics, functions, connectors, etc. +Each tenant you create in {product} comes with its own custom configuration for SSO, endpoints, and so on. +You must download the tenant configuration from {product}, and then overwrite the `./conf/client.conf` file. -| `./bin/pulsar-client` -| Interactive commands for producing and consuming messages. -|=== +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -== Validating your configuration and connection +. Click the *Connect* tab, in the *Pulsar CLI* section, click *Download client.conf*. -With the Pulsar folder in place and the correct client configuration saved, the next step is to validate everything. -Run each command to validate binary conf. +. Save the file in `apache-pulsar-**PULSAR_VERSION**/conf`. +This overwrites the default `client.conf` in the `/conf` folder. +. With your {product} tenant's configuration in place, use the binaries to interact with a Pulsar cluster: ++ +* `./bin/pulsar-admin`: Administrative commands to manage namespaces, topics, functions, connectors, and so on +* `./bin/pulsar-client`: Interactive commands for producing and consuming messages ++ For a full reference of all commands within the CLI, see the https://pulsar.apache.org/docs/reference-cli-tools/[Pulsar's CLI docs]. -List all tenants: +== Validate the connection -[source,shell,subs="attributes+"] +Run some commands to validate the binary configuration. + +. List all tenants: ++ +[source,shell] ---- ./bin/pulsar-admin tenants list ---- -Produce a message: - -[source,shell,subs="attributes+"] +. Produce a message: ++ +[source,shell,subs="+quotes"] ---- -./bin/pulsar-client produce /default/ --messages "Hi there" --num-produce 1 +./bin/pulsar-client produce **TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** --messages "Hi there" --num-produce 1 ---- == See also diff --git a/modules/developing/pages/gpt-schema-translator.adoc b/modules/developing/pages/gpt-schema-translator.adoc index fc1531b..a636247 100644 --- a/modules/developing/pages/gpt-schema-translator.adoc +++ b/modules/developing/pages/gpt-schema-translator.adoc @@ -1,113 +1,48 @@ = {gpt-schema-translator} -Systems within streaming pipelines typically represent schema and data types differently. -This requires schemas within a pipeline to be mapped to each other, a process which is complicated, tedious, and error-prone. -For example, to send data from a CDC-enabled Cassandra (C*) table to a Pulsar topic, you must define a schema mapping between the C* table and the Pulsar topic, represented below: +Systems in streaming pipelines can use different representations for schema and data types. -[tabs] -====== -Cassandra table schema:: -+ --- -[source,cql] ----- -{ - "primaryKey": { - "partitionKey": [ - "id" - ] - }, - "columnDefinitions": [ - { - "name": "id", - "typeDefinition": "uuid", - "static": false - }, - { - "name": "file1", - "typeDefinition": "text", - "static": false - }, - { - "name": "file2", - "typeDefinition": "text", - "static": false - }, - { - "name": "file3", - "typeDefinition": "text", - "static": false - } - ] -} ----- --- - -Pulsar JSON schema:: -+ --- -[source,json] ----- -{ - "type": "record", - "name": "sample.schema", - "namespace": "default", - "fields": [ - { - "name": "file1", - "type": [ - "null", - "string" - ], - "default": null - }, - { - "name": "file2", - "type": [ - "null", - "string" - ], - "default": null - }, - { - "name": "file3", - "type": [ - "string", - "null" - ], - "default": "dfdf" - } - ] -} ----- --- -====== +Schema mapping is required to align congruent types in a pipeline. +For example, to send data from a CDC-enabled {cassandra} table to a Pulsar topic, you must define schema mapping between the Cassandra table and the Pulsar topic. +If the schema includes more complex data types, like maps and nested data structures, schema management becomes more difficult. -As you add more complex data types like maps and nested data structures, schema management becomes more difficult. Schema management is tedious work that requires understanding rules across various domains and translating between them, but it is trivial work for the GPT-4 AI model. +Schema management is a complicated, tedious, and error-prone tasks that requires you to understand and translate multiple sets of schema rules. -The {gpt-schema-translator} uses generative AI to automatically generate schema mappings between {product} (Pulsar topics) and the {astra_db} sink connector (Cassandra tables). +Instead, you can use the {gpt-schema-translator} to save time and reduce schema mapping toil. +This tool uses generative AI,based on the `GPT-4` model, to automatically generate schema mappings between {product} (Pulsar topics) and the {astra_db} sink connector (Cassandra tables). == Prerequisites -* An {product} cluster with a tenant and topic. For more information, see the xref:getting-started:index.adoc[Getting Started] documentation. -* An {astra_db} database with a keyspace and table. For more, see the https://docs.datastax.com/en/astra-serverless/docs/[{astra_db} documentation]. +* An {product} tenant with a namespace and topic. +* An {astra_db} database with a keyspace and table. +* An xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[{astra_db} sink connector]. +The {gpt-schema-translator} is available for the {astra_db} sink connector only. -== Usage +== JSON-to-CQL mapping example -The {gpt-schema-translator} is available for the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[{astra_db} Sink Connector]. -To use the {gpt-schema-translator}, create an {astra_db} sink connector. +This example uses a JSON schema for a Pulsar topic, and a CQL schema for an {astra_db} table. +The {gpt-schema-translator} generates a mapping between the two schemas that the {astra_db} sink connector can use to write data from the Pulsar topic to the {astra_db} table. -In this example, we'll continue with the two schemas from above: one JSON schema for the Pulsar topic, and one CQL schema for the {astra_db} table. The {gpt-schema-translator} will generate a mapping between the two schemas, which can be used by the {astra_db} sink connector to write data from the Pulsar topic to the {astra_db} table. +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -image::two-schemas.png[Schema mapping,320,240] +. On the *Namespaces and Topics* tab, locate the topic that you want to map to a table, and then click *Create mapping*. -To generate a schema mapping, click the "Generate Mapping With GPT" button. This will generate a schema mapping. +. Select your database's keyspace, and then enter the name of the table that you want to map to. -[tabs] -====== -Cassandra table schema:: +. Click *Generate Mapping*. ++ +If this button isn't available, the GPT schema translator doesn't have an available schema for the topic or schema mapping isn't available for the selected topic and table. + --- +image::two-schemas.png[Schema mapping,320,240] ++ +.Cassandra-to-Pulsar schema mapping example +[%collapsible] +==== +[cols="1,1,1"] +|=== +| Cassandra table schema | Pulsar JSON schema | Generated mapping + +a| [source,cql] ---- { @@ -140,11 +75,8 @@ Cassandra table schema:: ] } ---- --- -Pulsar JSON schema:: -+ --- +a| [source,json] ---- { @@ -179,31 +111,29 @@ Pulsar JSON schema:: ] } ---- --- -Generated schema mapping:: -+ --- -[source,] +a| +[source,plain] ---- id=key, file1=value.file1, file2=value.file2, file3=value.file3 ---- --- -====== -Great! Now, once your connector is created, messages will flow smoothly between the two different schemas. Check in your {astra_ui} logs to see the data flowing into your table with no pesky error messages. +|=== +==== + +. Save the mapping configuration. -[#pulsar-topic-to-cql-table] -== Pulsar topic with an AVRO schema to Cassandra table -This example will produce a mapping between a Pulsar Topic in AVRO schema and a Cassandra table schema. -Avro schema definitions are JSON records, so this example isn't radically different from the first, but this time, we'll use the DataGenerator source connector to generate data for the Pulsar topic, the {astra_db} sink connector to write data to the Cassandra table, and the {gpt-schema-translator} to generate a schema mapping between the two as the messages are processed. +. After configuring your {astra_db} sink connector for the given topic and table, messages should flow between the two schemas without error. +You can check the {astra_ui} logs to confirm that the data is flowing into your table without errors. -* For more on creating the {astra_db} sink connector, see the xref:streaming-learning:pulsar-io:connectors/sinks/astra-db.adoc[{astra_db} Sink Connector documentation]. -* For more on creating the DataGenerator source connector, see the xref:streaming-learning:pulsar-io:connectors/sources/data-generator.adoc[DataGenerator Connector documentation]. +[#pulsar-topic-to-cql-table] +=== AVRO-to-CQL mapping example -The DataGenerator source connector will generate data for the Pulsar topic using the following schema: +This example demonstrates how you can generates schema mapping in real time. -.DataGenerator source connector schema +. The xref:streaming-learning:pulsar-io:connectors/sources/data-generator.adoc[DataGenerator source connector] generates data for a Pulsar topic with an AVRO schema. ++ +.AVRO schema example [%collapsible] ==== [source,avro] @@ -435,12 +365,13 @@ The DataGenerator source connector will generate data for the Pulsar topic using } ] }, +} ---- ==== -The Cassandra table for the {astra_db} sink has the following schema: - -.CQL schema +. The {astra_db} sink connector writes data to the Cassandra table with a CQL schema. ++ +.CQL schema example [%collapsible] ==== [source,] @@ -475,26 +406,32 @@ The Cassandra table for the {astra_db} sink has the following schema: } ] }, +} ---- ==== -When a topic schema is available to the {gpt-schema-translator}, the button prompt will change to "Generate Mapping". generate a mapping between the two schemas. - +. In the {astra_ui}, create a mapping for the tenant. +When a topic schema is available to the {gpt-schema-translator}, click *Generate Mapping*. ++ image::create-schema-mapping.png[Schema mapping,320,240] - -GPT examines the schemas and generates a mapping. The mapping is displayed in the {astra_ui}, and can be copied to the clipboard. -[source,] ++ +The {gpt-schema-translator} generates an AVRO-to-CQL schema mapping while messages are processed. ++ +[source,plain] ---- passportnumber=value.passportNumber, age=value.age, firstname=value.firstName, lastname=value.lastName ---- - -Notice that the `firstname` value becomes `firstName` because the Pulsar topic JSON schema supersedes the Cassandra table schema. ++ +Notice that the `firstname` value became `firstName` because the Pulsar topic AVRO schema superseded the Cassandra table schema. == No schema on Pulsar topic -If you don't declare a schema in the Pulsar topic, the schema translator provides a default schema mapping that mirrors the values of your Cassandra table schema, without using GPT. +If you don't declare a schema in the Pulsar topic, the schema translator can generate a default schema mapping based on the values of your Cassandra table schema, without using GPT. + +When you create the mapping in the {astra_ui}, you can click *Generate Mapping* to create a generic Pulsar topic schema based on your Cassandra table schema. +If schema mapping isn't possible for the selected table and topic, the *Generate Mapping* button isn't available. -For example, assuming you have the following schema on a Cassandra table: +For example, assume you have the following Cassandra table schema: [source,cql] ---- @@ -529,15 +466,13 @@ For example, assuming you have the following schema on a Cassandra table: } ---- -Since you have an available schema in your Cassandra table, you can click *Generate Mapping* to create a Pulsar topic schema from the Cassandra table schema: +The schema translator would generate the following Pulsar JSON schema mapping based on the given Cassandra table schema: -[source,bash] +[source,plain] ---- passportnumber=value.passportnumber, age=value.age, firstname=value.firstname, lastname=value.lastname ---- -If there is no *Generate Mapping* button, then this function isn't available. - == See also * xref:streaming-learning:use-cases-architectures:change-data-capture/index.adoc[] \ No newline at end of file diff --git a/modules/developing/pages/produce-consume-pulsar-client.adoc b/modules/developing/pages/produce-consume-pulsar-client.adoc index 0a887d2..461cfd5 100644 --- a/modules/developing/pages/produce-consume-pulsar-client.adoc +++ b/modules/developing/pages/produce-consume-pulsar-client.adoc @@ -1,44 +1,47 @@ -= Producing and consuming messages with the Pulsar Client on {product} += Produce and consume messages with the {product} Pulsar Client :navtitle: Pulsar CLI -:description: Use this guide to interact with your {product} tenant via the pulsar-client cli. +:description: Use the pulsar-client CLI to interact with your {product} tenants -This guide assumes you have completed the xref:getting-started:index.adoc[{product} quickstart] and you have xref:developing:configure-pulsar-env.adoc[set up your Pulsar binaries] to work with your tenant. +You can use the `pulsar-client` CLI to produce and consume messages in your {product} tenants. -Now it's time to produce and consume messages in your new streaming tenant. +. xref:getting-started:index.adoc[Create a {product} tenant.] -Set the required variables. If you've just completed the quickstart, these values are already set. +. xref:developing:configure-pulsar-env.adoc[Configure Pulsar binaries for {product}.] -[source,shell,subs="attributes+"] +. Set the following environment variables: ++ +[source,shell,subs="+quotes"] ---- TOPIC="my-topic" NAMESPACE="my-namespace" -TENANT="my-stream-" #replace with a few alphanumeric characters +TENANT="my-stream-**RANDOM_UUID**" ---- ++ +Replace **RANDOM_UUID** with any random letters and numbers to create a unique tenant name. -Run the following script to create 1 new message. - +. Produce a message: ++ [source,shell,subs="attributes+"] ---- include::{astra-streaming-examples-repo}/pulsar-client/produce-message.sh[] ---- ++ +Your topic now has a new, unacknowledged message. -Now you have a new, unacknowledged message in your topic. - -Run the following script to create a consumer that retrieves and acknowledges that message. - +. Create a consumer to retrieve and acknowledge the message: ++ [source,shell,subs="attributes+"] ---- include::{astra-streaming-examples-repo}/pulsar-client/consume-message.sh[] ---- -The output should look similar to this: +. Make sure the output includes your message: ++ [source,shell,subs="attributes+"] ---- include::{astra-streaming-examples-repo}/pulsar-client/consume-output.sh[] ---- -If you made it here then your produce & consume journey was successful. Congrats{emoji-tada}! Now it's time to take your skills further {emoji-rocket}{emoji-rocket}. - == See also * xref:astream-functions.adoc[] diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index 02221cd..4402f73 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -1,63 +1,59 @@ -= Restful requests using curl with {product} -:navtitle: Using curl with {product} -:description: This guide will provide the necessary steps to configure curl calls for use with {product}. += {product} HTTP requests +:navtitle: Form requests +:description: Interact with {product} over HTTP, such as with curl commands. -Pulsar offers a RESTful API for administrative tasks. -When you create a tenant in {product}, all supporting APIs are enabled. +You can use the xref:apis:index.adoc[{product} APIs] and https://pulsar.apache.org/docs/reference-rest-api-overview/[Pulsar REST APIs] to programmatically interact with your tenants and related {product} configurations. +Each API supports different functionalities. -To interact with your {product} tenant you will need two pieces of information: a token and the service URL. -This guide will show you how to gather that information and test your connection. +Depending on the API you use, you need certain information to form HTTP requests: -[TIP] -==== -The https://pulsar.apache.org/docs/2.10.x/reference-rest-api-overview/[Pulsar documentation] has a full reference for each API supported in a cluster. -==== - -This reference will be helpful as you look to automate certain actions in your {product} tenant. - -== Find your tenant's web service URL +* To use the {product} Pulsar Admin API and Pulsar REST APIs, you need the tenant's Web Service URL and Pulsar token. +* To use the {product} DevOps API, you need an xref:operations:astream-token-gen.adoc[{astra_db} application token]. -You send HTTP requests to your tenant's _Web Service URL_: +== Get the tenant web service URL . In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. . Click the *Connect* tab. -+ -image:connect-tab.png[Connect tab in {product}] . In the *Details* section, locate the *Tenant Details*. -Here you can find the essential information you need to communicate with your Pulsar tenant. +Here you can find the essential information you need to communicate with your Pulsar tenant, including the *Web Service URL*. + image:tenant-details.png[Tenant details in {product}] - -. Copy the *Web Service URL*. - ++ [TIP] ==== -The *Web Service URL* is different from a Pulsar *Broker Service URL*. +The *Web Service URL* is _not_ the same as the Pulsar *Broker Service URL*. Web Service URLs start with `http`. Broker Service URLs start with `pulsar(+ssl)`. ==== -== Retrieve your Pulsar token in {product} - -You need a Pulsar token to authenticate requests. +== Create a {product} Pulsar token +[IMPORTANT] +==== An xref:operations:astream-token-gen.adoc[{astra_db} application token] is _not_ the same as a Pulsar token. +==== -. To create a new Pulsar token in your {product} tenant, navigate to the "Settings" tab in the {astra_ui}. -+ -image:settings-tab.png[Settings tab in {product}] +To create a new Pulsar token for your {product} tenant, do the following: + +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -. Click "Create Token" and choose the time to expire. -Be smart about this choice - "Never Expire" isn't always the best option. -A window will appear with the newly generated token - it's a long string of letters and numbers. +. Click the *Settings* tab. + +. Click *Create Token*. + +. Select a token lifetime. + -image:copy-token.png[Copy token in {product}] +Practice responsible credential management: ++ +* Be aware of the security implications for tokens that never expire. +* Consider how long you actually plan to use the token. -. Click the clipboard icon to copy the token to your clipboard, and paste the token in a safe place. -This is the only time you will be able to copy it. +. Copy the generated token and store it securely. ++ +The token is shown only once. == Make a request diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index 28a88ab..ef1dd89 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -75,14 +75,14 @@ For more information, see the https://docs.datastax.com/en/astra-cli/docs/0.2/[d Optionally, you can choose a different cloud provider and region. Use `astra streaming list-regions` to see available values. + -[source,shell] +[source,shell,subs="+quotes"] ---- TENANT="my-stream-**RANDOM_UUID**" CLOUD_PROVIDER_STREAMING="gcp" CLOUD_REGION_STREAMING="uscentral-1" ---- -. Run the following script to create a new streaming tenant. +. Run the following script to create a new streaming tenant: + [source,shell] ---- @@ -130,10 +130,10 @@ Pulsar Admin:: -- . Set the required variables: + -[source,shell] +[source,shell,subs="+quotes"] ---- -NAMESPACE="my-namespace" -# TENANT="my-stream-" # set previously +NAMESPACE="**NAMESPACE_NAME**" +TENANT="my-stream-**RANDOM_UUID**" # set previously ---- . Run the following script to create a new namespace: @@ -149,12 +149,12 @@ curl:: -- . Set the required variables: + -[source,shell] +[source,shell,subs="+quotes"] ---- -PULSAR_TOKEN="" -WEB_SERVICE_URL="" -NAMESPACE="my-namespace" -# TENANT="my-stream-" # set previously +PULSAR_TOKEN="**PULSAR_TOKEN**" +WEB_SERVICE_URL="**TENANT_WEB_SERVICE_URL**" +NAMESPACE="**NAMESPACE_NAME**" +TENANT="my-stream-**RANDOM_UUID**" # set previously ---- . Run the following script to create a new namespace: @@ -182,13 +182,15 @@ Learn more about topics in the https://pulsar.apache.org/docs/concepts-messaging + -- . In the *Namespace And Topics* tab, locate the namespace created above and click its *Add Topic* button. + . Provide a name for the topic like "my-topic". -It must start with a lowercase letter, be alphanumeric, and can contain hyphens. -. Leave the choice of persistence and partitioning alone for now - those can be a part of https://pulsar.apache.org/docs/concepts-messaging/#partitioned-topics[future learning]. +It must start with a lowercase letter, and it can contain only letters, numbers, and hyphens (`-`). ++ +Disregard https://pulsar.apache.org/docs/concepts-messaging/#partitioned-topics[persistence and partitioning] for now. + image:add-topic.png[Add topic in {product}] -. Click the *Add Topic* button to create the topic within the namespace. +. Click *Add Topic* to create the topic within the namespace. + image:topic-listing.png[Topic listing in {product}] -- @@ -200,11 +202,11 @@ To learn more about connecting to {product} with the pulsar-admin CLI, see xref: . Set the required variables: + -[source,shell] +[source,shell,subs="+quotes"] ---- -TOPIC="my-topic" -# NAMESPACE="my-namespace" # set previously -# TENANT="my-stream-" # set previously +TOPIC="**TOPIC_NAME**" +NAMESPACE="**NAMESPACE_NAME**" # set previously +TENANT="my-stream-**RANDOM_UUID**" # set previously ---- . Run the following script to create a new topic: @@ -222,13 +224,13 @@ To learn more about interacting with {product} through HTTP, see xref:developing . Set the required variables: + -[source,shell] +[source,shell,subs="+quotes"] ---- -TOPIC="my-topic" -# PULSAR_TOKEN="" # set previously -# WEB_SERVICE_URL="" # set previously -# NAMESPACE="my-namespace" # set previously -# TENANT="my-stream-" # set previously +TOPIC="**TOPIC_NAME**" +PULSAR_TOKEN="**PULSAR_TOKEN**" # set previously +WEB_SERVICE_URL="**TENANT_WEB_SERVICE_URL**" # set previously +NAMESPACE="**NAMESPACE_NAME**" # set previously +TENANT="my-stream-**RANDOM_UUID**" # set previously ---- . Run the following script to create a new topic: diff --git a/modules/operations/nav.adoc b/modules/operations/nav.adoc index deceee1..83dd3c7 100644 --- a/modules/operations/nav.adoc +++ b/modules/operations/nav.adoc @@ -1,18 +1,21 @@ .Operations -* xref:astream-georeplication.adoc[] -* xref:astream-limits.adoc[] -* xref:astream-pricing.adoc[] -* xref:astream-regions.adoc[] -* Monitoring streaming tenants -** xref:monitoring/index.adoc[] -** xref:astream-scrape-metrics.adoc[] -** xref:monitoring/integration.adoc[] -** xref:monitoring/new-relic.adoc[] -** xref:monitoring/stream-audit-logs.adoc[] +* xref:operations:astream-limits.adoc[] +* xref:operations:astream-pricing.adoc[] +* xref:operations:astream-regions.adoc[] +* xref:operations:astream-georeplication.adoc[] +* Monitor streaming tenants +** xref:operations:monitoring/index.adoc[] +** xref:operations:astream-scrape-metrics.adoc[] +** xref:operations:monitoring/integration.adoc[] +** xref:operations:monitoring/new-relic.adoc[] +** xref:operations:monitoring/stream-audit-logs.adoc[] ** Grafana dashboards -*** xref:monitoring/metrics.adoc[] -*** xref:monitoring/overview-dashboard.adoc[] -*** xref:monitoring/namespace-dashboard.adoc[] -*** xref:monitoring/topic-dashboard.adoc[] -* xref:astream-token-gen.adoc[] -* xref:private-connectivity.adoc[] +*** xref:operations:monitoring/metrics.adoc[] +*** xref:operations:monitoring/overview-dashboard.adoc[] +*** xref:operations:monitoring/namespace-dashboard.adoc[] +*** xref:operations:monitoring/topic-dashboard.adoc[] +* Administration +** xref:operations:astream-token-gen.adoc[] +** xref:operations:private-connectivity.adoc[] +** xref:ROOT:astream-org-permissions.adoc[] +** xref:ROOT:astream-custom-roles.adoc[] \ No newline at end of file diff --git a/modules/operations/pages/astream-georeplication.adoc b/modules/operations/pages/astream-georeplication.adoc index 8565fa3..18eed5b 100644 --- a/modules/operations/pages/astream-georeplication.adoc +++ b/modules/operations/pages/astream-georeplication.adoc @@ -73,15 +73,16 @@ To disable replication on a namespace, select **Disable Replication**. [#test] == Test georeplicated clusters -. Verify your user token can access tenant metadata. +. Verify that your user token can access tenant metadata: + -[source,bash] +[source,bash,subs="+quotes"] ---- -bin/pulsar-admin tenants get +bin/pulsar-admin tenants get **TENANT_NAME** ---- + -In the result, the `allowedClusters` are the clusters the tenant can be replicated to: +In the result, the `allowedClusters` are the clusters where you can replicate the tenant: + +.Result [source,json] ---- { @@ -90,11 +91,11 @@ In the result, the `allowedClusters` are the clusters the tenant can be replicat } ---- -. Verify your `pulsar-admin` can view the replicated clusters for your namespace: +. Verify your `pulsar-admin` can read replicated clusters for your namespace: + -[source,bash] +[source,bash,subs="+quotes"] ---- -bin/pulsar-admin namespaces get-clusters / +bin/pulsar-admin namespaces get-clusters **TENANT_NAME**/**NAMESPACE_NAME** ---- + .Result @@ -107,22 +108,23 @@ pulsar-aws-useast2-staging ---- ==== -. Create a Pulsar consumer with a subscription to the `//` topic: +. Create a Pulsar consumer with a subscription to a specified topic: + -[source,bash] +[source,bash,subs="+quotes"] ---- -bin/pulsar-client consume -s "subscription-test" // -n 0 +bin/pulsar-client consume -s "subscription-test" **TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** -n 0 ---- -. Create a Pulsar producer to produce messages to the `//` topic: +. Create a Pulsar producer to produce messages from a specific topic: + -[source,bash] +[source,bash,subs="+quotes"] ---- -bin/pulsar-client produce // --messages "hello-from-pulsar" -n 10 +bin/pulsar-client produce **TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** --messages "hello-from-pulsar" -n 10 ---- - -. Your consumer will acknowledge receipt of the messages: + +The consumer acknowledges the messages: ++ +.Result [source,console] ---- ----- got message ----- @@ -147,17 +149,18 @@ key:[null], properties:[], content:hello-from-pulsar key:[null], properties:[], content:hello-from-pulsar ---- -. Navigate to the **Namespaces and Topics** tab in your geo-replicated {product} clusters. + -`persistent:////` should be visible and showing traffic across all regions. +. In the {astra_ui}, go to your tenant's **Namespaces and Topics** tab. + +. Make sure `persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME**` is visible and shows traffic across all regions. [#replicated-subscriptions] == Replicated subscriptions The `isReplicated` value controls subscription behavior during replication. -Subscriptions are created with `isReplicated=false` by default, and will not replicate when the cluster is replicated. +Subscriptions are created with `isReplicated=false` by default, which means they do not replicate when the cluster is replicated. -. Specify `replicateSubscriptionState(true)` at subscription creation to replicate the subscription when the cluster is replicated. +. Specify `replicateSubscriptionState(true)` at subscription creation to replicate the subscription when the cluster is replicated: + [source,java] ---- @@ -170,13 +173,16 @@ Consumer consumer = pulsarClient.newConsumer(Schema.STRING) . Check topic stats: + -[source,bash] +[source,bash,subs="+quotes"] ---- -bin/pulsar-admin topics stats persistent://// +bin/pulsar-admin topics stats persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** ---- + -`isReplicated` is now `true` for this subscription: +In the configuration, `isReplicated` is now `true` for this subscription. + +.config.json +[%collapsible] +==== [source,json] ---- { @@ -248,7 +254,7 @@ bin/pulsar-admin topics stats persistent://// "replicationDelayInSeconds" : 0, "inboundConnection" : "/192.168.98.62:40346", "inboundConnectedSince" : "2022-09-14T20:20:35.128325Z", - "outboundConnection" : "[id: 0xd3b42242, L:/192.168.71.231:42272 - R:pulsar-aws-useast2.staging.streaming.datastax.com/3.14.0.138:6651]", + "outboundConnection" : "[id: 0xd3b42242, L:/192.168.71.231:42272 - R:pulsar-aws-useast2.streaming.datastax.com/3.14.0.138:6651]", "outboundConnectedSince" : "2022-09-14T18:37:16.060159Z" } }, @@ -263,11 +269,12 @@ bin/pulsar-admin topics stats persistent://// } } ---- +==== [#monitor] == Monitor replicated clusters -{product} exposes the following topic-level replication metrics, which can be viewed in the **Overview** tab of the Namespaces and Topics page. +{product} exposes the following topic-level replication metrics, which can be viewed in the **Overview** section of **Namespaces and Topics** in the {astra_ui}: include::operations:partial$georeplication-monitoring.adoc[] From d96bb9add6dba4b5baa06f3f4ddd9fba0f097377 Mon Sep 17 00:00:00 2001 From: April M Date: Tue, 15 Oct 2024 19:25:45 -0700 Subject: [PATCH 09/18] source pt 3 --- modules/ROOT/pages/index.adoc | 2 +- modules/apis/pages/api-operations.adoc | 519 +++++++++-------- modules/developing/pages/astra-cli.adoc | 16 +- modules/developing/pages/astream-cdc.adoc | 8 +- .../developing/pages/astream-functions.adoc | 547 +++++++++--------- modules/developing/pages/using-curl.adoc | 26 +- .../partials/client-variables-table.adoc | 3 +- modules/getting-started/pages/index.adoc | 237 ++------ .../operations/pages/astream-token-gen.adoc | 207 ++----- .../pages/monitoring/integration.adoc | 2 +- .../pages/monitoring/stream-audit-logs.adoc | 4 +- modules/operations/pages/onboarding-faq.adoc | 12 +- 12 files changed, 635 insertions(+), 948 deletions(-) diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index 12eb61b..804c77c 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -1,4 +1,4 @@ -= Introduction to {product}? += Introduction to {product} :navtitle: Intro to {product} :page-tag: astra-streaming,planner,admin,dev,pulsar diff --git a/modules/apis/pages/api-operations.adoc b/modules/apis/pages/api-operations.adoc index 2ea982d..fc0cc22 100644 --- a/modules/apis/pages/api-operations.adoc +++ b/modules/apis/pages/api-operations.adoc @@ -1,5 +1,6 @@ = API operations +//TODO: Needs more work and clean up of all source blocks. This {product} workbook is a comprehensive guide that provides detailed examples and practices for managing the {product} platform using the DevOps APIs. It provides details on the most commonly used APIs for managing {product} and Pulsar instances. These details include required parameters and the expected output from the API. The workbook is designed to fill the gap between detailed API reference docs and HowTo guides. The result is to help customers in operating and managing {product} and provide guidance on how to use DevOps API to automate many common tasks. The workbook covers a wide range of topics, including provisioning of resources, monitoring, and troubleshooting. It provides instructions for various operations, such as creating a new tenant, namespace, topics, geo-replication, and access tokens, to setting up monitoring and alerting, and troubleshooting common issues. @@ -8,34 +9,44 @@ Overall, this {product} Workbook is a valuable resource for customers who want t == Prerequisites -* An active {astra_db} account -* An application token and Pulsar token for {product} -* Export the following environmental variables to your path: -+ -[source,bash] +* An active {astra_db} account with access to {product} +* Credentials and values required to xref:developing:using-curl.adoc[form HTTP requests] + +=== Set environment variables + +Due to their frequency in {product} API calls, you might find it helpful to set the following environment variables: + +[source,bash,subs="+quotes"] ---- -export PULSAR_TOKEN="" -export WEB_SERVICE_URL="" -export NAMESPACE="" -export TENANT="" -export TOPIC="" -export NUM_OF_PARTITIONS="" -export SUBSCRIPTION="" -export INSTANCE="" -export SOURCE="" -export SINK="" -export FUNCTION="" -export TOKENID="" +export PULSAR_TOKEN="**PULSAR_TOKEN**" +export WEB_SERVICE_URL="**PULSAR_WEB_SERVICE_URL**" +export NAMESPACE="**STREAMING_NAMESPACE_NAME**" +export TENANT="**STREAMING_TENANT_NAME**" +export TOPIC="**STREAMING_TOPIC_NAME**" +export NUM_OF_PARTITIONS="**PARTITIONS**" +export SUBSCRIPTION="**SUBSCRIPTION_NAME**" +export INSTANCE="**TENANT_INSTANCE_NAME**" +export SOURCE="**SOURCE_CONNECTOR_NAME**" +export SINK="**SINK_NAME**" +export FUNCTION="**FUNCTION_DISPLAY_NAME**" +export TOKENID="**PULSAR_TOKEN_UUID**" +export ASTRA_TOKEN="**ASTRA_DB_APPLICATION_TOKEN**" ---- -* (Optional) The following examples use `python3 -mjson.tool` to format the output into JSON. -This is not required to execute the API requests. +The examples in this guide use environment variables for these values. + +Additionally, some examples in this guide use `python3 -mjson.tool` to format the JSON response. +This is optional; it is not required to execute API requests. -== List tenants with details +== {product} DevOps API tenant operations + +The following examples demonstrate how to use the {product} DevOps API to manage {product} resources. + +=== Get tenant information [source,curl] ---- -curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/tenants' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" | python3 -mjson.tool +curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/tenants' --header "Authorization: Bearer $ASTRA_TOKEN" | python3 -mjson.tool ---- .Result @@ -94,11 +105,11 @@ curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/tena ---- ==== -== List {product} cloud providers +=== Get {product} cloud providers and regions [source,curl] ---- -curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/providers' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" | python3 -mjson.tool +curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/providers' --header "Authorization: Bearer $ASTRA_TOKEN" | python3 -mjson.tool ---- .Result @@ -128,32 +139,31 @@ curl --location --request GET 'https://api.astra.datastax.com/v2/streaming/prov ---- ==== -== Create DevOps API +=== Create a tenant Create a tenant: [source,curl] ---- -curl --location --request POST 'https://api.astra.datastax.com/v2/streaming/tenants' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --data-raw '{ +curl --location --request POST 'https://api.astra.datastax.com/v2/streaming/tenants' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_TOKEN" --data-raw '{ "cloudProvider": "aws", "cloudRegion": "useast2", "tenantName": "mytenant", "userEmail": "joshua@example.com" }' | python3 -mjson.tool ---- --- Create a tenant with file input: [source,curl] ---- -curl --fail --location --request POST 'https://api.astra.datastax.com/v2/streaming/tenants' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --data "@mytenant-config.json" | python3 -mjson.tool +curl --fail --location --request POST 'https://api.astra.datastax.com/v2/streaming/tenants' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_TOKEN" --data "@mytenant-config.json" | python3 -mjson.tool ---- .Result [%collapsible] ==== -The output includes the "pulsarToken" which is the JWT token for this Pulsar instance. +The output includes the "pulsarToken" which is the JWT for this Pulsar instance. [source,console] ---- @@ -185,24 +195,24 @@ The output includes the "pulsarToken" which is the JWT token for this Pulsar ins ---- ==== -== Delete a tenant +=== Delete a tenant [source,curl] ---- -curl --location --request DELETE 'https://api.astra.datastax.com/v2/streaming/tenants/{tenant}/clusters/{cluster}' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_ORG_TOKEN" +curl --location --request DELETE 'https://api.astra.datastax.com/v2/streaming/tenants/{tenant}/clusters/{cluster}' --header 'Content-Type: application/json' --header "Authorization: Bearer $ASTRA_TOKEN" ---- No response indicates success. -== Namespace DevOps APIs +== Puslar API namespace operations -For managing {product} Namespaces, we use the native https://pulsar.apache.org/admin-rest-api/[Pulsar REST APIs]. These are documented on the Apache Pulsar Docs for REST API. +To manage {product} namespaces, use the https://pulsar.apache.org/admin-rest-api/[Pulsar REST APIs]. -=== List Existing Namespaces +=== Get existing namespaces [source,curl] ---- -curl --location --request GET “https://$WEB_SERVER_URL/admin/v2/namespaces/$TENANT" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool +curl --location --request GET “https://$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- .Result @@ -217,7 +227,7 @@ curl --location --request GET “https://$WEB_SERVER_URL/admin/v2/namespaces/$TE ---- ==== -=== Create a Namespace +=== Create a namespace [source,curl] ---- @@ -233,7 +243,7 @@ Output: No reply means successful. ---- ==== -=== Delete a Namespace +=== Delete a namespace [source,curl] ---- @@ -242,7 +252,7 @@ curl -sS --fail --location --request DELETE --header "Authorization: Bearer $PUL No response indicates success. -=== Get Namespace Message Retention +=== Get namespace message retention [source,curl] ---- @@ -261,7 +271,7 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$ ---- ==== -=== Set Namespace Message Retention +=== Set namespace message retention [source,curl] ---- @@ -273,7 +283,7 @@ curl --location "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/retenti No response indicates success. -=== Get Namespace BacklogQuota +=== Get namespace backlog quota [source,curl] ---- @@ -292,10 +302,11 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$ "limitTime": 3600, "policy": "producer_exception" } +} ---- ==== -=== Set Namespace BacklogQuota Settings +=== Set namespace backlog quota settings [source,curl] ---- @@ -309,7 +320,7 @@ curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces No response indicates success. -=== Get Namespace Message TTL +=== Get namespace message TTL [source,curl] ---- @@ -318,7 +329,7 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$ The response is a number, such as `3600`. -=== Set Namespace Message TTL +=== Set namespace message TTL [source,curl] ---- @@ -327,7 +338,7 @@ curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces No response indicates success. -=== Set AutoTopicCreation True/False on Namespace +=== Set namespace AutoTopicCreation Input parameter “topicType" should be either “non-partitioned" or “partitioned". @@ -341,7 +352,7 @@ curl -sS --fail --location --request POST --header "Authorization: Bearer $PULSA No response indicates success. -=== Get Namespace MaxConsumersPerTopic +=== Get namespace MaxConsumersPerTopic [source,curl] ---- @@ -350,14 +361,14 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$ The response is a number, such as `50`. -=== Set Namespace MaxConsumersPerTopic +=== Set namespace MaxConsumersPerTopic [source,curl] ---- curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMESPACE/maxConsumersPerTopic" --header "Authorization: Bearer $PULSAR_TOKEN" --header 'Content-Type: application/json' --data 100 ---- -=== Get Namespace MaxTopicPerNamespace +=== Get namespace MaxTopicPerNamespace [source,curl] ---- @@ -366,7 +377,7 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/namespaces/$ The response is a number. -=== Set Namespace MaxTopicPerNamespace +=== Set namespace MaxTopicPerNamespace [source,curl] ---- @@ -375,7 +386,9 @@ curl -sS --fail --location "$WEB_SERVICE_URL/admin/v2/namespaces/$TENANT/$NAMES The response is a number. -== Topics DevOps APIs +== Pulsar Admin API topic operations + +=== Get topics in a namespace [source,curl] ---- @@ -400,7 +413,7 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$ ---- ==== -=== Create Non-partitioned Topic +=== Create non-partitioned topic [source,curl] ---- @@ -410,7 +423,7 @@ curl -sS --fail --location --request PUT "$WEB_SERVICE_URL/admin/v2/persistent/$ No response indicates success. -=== Create Partitioned Topic +=== Create partitioned topic [source,curl] ---- @@ -419,7 +432,7 @@ curl -sS --fail --location --request PUT "$WEB_SERVICE_URL/admin/v2/persistent/$ No response indicates success. -=== Delete a Persistent Topic +=== Delete a persistent topic [source,curl] ---- @@ -428,7 +441,7 @@ curl -sS --fail --location --request DELETE"$WEB_SERVICE_URL/admin/v2/persistent No response indicates success. -=== Get InternalStats of Non-Partitioned Topic +=== Get InternalStats of non-partitioned topic [source,curl] ---- @@ -473,11 +486,11 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$ ---- ==== -=== Get Stats of All Topics +=== Get stats of a non-partitioned topic [source,curl] ---- -curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/stats/topics/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool +curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/stats" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- .Result @@ -486,127 +499,43 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/stats/topics [source,console] ---- { - "persistent://testcreate/ns0/mytopic3": { - "name": "persistent://testcreate/ns0/mytopic3", - "totalMessagesIn": 0, - "totalMessagesOut": 0, - "totalBytesIn": 0, - "totalBytesOut": 0, - "msgRateIn": 0, - "msgRateOut": 0, - "throughputIn": 0, - "throughputOut": 0, - "subscriptionCount": 0, - "producerCount": 0, - "consumerCount": 0, - "subscriptionDelayed": 0, - "storageSize": 0, - "backlogStorageByteSize": 0, - "msgBacklogNumber": 0, - "updatedAt": "2023-04-25T16:00:24.252397617Z" - }, - "persistent://testcreate/ns0/t1": { - "name": "persistent://testcreate/ns0/t1", - "totalMessagesIn": 0, - "totalMessagesOut": 0, - "totalBytesIn": 0, - "totalBytesOut": 0, - "msgRateIn": 0, - "msgRateOut": 0, - "throughputIn": 0, - "throughputOut": 0, - "subscriptionCount": 0, - "producerCount": 0, - "consumerCount": 0, - "subscriptionDelayed": 0, - "storageSize": 0, - "backlogStorageByteSize": 0, - "msgBacklogNumber": 0, - "updatedAt": "2023-04-25T16:00:24.252466612Z" - }, - "persistent://testcreate/ns0/t1-partition-0": { - "name": "persistent://testcreate/ns0/t1-partition-0", - "totalMessagesIn": 516, - "totalMessagesOut": 514, - "totalBytesIn": 637776, - "totalBytesOut": 637674, - "msgRateIn": 0, - "msgRateOut": 0, - "throughputIn": 0, - "throughputOut": 0, - "subscriptionCount": 1, - "producerCount": 0, - "consumerCount": 0, - "subscriptionDelayed": 0, - "storageSize": 1899200, - "backlogStorageByteSize": 0, - "msgBacklogNumber": 0, - "updatedAt": "2023-04-25T16:00:24.252410963Z" - }, - "persistent://testcreate/ns0/t1-partition-1": { - "name": "persistent://testcreate/ns0/t1-partition-1", - "totalMessagesIn": 534, - "totalMessagesOut": 531, - "totalBytesIn": 696340, - "totalBytesOut": 692347, - "msgRateIn": 0, - "msgRateOut": 0, - "throughputIn": 0, - "throughputOut": 0, - "subscriptionCount": 1, - "producerCount": 0, - "consumerCount": 0, - "subscriptionDelayed": 0, - "storageSize": 2020678, - "backlogStorageByteSize": 2151, - "msgBacklogNumber": 3, - "updatedAt": "2023-04-25T16:00:24.252425482Z" - }, - "persistent://testcreate/ns0/t1-partition-2": { - "name": "persistent://testcreate/ns0/t1-partition-2", - "totalMessagesIn": 522, - "totalMessagesOut": 519, - "totalBytesIn": 653487, - "totalBytesOut": 649286, - "msgRateIn": 0, - "msgRateOut": 0, - "throughputIn": 0, - "throughputOut": 0, - "subscriptionCount": 1, - "producerCount": 0, - "consumerCount": 0, - "subscriptionDelayed": 0, - "storageSize": 1916574, - "backlogStorageByteSize": 0, - "msgBacklogNumber": 0, - "updatedAt": "2023-04-25T16:00:24.252438306Z" - }, - "persistent://testcreate/ns0/t1-partition-3": { - "name": "persistent://testcreate/ns0/t1-partition-3", - "totalMessagesIn": 516, - "totalMessagesOut": 514, - "totalBytesIn": 631638, - "totalBytesOut": 631536, - "msgRateIn": 0, - "msgRateOut": 0, - "throughputIn": 0, - "throughputOut": 0, - "subscriptionCount": 1, - "producerCount": 0, - "consumerCount": 0, - "subscriptionDelayed": 0, - "storageSize": 1890920, - "backlogStorageByteSize": 1586, - "msgBacklogNumber": 4, - "updatedAt": "2023-04-25T16:00:24.252452735Z" - ...TRUNCATED FOR READABILITY... + "msgRateIn": 0.0, + "msgThroughputIn": 0.0, + "msgRateOut": 0.0, + "msgThroughputOut": 0.0, + "bytesInCounter": 0, + "msgInCounter": 0, + "bytesOutCounter": 0, + "msgOutCounter": 0, + "averageMsgSize": 0.0, + "msgChunkPublished": false, + "storageSize": 0, + "backlogSize": 0, + "publishRateLimitedTimes": 0, + "earliestMsgPublishTimeInBacklogs": 0, + "offloadedStorageSize": 0, + "lastOffloadLedgerId": 0, + "lastOffloadSuccessTimeStamp": 0, + "lastOffloadFailureTimeStamp": 0, + "publishers": [], + "waitingPublishers": 0, + "subscriptions": {}, + "replication": {}, + "deduplicationStatus": "Disabled", + "nonContiguousDeletedMessagesRanges": 0, + "nonContiguousDeletedMessagesRangesSerializedSize": 0, + "compaction": { + "lastCompactionRemovedEventCount": 0, + "lastCompactionSucceedTimestamp": 0, + "lastCompactionFailedTimestamp": 0, + "lastCompactionDurationTimeInMills": 0 } ...TRUNCATED FOR READABILITY... } ---- ==== -=== Get Stats of Partitioned Topic +=== Get stats of a partitioned topic [source,curl] ---- @@ -726,11 +655,12 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$ ---- ==== -=== Get Stats of Non-partition Topic + +=== Get stats of all topics [source,curl] ---- -curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$TENANT/$NAMESPACE/$TOPIC/stats" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool +curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/stats/topics/$TENANT/$NAMESPACE" --header "Authorization: Bearer $PULSAR_TOKEN" | python3 -mjson.tool ---- .Result @@ -739,43 +669,127 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$ [source,console] ---- { - "msgRateIn": 0.0, - "msgThroughputIn": 0.0, - "msgRateOut": 0.0, - "msgThroughputOut": 0.0, - "bytesInCounter": 0, - "msgInCounter": 0, - "bytesOutCounter": 0, - "msgOutCounter": 0, - "averageMsgSize": 0.0, - "msgChunkPublished": false, - "storageSize": 0, - "backlogSize": 0, - "publishRateLimitedTimes": 0, - "earliestMsgPublishTimeInBacklogs": 0, - "offloadedStorageSize": 0, - "lastOffloadLedgerId": 0, - "lastOffloadSuccessTimeStamp": 0, - "lastOffloadFailureTimeStamp": 0, - "publishers": [], - "waitingPublishers": 0, - "subscriptions": {}, - "replication": {}, - "deduplicationStatus": "Disabled", - "nonContiguousDeletedMessagesRanges": 0, - "nonContiguousDeletedMessagesRangesSerializedSize": 0, - "compaction": { - "lastCompactionRemovedEventCount": 0, - "lastCompactionSucceedTimestamp": 0, - "lastCompactionFailedTimestamp": 0, - "lastCompactionDurationTimeInMills": 0 + "persistent://testcreate/ns0/mytopic3": { + "name": "persistent://testcreate/ns0/mytopic3", + "totalMessagesIn": 0, + "totalMessagesOut": 0, + "totalBytesIn": 0, + "totalBytesOut": 0, + "msgRateIn": 0, + "msgRateOut": 0, + "throughputIn": 0, + "throughputOut": 0, + "subscriptionCount": 0, + "producerCount": 0, + "consumerCount": 0, + "subscriptionDelayed": 0, + "storageSize": 0, + "backlogStorageByteSize": 0, + "msgBacklogNumber": 0, + "updatedAt": "2023-04-25T16:00:24.252397617Z" + }, + "persistent://testcreate/ns0/t1": { + "name": "persistent://testcreate/ns0/t1", + "totalMessagesIn": 0, + "totalMessagesOut": 0, + "totalBytesIn": 0, + "totalBytesOut": 0, + "msgRateIn": 0, + "msgRateOut": 0, + "throughputIn": 0, + "throughputOut": 0, + "subscriptionCount": 0, + "producerCount": 0, + "consumerCount": 0, + "subscriptionDelayed": 0, + "storageSize": 0, + "backlogStorageByteSize": 0, + "msgBacklogNumber": 0, + "updatedAt": "2023-04-25T16:00:24.252466612Z" + }, + "persistent://testcreate/ns0/t1-partition-0": { + "name": "persistent://testcreate/ns0/t1-partition-0", + "totalMessagesIn": 516, + "totalMessagesOut": 514, + "totalBytesIn": 637776, + "totalBytesOut": 637674, + "msgRateIn": 0, + "msgRateOut": 0, + "throughputIn": 0, + "throughputOut": 0, + "subscriptionCount": 1, + "producerCount": 0, + "consumerCount": 0, + "subscriptionDelayed": 0, + "storageSize": 1899200, + "backlogStorageByteSize": 0, + "msgBacklogNumber": 0, + "updatedAt": "2023-04-25T16:00:24.252410963Z" + }, + "persistent://testcreate/ns0/t1-partition-1": { + "name": "persistent://testcreate/ns0/t1-partition-1", + "totalMessagesIn": 534, + "totalMessagesOut": 531, + "totalBytesIn": 696340, + "totalBytesOut": 692347, + "msgRateIn": 0, + "msgRateOut": 0, + "throughputIn": 0, + "throughputOut": 0, + "subscriptionCount": 1, + "producerCount": 0, + "consumerCount": 0, + "subscriptionDelayed": 0, + "storageSize": 2020678, + "backlogStorageByteSize": 2151, + "msgBacklogNumber": 3, + "updatedAt": "2023-04-25T16:00:24.252425482Z" + }, + "persistent://testcreate/ns0/t1-partition-2": { + "name": "persistent://testcreate/ns0/t1-partition-2", + "totalMessagesIn": 522, + "totalMessagesOut": 519, + "totalBytesIn": 653487, + "totalBytesOut": 649286, + "msgRateIn": 0, + "msgRateOut": 0, + "throughputIn": 0, + "throughputOut": 0, + "subscriptionCount": 1, + "producerCount": 0, + "consumerCount": 0, + "subscriptionDelayed": 0, + "storageSize": 1916574, + "backlogStorageByteSize": 0, + "msgBacklogNumber": 0, + "updatedAt": "2023-04-25T16:00:24.252438306Z" + }, + "persistent://testcreate/ns0/t1-partition-3": { + "name": "persistent://testcreate/ns0/t1-partition-3", + "totalMessagesIn": 516, + "totalMessagesOut": 514, + "totalBytesIn": 631638, + "totalBytesOut": 631536, + "msgRateIn": 0, + "msgRateOut": 0, + "throughputIn": 0, + "throughputOut": 0, + "subscriptionCount": 1, + "producerCount": 0, + "consumerCount": 0, + "subscriptionDelayed": 0, + "storageSize": 1890920, + "backlogStorageByteSize": 1586, + "msgBacklogNumber": 4, + "updatedAt": "2023-04-25T16:00:24.252452735Z" + ...TRUNCATED FOR READABILITY... } ...TRUNCATED FOR READABILITY... } ---- ==== -=== Get List of Subscriptions for a Topic +=== Get topic subscriptions [source,curl] ---- @@ -794,9 +808,10 @@ curl -sS --fail --location --request GET "$WEB_SERVICE_URL/admin/v2/persistent/$ ---- ==== -=== Create a Subscription for a Topic (Replicated) +=== Create a subscription for a topic -"Replicated=true" can be set to “false" for non replicated subscriptions. +Create a replicated or non-replicated subscription. +"Replicated=true" can be set to “false" for non-replicated subscriptions. [source,curl] ---- @@ -805,7 +820,7 @@ curl -sS --fail --location --request PUT "$WEB_SERVICE_URL/admin/v2/persistent/$ No response indicates success. -=== Delete a Subscription for a Topic +=== Delete a subscription for a topic [source,curl] ---- @@ -814,7 +829,7 @@ curl -sS --fail --location --request DELETE"$WEB_SERVICE_URL/admin/v2/persistent No response indicates success. -=== Clear a Subscription for a Topic +=== Clear a subscription for a topic [source,curl] ---- @@ -823,13 +838,13 @@ curl -sS --fail --location --request POST "$WEB_SERVICE_URL/admin/v2/persistent/ No response indicates success. -== Geo-Replication DevOps APIs +== {product} DevOps API geo-replication operations -=== Get Status of Geo-Replication +=== Get status of geo-replication [source,curl] ---- -curl --location --fail --request GET "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" | python3 -mjson.tool +curl --location --fail --request GET "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" --header "Authorization: Bearer $ASTRA_TOKEN" | python3 -mjson.tool ---- .Result @@ -880,13 +895,13 @@ curl --location --fail --request GET "https://api.astra.datastax.com/v2/streamin ---- ==== -=== Create Geo-Replication between Namespaces +=== Create geo-replication between namespaces The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of {product} sections of this guide. [source,curl] ---- -curl --location --fail --request POST "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" --header "Content-Type: application/json" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --data-raw '{ +curl --location --fail --request POST "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" --header "Content-Type: application/json" --header "Authorization: Bearer $ASTRA_TOKEN" --data-raw '{ "bidirection": true, "destCluster": "pulsar-aws-uswest2", "email": "joshua@example.com", @@ -897,7 +912,7 @@ curl --location --fail --request POST "https://api.astra.datastax.com/v2/streami No response indicates success. -=== Delete Geo-Replication between Namespaces +=== Delete geo-replication between namespaces The JSON input parameters can be obtained from List Tenants with Details and Get a list cloud providers of {product} sections of this guide. @@ -905,7 +920,7 @@ The JSON input parameters can be obtained from List Tenants with Details and Get ---- curl --location --fail --request DELETE "https://api.astra.datastax.com/v2/streaming/replications/$INSTANCE/$TENANT/$NAMESPACE" \ --header "Content-Type: application/json" \ - --header "Authorization: Bearer $ASTRA_ORG_TOKEN" \ + --header "Authorization: Bearer $ASTRA_TOKEN" \ --data-raw '{ "bidirection": true, "destCluster": "pulsar-aws-uswest2", @@ -917,9 +932,9 @@ curl --location --fail --request DELETE "https://api.astra.datastax.com/v2/strea No response indicates success. -== Functions DevOps APIs +== Pulsar Admin API functions operations -=== List Existing Functions in a Namespace +=== List existing functions in a namespace [source,bash] ---- @@ -937,7 +952,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENAN ---- ==== -=== Get Status of a Function +=== Get status of a function [source,bash] ---- @@ -975,7 +990,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENAN ---- ==== -=== Get Stats of a Function +=== Get stats of a function [source,curl] ---- @@ -1026,7 +1041,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENAN ---- ==== -=== Get Function Details +=== Get function details [source,curl] ---- @@ -1113,7 +1128,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/functions/$TENAN ---- ==== -=== Start a Function +=== Start a function [source,curl] ---- @@ -1122,7 +1137,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENA No response indicates success. -=== Stop a Function +=== Stop a function [source,curl] ---- @@ -1131,7 +1146,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENA No response indicates success. -=== Restart a Function +=== Restart a function [source,curl] ---- @@ -1140,17 +1155,19 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/functions/$TENA No response indicates success. -== {product} JWT Token DevOps APIs +== {product} DevOps API JWT operations -=== List Existing Tokens IDs +=== List existing token IDs -Get a list of Token IDs for your Cluster. With the TokenID, you can then lookup and obtain the Pulsar JWT Token string. The TokenIDs are also listed in the {astra_ui} for that Tenant and Cluster. +Get a list of Token IDs for your Cluster. +With the TokenID, you can then lookup and obtain the Pulsar JWT string. +The TokenIDs are also listed in the {astra_ui} for that Tenant and Cluster. Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. [source,curl] ---- -curl --location --request GET "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" | python3 -mjson.tool +curl --location --request GET "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "Authorization: Bearer $ASTRA_TOKEN" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" | python3 -mjson.tool ---- .Result @@ -1169,11 +1186,11 @@ curl --location --request GET "https://api.astra.datastax.com/v2/streaming/tenan ---- ==== -=== List Token String by ID +=== List token string by ID [source,curl] ---- -curl --fail --location --request GET "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens/$TOKENID" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" +curl --fail --location --request GET "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens/$TOKENID" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_TOKEN" ---- .Result @@ -1181,21 +1198,21 @@ curl --fail --location --request GET "https://api.astra.datastax.com/v2/streamin ==== [source,console] ---- -Output: Raw string JWT token +Output: Raw string JWT eyJhbGciOiJSUzI1NiIsI . . . ---- ==== -=== Create a JWT Token +=== Create a JWT -Create a new Pulsar JWT Token. -The new JWT Token will also be visible in the {astra_ui} for that Tenant and Cluster. +Create a new Pulsar JWT. +The new JWT will also be visible in the {astra_ui} for that Tenant and Cluster. Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. [source,curl] ---- -curl --fail --location --request POST "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" +curl --fail --location --request POST "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_TOKEN" ---- .Result @@ -1203,28 +1220,28 @@ curl --fail --location --request POST "https://api.astra.datastax.com/v2/streami ==== [source,console] ---- -Output: new raw string JWT token +Output: new raw string JWT eyJhbGciOiJSUzI1NiIsI . . . ---- ==== -=== Delete a JWT Token +=== Delete a JWT Required parameters "CLUSTER" is obtained from the “List Tenants with Details" API command. List of "TOKENID" can be obtained from List Existing Tokens IDs. [source,curl] ---- -curl --fail --location --request DELETE "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_ORG_TOKEN" +curl --fail --location --request DELETE "https://api.astra.datastax.com/v2/streaming/tenants/$TENANT/tokens" --header "X-DataStax-Pulsar-Cluster: $CLUSTER" --header "Authorization: Bearer $ASTRA_TOKEN" ---- No response indicates success. -== Pulsar IO Connectors DevOps APIs +== Pulsar Admin API IO connectors operations -Pulsar Sources and Sinks share a similar API structure for most methods. As such, this guide will show both Source and Sink CURL examples together. +Pulsar Sources and Sinks share a similar API structure for most methods. -=== List Existing Sources in a Namespace +=== List existing sources in a namespace [source,curl] ---- @@ -1242,7 +1259,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/ ---- ==== -=== List Existing Sinks in a Namespace +=== List existing sinks in a namespace [source,curl] ---- @@ -1260,7 +1277,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$N ---- ==== -=== Get Status of a Source +=== Get status of a source [source,curl] ---- @@ -1297,7 +1314,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/ ---- ==== -=== Status of a Sink in a Namespace +=== Get status of a sink [source,curl] ---- @@ -1334,7 +1351,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/ ---- ==== -=== Get Source Connector Details +=== Get source connector details [source,curl] ---- @@ -1384,7 +1401,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sources/$TENANT/ ---- ==== -=== Get Sink Details +=== Get sink details [source,curl] ---- @@ -1449,7 +1466,7 @@ curl --fail --location --request GET "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$N ---- ==== -=== Start a Source Connector +=== Start a source connector [source,curl] ---- @@ -1458,7 +1475,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sources/$TENANT No response indicates success. -=== Start a Sink +=== Start a sink [source,curl] ---- @@ -1467,7 +1484,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$ No response indicates success. -=== Stop a Source Connector +=== Stop a source connector [source,curl] ---- @@ -1476,7 +1493,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sources/$TENANT No response indicates success. -=== Stop a Sink Connector +=== Stop a sink [source,curl] ---- @@ -1485,7 +1502,7 @@ curl --fail --location --request POST "$WEB_SERVICE_URL/admin/v3/sinks/$TENANT/$ No response indicates success. -=== Create a Source Connector +=== Create a source connector [source,curl] ---- @@ -1503,7 +1520,7 @@ The curl parameter `@` indicates an input file. When executing the curl command, ensure the input file is accessible and in the proper directory for reading. ==== -=== Delete a Source Connector +=== Delete a source connector [source,curl] ---- @@ -1512,7 +1529,7 @@ curl --fail --location --request DELETE "$WEB_SERVICE_URL/admin/v3/sources/$TENA No response indicates success. -=== Create a Sink Connector +=== Create a sink [source,curl] ---- @@ -1558,7 +1575,7 @@ The curl parameter `@` indicates an input file. When executing the curl command, ensure the input file is accessible and in the proper directory for reading. ==== -=== Delete a Sink Connector +=== Delete a sink [source,curl] ---- diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index f7ba9b0..3452275 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -11,21 +11,13 @@ The xref:astra-cli:ROOT:index.adoc[{astra_db} Command-Line Interface ({astra_cli {astra_cli} commands are available for {astra_db} and {astra_stream}. They're designed to get you working quickly, with an emphasis on automation. -For example, the following command create a Pulsar tenant: -[source,bash] ----- -astra streaming create cli-tenant-demo ----- -+ -.Result -[%collapsible] -==== -[source,console] +For example, the following command creates a Pulsar tenant: + +[source,bash,subs="+quotes"] ---- -[OK] Tenant 'cli-tenant-demo' has been created. +astra streaming create **TENANT_NAME** --cloud **CLOUD_PROVIDER** --region **REGION_NAME** --plan **PLAN_TYPE** --namespace **TENANT_INITIAL_NAMESPACE_NAME** ---- -==== You can use {astra_cli} instead of or in addition to the {astra_ui} and {astra_db} APIs. diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index 3332b5e..15a81d9 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -196,7 +196,7 @@ CREATE TABLE IF NOT EXISTS *KEYSPACE_NAME*.*TABLE_NAME* (key text PRIMARY KEY, c . Confirm that your table was created: + -[source,sql,subs="+quotes"] +[source,cql,subs="+quotes"] ---- select * from *KEYSPACE_NAME*.*TABLE_NAME*; ---- @@ -204,7 +204,7 @@ select * from *KEYSPACE_NAME*.*TABLE_NAME*; .Result [%collapsible] ==== -[source,sql,subs="verbatim,quotes"] +[source,console,subs="verbatim,quotes"] ---- key | c1 -----+---- @@ -380,7 +380,7 @@ bin/pulsar-admin topics list-partitioned-topics astracdc .Result [%collapsible] ==== -[source,bash] +[source,console] ---- persistent://ten01/astracdc/data-7e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c-keysp.table1-partition-1 persistent://ten01/astracdc/log-7e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c-keysp.table1-partition-2 @@ -412,7 +412,7 @@ bin/pulsar-admin topics list ten01/astracdc .Result [%collapsible] ==== -[source,bash] +[source,console] ---- persistent://ten01/astracdc/log-7e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c-keysp.table1-partition-2 persistent://ten01/astracdc/log-7e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c-keysp.table1-partition-0 diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index 177a1b6..e1b4023 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -1,29 +1,49 @@ = {product} Functions :page-tag: astra-streaming,dev,develop,pulsar,java,python -Functions are lightweight compute processes that enable you to process each message received on a topic. You can apply custom logic to that message, transforming or enriching it, and then output it to a different topic. +Functions are lightweight compute processes that you can run on each message a topic receives. +You can apply custom logic to a message, transforming or enriching it, and then output it to a different topic. -Functions run inside {product} and are therefore serverless. You write the code for your function in Java, Python, or Go, then upload the code. It will be automatically run for each message published to the specified input topic. +Functions run inside {product}, which makes them serverless. +You write the code for your function in Java, Python, or Go, and then upload the code. +It automatically runs for each message published to the specified input topic. -Functions are implemented using Apache Pulsar(R) functions. See https://pulsar.apache.org/docs/en/functions-overview/[Pulsar Functions overview] for more information about Pulsar functions. +Functions are implemented using https://pulsar.apache.org/docs/en/functions-overview/[Apache Pulsar(R) functions]. +See [Pulsar Functions overview] for more information about Pulsar functions. -[TIP] +.Pulsar functions video +[%collapsible] +==== +video::OCqxcNK0HEo[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] ==== -Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. + +[IMPORTANT] +==== +Custom functions require a https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. ==== -== Deploy Python functions in a ZIP file +[#create-an-archive] +== Deploy a function archive -{product} supports Python-based Pulsar Functions. -These functions can be packaged in a ZIP file and deployed to {product} or Pulsar. The same ZIP file can be deployed to either environment. -We'll create a ZIP file in the proper format, then use the pulsar-admin command to deploy the ZIP. -We'll pass a create function YAML configuration file as a parameter to pulsar-admin, which defines the Pulsar Function options and parameters. +You can write Pulsar functions for {product} in Python, Java, or Go. -Assuming the ZIP file is named `testpythonfunction.zip`, an unzipped `testpythonfunction.zip` folder looks like this: +To deploy a function to {product} or Pulsar, you can package the project into an archive, including the function code and any dependencies. +You can deploy the same archive to either environment. -[source,console] +This is recommended for complex functions with long scripts, multiple scripts, or many dependencies. +You can also use this approach to deploy multiple function classes with a single package. + +[tabs] +====== +Python functions:: ++ +-- +To create a Python function, the `.zip` file must have the correct structure. +For example, assuming a project named `testpythonfunction`, the extracted archive has the following structure: + +[source,plain] ---- /my-python-function python-code/my-python-function.zip @@ -31,19 +51,20 @@ Assuming the ZIP file is named `testpythonfunction.zip`, an unzipped `testpython python-code/src/my-python-function.py ---- -. To deploy a ZIP, first create the proper ZIP file directory structure. That file format/layout looks like this: +. Prepare the directory structure: + [source,shell] ---- -mkdir my-python-function -mkdir my-python-function/python-code -mkdir my-python-function/python-code/deps/ -mkdir my-python-function/python-code/src/ +mkdir **FUNCTION_NAME** +mkdir **FUNCTION_NAME**/python-code +mkdir **FUNCTION_NAME**/python-code/deps/ +mkdir **FUNCTION_NAME**/python-code/src/ -touch my-python-function/python-code/src/my-python-function.py +touch **FUNCTION_NAME**/python-code/src/**FUNCTION_NAME**.py ---- -. Add your code to my-python-function.py. For this example, we'll just use a basic exclamation function: +. Write your function code in a `.py` file. +This example adds an exclamation point to the end of any messages. + [source,python] ---- @@ -57,149 +78,95 @@ class ExclamationFunction(Function): return input + '!' ---- -. Add your dependencies to the /deps folder. For this example, we'll use the pulsar-client library. +. Add dependencies to `/deps`. +This example uses the `pulsar-client` library. + -[source,bash] +[source,shell] ---- cd deps pip install pulsar-client==2.10.0 ---- -. Run the following command to add my-pulsar-function.zip to the root of the file structure: +. Create a `.zip` file at the root of the project: + -[source,bash] +[source,shell] ---- cd deps zip -r ../my-python-function.zip . - adding: sh-1.12.14-py2.py3-none-any.whl (deflated 2%) ---- +-- -. Ensure your package has the ZIP file at the root of the file structure: +Java functions:: + -[source,shell] ----- -python-code ls -al -deps -my-python-function.zip -src ----- +-- +To deploy a Java function, you must create a `.jar` file. -=== Deploy a Python function with configuration file - -. Create a deployment configuration file with contents similar to the following `func-create-config.yaml` example: -+ -.func-create-config.yaml -[source,yaml] ----- -py: -className: pythonfunc.ExclamationFunction -parallelism: 1 -inputs: - - persistent://mytenant/n0/t1 -output: persistent://mytenant/ns/t2 -autoAck: true -tenant: mytenant -namespace: ns0 -name: testpythonfunction -logTopic: -userConfig: - logging_level: ERROR ----- -+ -This file will be passed to the pulsar-admin create function command. +. Create a Java project for your function. -. Use pulsar-admin to deploy the Python ZIP to {product} or Pulsar. -The command below assumes you've properly configured the client.conf file for pulsar-admin commands against your Pulsar cluster. See the documentation xref:configure-pulsar-env.adoc[here] for more information. +. Declare dependencies in `pom.xml`: + -[source,bash] ----- -bin/pulsar-admin functions create --function-config-file +.pom.xml +[source,xml] ---- - -. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product}. - -.. You can also use the pulsar-admin command to list your functions: -+ -[source,bash] ----- -bin/pulsar-admin functions list --tenant --namespace ----- - -== Deploy Java functions in a JAR file - -{product} supports Java-based Pulsar Functions which are packaged in a JAR file. -The JAR can be deployed to {product} or Pulsar. The same JAR file can be deployed to either environment. - -We'll create a JAR file using Maven, then use the pulsar-admin command to deploy the JAR. -We'll pass a create function YAML configuration file as a parameter to pulsar-admin, which defines the Pulsar function options and parameters. - -. To deploy a JAR, first create the proper JAR with the Java code of the Pulsar Function. -An example pom.xml file is shown below: -+ -.Function pom.xml -[%collapsible] -==== -[source,pom] ----- - - + - 4.0.0 - - java-function - java-function - 1.0-SNAPSHOT - - - - org.apache.pulsar - pulsar-functions-api - 3.0.0 - - - - - - - maven-assembly-plugin - - false - - jar-with-dependencies - - - - org.example.test.ExclamationFunction - - - - - - make-assembly - package - - assembly - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 17 - - - - - - ----- -==== - -. Package the JAR file with Maven: + 4.0.0 + + java-function + java-function + 1.0-SNAPSHOT + + + + org.apache.pulsar + pulsar-functions-api + 3.0.0 + + + + + + + maven-assembly-plugin + + false + + jar-with-dependencies + + + + org.example.test.ExclamationFunction + + + + + + make-assembly + package + + assembly + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + + +---- + +. Write your function code in your project. + +. Package the `.jar` file with Maven: + [source,bash] ---- @@ -209,7 +176,7 @@ mvn package .Result [%collapsible] ==== -[source,bash] +[source,console] ---- [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS @@ -219,224 +186,248 @@ mvn package [INFO] ------------------------------------------------------------------------ ---- ==== +-- + +//// +TODO: +Go functions:: ++ +-- +Must have 4 steps to maintain numbering. +-- +//// +====== + +. If you haven't done so already, xref:configure-pulsar-env.adoc[set up your environment for the Pulsar binaries]. -. Create a deployment configuration file with contents similar to the following `func-create-config.yaml` example: +. Create a deployment configuration YAML file that defines the function metadata and associated topics: + .func-create-config.yaml -[source,yaml] +[source,yaml,subs="+quotes"] ---- -jar: -className: com.example.pulsar.ExclamationFunction +py: **PATH_TO_FUNCTION_ARCHIVE** +className: **FILE_NAME**.**CLASS_NAME** parallelism: 1 inputs: - - persistent://mytenant/n0/t1 -output: persistent://mytenant/ns/t2 + - persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** +output: persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** autoAck: true -tenant: mytenant -namespace: ns0 -name: testjarfunction +tenant: **TENANT_NAME** +namespace: **NAMESPACE_NAME** +name: **DISPLAY_NAME** logTopic: userConfig: - logging_level: ERROR + logging_level: ERROR ---- + -This file will be passed to the pulsar-admin create function command. +Replace the following: ++ +* `**PATH_TO_FUNCTION_ARCHIVE**`: The path to the function archive. + +* `**FILE_NAME**.**CLASS_NAME**`: The class to execute. +An archive can contain multiple classes, but only one is used per deployment. ++ +** For Python scripts, the `className` is the Python filename (without the extension) and the class to execute, such as `pythonfunc.ExclamationFunction`. +If there is no class in the file, the `className` is the filename without the extension, such as `pythonfunc`. +** For Java scripts, the `className` is the path and the class to execute, such as `com.example.pulsar.ExclamationFunction`. + +* `**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME**`: Define the tenant, namespace, and topic for both `input` (incoming messages passed to the function) and `output` (the results of the function). + [IMPORTANT] ==== -{product} requires the `inputs` topic to have a message schema defined before deploying the function. -Otherwise, deployment errors may occur. -Use the {astra_ui} to define the message schema for a topic. +To avoid errors, make sure the `inputs` topic (declared in your configuration YAML file) has a defined message schema before you deploy the function. +You can define a topic's message schema in the {astra_ui}. ==== + -. Use pulsar-admin to deploy your new JAR to {product} or Pulsar. -The command below assumes you've properly configured the client.conf file for pulsar-admin commands against your Pulsar cluster. See the documentation xref:configure-pulsar-env.adoc[here] for more information. +Optionally, you can declare a `logTopic` in the same way: `persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME**`. + +* `**DISPLAY_NAME**`: The function display name in {product}, such as `testpythonfunction`. + +. Use `pulsar-admin` to deploy the function to {product} or Pulsar using your configuration file: + -[source,bash] +[source,bash,subs="+quotes"] ---- -bin/pulsar-admin functions create --function-config-file +bin/pulsar-admin functions create --function-config-file **PATH_TO_FUNCTION_CONFIG_YAML** ---- ++ +.Result +[%collapsible] +==== +A response of `Created Successfully!` indicates the function is deployed and ready to accept messages. -. Check results: Go to the {astra_ui} to see your newly deployed function listed under the *Functions* tab for your Tenant. See <> for more information on testing and monitoring your function in {product}. - -.. You can also use the pulsar-admin command to list your functions: +If the response is a 402 error with `Reason: only qualified organizations can create functions`, then your organization must be upgraded to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. +==== + -[source,bash] +[TIP] +==== +If your Python function contains only a single script and no dependencies, you can deploy the `.py` file directly, without packaging it into a `.zip` file or creating a configuration file: ++ +[source,bash,subs="+quotes"] ---- -bin/pulsar-admin functions list --tenant --namespace +$ ./pulsar-admin functions create \ + --py **PATH_TO_PYTHON_FILE** \ + --classname **FILE_NAME**.**CLASS_NAME** \ + --tenant **TENANT_NAME** \ + --namespace **NAMESPACE_NAME** \ + --name **DISPLAY_NAME** \ + --auto-ack true \ + --inputs persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** \ + --output persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** \ + --log-topic persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** ---- -== Add functions in {product} dashboard - -Add functions in the Functions tab of the {product} dashboard. +If there is no class in the file, the `className` is only the filename without the extension. +==== -. Select *Create Function* to get started. -. Choose your function name and namespace. +. Verify the deployment: + -image::astream-name-function.png[Function and Namespace] - -. Select the file you want to pull the function from and which function you want to use within that file. +[source,bash,subs="+quotes"] +---- +bin/pulsar-admin functions list --tenant **TENANT_NAME** --namespace **NAMESPACE_NAME** +---- ++ +You can also check the {astra_ui} to confirm the function is listed on the tenant's *Functions* tab. ++ +See <> for more information on testing and monitoring your function in {product}. -{astra_db} generates a list of acceptable classes. Python and Java functions are added a little differently from each other. +== Deploy functions in the {astra_ui} -Python functions are added by loading a Python file (.py) or a zipped Python file (.zip). When adding Python files, the Class Name is specified as the name of the Python file without the extension plus the class you want to execute. +. In the {astra_ui} navigation menu, click *Streaming*, and then select a tenant. -For example, if the Python file is called `testfunction.py` and the class is `ExclamationFunction`, then the class name is `testfunction.ExclamationFunction`. The file can contain multiple classes, but only one is used. If there is no class in the Python file (when using a basic function, for example), specify the filename without the extension (ex. `function`). +. On the *Functions* tab, click *Create Function*. -Java functions are added by loading a Java jar file (.jar). When adding Java files, you also need to specify the name of the class to execute as the function. +. Enter a function name, and then select the namespace within the tenant. -image::astream-exclamation-function.png[Exclamation Function] -[start=4] -. Choose your input topics. +. Upload function code: + -image::astream-io-topics.png[IO Topics] - -. Choose *Optional Destination Topics* for output and logging. +[tabs] +====== +Upload your own code:: + -image::astream-optional-destination-topics.png[Optional Topics] +-- +. Select *Upload my own code*. -. Choose *Advanced Options* and run at least one sink instance. +. Select your function file: + -image::astream-advanced-config.png[Advanced Configuration] +* `.py`: A single, independent Python script +* `.zip`: A Python script with dependencies +* `.jar`: A Java function +* `.go`: A Go function -. Choose your *Processing Guarantee*. The default value is *ATLEAST_ONCE*. Processing Guarantee offers three options: +. Based on the uploaded file, select the specific class (function) to deploy. + -* *ATLEAST_ONCE*: Each message sent to the function can be processed more than once. -* *ATMOST_ONCE*: The message sent to the function is processed at most once. Therefore, there is a chance that the message is not processed. -* *EFFECTIVELY_ONCE*: Each message sent to the function will have one output associated with it. - -. Provide an *Option Configuration Key*. See the https://pulsar.apache.org/functions-rest-api/#operation/registerFunction[Pulsar Docs] for a list of configuration keys. +{astra_db} generates a list of acceptable classes detected in the code. +A file can contain multiple classes, but only one is used per deployment. ++ +For Python scripts, the class name is the Python filename (without the extension) and the class to execute. +For example, if the Python file is called `testfunction.py` and the class is `ExclamationFunction`, then the class name is `testfunction.ExclamationFunction`. +If there is no class in the Python file, the class name is the filename without the extension, such as `testfunction`. + -image::astream-provide-config-keys.png[Provide Config Key] +For Java scripts, the class name is the class to execute. ++ +image::astream-exclamation-function.png[Exclamation Function] +-- -. Select *Create*. +Use {company} transform function:: ++ +-- +. Select *Use {company} transform function*. -You have created a function for this namespace. You can confirm your function was created in the *Functions* tab. +This is the only function option available for organizations on the *Free* plan. +-- +====== -== Add function with Pulsar CLI +. Select input topics. -You can also add functions using the Pulsar CLI. We will create a new Python function to consume a message from one topic, add an exclamation point, and publish the results to another topic. +. (Optional) Select output and log topics. -. Create the following Python function in `testfunction.py`: +. (Optional) Configure advanced settings: + -[source,python] ----- -from pulsar import Function +* *Instances*: Enter a number of sink instances to run. +* *Processing Guarantee*: Select one of the following: +** *ATLEAST_ONCE* (default): Each message sent to the function can be processed one or more times. +** *ATMOST_ONCE*: Each message sent to the function is processed 0 or 1 times. +This means there is a change that a message is not processed. +** *EFFECTIVELY_ONCE*: Each message sent to the function has only one output associated with it. +* *Timeout*: Set a timeout limit. +* *Auto Acknowledge*: Enable or disable automatic message acknowledgement. -class ExclamationFunction(Function): - def __init__(self): - pass +. (Optional) Provide a config key, if required. +For more information, see the https://pulsar.apache.org/functions-rest-api/#operation/registerFunction[Pulsar documentation]. - def process(self, input, context): - return input + '!' ----- -+ -. Deploy `testfunction.py` to your Pulsar cluster using the Pulsar CLI: -+ -[source,bash] ----- -$ ./pulsar-admin functions create \ - --py /full/path/to/testfunction.py \ - --classname testfunction.ExclamationFunction \ - --tenant \ - --namespace default \ - --name exclamation \ - --auto-ack true \ - --inputs persistent:///default/in \ - --output persistent:///default/out \ - --log-topic persistent:///default/log ----- -+ -You will see "Created Successfully!" if the function is set up and ready to accept messages. -+ -[TIP] -==== -If you receive a 402 error with "Reason: only qualified organizations can create functions", this means your organization needs to be upgraded to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. -==== +. Click *Create*. + +. Confirm your function was created on the *Functions* tab. -. Use `./pulsar-admin functions list --tenant ` to list the functions in your tenant and confirm your new function was created. +== Manage deployed functions -== Testing Your Function +After you deploy a function, you can test, start, stop, monitor, edit, and delete it. -Triggering a function is a convenient way to test that the function is working. When you trigger a function, you are publishing a message on the function's input topic, which triggers the function to run. If the function has an output topic and the function returns data to the output topic, that data is displayed. +=== Test functions -Send a test value with Pulsar CLI's `trigger` to test a function you've set up. +To test the function, publish a message to the function's `input` topic or use `pulsar-admin functions trigger`. +If the function produces output and it has an `output` topic, the output data is returned. -. Listen for messages on the output topic: +. Listen for messages on the `output` topic: + [source,bash] ---- -$ ./pulsar-client consume persistent:///default/ \ +$ ./pulsar-client consume persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** \ --subscription-name my-subscription \ --num-messages 0 # Listen indefinitely ---- + -. Test your exclamation function with `trigger`: +. Test your function: + [source,bash] ---- $ ./pulsar-admin functions trigger \ - --name exclamation \ - --tenant \ - --namespace default \ - --trigger-value "Hello world" + --name **FUNCTION_DISPLAY_NAME** \ + --tenant **TENANT_NAME** \ + --namespace **NAMESPACE_NAME** \ + --trigger-value "**MESSAGE**" ---- + -The trigger sends the string `Hello world` to your exclamation function. Your function should output `Hello world!` to your consumed output. +The trigger sends the message string to the function. +Your function should output the result of processing the message. [#controlling-your-function] -== Controlling Your Function +=== Control functions -You can start, stop, and restart your function by selecting it in the *Functions* dashboard. +In the {astra_ui}, on your tenant's *Functions* tab, you can use *Function Controls* to start, stop, and restart functions. -image::astream-function-controls.png[Function Controls] +=== Monitor functions -== Monitoring Your Function - -Functions produce logs to help you in debugging. To view your function's logs, open your function in the *Functions* dashboard. +Functions produce logs to help you debug them. +In the {astra_ui}, on your tenant's *Functions* tab, you can view, refresh, copy, and download your functions' logs. image::astream-function-log.png[Function Log] -In the upper right corner of the function log are controls to *Refresh*, *Copy to Clipboard*, and *Save* your function log. +If you specified a log topic when deploying your function, function logs also output to that topic. -== Updating Your Function +== Edit functions -A function that is already running can be updated with new configuration. The following settings can be updated: +. In the {astra_ui}, on your tenant's *Functions* tab, click *Update Function*. +. Edit the following settings as needed, and then click *Update*. ++ * Function code * Output topic * Log topic * Number of instances * Configuration keys -If you need to update any other setting of the function, delete and then re-add the function. - -To update your function, select your function in the *Functions* dashboard. - -image::astream-function-update.png[Update Function] +If you need to change any other function settings, you must delete and redeploy the function with the desired settings. -. Select *Change File* to find your function locally and click *Open*. +== Delete functions -. Update your function's *Instances* and *Timeout*. When you're done, click *Update*. - -. An *Updates Submitted Successfully* flag will appear to let you know your function has been updated. - -== Deleting Your Function - -To delete a function, select the function to be deleted in the *Functions* dashboard. +[IMPORTANT] +==== +Deleting a function is permanent. +==== -image::astream-delete-function.png[Delete Function] +. In the {astra_ui}, on your tenant's *Functions* tab, select the function to delete. . Click *Delete*. -. A popup will ask you to confirm deletion by entering the function's name and clicking *Delete*. -. A *Function-name Deleted Successfully!* flag will appear to let you know you've deleted your function. - -== Pulsar functions video - -Follow along with this video from our *Five Minutes About Pulsar* series to see a Pulsar Python function in action. - -video::OCqxcNK0HEo[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] - -== Next -Learn more about developing functions for {product} and Pulsar https://pulsar.apache.org/docs/en/functions-develop/[here]. \ No newline at end of file +. To confirm deletion, enter the function's name, and then click *Delete*. \ No newline at end of file diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index 4402f73..9197f0e 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -36,24 +36,7 @@ Broker Service URLs start with `pulsar(+ssl)`. An xref:operations:astream-token-gen.adoc[{astra_db} application token] is _not_ the same as a Pulsar token. ==== -To create a new Pulsar token for your {product} tenant, do the following: - -. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. - -. Click the *Settings* tab. - -. Click *Create Token*. - -. Select a token lifetime. -+ -Practice responsible credential management: -+ -* Be aware of the security implications for tokens that never expire. -* Consider how long you actually plan to use the token. - -. Copy the generated token and store it securely. -+ -The token is shown only once. +For information about creating Pulsar tokens, see xref:operations:astream-token-gen.adoc[]. == Make a request @@ -74,9 +57,12 @@ curl -sS --location -X GET "$WEB_SERVICE_URL/admin/v3/sinks/builtinsinks" \ --header "Authorization: $PULSAR_TOKEN" \ --header "Content-Type: application/json" ---- -+ + +== Format responses + The default response is a single JSON string. -You can use modifications like ` | jq .` to format the output for easier reading. + +You can use modifications like ` | jq .` or ` | python3 -mjson.tool` to format the output for easier reading. == See also diff --git a/modules/developing/partials/client-variables-table.adoc b/modules/developing/partials/client-variables-table.adoc index 6fc9af2..bef1400 100644 --- a/modules/developing/partials/client-variables-table.adoc +++ b/modules/developing/partials/client-variables-table.adoc @@ -9,8 +9,7 @@ In the *Details* section, get the *Broker Service URL*. |`pulsarToken` |The token for Pulsar cluster authentication -|In the {astra_ui} navigation menu, click *Streaming*, select your streaming tenant, and then click the *Settings* tab. -Click *Create Token* to generate a Pulsar token. +|For information about creating Pulsar tokens, see xref:operations:astream-token-gen.adoc[]. |`tenantName` |The name of your streaming tenant diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index ef1dd89..ea26ac4 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -1,246 +1,89 @@ -= Getting started with {product} += {product} quickstart :navtitle: Get started :page-tag: astra-streaming,planner,quickstart,pulsar -{product} is a cloud native data streaming and event stream processing -service tightly integrated into the {astra_ui} and powered by Apache Pulsar(TM). -Using {product}, customers can quickly create Pulsar instances, -manage their clusters, scale across cloud regions, and manage Pulsar resources -such as topics, connectors, functions and subscriptions. +{product} is a serverless data streaming and event stream processing service integrated in the {astra_ui} and powered by Apache Pulsar(TM). +Using {product}, you can create Pulsar instances, manage streaming clusters, scale across cloud regions, and manage Pulsar resources such as topics, connectors, functions and subscriptions. -Follow this guide to create a new account in {astra_db}, along with a streaming tenant running Apache Pulsar. +This quickstart demonstrates how to create and use a streaming tenant running Apache Pulsar. == Prerequisites -You will need the following to complete this guide: +* Sign in or create an {login_url}[{astra_db} account^]. -* A working email address +== Create a streaming tenant -== Create your {astra_db} account +A {product} tenant is a portion of {company}-managed Apache Pulsar that is only yours. +Within tenants, you create namespaces, topics, functions, and more. +To learn more about the concept of tenancy, see the https://pulsar.apache.org/docs/concepts-multi-tenancy/[Pulsar documentation]. -. Navigate to the {streaming_signup_url}[{astra_ui} sign in page^]. -+ -image:astream-login.png[Login to {astra_db}] - -. Click the "Sign Up" link (toward the bottom). - -. Provide your information along with a valid email. -+ -image:astream-create-account.png[Create {astra_db} account] - -. You should receive an email from {company} verifying your address - click the button within. - -. If you aren't automatically signed in, visit the {login_url}[{astra_db} sign in page^] and use your new account creds. - -== Your first streaming tenant +You can create a tenant in the {astra_ui} or programmatically. +For this quickstart, use the {astra_ui}. -Think of a tenant in {product} as your safe space to work. -It is a portion of {company}'s managed Apache Pulsar that is only yours. -Learn more about the concept of tenancy in the https://pulsar.apache.org/docs/concepts-multi-tenancy/[Pulsar docs]. - -The steps in the below tabs will guide you through creating a streaming tenant. -You'll use this tenant to create namespaces, topics, functions, and pretty much everything else. -The only difference between the tabs is how your tenant is created - they all have the same result. - -[tabs] -====== -{astra_ui}:: -+ --- . In the {astra_ui} navigation menu, click *Streaming*. . Click *Create a Tenant*. . Name your streaming tenant something memorable like `my-stream-**RANDOM_UUID**`. All tenant names in {product} must be unique. +`**RANDOM_UUID**` can be any string of letters and numbers, as long as it is unique. . Choose your preferred cloud provider and region. -For your first example tenant, the region doesn't really matter. -+ -image:create-a-stream.png[Create a new tenant in {product}] +For this example tenant, the region doesn't really matter. . Click *Create Tenant*. You are directed to the quickstart page for your new tenant. + image:new-tenant-quickstart.png[New tenant quickstart in {product}] --- - -{astra_cli}:: -+ --- -{astra_cli} is a set of commands for creating and managing all {astra_db} resources. -For more information, see the https://docs.datastax.com/en/astra-cli/docs/0.2/[documentation]. - -. Set the required variables, replacing `**RANDOM_UUID**` with a few random numbers or letters. -Optionally, you can choose a different cloud provider and region. -Use `astra streaming list-regions` to see available values. -+ -[source,shell,subs="+quotes"] ----- -TENANT="my-stream-**RANDOM_UUID**" -CLOUD_PROVIDER_STREAMING="gcp" -CLOUD_REGION_STREAMING="uscentral-1" ----- - -. Run the following script to create a new streaming tenant: -+ -[source,shell] ----- -include::{astra-streaming-examples-repo}/astra-cli/create-tenant.sh[] ----- --- -====== - -Next, create a namespace in your new tenant. -== A namespace to hold topics +== Create a namespace in your tenant -A namespace exists within a tenant. -A namespace is a logical grouping of message topics. - -{product} automatically create a `default` namespaces when you create a tenant. -You can use the default namespace or create new ones. +Namespaces are logical groupings, such as environments or applications, for message topics within tenants. +{product} automatically creates a `default` namespaces when you create a tenant. +You can use the default namespace or create other namespaces. Tenants usually have many namespaces. -What is contained within namespace is limited only by your imagination. -It could be an environment (dev, stage, prod) or by application (catalog, cart, user) or whatever logical grouping makes sense to you. +To learn more about namespaces, see the https://pulsar.apache.org/docs/concepts-messaging/#namespaces[Pulsar documentation]. -Learn more about namespaces in the https://pulsar.apache.org/docs/concepts-messaging/#namespaces[Pulsar docs]. +You can create namespaces in the {astra_ui} or programmatically. +For this quickstart, use the {astra_ui}. +For information about the Pulsar CLI or APIs, see xref:developing:configure-pulsar-env.adoc[] and xref:developing:using-curl.adoc[]. -[tabs] -====== -{astra_ui}:: -+ --- -. Navigate to the "Namespace And Topics" tab. -+ -image:namespace-tab.png[Namespace tab in {product}] +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -. Click the "Create Namespace" button, and give your namespace a super original name like "my-namespace". -+ -image:create-namespace.png[Create namespace in {product}] +. Click the *Namespace And Topics* tab, and then click *Create Namespace*. +. Enter a namespace name, and then click *Create*. . Click "Create" to create the namespace. -+ -image:namespace-listing.png[Namespaces in {product}] --- - -Pulsar Admin:: -+ --- -. Set the required variables: -+ -[source,shell,subs="+quotes"] ----- -NAMESPACE="**NAMESPACE_NAME**" -TENANT="my-stream-**RANDOM_UUID**" # set previously ----- - -. Run the following script to create a new namespace: -+ -[source,shell] ----- -include::{astra-streaming-examples-repo}/pulsar-admin/create-namespace.sh[] ----- --- - -curl:: -+ -- -. Set the required variables: -+ -[source,shell,subs="+quotes"] ----- -PULSAR_TOKEN="**PULSAR_TOKEN**" -WEB_SERVICE_URL="**TENANT_WEB_SERVICE_URL**" -NAMESPACE="**NAMESPACE_NAME**" -TENANT="my-stream-**RANDOM_UUID**" # set previously ----- - -. Run the following script to create a new namespace: -+ -[source,shell] ----- -include::{astra-streaming-examples-repo}/curl/create-namespace.sh[] ----- --- -====== -== A topic to organize messages +== Create a topic Topics are the core construct of a messaging system. Topics provide a way to group messages matching certain criteria. -The name of the topic usually loosely defines the criteria, and more advanced characteristics like schemas might be applied at the topic level as well. -Topics are where others can "listen" for new messages. -Consumers subscribe to topics to "listen" for messages, and functions and connectors can also "listen" for messages and automate workflows. -In Pulsar, topic addresses look like a URL (ie: persistent://tenant/namespace/topic). -Learn more about topics in the https://pulsar.apache.org/docs/concepts-messaging/#topics[Pulsar docs]. - -[tabs] -====== -{astra_ui}:: -+ --- -. In the *Namespace And Topics* tab, locate the namespace created above and click its *Add Topic* button. -. Provide a name for the topic like "my-topic". -It must start with a lowercase letter, and it can contain only letters, numbers, and hyphens (`-`). -+ -Disregard https://pulsar.apache.org/docs/concepts-messaging/#partitioned-topics[persistence and partitioning] for now. -+ -image:add-topic.png[Add topic in {product}] +The name of the topic usually broadly defines the criteria. +You can also apply more advanced characteristics, like schemas, at the topic level. -. Click *Add Topic* to create the topic within the namespace. -+ -image:topic-listing.png[Topic listing in {product}] --- +Topics are where other applications or services can "listen" for new messages. +Consumers subscribe to topics to receive messages, and functions and connectors can listen for messages to trigger automated workflows. -Pulsar Admin:: -+ --- -To learn more about connecting to {product} with the pulsar-admin CLI, see xref:developing:configure-pulsar-env.adoc[]. +In Pulsar, topic addresses are formatted like URLs, such as `persistent://tenant/namespace/topic`. +To learn more about topics, see the https://pulsar.apache.org/docs/concepts-messaging/#topics[Pulsar documentation]. -. Set the required variables: -+ -[source,shell,subs="+quotes"] ----- -TOPIC="**TOPIC_NAME**" -NAMESPACE="**NAMESPACE_NAME**" # set previously -TENANT="my-stream-**RANDOM_UUID**" # set previously ----- - -. Run the following script to create a new topic: -+ -[source,shell] ----- -include::{astra-streaming-examples-repo}/pulsar-admin/create-topic.sh[] ----- --- +As in the previous steps, you can create topics in the {astra_ui} or programmatically. -curl:: -+ --- -To learn more about interacting with {product} through HTTP, see xref:developing:using-curl.adoc[]. +. In the {astra_ui} navigation menu, click *Streaming*, and then select your tenant. -. Set the required variables: +. Click the *Namespace And Topics* tab. + +. Locate a namespace where you want to create a topic, and then click *Add Topic*. + +. Enter a topic name, and then click *Add Topic*. + -[source,shell,subs="+quotes"] ----- -TOPIC="**TOPIC_NAME**" -PULSAR_TOKEN="**PULSAR_TOKEN**" # set previously -WEB_SERVICE_URL="**TENANT_WEB_SERVICE_URL**" # set previously -NAMESPACE="**NAMESPACE_NAME**" # set previously -TENANT="my-stream-**RANDOM_UUID**" # set previously ----- - -. Run the following script to create a new topic: +Topic names must start with a lowercase letter, and they can contain only letters, numbers, and hyphens (`-`). + -[source,shell] ----- -include::{astra-streaming-examples-repo}/curl/create-topic.sh[] ----- --- -====== +Disregard the https://pulsar.apache.org/docs/concepts-messaging/#partitioned-topics[*Persistent* and *Partitioned*] options for now. == Next steps diff --git a/modules/operations/pages/astream-token-gen.adoc b/modules/operations/pages/astream-token-gen.adoc index 8a905d4..cfecbe2 100644 --- a/modules/operations/pages/astream-token-gen.adoc +++ b/modules/operations/pages/astream-token-gen.adoc @@ -1,200 +1,70 @@ -= Manage Tokens += Manage tokens -There are *two* different tokens within {astra_db} - the *{astra_db} application token* and the *{product} Pulsar token*. +{product} uses two types of tokens that you can generate in {astra_db}. -The <> is used for authentication within {astra_ui} and the xref:apis:index.adoc[DevOps API]. +[#astra-token] +== {astra_db} application tokens -The <> is a native Pulsar JSON Web Token (JWT) token that controls authentication to the Pulsar cluster. +{astra_db} application tokens are access tokens for authentication to the {astra_ui} and the xref:apis:index.adoc[{product} DevOps API]. -== What's the difference? +You need an application token for actions related to {astra_db} organizations and databases. -The {astra_db} token is an access token. It is used to authenticate your service account in the DevOps API and {astra_ui}. +For information about creating and managing {astra_db} application tokens, see xref:astra-db-serverless:administration:manage-application-tokens.adoc[]. -The {product} Pulsar token is a JWT token. {astra_db} forwards the token on to the Pulsar cluster, which verifies if the role in allowed. +[#pulsar-token] +== {product} Pulsar tokens -In general, actions related to your {astra_db} Org (tenant management, members, org billing, usage metrics, etc.) use your {astra_db} Token, and actions specific to a Pulsar tenant (message namespaces, topics, message metrics, etc.) use a Pulsar JWT token. +{product} Pulsar token are Pulsar https://jwt.io/introduction/[JSON Web Token (JWT)] for authentication to your {product} Pulsar cluster through the Pulsar CLI, runtime clients, or the Pulsar Admin API. +If necessary, {astra_db} forwards the JWT to the Pulsar cluster for role verification. -For more, see https://docs.datastax.com/en/streaming/astra-streaming/operations/onboarding-faq.html#secure-sign-on-roles-and-permissions[SSO Roles and Permissions]. +You need a Pulsar JWT for actions related to Pulsar tenants, namespaces, messages, topics, and functions, as well as Pulsar CLI/API commands. -[#astra-token] -== Generate {astra_db} token +You can generate, copy, or delete {product} Pulsar tokens for each of your {product} tenants: -The {astra_db} token can be generated in the <> or the <>. +. In the {astra_ui} navigation menu, click *Streaming*. -=== DevOps API +. Select the tenant for which you want to manage tokens. -. Create an application token to https://docs.datastax.com/en/astra/docs/_attachments/devopsv2.html[authenticate your service account] in the DevOps API. -. Once you have authenticated your service account, you can create and revoke tokens with the https://docs.datastax.com/en/astra/docs/_attachments/devopsv2.html[DevOps API]. -. Get all clients within the organization: -+ -[source,shell] ----- -curl --request GET \ - --url 'https://api.astra.datastax.com/v2/clientIdSecrets' \ - --header 'Accept: application/json' \ - --header 'Authorization: Bearer ' ----- -+ -.Response -[%collapsible] -==== -[source,json] ----- -{"clients":[ - {"clientId":"DkFtHKMhDQDuQtlExkSzwbya", - "roles":["21ef3576-0197-415a-b167-d510af12ecf0"], - "generatedOn":"2021-02-22T17:09:58.668Z"}, - {"clientId":"eYSboCJaESiblJZnKZWMxROv", - "roles":["21ef3576-0197-415a-b167-d510af12ecf0"], - "generatedOn":"2021-04-28T18:49:11.323Z"} -]} ----- -==== +. Click the *Settings* tab. -. Create an application token for a specific client: -+ -[source,shell] ----- -curl --request POST \ - --url 'https://api.astra.datastax.com/v2/clientIdSecrets' \ - --header 'Accept: application/json' \ - --header 'Authorization: Bearer ' \ - --data '{"roles": [""]}' ----- -+ -.Response -[%collapsible] -==== -[source,json] ----- -{ - "clientId":"zjCEYwRGWocLfQJHBNQxvorr", - "secret":"SLR.cllL1Yz...", - "orgId":"dccb8c32-cc2a-4bea-bd95-47ab8eb20510", - "roles":["21ef3576-0197-415a-b167-d510af12ecf0"], - "token":"AstraCS:...", - "generatedOn":"2021-04-30T19:38:26.147847107Z" -} ----- -==== +. In the *Token Management* section, click *Create Token*. + +. Select a token expiration from 7 days to never. + -[TIP] -==== -For the `roleId`, provide the relevant role's `id` UUID value from a prior `GET` query, as shown in -https://docs.datastax.com/en/astra-serverless/docs/manage/devops/devops-roles.html#_creating_a_new_role[Getting existing roles in your organization]. -The API results will show the UUID for each role id. -==== - -. In the command-line interface associated with your environment, paste the following environment variable copied from token generation: +Practice responsible credential management: + -[source,shell,subs="+quotes"] ----- -export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** ----- - -[#astra-token-ui] -=== Generate an {astra_db} application token - -. From any page in {astra_ui}, select the *Organizations* dropdown. +* Be aware of the security implications for tokens that never expire. +* Consider how long you actually plan to use the token. -. In the main dropdown, select *Organization Settings*. - -. From your Organization page, select *Token Management*. - -. Select the role you want to attach to your token. The permissions for your selected role will be displayed. - -. Select *Generate Token*. {product} will generate your token and display the _Client ID_, _Client Secret_, and _Token_. - -. Download your _Client ID_, _Client Secret_, and _Token_. +. Copy the token and store it securely. + -[IMPORTANT] -==== -After you navigate away from the page, you won't be able to download your _Client ID_, _Client Secret_, and _Token_ again. -==== +The token is shown only once. -. In the command-line interface associated with your environment, paste the following environment variable copied from token generation: +. To delete a token, click *Delete*. + -[source,shell,subs="+quotes"] ----- -export ASTRA_DB_APPLICATION_TOKEN=**APPLICATION_TOKEN** ----- - -You can now execute DevOps API commands from your terminal to your database. +Ensure you update your application with a new, valid Pulsar token before deletion. +Applications using the deleted Pulsar token will no longer be able to connect to {product}. -=== Delete {astra_db} token +== Usage examples -If you need to limit access to your database, you can delete a token. - -. Select the overflow menu for the token you want to delete. - -. Select *Delete* to delete that token. - -. If necessary, generate a new token for the same user role. - -[#pulsar-token] -== Generate Pulsar token - -To generate, copy, or delete {product} Pulsar tokens within your streaming tenant, visit the **Token Management** section of your streaming tenant's **Settings** page. -Select **Create Token** to generate a Pulsar token for this streaming tenant, and then copy the Pulsar token and store it securely. - -Token duration ranges from 7 days to never expiring. -If you choose a token with an expiration, ensure you replace your token with a new, valid Pulsar token before the expiration date. - -=== Set environment variables - -Download your Pulsar connection info as detailed https://docs.datastax.com/en/astra-streaming/docs/astream-quick-start.html#download-connect-info[here]. - -In the command-line interface associated with your environment, paste the following environment variables copied for {product}: - -[source,shell,subs="+quotes"] ----- -export TENANT=**TENANT_NAME** -export INPUT_TOPIC=**INPUT_TOPIC_NAME** -export NAMESPACE=default -export BEARER_TOKEN=**PULSAR_TOKEN** ----- - -You can now execute Pulsar admin commands from your terminal to your database. - -=== Delete Pulsar token - -Select the **trashcan** icon to delete a Pulsar token. - -Ensure you update your application with a new, valid Pulsar token before deletion. Applications using the deleted Pulsar token will no longer be able to connect to {product}. - -For more on JSON Web Tokens, see the https://jwt.io/introduction/[JWT documentation]. - -== Which token should I use? - -The line between {astra_db} and {product} tokens can be a little unclear. - -Think of `pulsar-admin` and the DevOps API as complementary tools with different scopes. - -Use `pulsar-admin` for interacting with your Pulsar clusters. Topics, namespaces, tenants, and their metrics fall under this scope. - -Use the DevOps API for org-wide {astra_db} scope. Users, tenants, billing, and usage metrics fall under this scope. - -Some cases can use `pulsar-admin` **or** the DevOps API - we want the tools to be complementary, not restrictive, so do what works best for you! - -This section should help you choose which tool to use, and which token is required: +The following examples show how you can use the two tokens for different actions. === Track monthly usage Use an {astra_db} application token to track monthly usage. For example: -[source,shell] +[source,curl,subs="+quotes"] ---- -curl --request GET \ ---url 'https://api.astra.datastax.com/v2/databases/' \ ---header 'Accept: application/json' \ ---header 'Authorization: Bearer ' +curl -sS --location -X GET "https://api.astra.datastax.com/v2/databases/**DATABASE_ID**" \ +--header "Accept: application/json" \ +--header "Authorization: Bearer **APPLICATION_TOKEN**" ---- === Monitor a topic's health Use a Pulsar token to monitor a topic's health. -For example: +For example, the following command would authenticate through a Pulsar token declared in the `pulsar-admin` binary configuration: [source,shell] ---- @@ -216,12 +86,11 @@ bin/pulsar-admin sinks status Use an {astra_db} application token to get tenant billing reports. For example: -[source,shell] +[source,curl,subs="+quotes"] ---- -curl --request GET \ ---url https://api.astra.datastax.com/admin/v2/stats/namespaces/ ---header 'Accept: application/json' \ ---header 'Authorization: Bearer ' +curl -sS --location -X GET "https://api.astra.datastax.com/v2/stats/namespaces/**TENANT_NAME**" \ +--header "Accept: application/json" \ +--header "Authorization: Bearer **APPLICATION_TOKEN**" ---- == See also diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index ecf7e0e..31b4401 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -103,7 +103,7 @@ If not, there are issues in the previous configuration procedures. == Troubleshoot 401 Unauthorized errors -If the additional scrape job returns a 401 Unauthorized error, make sure your Pulsar JWT token isn't expired. +If the additional scrape job returns a 401 Unauthorized error, make sure your Pulsar JWT isn't expired. For more information, see xref:astream-token-gen.adoc[]. == See also diff --git a/modules/operations/pages/monitoring/stream-audit-logs.adoc b/modules/operations/pages/monitoring/stream-audit-logs.adoc index 10de31e..e63fcab 100644 --- a/modules/operations/pages/monitoring/stream-audit-logs.adoc +++ b/modules/operations/pages/monitoring/stream-audit-logs.adoc @@ -36,7 +36,7 @@ You can use the xref:astra-api-docs:ROOT:attachment$devops-api/index.html#tag/Or + [source,curl,subs="+quotes"] ---- -curl -sS -X POST "https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs" \ +curl -sS --location -X POST "https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs" \ --header "Authorization: Bearer **APPLICATION_TOKEN**" \ --header "Accept: application/json" --data '{ @@ -71,7 +71,7 @@ HTTP/1.1 202 Accepted + [source,curl,subs="+quotes"] ---- -curl -sS -X GET "https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs" \ +curl -sS --location -X GET "https://api.astra.datastax.com/v2/organizations/**ORG_ID**/telemetry/auditLogs" \ --header "Authorization: Bearer **APPLICATION_TOKEN**" \ --header "Accept: application/json" ---- diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index a359d3e..b22424d 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -79,17 +79,7 @@ All tokens created within a tenant are assigned roles similar to the assigning t .What is the difference between the {astra_db} application token and the Pulsar token? Are they interchangeable? [%collapsible] ==== -{astra_db} offers different layers of authentication based on the desired action. - -For actions related to your {astra_db} organization or the resources within, including users, roles, databases, and so on, you use an {astra_db} application token with the appropriate role. - -For actions specific to a Pulsar tenant, such as message namespaces, topics, and message metrics, you use a Pulsar JWT token. - -For more information, see the following: - -* xref:astra-streaming:developing:astra-cli.adoc[] -* xref:astra-streaming:developing:using-curl.adoc[] -* xref:astra-streaming:developing:configure-pulsar-env.adoc[] +See xref:monitoring:astream-token-gen.adoc[]. ==== == Data Migration and Geo-replication From 0e18e717766dce5d539e932209051a1c6a7f0ddb Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 10:55:57 -0700 Subject: [PATCH 10/18] last cleanup wave --- modules/ROOT/pages/astream-custom-roles.adoc | 48 --- modules/ROOT/pages/astream-faq.adoc | 26 +- .../ROOT/pages/astream-org-permissions.adoc | 105 ++---- modules/apis/pages/index.adoc | 3 +- modules/developing/pages/astream-cdc.adoc | 2 +- .../developing/pages/astream-functions.adoc | 13 +- .../pages/clients/csharp-produce-consume.adoc | 6 +- .../pages/clients/golang-produce-consume.adoc | 6 +- modules/developing/pages/clients/index.adoc | 2 +- .../pages/clients/java-produce-consume.adoc | 6 +- .../pages/clients/nodejs-produce-consume.adoc | 6 +- .../pages/clients/python-produce-consume.adoc | 6 +- .../pages/clients/spring-produce-consume.adoc | 6 +- .../pages/produce-consume-astra-portal.adoc | 2 +- .../pages/produce-consume-pulsar-client.adoc | 2 +- modules/operations/nav.adoc | 3 +- .../pages/astream-georeplication.adoc | 38 +- modules/operations/pages/astream-limits.adoc | 337 +++++++++--------- modules/operations/pages/astream-pricing.adoc | 71 +++- modules/operations/pages/astream-regions.adoc | 2 +- .../pages/astream-release-notes.adoc | 4 +- .../operations/pages/monitoring/index.adoc | 14 +- .../pages/monitoring/integration.adoc | 5 +- .../pages/monitoring/new-relic.adoc | 2 +- .../pages/monitoring/stream-audit-logs.adoc | 11 +- modules/operations/pages/onboarding-faq.adoc | 147 +++----- .../pages/private-connectivity.adoc | 52 +-- 27 files changed, 438 insertions(+), 487 deletions(-) delete mode 100644 modules/ROOT/pages/astream-custom-roles.adoc diff --git a/modules/ROOT/pages/astream-custom-roles.adoc b/modules/ROOT/pages/astream-custom-roles.adoc deleted file mode 100644 index 2a02cf4..0000000 --- a/modules/ROOT/pages/astream-custom-roles.adoc +++ /dev/null @@ -1,48 +0,0 @@ -= Manage custom roles -:page-tag: astra-streaming,security,secure,pulsar - - -Within *Settings* > *Users*, you can see the permissions for a specific role by hovering over the number in the *Roles* column of the table. This will show the permissions granted to the role. - -image::ROOT:astream-roles.png[] - -If the default roles don't meet your requirements, you can use custom roles that meet your organizational needs. - -== Create custom role - -In the {astra_ui} or {astra_db} DevOps API, you can create custom roles. - -. From your Organization page, select *Role Management*. - -. In the main dropdown, select the organization for which you want to add your custom role. - -. From *Settings* page, select *Roles*. - -. Select *Add Custom Role*. - -. Enter the name you want to use for your custom role. This name should help you easily identify when you want to assign this role to users. - -. Select the Organization, Keyspace, Table, and API permissions you want to assign to your custom role. -+ -[IMPORTANT] -==== -If you want users with this role to be able to see the {product} user interface, make sure you select _Manage Streaming_ permissions. -==== - -. If you want to apply your selected permissions to specific databases or keyspaces, toggle the switch to not apply the permissions to all databases in an organization. Then select the specific databases or keyspaces to which you want to apply the permissions. - -. Once you have selected your permissions, select *Create Role*. - -To see your custom roles, select *Role Management* within your Organization. You can now invite users using your new custom role. - -== Edit user roles - -. From *Settings* page, select *Roles*. - -. Select *Edit Role* from the overflow menu for the custom role you want to update. - -. When editing the role, you can edit the name, permissions, database, and keyspace. - -. Once you have updated your permissions, select *Edit Role*. - -Your updated custom role will show up in *Roles* within your Organization. diff --git a/modules/ROOT/pages/astream-faq.adoc b/modules/ROOT/pages/astream-faq.adoc index c373531..6ea842f 100644 --- a/modules/ROOT/pages/astream-faq.adoc +++ b/modules/ROOT/pages/astream-faq.adoc @@ -2,21 +2,13 @@ :navtitle: FAQs :page-tag: astra-streaming,dev,admin,planner,plan,pulsar -== How do I sign up for the {product}? +== How do I get started with {product}? -Follow our simple xref:getting-started:index.adoc[getting started guide] to sign up for {astra_db} and get your first streaming tenant created. +See xref:getting-started:index.adoc[]. == How is {product} priced? -{product} offers three subscription plans, as well as pricing for *Dedicated Clusters* plans. -For more information, see https://www.datastax.com/products/astra-streaming/pricing[{product} Pricing]. - -Additional customizations can impact billed charges, such as the following: - -* Message retention duration -* Maximum message retention storage -* Number of tenants -* Region of tenant +See xref:operations:astream-pricing.adoc[]. == Why is {product} based on Apache Pulsar? @@ -26,19 +18,19 @@ See our https://www.datastax.com/blog/four-reasons-why-apache-pulsar-essential-m {product} is based heavily on technology originally created as part of Kesque. With the launch of {product} we will begin the process of shutting down the Kesque service and migrating customers to the new {product} platform. -== How can I get started with {product}? -To get started with {product}, you can create a free account at https://astra.datastax.com and create your first streaming instance immediately. No credit card required. +== Who should use {product}? -== Who are the target customers for {product}? -{product} has been architected to satisfy the most stringent enterprise requirements around availability, scale and latency. {product} was built to handle mission critical use cases for Fortune 100 companies across BFSI, Telecommunications, Technology, Retail, Oil & Gas and Healthcare. +{product} has been architected to satisfy the most stringent enterprise requirements around availability, scale, and latency. +{product} was built to handle mission critical use cases for Fortune 100 companies across BFSI, Telecommunications, Technology, Retail, Oil and Gas, and Healthcare. The pricing model also makes {product} accessible to mid market and small/medium business customers who need event stream processing capabilities to run core parts of their business. -And finally {product} offers a user friendly interface and free tier to satisfy the needs of individual developers and technologists who want to learn more about Apache Pulsar and data streaming in general. +And finally {product} offers a user-friendly interface and free tier to satisfy the needs of individual developers and technologists who want to learn more about Apache Pulsar and data streaming in general. == What is CDC for {astra_db}? -Change Data Capture (CDC) for {astra_db} uses a Pulsar IO source connector that processes changes from the Cassandra Change Agent via a Pulsar topic. For more, see https://docs.datastax.com/en/astra/docs/astream-cdc.html[CDC for {astra_db}]. +Change Data Capture (CDC) for {astra_db} uses a Pulsar IO source connector that processes changes from the Cassandra Change Agent via a Pulsar topic. +For more information, see xref:developing:astream-cdc.adoc[]. == What are tenants, namespaces, topics, and sinks? diff --git a/modules/ROOT/pages/astream-org-permissions.adoc b/modules/ROOT/pages/astream-org-permissions.adoc index cd72125..b620d5e 100644 --- a/modules/ROOT/pages/astream-org-permissions.adoc +++ b/modules/ROOT/pages/astream-org-permissions.adoc @@ -1,90 +1,59 @@ -= User permissions += Manage roles and permissions :page-tag: astra-streaming,security,secure,pulsar -Default and xref:astream-custom-roles.adoc[custom roles] allow admins to manage unique permissions for users based on your organization and database requirements. +You manage role-based access control (RBAC) for {product} through your {astra_db} organization. +For information about {astra_db} RBAC, including default roles, custom roles, permissions, and user management, see xref:astra-db-serverless:administration:manage-database-access.adoc[]. -You can manage roles using the {astra_ui} or the {astra_db} DevOps API. -For more information, see https://docs.datastax.com/en/astra/docs/user-permissions.html[{astra_db} User Permissions]. +== {product} permissions -== {product} Organization permissions +Permissions specific to {product} include the following: -[cols="1,1,1"] -|=== -|Console name |Description |DevOps API parameter +* *Manage Streaming* (`org-stream-manage`): View, add, edit, or remove Astra Streaming configurations. -|Read Audits -|Enables read and download audits. -|`org-audits-read` +=== Default roles for {product} -|Write IP Access List -|Create or modify an access list using the DevOps API or the {astra_ui}. -|`accesslist-write` +There are no default {astra_db} roles specifically scoped to {product}. +However, the following default roles have the *Manage Streaming* permission: -|Delete Custom Role -|Delete of custom role. -|`org-role-delete` +* *Organization Administrator* +* *Administrator Service Account* +* *API Administrator Service Account* +* *API Administrator User* -|Manage Streaming -|Create an {product} Service using the DevOps API or the {astra_ui}. -|`org-stream-manage` +For information about permissions assigned to default roles, see xref:astra-db-serverless:administration:manage-database-access.adoc[]. -|Write Organization -|Create new organizations or delete an existing organization. Hides manage org and org settings. -|`org-write` +=== Custom roles for {product} -|Read Billing -|Enables links and access to billing details page. -|`org-billing-read` +If you xref:astra-db-serverless:administration:manage-database-access.adoc#custom-roles[create custom roles] for {product}, those roles must have the following permissions, at minimum: -|Read IP Access List -|Enables links and access to access list page. -|`accesslist-read` +* *Manage Streaming* (`org-stream-manage`): View and manage {product} in the {astra_ui}. +* *View DB* (`org-db-view`): View the {astra_ui} in general. -|Read User -|Access to viewing users of an organization. -|`org-user-read` +Additional permissions might be required, depending on the tasks the role needs to perform. -|Read Organization -|View organization in the {astra_ui}. -|`org-read` +[TIP] +==== +To control access to specific streaming tenants, you can set granular xref:astra-db-serverless:administration:manage-database-access.adoc#role-scopes[resource scopes] on custom roles. +==== -|Read Custom Role -|See a custom role and its associated permissions. -|`org-role-read` +== Authentication and authorization in Pulsar and {astra_db} -|Read External Auth -|See security settings related to external authentication providers. -|`org-external-auth-read` +Pulsar has the concept of https://pulsar.apache.org/docs/security-authorization/[clients with role tokens]. +In Pulsar, authentication is the process of verifying a provided token (JWT), and authorization is the process of determining if the role claimed in that token is allowed to complete the requested action. -|Read Token -|Read token details. -|`org-token-read` +{product} uses the {company} version of Apache Pulsar (xref:luna-streaming::index.adoc[Luna Streaming]). +The https://github.com/datastax/pulsar[Luna project] is an open fork of the Pulsar project that maintains feature parity with OSS Pulsar. {product}, as a managed service, abstracts some features/options of Pulsar to ensure continuous, reliable service. -|Delete Custom Role -|Delete of custom role. -|`org-role-delete` +On a shared cluster, your {astra_db} organization has one or more tenants on a shared Pulsar cluster. +Each of your tenants is secured by Pulsar authentication and authorization models, as well as your {astra_db} organization's authentication and authorization ({astra_db} RBAC). -|Notification Write -|Enable or disable notifications in organization notification settings. -|org-notification-write -|Write Billing -|Enables links and ability to add or edit billing payment info. -|`org-billing-write` +{product} shared clusters are created and administered by {product} administrators. +Each tenant is assigned a custom role and permissions limited to that tenant only. +All tokens created within a tenant are assigned roles similar to the assigning tenant. -|Write User -|Add, create, or remove a user using the DevOps API or the {astra_ui}. -|`org-user-write` +For programmatic access, you use {astra_db} application tokens or Pulsar JWT, depending on the operation you need to perform. +For more information, see xref:operations:astream-token-gen.adoc[]. -|Write Custom Role -|Create custom role. -|`org-role-write` +== See also -|Write External Auth -|Update security settings related to external auth providers. -|`org-external-auth-write` - -|Write Token -|Create application token. -|`org-token-write` - -|=== \ No newline at end of file +* xref:operations:astream-pricing.adoc[] \ No newline at end of file diff --git a/modules/apis/pages/index.adoc b/modules/apis/pages/index.adoc index e9e81e3..f90b68f 100644 --- a/modules/apis/pages/index.adoc +++ b/modules/apis/pages/index.adoc @@ -1,5 +1,4 @@ -= API References -:navtitle: API references overview += {product} APIs :description: Learn about {product} APIs :page-tag: astra-streaming,dev,develop,pulsar diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index 15a81d9..e720652 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -364,7 +364,7 @@ Make sure the response includes your changes to the index: ---- [[increase-partitions]] -== Increase the CDC data-topic Partitions +== Increase the CDC data-topic partitions After enabling CDC, 3 data and 3 log partitions are created under the `astracdc` namespace. Increasing the number of partitions will create new partitions, but existing data will remain in the old partitions. diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index e1b4023..5b45982 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -1,4 +1,4 @@ -= {product} Functions += {product} functions :page-tag: astra-streaming,dev,develop,pulsar,java,python Functions are lightweight compute processes that you can run on each message a topic receives. @@ -19,9 +19,9 @@ video::OCqxcNK0HEo[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445p [IMPORTANT] ==== -Custom functions require a https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. +Custom functions require a xref:operations:astream-pricing.adoc[paid {product} plan]. -Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. +Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions] only. ==== [#create-an-archive] @@ -255,7 +255,8 @@ bin/pulsar-admin functions create --function-config-file **PATH_TO_FUNCTION_CONF ==== A response of `Created Successfully!` indicates the function is deployed and ready to accept messages. -If the response is a 402 error with `Reason: only qualified organizations can create functions`, then your organization must be upgraded to the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. +If the response is `402 Payment Required` with `Reason: only qualified organizations can create functions`, then you must upgrade to a xref:operations:astream-pricing.adoc[paid {product} plan]. +Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions] only. ==== + [TIP] @@ -333,7 +334,9 @@ Use {company} transform function:: -- . Select *Use {company} transform function*. -This is the only function option available for organizations on the *Free* plan. +This is the only function option available on the {product} *Free* plan. + +For more information, see xref:streaming-learning:functions:index.adoc[] and xref:operations:astream-pricing.adoc[]. -- ====== diff --git a/modules/developing/pages/clients/csharp-produce-consume.adoc b/modules/developing/pages/clients/csharp-produce-consume.adoc index 4021d92..72a6c58 100644 --- a/modules/developing/pages/clients/csharp-produce-consume.adoc +++ b/modules/developing/pages/clients/csharp-produce-consume.adoc @@ -1,6 +1,8 @@ -= Producing and consuming messages using the C# pulsar client on {product} += Use the C# Pulsar client on {product} :navtitle: C# -:description: This is a guide to create a simple producer and consumer using Pulsar's C# client. +:description: Produce and consume messages with the C# Pulsar client and {product}. + +You can produce and consume messages with the C# Pulsar client and {product}. Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. diff --git a/modules/developing/pages/clients/golang-produce-consume.adoc b/modules/developing/pages/clients/golang-produce-consume.adoc index 3a91b94..2935420 100644 --- a/modules/developing/pages/clients/golang-produce-consume.adoc +++ b/modules/developing/pages/clients/golang-produce-consume.adoc @@ -1,8 +1,10 @@ -= Producing and consuming messages using the Golang pulsar client on {product} += Use the Golang Pulsar client with {product} :navtitle: Golang -:description: This is a guide to create a simple producer and consumer using Pulsar's golang client. +:description: Produce and consume messages with the Golang Pulsar client and {product}. :page-tag: astra-streaming,dev,develop,pulsar,go +You can produce and consume messages with the Golang Pulsar client and {product}. + Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. == Prerequisites diff --git a/modules/developing/pages/clients/index.adoc b/modules/developing/pages/clients/index.adoc index 0a978a3..fbd81f9 100644 --- a/modules/developing/pages/clients/index.adoc +++ b/modules/developing/pages/clients/index.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages on {product} with Pulsar clients += Use Pulsar clients with {product} :navtitle: Pulsar clients :description: Use any of the Pulsar Clients to interact with your {product} topics. diff --git a/modules/developing/pages/clients/java-produce-consume.adoc b/modules/developing/pages/clients/java-produce-consume.adoc index aa2d7e0..cd4451d 100644 --- a/modules/developing/pages/clients/java-produce-consume.adoc +++ b/modules/developing/pages/clients/java-produce-consume.adoc @@ -1,8 +1,10 @@ -= Producing and consuming messages using the Java pulsar client with {product} += Use the Java Pulsar client with {product} :navtitle: Java -:description: This is a guide to create a simple producer and consumer using Pulsar's java client. +:description: Produce and consume messages with the Java Pulsar client and {product}. :page-tag: astra-streaming,dev,develop,pulsar,java +You can produce and consume messages with the Java Pulsar client and {product}. + Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. == Prerequisites diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index 461fc74..6eb0089 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -1,8 +1,10 @@ -= Producing and consuming messages using the Node.js Pulsar client on {product} += Use the Node.js Pulsar client with {product} :navtitle: Node.js -:description: This is a guide to create a simple producer and consumer using Pulsar's Node.js client. +:description: Produce and consume messages with the Node.js Pulsar client and {product}. :page-tag: astra-streaming,connect,dev,develop,nodejs,pulsar +You can produce and consume messages with the Node.js Pulsar client and {product}. + Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. == Prerequisites diff --git a/modules/developing/pages/clients/python-produce-consume.adoc b/modules/developing/pages/clients/python-produce-consume.adoc index ac78dda..4b60244 100644 --- a/modules/developing/pages/clients/python-produce-consume.adoc +++ b/modules/developing/pages/clients/python-produce-consume.adoc @@ -1,8 +1,10 @@ -= Producing and consuming messages using the python pulsar client on {product} += Use the Python Pulsar client with {product} :navtitle: Python -:description: This is a guide to create a simple producer and consumer using Pulsar's python client. +:description: Use the Python Pulsar client with {product} to produce and consume messages. :page-tag: astra-streaming,dev,develop,python,pulsar +You can use the Python Pulsar client with {product} to produce and consume messages. + Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. == Prerequisites diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index af7a1dd..c5735d8 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -1,8 +1,10 @@ -= Producing and consuming messages using the Java pulsar client with {product} and Spring += Use the Java Pulsar client with {product} and Spring :navtitle: Spring -:description: This is a guide to create a simple producer and consumer using Pulsar's java client with Spring. +:description: Produce and consume messages with the Java Pulsar client, {product}, and Spring. :page-tag: astra-streaming,dev,develop,pulsar,java +You can produce and consume messages with the Java Pulsar client, {product}, and Spring. + == Prerequisites You will need the following prerequisites in place to complete this guide: diff --git a/modules/developing/pages/produce-consume-astra-portal.adoc b/modules/developing/pages/produce-consume-astra-portal.adoc index fa0be50..f191103 100644 --- a/modules/developing/pages/produce-consume-astra-portal.adoc +++ b/modules/developing/pages/produce-consume-astra-portal.adoc @@ -1,4 +1,4 @@ -= Producing and consuming messages using the {astra_ui} += Produce and consume messages in the {astra_ui} :navtitle: {astra_ui} :description: Use this guide to create and consume a topic message using the {astra_ui}. diff --git a/modules/developing/pages/produce-consume-pulsar-client.adoc b/modules/developing/pages/produce-consume-pulsar-client.adoc index 461cfd5..c84ea00 100644 --- a/modules/developing/pages/produce-consume-pulsar-client.adoc +++ b/modules/developing/pages/produce-consume-pulsar-client.adoc @@ -1,4 +1,4 @@ -= Produce and consume messages with the {product} Pulsar Client += Use the {product} Pulsar Client :navtitle: Pulsar CLI :description: Use the pulsar-client CLI to interact with your {product} tenants diff --git a/modules/operations/nav.adoc b/modules/operations/nav.adoc index 83dd3c7..b1748e4 100644 --- a/modules/operations/nav.adoc +++ b/modules/operations/nav.adoc @@ -17,5 +17,4 @@ * Administration ** xref:operations:astream-token-gen.adoc[] ** xref:operations:private-connectivity.adoc[] -** xref:ROOT:astream-org-permissions.adoc[] -** xref:ROOT:astream-custom-roles.adoc[] \ No newline at end of file +** xref:ROOT:astream-org-permissions.adoc[] \ No newline at end of file diff --git a/modules/operations/pages/astream-georeplication.adoc b/modules/operations/pages/astream-georeplication.adoc index 18eed5b..a9eebbe 100644 --- a/modules/operations/pages/astream-georeplication.adoc +++ b/modules/operations/pages/astream-georeplication.adoc @@ -6,40 +6,22 @@ Pulsar's serving layer (brokers) and storage layer (bookies) are decoupled in Pu Geo-replication typically provides redundancy in the event of datacenter outages, but can also be used for any application where Pulsar messages are produced and consumed across regions. -This doc provides: - -* xref:astream-georeplication.adoc#overview[] -* xref:astream-georeplication.adoc#async[] -* xref:astream-georeplication.adoc#astra-ui[] -* xref:astream-georeplication.adoc#test[] -* xref:astream-georeplication.adoc#replicated-subscriptions[] -* xref:astream-georeplication.adoc#monitor[] - -If you're already familiar with Pulsar's messaging replication and asynchronous geo-replication, skip ahead to xref:astream-georeplication.adoc#astra-ui[enabling geo-replication in {product}]. - [#overview] -== Overview of message replication in Pulsar - -In Pulsar, cross-cluster message replication can be implemented with *synchronous* or *asynchronous* message replication, and with or without a global configuration store in ZooKeeper. - -{product} only supports *asynchronous* geo-replication without a global configuration store. This approach offers better performance and lower latency. - -In asynchronous geo-replication, each region has its own local Pulsar cluster. Messages published in a cluster of one region are automatically replicated asynchronously to remote clusters in other regions. This is achieved through Pulsar’s built-in geo-replication capability. - -[#async] -== Asynchronous geo-replication overview - -{product} only supports *asynchronous* geo-replication without a global configuration store. +== Message replication in Pulsar -An *asynchronous* geo-replication cluster consists of two or more independent Pulsar clusters running in different regions. +In Pulsar, cross-cluster message replication can be implemented with _synchronous_ or _asynchronous_ message replication, and with or without a global configuration store in ZooKeeper. -Each Pulsar cluster contains its own set of brokers, bookies, and ZooKeeper nodes that are completely isolated from one another. +{product} supports only asynchronous geo-replication without a global configuration store. +This approach offers better performance and lower latency. -When messages are produced on a Pulsar topic, they are first persisted to the local cluster, and are then replicated asynchronously to the remote clusters. +In asynchronous geo-replication, each region has its own local Pulsar cluster. Each Pulsar cluster contains its own set of brokers, bookies, and ZooKeeper nodes that are completely isolated from one another. -The message producer doesn't wait for confirmation from multiple Pulsar clusters. The producer receives a response immediately after the nearest cluster successfully persists the data. The data is then asynchronously replicated to the other Pulsar clusters in the background. +When messages are produced on a Pulsar topic, they are first persisted to the local cluster, and are then replicated asynchronously to the remote clusters in other regions. +This is achieved through Pulsar's built-in geo-replication capability. -To set up asynchronous geo-replication in {product}, see xref:astream-georeplication.adoc#astra-ui[]. +The message producer doesn't wait for confirmation from multiple Pulsar clusters. +The producer receives a response immediately after the nearest cluster successfully persists the data. +The data is then asynchronously replicated to the other Pulsar clusters in the background. [#astra-ui] == Enable geo-replication in {product} diff --git a/modules/operations/pages/astream-limits.adoc b/modules/operations/pages/astream-limits.adoc index 5d5952b..707a85b 100644 --- a/modules/operations/pages/astream-limits.adoc +++ b/modules/operations/pages/astream-limits.adoc @@ -1,119 +1,102 @@ = {product} limits :page-tag: astra-streaming,admin,manage,pulsar -{product} includes guardrails and limits to ensure good practices, foster availability, and promote optimal configurations for your databases. +{product} includes guardrails and limits to ensure best practices, foster availability, and promote optimal configurations for your databases. -The below limits are for *Pay As You Go* {product} plans. -These limits can be different on the <>. +Unless otherwise noted, the following limits apply to shared clusters. +In general, xref:operations:astream-pricing.adoc#dedicated-clusters[dedicated clusters] have more relaxed limits than shared clusters, and some limits can be changed by contacting {support_url}[{company} Support]. == {product} guardrails -Guardrails are initially provisioned in the default settings by {product}. -You can't change these guardrails. +Guardrails are provisioned in the default settings for {product}. +You can't directly change these guardrails. +For dedicated clusters, some guardrails can be changed by contacting {support_url}[{company} Support]. [cols="1,1"] |=== -|Guardrail |Limit +|Guardrail |Limit |Comments -|Number of tenants per organization* -|10 +|Number of tenants per organization +a| +* Shared clusters: 10 +* Dedicated clusters: Unlimited + +|This limit can't be changed for shared clusters. |Number of namespaces per tenant -|10 +|All cluster: 10 +| |Number of topics per namespace -|100 - -|Max message size** -|5MB (default) - -|Max throughput per topic for consumer -|5000 messages per second, 25MB per second - -Max throughput for a *non-partitioned topic* or a *single partition of a partitioned topic*. - -|Max throughput per topic for producer -|5000 messages per second, 25MB per second - -*Multiple producers* to a *non-partitioned topic* or a *single partition of a partitioned topic* can produce a *combined* 5000 messages per second maximum. +a| +* Shared clusters: 100 +* Dedicated clusters: 5000 +| -|Max throughput per subscription -|5000 messages per second, 25MB per second +|Max message size +a| +* Shared clusters: 5MB +* Dedicated clusters: Customizable +| -*Multiple subscriptions* from a *non-partitioned topic* or a *single partition of a partitioned topic* can dispatch a *combined* 5000 messages per second maximum. +|Max throughput per topic for consumers +a| +* Shared clusters: 5000 messages per second, 25MB per second +* Dedicated clusters: Customizable +|This limit applies to non-partitioned topics and individual partitions of partitioned topics. + +|Max throughput per topic for producers +a| +* Shared clusters: 5000 messages per second, 25MB per second +* Dedicated clusters: Customizable +|This limit applies to non-partitioned topics and individual partitions of partitioned topics, regardless of the number of producers to a topic. +If there are multiple producers to a topic, they can produce a _combined maximum_ of 5000 messages per second. + +|Max throughput per topic for subscriptions +a| +* Shared clusters: 5000 messages per second, 25MB per second +* Dedicated clusters: Customizable +|This limit applies to non-partitioned topics and individual partitions of partitioned topics, regardless of the number of subscribers to a topic. +If there are multiple subscriptions to a topic, they can dispatch a _combined maximum_ of 5000 messages per second. |Auto topic creation -|Disabled +|All clusters: Disabled +| |Number of producers per topic -|50 +a| +* Shared clusters: 50 +* Dedicated clusters: 500 +| |Number of consumers per topic -|50 +a| +* Shared clusters: 50 +* Dedicated clusters: 500 +| -|Number of consumers per subscription -|50 +|Number of subscriptions per topic +a| +* Shared clusters: 50 +* Dedicated clusters: 500 +| -|Max unacked messages per consumer -|50000 +|Max unacknowledged messages per consumer +|All clusters: 50000 +| -|Max unacked messages per subscription -|200000 +|Max unacknowledged messages per subscription +|All clusters: 200000 +| -|Subscription Expiry -|Not set +|Subscription expiry +|All clusters: Not set +| |=== -Max message size can be changed for Classic databases, but not Serverless databases. -If you need assistance, contact {support_url}[{company} Support]. - -== Function and connector resource limits - -Functions and connector resources are set in the control plane per pod resource limits. The user cannot customize these resources. -The default settings are: - -* CPU: 0.25 core -* RAM: 500MB - -=== Custom functions - -Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. - -Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. - -== Non-changeable configurations - -* Data persistency (En,Qw,Qa) -* `Managedledger` policy/deletion -* Namespace bundle related - * Bundle split - * Bundle level clear backlog - * Bundle level unload - * Bundle level subscribe and unsubscribe -* Replication -* Delayed delivery -* Offload policy -* Offload deletion - -== Pulsar-admin CLI limits - -The following `pulsar-admin` commands don't work with {product}, -either because they're not applicable in a cloud environment or they would cause -issues with privacy or data integrity: - -* `bookies` -* `brokers` -* `broker-stats` -* `clusters` -* `ns-isolation-policies` -* `resource-quotas` -* `tenants` - -For more on `pulsar-admin`, see the Apache Pulsar https://pulsar.apache.org/docs/pulsar-admin/[documentation]. - == {product} namespace policies -These namespace policies are initially provisioned in the default settings by {product} and _can_ be changed by users. +These namespace policies are initially provisioned in the default settings for {product}, and you _can_ change them: [cols="1,1,1"] |=== @@ -161,92 +144,19 @@ These namespace policies are initially provisioned in the default settings by {p |=== -=== Pod label limit +=== Character limit The total characters of tenant name, namespace name, and function name cannot exceed 54 characters. This is a Kubernetes restriction based on a pod label's maximum size of 63 characters. You can read more about Kubernetes pod naming restrictions https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set[here]. -== {product} topic and namespace actions - -These topic and namespace actions are initially provisioned in the default settings by {product} and can be performed by users. - -[cols="1,1,1"] -|=== -|Allowed Action |Default Setting |Available in the {astra_ui} - -|Terminate topic -|Enabled -|No - -|Unload namespace -|Enabled -|No - -|Clear backlog at topic level -|Enabled -|No - -|Clear backlog at namespace level -|Enabled -|No - -|Set compaction threshold at namespace level -|Disabled -|No - -|Trigger compaction at topic level -|Enabled -|No - -|Topic compaction -|Enabled -|No - -|All subscription expiration -| -|No - -|=== - -== Dedicated clusters - -Message throughput, rate, and message max size can be customized on dedicated clusters. If you need assistance, open {support_url}[a support ticket]. - -[cols="1,1"] -|=== -|Guardrail |Limit - -|Number of tenants per organization -|No limit - -|Number of namespaces per tenant -|10 - -|Number of topics per namespace -|5000 - -|Number of functions per namespace -|5000 - -|Functions resources -|0.50 core, 1GB RAM - -|Consumers per topic -|500 +=== Namespace policy configuration file -|Subscriptions per topic -|500 - -|Producers per topic -|500 - -|=== - -== Configuration file - -Here is an example default namespace policy with limits set: +The following is an example of a default namespace policy with limits set. +.Default namespace policy with limits set +[%collapsible] +==== [source,yaml] ---- { @@ -340,3 +250,102 @@ Here is an example default namespace policy with limits set: "schema_validation_enforced" : false } ---- +==== + +== {product} topic and namespace actions + +These topic and namespace actions are provisioned in the default settings by {product}. +You can perform these actions in the {astra_ui} or programmatically with a sufficiently authorized role or authentication token. + +[cols="1,1,1"] +|=== +|Allowed Action |Default Setting |Available in the {astra_ui} + +|Terminate topic +|Enabled +|No + +|Unload namespace +|Enabled +|No + +|Clear backlog at topic level +|Enabled +|No + +|Clear backlog at namespace level +|Enabled +|No + +|Set compaction threshold at namespace level +|Disabled +|No + +|Trigger compaction at topic level +|Enabled +|No + +|Topic compaction +|Enabled +|No + +|All subscription expiration +|Disabled +|No + +|=== + +== Function and connector resources + +A function instance is a unit used for scaling Pulsar functions or Pulsar IO connectors running on {product}. +Each function instance is assigned a specific amount of CPU and memory which can be allocated to a Pulsar function. +Each Pulsar function running on {product} has a minimum of one function instance allocated to it. + +Functions and connector resources for function instances are set in the control plane based as resource limits. +You can't customize these resources. + +The default settings are as follows: + +[cols="1,1,1,1"] +|=== +|Cluster type |CPU per instance |RAM per instance |Maximum function instances per namespace + +|Shared +|0.25 core +|500MB +|Varies by plan + +|Dedicated +|0.50 core +|1GB +|5000 + +|=== + +== Immutable configurations + +The following configurations can't be changed: + +* Data persistency (`En`, `Qw`, `Qa`) +* `Managedledger` policy/deletion +* Namespace bundle configurations: + * Bundle split + * Bundle level clear backlog + * Bundle level unload + * Bundle level subscribe and unsubscribe +* Replication +* Delayed delivery +* Offload policy +* Offload deletion + +== Unsupported pulsar-admin commands + +{product} doesn't support the following `https://pulsar.apache.org/docs/pulsar-admin/[pulsar-admin]` commands because they aren't applicable in a cloud environment or they could cause privacy or data integrity issues: + +* `bookies` +* `brokers` +* `broker-stats` +* `clusters` +* `ns-isolation-policies` +* `resource-quotas` +* `tenants` diff --git a/modules/operations/pages/astream-pricing.adoc b/modules/operations/pages/astream-pricing.adoc index a4e2239..a1c8b09 100644 --- a/modules/operations/pages/astream-pricing.adoc +++ b/modules/operations/pages/astream-pricing.adoc @@ -1,5 +1,72 @@ -= {product} Pricing += {product} pricing :page-tag: astra-streaming,planner,plan,pulsar -For information on pricing for {product}, see https://www.datastax.com/products/astra-streaming/pricing[{product} pricing]. +{product} is a fully-managed, software-as-a-service (SaaS) offering embedded in {astra_db}. +{product} offers three subscription plans, as well as pricing for dedicated clusters. +A {product} subscription is associated with an {astra_db} organization, but {product} subscription plans are separate from {astra_db} organization subscription plans. + +For {product} plans and pricing details, see https://www.datastax.com/pricing/astra-streaming[{product} pricing]. +For information about {astra_db} plans, billing, and usage, see xref:astra-db-serverless:administration:subscription-plans.adoc[]. + +== {product} subscription plans + +{product} subscription plans include *Free*, *Pay As You Go*, and *Annual*. + +The *Free* plan allows only one topic. +It is meant for evaluation before upgrading to a paid plan. + +*Pay As You Go* and *Annual* plans realize the benefits of fully-managed SaaS offerings: You only pay for the resources you use to produce, process, and consume messages. +These plans require a payment method on file. +For metering details, see see https://www.datastax.com/pricing/astra-streaming[{product} pricing]. + +By default, {product} plans use secure, shared Pulsar clusters. +Your data and interaction with Pulsar are secured over a public internet connection. +If desired, you can opt in to <>. + +[#dedicated-clusters] +== Dedicated clusters + +If you prefer not to use shared clusters, you can opt for dedicated clusters. + +With dedicated clusters, you have your own private Pulsar cluster with all the benefits of {product} as a managed service. +You sign in to {astra_db} and use {product} in the same way that you would on shared clusters. +When you create a tenant, you have the option to deploy to your private cluster. +This means that you can use both shared or dedicated clusters, depending on your tenants' use cases. + +Dedicated clusters are an addition to your base {product} subscription plan, and billing for dedicated clusters is unique to each customer. +For information about dedicated cluster pricing and metering, see https://www.datastax.com/astra-streaming-dedicated-clusters[{product} Dedicated Clusters pricing]. + +Like shared clusters, all connections for dedicated clusters in {product} are guarded by authentication, authorization, and secure (TLS) communications. +By default, these connections are over the public internet. +Additionally, dedicated clusters support xref:operations:private-connectivity.adoc[private connectivity]. + +== Additional billed charges + +Regardless of your subscription plan or cluster type, additional customizations can impact your {product} billed charges. +These include, but are not limited to, the following: + +* Message retention duration +* Maximum message retention storage +* Number of tenants +* Region of tenant + +== Limits + +Regardless of your subscription plan or cluster type, {product} applies xref:astream-limits.adoc[guardrails and limits] on resource creation to ensure best practices, foster availability, and promote optimal configurations for your databases. + +Dedicated clusters have fewer limits than shared clusters. + +== Regions + +With shared clusters, you can create tenants in any of the xref:astream-regions.adoc[supported regions]. + +Dedicated clusters are open to almost any public cloud region. +For this reason, dedicated clusters offer more flexibility for xref:operations:astream-georeplication.adoc[geo-replication]. +If your preferred region isn't already available, you can contact your account representative or {support_url}[{company} Support] to request it. + +== See also + +* xref:operations:onboarding-faq.adoc[] +* xref:ROOT:astream-org-permissions.adoc[] +* xref:operations:monitoring/index.adoc#aggregate-astra-streaming-metrics[Aggregate {product} metrics] \ No newline at end of file diff --git a/modules/operations/pages/astream-regions.adoc b/modules/operations/pages/astream-regions.adoc index 730f5f8..0fe6b88 100644 --- a/modules/operations/pages/astream-regions.adoc +++ b/modules/operations/pages/astream-regions.adoc @@ -1,4 +1,4 @@ -= {product} Regions += {product} regions :page-tag: astra-streaming,admin,manage,pulsar When creating a tenant, select a region for your tenant. diff --git a/modules/operations/pages/astream-release-notes.adoc b/modules/operations/pages/astream-release-notes.adoc index 453f5e8..2576892 100644 --- a/modules/operations/pages/astream-release-notes.adoc +++ b/modules/operations/pages/astream-release-notes.adoc @@ -63,8 +63,8 @@ For more information, see xref:developing:clients/spring-produce-consume.adoc[Sp === {new} -* Custom functions can only be created by organizations on the https://docs.datastax.com/en/astra-serverless/docs/manage/org/manage-billing.html#_pay_as_you_go_plans[*Pay As You Go* or *Enterprise* {astra_db} plan]. -Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions]. +* Custom functions require a xref:operations:astream-pricing.adoc[paid {product} plan]. +Organizations on the *Free* plan can use xref:streaming-learning:functions:index.adoc[transform functions] only. == February 27, 2023 diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index a113da4..64afc3e 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -1,4 +1,4 @@ -= Monitoring streaming tenants += Monitor streaming tenants :navtitle: Monitoring overview Because {product} is a software-as-a-service product, not all Apache Pulsar metrics (https://pulsar.apache.org/docs/reference-metrics/[Pulsar Metrics Reference]) are exposed for external integration purposes. At a high level, {product} only exposes metrics that are related to namespaces. Other metrics that are not directly namespace related are not exposed externally, such as the Bookkeeper ledger and journal metrics and Zookeeper metrics. @@ -29,7 +29,7 @@ The following table lists recommended namespace and/or topic metrics as a starti include::example$namespace-topic-metrics.csv[] |=== -=== Replication Metrics +=== Replication metrics When geo-replication is enabled for a particular namespace, a subset of namespace metrics is available specifically for geo-replication purposes. Below is the list of recommended geo-replication metrics as a starting point. @@ -47,7 +47,7 @@ The following table gives the list of recommended subscription metrics as a star include::example$subscription-metrics.csv[] |=== -=== Function Metrics +=== Function metrics The following table gives the list of recommended function metrics as a starting point. This is only relevant when Pulsar functions are deployed in {product}. @@ -74,12 +74,12 @@ include::example$sink-connector-metrics.csv[] |=== [#aggregate-astra-streaming-metrics] -== Aggregate {product} Metrics +== Aggregate {product} metrics [IMPORTANT] ==== -Do _not_ aggregate metrics if you are on the *Pay As You Go* {product} plan because one cluster can be shared among multiple organizations. -For more information, see xref:astream-limits.adoc[]. +Do _not_ aggregate metrics on shared clusters because one cluster can be shared among multiple organizations. +For more information, see xref:astream-limits.adoc[] and xref:operations:astream-pricing.adoc[]. ==== Each externally exposed raw {product} metric is reported at a very low level, at each individual server instance (the `exported_instance` label) and each topic partition (the `topic` label). The same raw metrics could come from multiple server instances. From a {product} user's perspective, the direct monitoring of raw metrics is not really useful. Raw metrics need to be aggregated first - for example, by averaging or summing the raw metrics over a period of time. @@ -110,8 +110,6 @@ For more information, see the https://prometheus.io/docs/prometheus/latest/query These examples use the `pulsar_msg_backlog` raw metric to demonstrate the patterns. Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher and they exclude system topics (with the PromQL statement `{topic !~ ".*__.*"}`). -//TODO: Check the styles of double underscore and query filters - .About the system topics filter [%collapsible] ==== diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index 31b4401..708979a 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -1,4 +1,5 @@ -= External Prometheus and Grafana Integration += Integrate with an external Prometheus and Grafana instance +:navtitle: Integrate external Prometheus and Grafana {product} exposes some of the Pulsar metrics through Prometheus endpoints. The Prometheus configuration information to scrape {product} metrics into an external Prometheus server is found in the {astra_ui}. @@ -103,7 +104,7 @@ If not, there are issues in the previous configuration procedures. == Troubleshoot 401 Unauthorized errors -If the additional scrape job returns a 401 Unauthorized error, make sure your Pulsar JWT isn't expired. +If the additional scrape job returns a `401 Unauthorized` error, make sure your Pulsar JWT isn't expired. For more information, see xref:astream-token-gen.adoc[]. == See also diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index a3cc6e9..bb4e94f 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -1,4 +1,4 @@ -= New Relic Integration += Integrate with New Relic While there are multiple ways to https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic[send external Prometheus data to New Relic], one the https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] option is relevant to {product}. diff --git a/modules/operations/pages/monitoring/stream-audit-logs.adoc b/modules/operations/pages/monitoring/stream-audit-logs.adoc index e63fcab..37aae5b 100644 --- a/modules/operations/pages/monitoring/stream-audit-logs.adoc +++ b/modules/operations/pages/monitoring/stream-audit-logs.adoc @@ -13,13 +13,20 @@ Audit log streaming requires a streaming tenant in the AWS `us-east-2` region. You can create a new tenant with the xref:astra-streaming:getting-started:index.adoc[{product} quickstart] or use an existing {product} tenant. . In the {link-astra-portal}, go to **Streaming**. -. Select an existing tenant or xref:astra-streaming:getting-started:index.adoc#your-first-streaming-tenant[Create a streaming tenant] in AWS `us-east-2`. -. Add a xref:astra-streaming:getting-started:index.adoc#add-a-namespace-to-hold-topics[namespace] and xref:astra-streaming:getting-started:index.adoc#a-topic-to-organize-messages[topic] to the tenant. + +. Select an existing tenant or create a streaming tenant in AWS `us-east-2`, and then create a namespace and topic in the tenant. ++ +For more information about creating tenants, namespaces, and topics, see xref:astra-streaming:getting-started:index.adoc[]. + . On the *Namespace and Topics* page, click the new topic, and then copy the topic's **Full Name**, such as `persistent://aws-us-east-2-mk/*NAMESPACE_NAME*/*TOPIC_NAME*`. + . If necessary, create additional audit log topics, and then record the **Full Name** for each topic. You can use topics to organize audit logs by event type or other criteria. + . In the {link-astra-portal}, go to **Streaming**, and then click your audit log streaming tenant. + . On the *Connect* tab, click **Download client.conf**. + . To finalize the configuration, do one of the following: + * Send your topic's full name and the `client.conf` file to {support_url}[{company} Support] or your account representative, and then {company} will complete the setup. diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index b22424d..2b250b1 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -6,153 +6,104 @@ When considering {product} for your production workloads, you might have questions about what options are available for connecting and consuming your serverless clusters. This page answers the most common questions we receive about enrollment. -== {product} Environments +== Why does {company} call {product} "serverless"? -.What are the different ways I can use {product}? -[%collapsible] -==== -{product} offers three subscription plans, as well as pricing for *Dedicated Clusters* plans. -For more information, see https://www.datastax.com/products/astra-streaming/pricing[{product} Pricing]. +Running a production grade Pulsar cluster that can handle at-scale workloads is not a trivial task. +It requires many (virtual) machines to be configured in a very particular way. -If your {astra_db} organization is on the *Free* plan, you use the *Pay As You Go* streaming plan. -When you provide a payment method, you only pay for resources used when messages are produced and consumed. +In traditional cloud environments, you would pay hourly for every machine whether they are being used for workloads or not, and you would carry the burden of maintaining the server infrastructure. -In *Pay As You Go* streaming environments, your data and interaction with Pulsar are secured over a public internet connection, and there are limitations to how many resources you can create. -For more information, see xref:astream-limits.adoc[]. +Serverless removes those operational burdens, and you pay only for the resources you actually use. -For *Dedicated Clusters* plans, you have your own private Pulsar cluster with the additional benefits of {product}. -You sign in to {astra_db} as you would on any other plan. -However, when you create new tenants, you have additional options to deploy to your private clusters. -*Dedicated Clusters* plans have fewer limits than *Pay As You Go* plans, and billing for dedicated clusters is unique to each customer. +== What is the difference between shared and dedicated clusters for {product}? -Finally, with *Pay As You Go* plans, you can create tenants in any of the xref:astream-regions.adoc[supported regions]. -*Dedicated Clusters* are open to almost any public cloud region. -==== +See xref:operations:astream-pricing.adoc[]. -.Why does {company} call it "Serverless"? -[%collapsible] -==== -Running a production grade Pulsar cluster that can handle at-scale workloads is not a trivial task. It requires many (virtual) machines to be configured in a very particular way. +== Are connections to shared clusters less secure than dedicated clusters? -In traditional cloud environments, you would pay hourly for every machine whether they are being used for workloads or not, and you would carry the burden of maintaining the server infrastructure. +Connections to both cluster types are secure. +However, dedicated clusters offer the option for private connectivity. -Serverless removes those operational burdens, and you pay only for the resources you actually use. -==== +For dedicated Pulsar clusters, all connections in {product} are guarded by authentication, authorization, and secure (TLS) communications. +You can connect over the public internet or xref:operations:private-connectivity.adoc[establish a private connection]. -== Cluster connections +Shared Pulsar clusters also use a secure connection over the public internet. +However, shared clusters don't support private links. -On a *Dedicated Clusters* plan, all connections in {product} are guarded by AuthN, AuthZ, and secure (TLS) communications. -You can connect over the public internet or establish a private connection. -To learn more about private connections, refer to your cloud provider's private link documentation: +For more information, see xref:operations:astream-pricing.adoc[]. -* https://aws.amazon.com/privatelink/[AWS PrivateLink] -* https://learn.microsoft.com/en-us/azure/private-link/private-link-overview[Azure Private Link] -* https://cloud.google.com/vpc/docs/private-service-connect[GCP Private Service Connect] +== Does {product} support single-sign on? -On a *Pay As You Go* plan, you use a shared Pulsar cluster. -Connections are secured over the public internet, but private links typically can't be established. +If your {astra_db} organization is on the *Pay As You Go* and *Enterprise* {astra_db} subscription plan, your users can use SSO to sign in to the {astra_ui}. +For more information, see xref:astra-db-serverless:administration:configure-sso.adoc[]. -== User access +== How do {astra_db} roles and permissions map to Pulsar roles and permissions? -.Can I use my own single sign-on with {product}? -[%collapsible] -==== -*Pay As You Go* and *Enterprise* organization can use SSO to sign in to the {astra_ui} -For more information, see https://docs.datastax.com/en/astra-serverless/docs/manage/org/configuring-sso.html[{astra_db} Serverless SSO documentation]. -==== +See xref:ROOT:astream-org-permissions.adoc[]. -.How do {astra_db} roles and permissions map to Pulsar roles and permissions? -[%collapsible] -==== -Pulsar has the concept of https://pulsar.apache.org/docs/security-authorization/[clients with role tokens]. Authentication in Pulsar is the process of verifying a provided (JWT) token, and authorization is the process of determining if the role claimed in that token is allowed to complete the requested action. +== What is the difference between {astra_db} application tokens and the Pulsar tokens? -{product} uses the {company} version of Apache Pulsar (called xref:luna-streaming::index.adoc[Luna Streaming]). -The https://github.com/datastax/pulsar[Luna project] is an open fork of the Pulsar project that maintains feature parity with OSS Pulsar. {product}, as a managed service, abstracts some features/options of Pulsar to ensure continuous, reliable service. - -On a *Pay As You Go* plan, your {astra_db} organization has one or more tenants on a shared Pulsar cluster. -Each of your tenants is secured by Pulsar AuthN & AuthZ models as well as your {astra_db} organization's AuthN and AuthZ. -The shared cluster is created and administered by {product} administrators. -Each tenant is assigned a custom role and permissions limited to that tenant only. -All tokens created within a tenant are assigned roles similar to the assigning tenant. -==== - -.What is the difference between the {astra_db} application token and the Pulsar token? Are they interchangeable? -[%collapsible] -==== See xref:monitoring:astream-token-gen.adoc[]. -==== - -== Data Migration and Geo-replication -.What are the differences in geo-replication for Pay As You Go and Dedicated Clusters plan? -[%collapsible] -==== -Geo-replication is available to *Pay As You Go* and *Dedicated Clusters* {product} plans. -Both can replicate to other clusters, but there are some differences. +== What are the differences in geo-replication for shared and dedicated clusters? -For *Pay As You Go* {product} plans, traffic between clusters is secured over the public internet, while dedicated clusters have the option for private communication. +Geo-replication is available for both shared and dedicated clusters. +Both cluster types can replicate to other clusters, but there are some differences: -*Pay As You Go* plans can replicate between any xref:astream-regions.adoc[supported region] of the same cloud provider. +Connections:: +For shared clusters, traffic between clusters is secured over the public internet. ++ +Dedicated clusters can use either the default secure public internet connection or enable private links. -With *Dedicated Clusters*, you can use almost any region supported by your cloud provider, as well as across cloud providers. +Regions:: +Shared clusters can replicate between any xref:astream-regions.adoc[supported region] of the same cloud provider. ++ +Dedicated clusters can use almost any region supported by your cloud provider, as well as across cloud providers. For more on geo-replication, see xref:astream-georeplication.adoc[]. -==== -.Can I migrate data from my existing Pulsar cluster to {product}? -[%collapsible] -==== +== Application development + +=== Can I migrate data from my existing Pulsar cluster to {product}? + Unless you are starting a project from scratch, you likely have message data that needs to be brought over to your {product} tenants. For migration assistance, contact {support_url}[{company} Support]. Every tenant in {product} comes with custom ports for Kafka and RabbitMQ workloads. {company} also offers a fully-compatible JMS implementation for your Java workloads. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] -==== -== Application Development +=== How do I separate messaging traffic? -.How do I separate messaging traffic? -[%collapsible] -==== It is common to have a hierarchy of development environments which app changes are promoted through before reaching production. The configurations of middleware and platforms supporting the app should be kept in parity to promote stability and fast iterations with low volatility. -[discrete] -==== By Tenant +By Tenant:: To support the hierarchy of development environments pattern, we recommend using Tenants to represent each development environment. This gives you the greatest flexibility to balance a separation of roles with consistent service configuration. All tokens created within a Tenant are limited to that Tenant. - ++ For example, start with a tenant named `Dev`` that development teams have access to (and create tokens from), then create other tenants named `Staging` and `Production`. Each Tenant has progressively less permissions to create tokens, but maintains parity between the three running environments. -[discrete] -==== By Namespace +By Namespace:: Alternatively, you might choose to separate development environments by namespace within your {product} tenant. While this doesn't offer as much flexibility as separation by tenant, it does offer a much simpler model to manage. Also, note that in this scheme you cannot limit access by namespace. All tokens would have access to all namespaces. -==== -.Can we develop applications on open source Pulsar and then move to {product}? -[%collapsible] -==== -As mentioned previously, {product} is actively maintained to keep parity with the official https://pulsar.apache.org[Apache Pulsar project]. +=== Can I develop applications on open source Pulsar and then move to {product}? + +{product} is actively maintained to keep parity with the official https://pulsar.apache.org[Apache Pulsar project]. The notable differences arise from accessibility and security. Naturally, you have less control in a managed, serverless cluster than you do in a cluster running in your own environment. Beyond those differences, the effort to develop locally and then move to {product} should not be significant, but it is recommended to develop directly in {product}. -If you are trying to save costs, use the free tier of {product} and then switch when you are ready to stage your production services. -==== +If you are trying to reduce costs, use the free tier of {product} and then switch when you are ready to stage your production services. + +=== Can I use {product} with my existing Kafka or RabbitMQ applications? -.Can I use {product} with my existing Kafka or RabbitMQ applications? -[%collapsible] -==== Yes, {product} offers a fully compatible Kafka and RabbitMQ implementation. This means you can use your existing Kafka or RabbitMQ applications with {product}. You can also use the {product} Kafka or RabbitMQ implementation with your existing Pulsar applications. {product} comes with custom ports for Kafka and RabbitMQ workloads. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. -==== -.Can I use {product} with my existing Java applications? -[%collapsible] -==== -Yes, {product} offers a fully compatible JMS implementation. This means you can use your existing JMS applications with {product}. You can also use the {product} JMS implementation with your existing Pulsar applications. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. -==== \ No newline at end of file +=== Can I use {product} with my existing Java applications? + +Yes, {product} offers a fully compatible JMS implementation. This means you can use your existing JMS applications with {product}. You can also use the {product} JMS implementation with your existing Pulsar applications. xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] about the Starlight suite of APIs. \ No newline at end of file diff --git a/modules/operations/pages/private-connectivity.adoc b/modules/operations/pages/private-connectivity.adoc index ced1fef..9cf4359 100644 --- a/modules/operations/pages/private-connectivity.adoc +++ b/modules/operations/pages/private-connectivity.adoc @@ -1,21 +1,31 @@ = Private connectivity +[IMPORTANT] +==== +Private links are available only for xref:operations:astream-pricing.adoc#dedicated-clusters[dedicated clusters]. +==== + To better protect your streaming connections, connect {product} to a private link service for <> connectivity, or to a private endpoint for <> connectivity. Private connections are only available within the same cloud provider and region as your {product} cluster. -To open a private link service or private endpoint, open {support_url}[a support ticket] and include the <> required for your cloud provider. +== Enable private links + +To enable a private link service or private endpoint for {product}, contact {support_url}[{company} Support]. +Be prepared to provide the <> required for your cloud provider. == Inbound traffic -{product} supports inbound traffic (i.e. Your private endpoint → {product}). +{product} supports inbound traffic flowing from your private endpoint to {product}. + The first inbound traffic pattern describes Pulsar, Kafka, and RabbitMQ messaging traffic, as well as Prometheus metrics traffic, flowing from a user's private endpoint to {product}. -You create a connection to our private link service, and we route traffic to your {product} cluster. -If you have multiple tenants, they can have different VPCs. The different VPCs will have the same private FQDN with differing VNETs. -The traffic on different private end point connections is isolated until it reaches our load balancer. +You create a connection to the {company} private link service, and then {company} routes traffic to your {product} dedicated cluster. +If you have multiple tenants, they can have different VPCs. +Each VPC will have the same private FQDN with different VNETs. +The traffic on separate private end point connections is isolated until it reaches the {company} load balancer. -The private link service pattern is the same across cloud providers, but the hostname will vary depending on your cloud provider and region. +The private link service pattern is the same across cloud providers, but the hostname depends on your cloud provider and region: [#inbound] .Inbound private link service endpoints @@ -39,32 +49,32 @@ The private link service pattern is the same across cloud providers, but the hos [#outbound] == Outbound traffic -{product} also supports private outbound traffic (from {product} to your private endpoint) on a case-by-case basis. +On a case-by-case basis, {product} can support private outbound traffic flowing from {product} to your private endpoint. -The outbound traffic pattern creates a private endpoint in {product} that connects to your private link service. We open a port on the tenant's firewall (firewalls are per tenant) so connectors and functions (running in a dedicated namespace on our cluster) can connect to your private network. - -To open an outbound private endpoint, open {support_url}[a support ticket] and include the <> required for your cloud provider. +The outbound traffic pattern creates a private endpoint in {product} that connects to your private link service. +{company} opens a port on the tenant's firewall to allow connectors and functions running in a dedicated namespace on a {product} cluster to connect to your private network. +Each tenant has its own firewall. +[#credentials] == Cloud provider credentials -For more on connecting to your cloud provider, see your cloud provider's documentation. -Each cloud provider will require different credentials to connect to the private endpoint. +Each cloud provider requires specific credentials to connect to a private endpoint. +For information about private link configuration and credentials, see your cloud provider's documentation. -[#credentials] -.Cloud providers +.Private link credentials and documentation [cols="1,1,3"] |=== |Cloud provider |Credentials required |Documentation |AWS -|AWS account number(s) +|AWS account numbers |https://docs.aws.amazon.com/vpc/latest/privatelink/endpoint-service.html[AWS Private Link] -|Azure -|Azure subscription id(s) -|https://learn.microsoft.com/en-us/azure/private-link/create-private-endpoint-portal?tabs=dynamic-ip[Azure Portal] +|Microsoft Azure +|Azure subscription IDs +|https://learn.microsoft.com/en-us/azure/private-link/create-private-endpoint-portal?tabs=dynamic-ip[Azure Private Link] -|GCP -|GCP project id(s) -|https://console.cloud.google.com/net-services/psc/[GCP Private Service Connect] +|Google Cloud +|GCP project IDs +|https://console.cloud.google.com/net-services/psc/[Google Cloud Private Service Connect] |=== From ad049c1e5d3d152c7ccc33238be88004edcf8fdf Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 11:10:46 -0700 Subject: [PATCH 11/18] fix build errors --- modules/developing/pages/astra-cli.adoc | 3 +-- modules/developing/pages/astream-cdc.adoc | 4 ++-- modules/getting-started/pages/index.adoc | 1 - modules/operations/pages/onboarding-faq.adoc | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index 3452275..4f56a6f 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -23,5 +23,4 @@ You can use {astra_cli} instead of or in addition to the {astra_ui} and {astra_d For more information, see the following: -* xref:api-reference:databases.adoc[] -* https://www.datastax.com/blog/introducing-cassandra-astra-cli[Accelerate your Cassandra development and automation with the {astra-cli}] +* https://www.datastax.com/blog/introducing-cassandra-astra-cli[Accelerate your Cassandra development and automation with the {astra_cli}] diff --git a/modules/developing/pages/astream-cdc.adoc b/modules/developing/pages/astream-cdc.adoc index e720652..cf46cc6 100644 --- a/modules/developing/pages/astream-cdc.adoc +++ b/modules/developing/pages/astream-cdc.adoc @@ -483,5 +483,5 @@ bin/pulsar-admin topics partitioned-stats persistent://ten01/astracdc/data-7e3a1 == See also -* xref:ROOT:astream-faq.html[] -* xref:developing:clients/index.html[] \ No newline at end of file +* xref:ROOT:astream-faq.adoc[] +* xref:developing:clients/index.adoc[] \ No newline at end of file diff --git a/modules/getting-started/pages/index.adoc b/modules/getting-started/pages/index.adoc index ea26ac4..c37d5f0 100644 --- a/modules/getting-started/pages/index.adoc +++ b/modules/getting-started/pages/index.adoc @@ -55,7 +55,6 @@ For information about the Pulsar CLI or APIs, see xref:developing:configure-puls . Enter a namespace name, and then click *Create*. . Click "Create" to create the namespace. --- == Create a topic diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index 2b250b1..c5dd9ad 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -43,7 +43,7 @@ See xref:ROOT:astream-org-permissions.adoc[]. == What is the difference between {astra_db} application tokens and the Pulsar tokens? -See xref:monitoring:astream-token-gen.adoc[]. +See xref:operations:astream-token-gen.adoc[]. == What are the differences in geo-replication for shared and dedicated clusters? From 267cf69daaa2a263eee4126fe7eff08634bbb782 Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 11:24:12 -0700 Subject: [PATCH 12/18] index page --- modules/ROOT/pages/index.adoc | 9 --------- modules/developing/nav.adoc | 2 -- 2 files changed, 11 deletions(-) diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index 804c77c..0aa3a62 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -36,12 +36,3 @@ frictionless path to modernization. By combining {product} with {astra_db}, customers can create read-optimized views of data that can be quickly read from {astra_db} and ensure that data is always up to date by leveraging the event stream processing capabilities of {product}. - -== {product} limits - -For details about about the {product} limits, see xref:operations:astream-limits.adoc[{product} Limits]. - -== See also - -* xref:getting-started:index.adoc[] -* xref:apis:index.adoc[] diff --git a/modules/developing/nav.adoc b/modules/developing/nav.adoc index 439c3da..1c1fa17 100644 --- a/modules/developing/nav.adoc +++ b/modules/developing/nav.adoc @@ -1,5 +1,3 @@ -* xref:ROOT:index.adoc[] - .Develop * xref:configure-pulsar-env.adoc[] * xref:astream-functions.adoc[] From d9df176b668c58abea1a41bf764f665e4511209d Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 11:28:37 -0700 Subject: [PATCH 13/18] special character handling --- modules/developing/nav.adoc | 2 +- modules/operations/pages/monitoring/index.adoc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/developing/nav.adoc b/modules/developing/nav.adoc index 1c1fa17..8c42056 100644 --- a/modules/developing/nav.adoc +++ b/modules/developing/nav.adoc @@ -1,4 +1,4 @@ -.Develop +.Development * xref:configure-pulsar-env.adoc[] * xref:astream-functions.adoc[] * xref:astream-kafka.adoc[] diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index 64afc3e..01298d2 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -98,8 +98,8 @@ To transform raw metrics into a usable state, {company} recommends the following * Aggregate metrics at the parent topic level, at minimum, instead of at the partition level. In Pulsar, end user applications only deal with messages at the parent topic level; however, internally, Pulsar handles message processing at the partition level. -* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores (`__`). -For example, enabling Pulsar's Kafka protocol handler through S4K integration, creates a system namespace called `__kafka` that has one system topic called `\__transaction_producer_state`. +* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores. +For example, enabling Pulsar's Kafka protocol handler through S4K integration, creates a system namespace called `\__kafka` that has one system topic called `\__transaction_producer_state`. === PromQL query patterns @@ -108,13 +108,13 @@ For more information, see the https://prometheus.io/docs/prometheus/latest/query {company} recommends the following PromQL query patterns for aggregating raw {product} metrics. These examples use the `pulsar_msg_backlog` raw metric to demonstrate the patterns. -Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher and they exclude system topics (with the PromQL statement `{topic !~ ".*__.*"}`). +Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher and they exclude system topics (with the PromQL statement `{topic !~ ".\*__.*"}`). .About the system topics filter [%collapsible] ==== -The PromQL pattern `{topic !~ ".*__.*"}` filters out messages with topic labels that do not include two consecutive underscores. -This works because Pulsar system topics and namespaces are usually prefixed by two underscores, such as `persistent:///__kafka/__consumer_offsets_partition_0`. +The PromQL pattern `{topic !~ ".\*_\_.*"}` filters out messages with topic labels that do not include two consecutive underscores. +This works because Pulsar system topics and namespaces are usually prefixed by two underscores, such as `persistent://**TENANT_NAME**/\__kafka/\__consumer_offsets_partition_0`. However, this pattern assumes that your applications' namespace and topic names don't contain double underscores. If they do, they are also filtered. ==== From 046bbc175042b0b0888c0ba73540efb31acc9bc2 Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 11:34:48 -0700 Subject: [PATCH 14/18] special characters --- .../operations/pages/monitoring/index.adoc | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index 01298d2..a6a9f56 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -98,8 +98,13 @@ To transform raw metrics into a usable state, {company} recommends the following * Aggregate metrics at the parent topic level, at minimum, instead of at the partition level. In Pulsar, end user applications only deal with messages at the parent topic level; however, internally, Pulsar handles message processing at the partition level. -* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores. -For example, enabling Pulsar's Kafka protocol handler through S4K integration, creates a system namespace called `\__kafka` that has one system topic called `\__transaction_producer_state`. +* Exclude reported metrics that are associated with {product}'s system namespaces and topics, which are usually prefixed by two underscores, such as: ++ +[source,plain] +---- +__kafka +__transaction_producer_state +---- === PromQL query patterns @@ -107,17 +112,24 @@ PromQL is Prometheus's simple and powerful query language that you can use to se For more information, see the https://prometheus.io/docs/prometheus/latest/querying/basics/[PromQL documentation]. {company} recommends the following PromQL query patterns for aggregating raw {product} metrics. -These examples use the `pulsar_msg_backlog` raw metric to demonstrate the patterns. -Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher and they exclude system topics (with the PromQL statement `{topic !~ ".\*__.*"}`). +The following examples use the `pulsar_msg_backlog` raw metric to demonstrate the patterns. +Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher, and they use the following expression to exclude system topics: -.About the system topics filter -[%collapsible] -==== -The PromQL pattern `{topic !~ ".\*_\_.*"}` filters out messages with topic labels that do not include two consecutive underscores. -This works because Pulsar system topics and namespaces are usually prefixed by two underscores, such as `persistent://**TENANT_NAME**/\__kafka/\__consumer_offsets_partition_0`. -However, this pattern assumes that your applications' namespace and topic names don't contain double underscores. -If they do, they are also filtered. -==== +[source,pgsql] +---- +{topic !~ ".*__.*"}` +---- + +This expression excludes messages with topic labels that include two consecutive underscores. +This works because Pulsar system topics and namespaces are usually prefixed by two underscores, such as: + +[source,plain] +---- +persistent://some_tenant/__kafka/__consumer_offsets_partition_0 +---- + +To use this expression, your applications' namespace and topic names don't contain double underscores. +If they do, they will also be excluded by this filter. ==== Get the total message backlog of a specific parent topic, excluding system topics From 7b5e82fbd8f403bad291181aed1b248d5b63f0ed Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 15:45:37 -0700 Subject: [PATCH 15/18] fix after preview --- modules/ROOT/pages/astream-faq.adoc | 7 ++-- .../astream-subscriptions-exclusive.adoc | 2 +- .../pages/astream-subscriptions-failover.adoc | 16 ++++---- .../astream-subscriptions-keyshared.adoc | 40 +++++++++---------- .../pages/astream-subscriptions-shared.adoc | 16 ++++---- modules/ROOT/pages/astream-subscriptions.adoc | 2 +- modules/developing/nav.adoc | 2 +- .../developing/pages/astream-functions.adoc | 17 ++++---- modules/developing/pages/astream-kafka.adoc | 14 ++++--- modules/developing/pages/clients/index.adoc | 14 +++---- .../pages/clients/spring-produce-consume.adoc | 19 ++++----- modules/operations/pages/astream-limits.adoc | 10 ++--- .../operations/pages/monitoring/index.adoc | 17 +++++--- .../pages/monitoring/integration.adoc | 11 +---- modules/operations/pages/onboarding-faq.adoc | 18 +++++---- 15 files changed, 105 insertions(+), 100 deletions(-) diff --git a/modules/ROOT/pages/astream-faq.adoc b/modules/ROOT/pages/astream-faq.adoc index 6ea842f..b0fc6f1 100644 --- a/modules/ROOT/pages/astream-faq.adoc +++ b/modules/ROOT/pages/astream-faq.adoc @@ -12,11 +12,12 @@ See xref:operations:astream-pricing.adoc[]. == Why is {product} based on Apache Pulsar? -See our https://www.datastax.com/blog/four-reasons-why-apache-pulsar-essential-modern-data-stack[blog post] that explains why we are excited about Apache Pulsar and why we decided it was the best technology to base {product} on. +For information about the decision to use Apache Pulsar, see https://www.datastax.com/blog/four-reasons-why-apache-pulsar-essential-modern-data-stack[Four Reasons Why Apache Pulsar is Essential to the Modern Data Stack]. -== What will happen to Kesque? +== What happened to Kesque? -{product} is based heavily on technology originally created as part of Kesque. With the launch of {product} we will begin the process of shutting down the Kesque service and migrating customers to the new {product} platform. +{product} is based heavily on technology originally created as part of Kesque. +With the launch of {product}, {company} began shutting down the Kesque service and migrated customers to {product}. == Who should use {product}? diff --git a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc index 8313567..c50a45e 100644 --- a/modules/ROOT/pages/astream-subscriptions-exclusive.adoc +++ b/modules/ROOT/pages/astream-subscriptions-exclusive.adoc @@ -85,4 +85,4 @@ Caused by: org.apache.pulsar.client.api.PulsarClientException$ConsumerBusyExcept * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-shared.adoc[Shared subscriptions] * xref:astream-subscriptions-failover.adoc[Failover subscriptions] -* xref:astream-subscriptions-keyshared.adoc[Key_shared subscriptions] +* xref:astream-subscriptions-keyshared.adoc[Key shared subscriptions] diff --git a/modules/ROOT/pages/astream-subscriptions-failover.adoc b/modules/ROOT/pages/astream-subscriptions-failover.adoc index 41c969e..a4c57ef 100644 --- a/modules/ROOT/pages/astream-subscriptions-failover.adoc +++ b/modules/ROOT/pages/astream-subscriptions-failover.adoc @@ -11,6 +11,14 @@ If the primary consumer disconnects, the standby consumers begin consuming the s This page explains how to use Pulsar's failover subscription model to manage your topic consumption. +.Failover subscription video +[%collapsible] +==== +This video from the *Five Minutes About Pulsar* series demonstrates failover subscriptions: + +video::ckB87OLs5eM[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] +==== + include::ROOT:partial$subscription-prereq.adoc[] [#example] @@ -84,15 +92,9 @@ In the second `SimplePulsarConsumer` terminal, the backup consumer begins consum You can configure as many backup consumers as you like. To test them, you can progressively end each `SimplePulsarConsumer` process, and then check that the next backup consumer has begun receiving messages. -=== Failover subscription video - -Follow along with this video from our *Five Minutes About Pulsar* series to see failover subscriptions in action: - -video::ckB87OLs5eM[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] - == See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] * xref:astream-subscriptions-shared.adoc[Shared subscriptions] -* xref:astream-subscriptions-keyshared.adoc[Key_shared subscriptions] \ No newline at end of file +* xref:astream-subscriptions-keyshared.adoc[Key shared subscriptions] \ No newline at end of file diff --git a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc index 50176eb..e39e3c8 100644 --- a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc @@ -1,5 +1,5 @@ = Key shared subscriptions in Pulsar -:navtitle: Key_Shared +:navtitle: Key shared :page-tag: pulsar-subscriptions,quickstart,admin,dev,pulsar _Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. @@ -15,13 +15,21 @@ Keys are generated with hashing that converts arbitrary values like `topic-name` This page explains how to use Pulsar's key shared subscription model to manage your topic consumption. +.Key shared subscription video +[%collapsible] +==== +This video from the *Five Minutes About Pulsar* series demonstrates key shared subscriptions: + +video::_49wlA53L_8[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] +==== + include::ROOT:partial$subscription-prereq.adoc[] [#example] == Key shared subscription example -To try out a Pulsar key shared subscription, add `.subscriptionType(SubscriptionType.Key_Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`: - +. To try out a Pulsar key shared subscription, add `.subscriptionType(SubscriptionType.Key_Shared)` to the `pulsarConsumer` in `SimplePulsarConsumer.java`: ++ .SimplePulsarConsumer.java [source,java] ---- @@ -37,19 +45,15 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .keySharedPolicy(KeySharedPolicy.autoSplitHashRange()) .subscribe(); ---- - -=== keySharedPolicy - ++ The `keySharedPolicy` defines the how hashed values are assigned to subscribed consumers. ++ +The above example uses `autoSplitHashRange`, which is an auto-hashing policy. +Running multiple consumers with auto-hashing balances the messaging load across all available consumers, like a xref:astream-subscriptions-shared.adoc[shared subscription] ++ +If you want to manually set a hash range, use `KeySharedPolicy.stickyHashRange()`, as demonstrated in the following steps. -The above example used `autoSplitHashRange`, which is an auto-hashing policy. -Running multiple consumers with auto-hashing balances the messaging load across all available consumers. - -If you want to manually set a hash range, use `KeySharedPolicy.stickyHashRange()`. - -==== Test sticky hashed key shared subscriptions - -. To test out sticky hashed key shared subscriptions, import the following additional classes: +. To use a sticky hashed key shared subscription, import the following classes to `SimplePulsarConsumer.java`: + .SimplePulsarConsumer.java [source,java] @@ -143,15 +147,9 @@ Caused by: org.apache.pulsar.client.api.PulsarClientException$ConsumerAssignExce at com.datastax.pulsar.SimplePulsarConsumer.main(SimplePulsarConsumer.java:47) ---- -. To run multiple consumers with sticky hashing, modify the `SimplePulsarConsumer.java` configuration to split the hash range between consumers. +. To run multiple consumers with sticky hashing, modify the `SimplePulsarConsumer.java` configuration to split the hash range between consumers or use auto-hashing. Then, you can launch multiple instances of `SimplePulsarConsumer.java` to consume messages from different hash ranges. -== Key shared subscription video - -Follow along with this video from our *Five Minutes About Pulsar* series to see key shared subscriptions in action: - -video::_49wlA53L_8[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] - == See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] diff --git a/modules/ROOT/pages/astream-subscriptions-shared.adoc b/modules/ROOT/pages/astream-subscriptions-shared.adoc index 2e32535..c44b183 100644 --- a/modules/ROOT/pages/astream-subscriptions-shared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-shared.adoc @@ -10,6 +10,14 @@ However, there is a risk of losing message ordering guarantees and acknowledgeme This page explains how you can use Pulsar's shared subscription model to manage your topic consumption. +.Shared subscription video +[%collapsible] +==== +This video from the *Five Minutes About Pulsar* series demonstrates shared subscriptions: + +video::mmukXqGsauA[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] +==== + include::ROOT:partial$subscription-prereq.adoc[] [#example] @@ -83,15 +91,9 @@ If you run this test with xref:astream-subscriptions-exclusive.adoc[exclusive su To continue testing the shared subscription configuration, you can continue running new instances of `SimplePulsarConsumer.java` in new temrinal windows. All the consumers subscribe to the topic and consume messages in a round-robin fashion. -== Shared subscription video - -Follow along with this video from our *Five Minutes About Pulsar* series to see shared subscriptions in action: - -video::mmukXqGsauA[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] - == See also * xref:astream-subscriptions.adoc[Subscriptions in Pulsar] * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] * xref:astream-subscriptions-failover.adoc[Failover subscriptions] -* xref:astream-subscriptions-keyshared.adoc[Key_shared subscriptions] +* xref:astream-subscriptions-keyshared.adoc[Key shared subscriptions] diff --git a/modules/ROOT/pages/astream-subscriptions.adoc b/modules/ROOT/pages/astream-subscriptions.adoc index 8e2fbaa..607612e 100644 --- a/modules/ROOT/pages/astream-subscriptions.adoc +++ b/modules/ROOT/pages/astream-subscriptions.adoc @@ -31,7 +31,7 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.BYTES) * xref:astream-subscriptions-exclusive.adoc[Exclusive subscriptions] * xref:astream-subscriptions-shared.adoc[Shared subscriptions] * xref:astream-subscriptions-failover.adoc[Failover subscriptions] -* xref:astream-subscriptions-keyshared.adoc[Key_shared subscriptions] +* xref:astream-subscriptions-keyshared.adoc[Key shared subscriptions] == See also diff --git a/modules/developing/nav.adoc b/modules/developing/nav.adoc index 8c42056..c4998bb 100644 --- a/modules/developing/nav.adoc +++ b/modules/developing/nav.adoc @@ -23,4 +23,4 @@ * xref:gpt-schema-translator.adoc[] * xref:astra-cli.adoc[] * xref:astream-cdc.adoc[] -* xref:streaming-learning:pulsar-io:connectors/index.adoc[IO Connectors] \ No newline at end of file +* xref:streaming-learning:pulsar-io:connectors/index.adoc[IO connectors] \ No newline at end of file diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index 5b45982..4848e17 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -198,6 +198,7 @@ Must have 4 steps to maintain numbering. //// ====== +[start=5] . If you haven't done so already, xref:configure-pulsar-env.adoc[set up your environment for the Pulsar binaries]. . Create a deployment configuration YAML file that defines the function metadata and associated topics: @@ -262,7 +263,7 @@ Organizations on the *Free* plan can use xref:streaming-learning:functions:index [TIP] ==== If your Python function contains only a single script and no dependencies, you can deploy the `.py` file directly, without packaging it into a `.zip` file or creating a configuration file: -+ + [source,bash,subs="+quotes"] ---- $ ./pulsar-admin functions create \ @@ -306,16 +307,16 @@ See <> for more information Upload your own code:: + -- -. Select *Upload my own code*. +.. Select *Upload my own code*. -. Select your function file: +.. Select your function file: + * `.py`: A single, independent Python script * `.zip`: A Python script with dependencies * `.jar`: A Java function * `.go`: A Go function -. Based on the uploaded file, select the specific class (function) to deploy. +.. Based on the uploaded file, select the specific class (function) to deploy. + {astra_db} generates a list of acceptable classes detected in the code. A file can contain multiple classes, but only one is used per deployment. @@ -332,7 +333,7 @@ image::astream-exclamation-function.png[Exclamation Function] Use {company} transform function:: + -- -. Select *Use {company} transform function*. +.. Select *Use {company} transform function*. This is the only function option available on the {product} *Free* plan. @@ -395,7 +396,7 @@ The trigger sends the message string to the function. Your function should output the result of processing the message. [#controlling-your-function] -=== Control functions +=== Stop and start functions In the {astra_ui}, on your tenant's *Functions* tab, you can use *Function Controls* to start, stop, and restart functions. @@ -408,7 +409,7 @@ image::astream-function-log.png[Function Log] If you specified a log topic when deploying your function, function logs also output to that topic. -== Edit functions +=== Edit functions . In the {astra_ui}, on your tenant's *Functions* tab, click *Update Function*. @@ -422,7 +423,7 @@ If you specified a log topic when deploying your function, function logs also ou If you need to change any other function settings, you must delete and redeploy the function with the desired settings. -== Delete functions +=== Delete functions [IMPORTANT] ==== diff --git a/modules/developing/pages/astream-kafka.adoc b/modules/developing/pages/astream-kafka.adoc index f7c48d6..e207e96 100644 --- a/modules/developing/pages/astream-kafka.adoc +++ b/modules/developing/pages/astream-kafka.adoc @@ -12,6 +12,14 @@ By integrating two popular event streaming ecosystems, {kafka_for_astra} unlocks This document will help you get started producing and consuming Kafka messages on a Pulsar cluster. +.Starlight for Kafka video +[%collapsible] +==== +This video from the *Five Minutes About Pulsar* series explains how to migrate from Kafka to Pulsar: + +video::Qy2ZlelLjXg[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] +==== + == {kafka_for_astra} Quickstart :page-tag: starlight-kafka,quickstart,install,admin,dev,pulsar,kafka @@ -98,12 +106,6 @@ Your Kafka messages are being produced and consumed in a Pulsar cluster: + image::astream-kafka-monitor.png[Monitor Kafka Activity] -== Starlight for Kafka video - -Follow along with this video from our *Five Minutes About Pulsar* series to migrate from Kafka to Pulsar: - -video::Qy2ZlelLjXg[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] - == See also * https://github.com/datastax/starlight-for-kafka[{company} Starlight for Kafka project] diff --git a/modules/developing/pages/clients/index.adoc b/modules/developing/pages/clients/index.adoc index fbd81f9..166d6e5 100644 --- a/modules/developing/pages/clients/index.adoc +++ b/modules/developing/pages/clients/index.adoc @@ -7,11 +7,11 @@ To connect to your service, use the open-source client APIs provided by the Apache Pulsar project. {product} is running Pulsar version {pulsar_version}. You should use this API version or higher. -Popular Pulsar clients include the following: +For more information and examples, see the following: -* xref:clients/csharp-produce-consume.adoc[image:csharp-icon.png[] C#] -* xref:clients/golang-produce-consume.adoc[image:golang-icon.png[] Golang] -* xref:clients/java-produce-consume.adoc[image:java-icon.png[] Java] -* xref:clients/nodejs-produce-consume.adoc[image:node-icon.png[] Node.js] -* xref:clients/python-produce-consume.adoc[image:python-icon.png[] Python] -* xref:clients/spring-produce-consume.adoc[image:spring-boot-icon.png[] Spring Boot] \ No newline at end of file +* xref:clients/csharp-produce-consume.adoc[C#] +* xref:clients/golang-produce-consume.adoc[Golang] +* xref:clients/java-produce-consume.adoc[Java] +* xref:clients/nodejs-produce-consume.adoc[Node.js] +* xref:clients/python-produce-consume.adoc[Python] +* xref:clients/spring-produce-consume.adoc[Spring Boot] \ No newline at end of file diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index c5735d8..c38bde6 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -5,23 +5,20 @@ You can produce and consume messages with the Java Pulsar client, {product}, and Spring. -== Prerequisites +Go to the https://github.com/datastax/astra-streaming-examples[examples repo] for the complete source of this example. -You will need the following prerequisites in place to complete this guide: +== Prerequisites -* JRE 8 installed (https://sdkman.io/[install now,title=Install java]) -* (https://maven.apache.org/install.html[Maven,title=Maven]) or (https://gradle.org/install/[Gradle,title=Gradle]) installed -* A working Pulsar topic (get started xref:getting-started:index.adoc[here] if you don't have a topic) -* A basic text editor or IDE +For this example, you need the following: -[TIP] -==== -Visit our https://github.com/datastax/astra-streaming-examples[examples repo] to see the complete source of this example. -==== +* JRE 8 +* https://maven.apache.org/install.html[Maven] +* A Pulsar topic in {product} +* A text editor or IDE == Create a Maven project in Spring Initializr -Spring Initializr is a great tool to quickly create a project with the dependencies you need. Let's use it to create a project with the Pulsar client dependency. +You can use Spring Initializr to quickly create a Java project with the required dependencies, including the Pulsar client dependency. . Go to https://start.spring.io/[Spring Initializr] to initialize a new project. diff --git a/modules/operations/pages/astream-limits.adoc b/modules/operations/pages/astream-limits.adoc index 707a85b..54cd127 100644 --- a/modules/operations/pages/astream-limits.adoc +++ b/modules/operations/pages/astream-limits.adoc @@ -12,7 +12,7 @@ Guardrails are provisioned in the default settings for {product}. You can't directly change these guardrails. For dedicated clusters, some guardrails can be changed by contacting {support_url}[{company} Support]. -[cols="1,1"] +[cols="1,1,1"] |=== |Guardrail |Limit |Comments @@ -329,10 +329,10 @@ The following configurations can't be changed: * Data persistency (`En`, `Qw`, `Qa`) * `Managedledger` policy/deletion * Namespace bundle configurations: - * Bundle split - * Bundle level clear backlog - * Bundle level unload - * Bundle level subscribe and unsubscribe +** Bundle split +** Bundle level clear backlog +** Bundle level unload +** Bundle level subscribe and unsubscribe * Replication * Delayed delivery * Offload policy diff --git a/modules/operations/pages/monitoring/index.adoc b/modules/operations/pages/monitoring/index.adoc index a6a9f56..5362413 100644 --- a/modules/operations/pages/monitoring/index.adoc +++ b/modules/operations/pages/monitoring/index.adoc @@ -1,9 +1,11 @@ = Monitor streaming tenants :navtitle: Monitoring overview -Because {product} is a software-as-a-service product, not all Apache Pulsar metrics (https://pulsar.apache.org/docs/reference-metrics/[Pulsar Metrics Reference]) are exposed for external integration purposes. At a high level, {product} only exposes metrics that are related to namespaces. Other metrics that are not directly namespace related are not exposed externally, such as the Bookkeeper ledger and journal metrics and Zookeeper metrics. +Because {product} is a managed SaaS offering, some https://pulsar.apache.org/docs/reference-metrics/[Apache Pulsar metrics] aren't exposed for external integration purposes. +At a high level, {product} only exposes metrics related to namespaces. +Metrics that are not directly related to namespaces aren't exposed externally, such as the Bookkeeper ledger and journal metrics and Zookeeper metrics. -In the following sections, we'll explore each of the {product} metrics categories that are available for external integration, and recommended metrics for external integration. +Additionally, of the exposed metrics, not all metrics are recommended for external integration. == Pulsar raw metrics @@ -113,7 +115,12 @@ For more information, see the https://prometheus.io/docs/prometheus/latest/query {company} recommends the following PromQL query patterns for aggregating raw {product} metrics. The following examples use the `pulsar_msg_backlog` raw metric to demonstrate the patterns. -Additionally, in accordance with the recommendations in <>, these patterns aggregate messages at the parent topic level or higher, and they use the following expression to exclude system topics: +In accordance with the recommendations in <>, the example patterns aggregate messages at the parent topic level or higher and they exclude system topics. + +.Filter system topics +[%collapsible] +==== +You can use the following expression to filter system topics: [source,pgsql] ---- @@ -130,6 +137,7 @@ persistent://some_tenant/__kafka/__consumer_offsets_partition_0 To use this expression, your applications' namespace and topic names don't contain double underscores. If they do, they will also be excluded by this filter. +==== ==== Get the total message backlog of a specific parent topic, excluding system topics @@ -202,5 +210,4 @@ If your receive too many false alarms, adjust the alert threshold to a higher va * xref:monitoring/metrics.adoc[] * xref:monitoring/integration.adoc[] -* xref:monitoring/new-relic.adoc[] - +* xref:monitoring/new-relic.adoc[] \ No newline at end of file diff --git a/modules/operations/pages/monitoring/integration.adoc b/modules/operations/pages/monitoring/integration.adoc index 708979a..5e26616 100644 --- a/modules/operations/pages/monitoring/integration.adoc +++ b/modules/operations/pages/monitoring/integration.adoc @@ -26,11 +26,6 @@ The values in your `config.yml` depend on your tenant configuration. .config.yml [source,yaml,subs="+quotes"] ---- -global: - scrape_interval: 60s - evaluation_interval: 60s - -scrape_configs: - job_name: "astra-pulsar-metrics-demo" scheme: 'https' @@ -100,10 +95,8 @@ kubectl rollout status daemonset \ . To confirm that {product} metrics are integrated with your external Prometheus server, go to your external Prometheus server UI. Make sure the additional scrape job is in `UP` status. -If not, there are issues in the previous configuration procedures. - -== Troubleshoot 401 Unauthorized errors - +If not, review the configuration instructions and YAML examples to ensure your configuration is correct. ++ If the additional scrape job returns a `401 Unauthorized` error, make sure your Pulsar JWT isn't expired. For more information, see xref:astream-token-gen.adoc[]. diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index c5dd9ad..bcbee45 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -1,10 +1,10 @@ = {product} enrollment FAQ :navtitle: Enrollment FAQ -:description: These are the most common questions we receive about getting started with {product}. +:description: Common questions about getting started with {product}. :page-tag: astra-streaming,onboarding,Orientation When considering {product} for your production workloads, you might have questions about what options are available for connecting and consuming your serverless clusters. -This page answers the most common questions we receive about enrollment. +This page answers some common questions about getting started with {product}. == Why does {company} call {product} "serverless"? @@ -75,16 +75,18 @@ xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] === How do I separate messaging traffic? -It is common to have a hierarchy of development environments which app changes are promoted through before reaching production. +It is common to have a hierarchy of development environments through which you promote app changes before they reach production. The configurations of middleware and platforms supporting the app should be kept in parity to promote stability and fast iterations with low volatility. By Tenant:: -To support the hierarchy of development environments pattern, we recommend using Tenants to represent each development environment. -This gives you the greatest flexibility to balance a separation of roles with consistent service configuration. -All tokens created within a Tenant are limited to that Tenant. +To support the hierarchy of development environments, {company} recommends creating separate tenants for each development environment. +This gives you the greatest flexibility to balance separation of roles with consistent service configuration. + -For example, start with a tenant named `Dev`` that development teams have access to (and create tokens from), then create other tenants named `Staging` and `Production`. -Each Tenant has progressively less permissions to create tokens, but maintains parity between the three running environments. +All tokens created within a tenant are limited to that tenant. ++ +For example, start with a tenant named `Dev` that your development teams can access and create tokens for, and then create other tenants named `Staging` and `Production`. +At each level of the hierarchy, there are fewer users with access to the environment's tenant, which means fewer opportunities to create tokens that can programmatically access that tenant. +Yet, you still maintain parity across the three environments. By Namespace:: Alternatively, you might choose to separate development environments by namespace within your {product} tenant. From 20fe3cc43a11e3465e066d21708cf586631fd0c9 Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 16:07:07 -0700 Subject: [PATCH 16/18] landing page title --- modules/ROOT/pages/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index 0aa3a62..15efce2 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -1,4 +1,4 @@ -= Introduction to {product} += {product} :navtitle: Intro to {product} :page-tag: astra-streaming,planner,admin,dev,pulsar From f2d955f8ff1d0d2d76208050a73e6c0a08c7e70f Mon Sep 17 00:00:00 2001 From: April M Date: Wed, 16 Oct 2024 16:39:04 -0700 Subject: [PATCH 17/18] capitalization --- modules/operations/pages/monitoring/new-relic.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index bb4e94f..7b1fa9e 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -6,7 +6,7 @@ While there are multiple ways to https://docs.newrelic.com/docs/infrastructure/p * Review xref:monitoring/index.adoc[]. * https://docs.newrelic.com/[Set up a New Relic account.] -* Save your new relic license key locally. +* Save your New Relic license key locally. == Prepare the extra Prometheus server From 9158a7560b08db6dc29d29fd6ea30ff8b6fa3dca Mon Sep 17 00:00:00 2001 From: April M Date: Fri, 18 Oct 2024 14:58:43 -0700 Subject: [PATCH 18/18] peer review --- modules/ROOT/pages/astream-faq.adoc | 4 ++-- .../pages/astream-subscriptions-failover.adoc | 10 +--------- .../pages/astream-subscriptions-keyshared.adoc | 16 ++++------------ .../ROOT/pages/astream-subscriptions-shared.adoc | 14 +++----------- modules/apis/pages/index.adoc | 2 +- modules/developing/pages/astra-cli.adoc | 4 +++- modules/developing/pages/astream-functions.adoc | 14 ++++---------- modules/developing/pages/astream-kafka.adoc | 8 -------- .../pages/clients/csharp-produce-consume.adoc | 10 +++++----- .../pages/clients/golang-produce-consume.adoc | 15 +++++++++++---- .../pages/clients/java-produce-consume.adoc | 4 ++-- .../pages/clients/nodejs-produce-consume.adoc | 16 ++++++++-------- .../pages/clients/python-produce-consume.adoc | 4 ++-- .../pages/clients/spring-produce-consume.adoc | 2 +- modules/developing/pages/using-curl.adoc | 6 +++--- modules/operations/pages/astream-pricing.adoc | 2 +- modules/operations/pages/astream-token-gen.adoc | 2 +- .../operations/pages/monitoring/new-relic.adoc | 4 ++-- .../pages/monitoring/topic-dashboard.adoc | 2 +- modules/operations/pages/onboarding-faq.adoc | 10 +++++----- 20 files changed, 60 insertions(+), 89 deletions(-) diff --git a/modules/ROOT/pages/astream-faq.adoc b/modules/ROOT/pages/astream-faq.adoc index b0fc6f1..d2c66fb 100644 --- a/modules/ROOT/pages/astream-faq.adoc +++ b/modules/ROOT/pages/astream-faq.adoc @@ -38,9 +38,9 @@ For more information, see xref:developing:astream-cdc.adoc[]. *Tenants* are an isolated administrative unit for which an authorization scheme can be set and a set of clusters can be defined. Each tenant can have multiple *namespaces*, a logical container for creating and managing a hierarchy of topics. A *topic* is a named channel for transmitting messages from producers to consumers. -A *sink* feeds data from {product} to an external system, such as Cassandra or Elastic Search. +A *sink* feeds data from {product} to an external system, such as {cassandra} or Elasticsearch. == See also * xref:getting-started:index.adoc[] -* Browse the xref:apis:index.adoc[] +* xref:apis:index.adoc[] diff --git a/modules/ROOT/pages/astream-subscriptions-failover.adoc b/modules/ROOT/pages/astream-subscriptions-failover.adoc index a4c57ef..e569ddf 100644 --- a/modules/ROOT/pages/astream-subscriptions-failover.adoc +++ b/modules/ROOT/pages/astream-subscriptions-failover.adoc @@ -11,14 +11,6 @@ If the primary consumer disconnects, the standby consumers begin consuming the s This page explains how to use Pulsar's failover subscription model to manage your topic consumption. -.Failover subscription video -[%collapsible] -==== -This video from the *Five Minutes About Pulsar* series demonstrates failover subscriptions: - -video::ckB87OLs5eM[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -==== - include::ROOT:partial$subscription-prereq.adoc[] [#example] @@ -77,7 +69,7 @@ In the `SimplePulsarConsumer` terminal, the primary consumer begins consuming me ---- . In a new terminal window, run a new instance of `SimplePulsarConsumer.java` as a backup consumer. -The backup consumer subscribes to the topic, but does not immediately begin consuming messages. +The backup consumer subscribes to the topic but does not immediately begin consuming messages. . In the primary `SimplePulsarConsumer` terminal, stop the process (`Ctrl+C`). In the second `SimplePulsarConsumer` terminal, the backup consumer begins consuming messages where the first consumer left off: diff --git a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc index e39e3c8..6c81be3 100644 --- a/modules/ROOT/pages/astream-subscriptions-keyshared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-keyshared.adoc @@ -4,7 +4,7 @@ _Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model enables increased message processing rate, but risks losing message ordering guarantees. +Pulsar's xref:astream-subscriptions-shared.adoc[shared subscription] model can increase the message processing rate, but it risks losing message ordering guarantees. In a round-robin delivery, there's no way for the broker to know which messages are going to which consumer. _Key shared subscriptions_ allow multiple consumers to subscribe to a topic, and provide additional metadata in the form of _keys_ that link messages to specific consumers. @@ -15,14 +15,6 @@ Keys are generated with hashing that converts arbitrary values like `topic-name` This page explains how to use Pulsar's key shared subscription model to manage your topic consumption. -.Key shared subscription video -[%collapsible] -==== -This video from the *Five Minutes About Pulsar* series demonstrates key shared subscriptions: - -video::_49wlA53L_8[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -==== - include::ROOT:partial$subscription-prereq.adoc[] [#example] @@ -46,12 +38,12 @@ pulsarConsumer = pulsarClient.newConsumer(Schema.JSON(DemoBean.class)) .subscribe(); ---- + -The `keySharedPolicy` defines the how hashed values are assigned to subscribed consumers. +The `keySharedPolicy` defines how hashed values are assigned to subscribed consumers. + The above example uses `autoSplitHashRange`, which is an auto-hashing policy. -Running multiple consumers with auto-hashing balances the messaging load across all available consumers, like a xref:astream-subscriptions-shared.adoc[shared subscription] +Running multiple consumers with auto-hashing balances the messaging load across all available consumers, like a xref:astream-subscriptions-shared.adoc[shared subscription]. + -If you want to manually set a hash range, use `KeySharedPolicy.stickyHashRange()`, as demonstrated in the following steps. +If you want to set a fixed hash range, use `KeySharedPolicy.stickyHashRange()`, as demonstrated in the following steps. . To use a sticky hashed key shared subscription, import the following classes to `SimplePulsarConsumer.java`: + diff --git a/modules/ROOT/pages/astream-subscriptions-shared.adoc b/modules/ROOT/pages/astream-subscriptions-shared.adoc index c44b183..286a325 100644 --- a/modules/ROOT/pages/astream-subscriptions-shared.adoc +++ b/modules/ROOT/pages/astream-subscriptions-shared.adoc @@ -4,20 +4,12 @@ _Subscriptions_ in Pulsar describe which consumers are consuming data from a topic and how they want to consume that data. -A _shared subscription_ allows multiple consumers to consume messages from a single topic in round-robin fashion. +A _shared subscription_ allows multiple consumers to consume messages from a single topic in a round-robin fashion. More consumers in a shared subscription can increase your Pulsar deployment's rate of message consumption. -However, there is a risk of losing message ordering guarantees and acknowledgement schemes. +However, there is a risk of losing message ordering guarantees and acknowledgment schemes. This page explains how you can use Pulsar's shared subscription model to manage your topic consumption. -.Shared subscription video -[%collapsible] -==== -This video from the *Five Minutes About Pulsar* series demonstrates shared subscriptions: - -video::mmukXqGsauA[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -==== - include::ROOT:partial$subscription-prereq.adoc[] [#example] @@ -88,7 +80,7 @@ The new consumer subscribes to the topic and consumes messages: Because this test uses shared subscriptions, you can attach multiple consumers to the topic. If you run this test with xref:astream-subscriptions-exclusive.adoc[exclusive subscriptions], you can't attach more than once subscriber to the exclusive topic. -To continue testing the shared subscription configuration, you can continue running new instances of `SimplePulsarConsumer.java` in new temrinal windows. +To continue testing the shared subscription configuration, you can continue running new instances of `SimplePulsarConsumer.java` in new terminal windows. All the consumers subscribe to the topic and consume messages in a round-robin fashion. == See also diff --git a/modules/apis/pages/index.adoc b/modules/apis/pages/index.adoc index f90b68f..b305b6a 100644 --- a/modules/apis/pages/index.adoc +++ b/modules/apis/pages/index.adoc @@ -6,7 +6,7 @@ You use two APIs to manage Pulsar tenants and their resources. == {product} DevOps API -Use the xref:astra-streaming:apis:attachment$devops.html[{product} DevOps API] to manage higher level objects associated with your account, such as the change data capture (CDC) settings, Pulsar tenants, geo-replications, Pulsar stats, and Pulsar tokens. +Use the xref:astra-streaming:apis:attachment$devops.html[{product} DevOps API] to manage higher-level objects associated with your account, such as the change data capture (CDC) settings, Pulsar tenants, geo-replications, Pulsar stats, and Pulsar tokens. This API uses an {astra_db} application token for authentication. diff --git a/modules/developing/pages/astra-cli.adoc b/modules/developing/pages/astra-cli.adoc index 4f56a6f..312bde2 100644 --- a/modules/developing/pages/astra-cli.adoc +++ b/modules/developing/pages/astra-cli.adoc @@ -16,7 +16,9 @@ For example, the following command creates a Pulsar tenant: [source,bash,subs="+quotes"] ---- -astra streaming create **TENANT_NAME** --cloud **CLOUD_PROVIDER** --region **REGION_NAME** --plan **PLAN_TYPE** --namespace **TENANT_INITIAL_NAMESPACE_NAME** +astra streaming create **TENANT_NAME** \ +--cloud **CLOUD_PROVIDER** --region **REGION_NAME** \ +--plan **PLAN_TYPE** --namespace **TENANT_INITIAL_NAMESPACE_NAME** ---- You can use {astra_cli} instead of or in addition to the {astra_ui} and {astra_db} APIs. diff --git a/modules/developing/pages/astream-functions.adoc b/modules/developing/pages/astream-functions.adoc index 4848e17..d0c2403 100644 --- a/modules/developing/pages/astream-functions.adoc +++ b/modules/developing/pages/astream-functions.adoc @@ -11,12 +11,6 @@ It automatically runs for each message published to the specified input topic. Functions are implemented using https://pulsar.apache.org/docs/en/functions-overview/[Apache Pulsar(R) functions]. See [Pulsar Functions overview] for more information about Pulsar functions. -.Pulsar functions video -[%collapsible] -==== -video::OCqxcNK0HEo[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -==== - [IMPORTANT] ==== Custom functions require a xref:operations:astream-pricing.adoc[paid {product} plan]. @@ -229,7 +223,7 @@ Replace the following: An archive can contain multiple classes, but only one is used per deployment. + ** For Python scripts, the `className` is the Python filename (without the extension) and the class to execute, such as `pythonfunc.ExclamationFunction`. -If there is no class in the file, the `className` is the filename without the extension, such as `pythonfunc`. +If there isn't a class in the file, the `className` is the filename without the extension, such as `pythonfunc`. ** For Java scripts, the `className` is the path and the class to execute, such as `com.example.pulsar.ExclamationFunction`. * `**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME**`: Define the tenant, namespace, and topic for both `input` (incoming messages passed to the function) and `output` (the results of the function). @@ -278,7 +272,7 @@ $ ./pulsar-admin functions create \ --log-topic persistent://**TENANT_NAME**/**NAMESPACE_NAME**/**TOPIC_NAME** ---- -If there is no class in the file, the `className` is only the filename without the extension. +If there isn't a class in the file, the `className` is only the filename without the extension. ==== . Verify the deployment: @@ -323,7 +317,7 @@ A file can contain multiple classes, but only one is used per deployment. + For Python scripts, the class name is the Python filename (without the extension) and the class to execute. For example, if the Python file is called `testfunction.py` and the class is `ExclamationFunction`, then the class name is `testfunction.ExclamationFunction`. -If there is no class in the Python file, the class name is the filename without the extension, such as `testfunction`. +If there isn't a class in the Python file, the class name is the filename without the extension, such as `testfunction`. + For Java scripts, the class name is the class to execute. + @@ -354,7 +348,7 @@ For more information, see xref:streaming-learning:functions:index.adoc[] and xre This means there is a change that a message is not processed. ** *EFFECTIVELY_ONCE*: Each message sent to the function has only one output associated with it. * *Timeout*: Set a timeout limit. -* *Auto Acknowledge*: Enable or disable automatic message acknowledgement. +* *Auto Acknowledge*: Enable or disable automatic message acknowledgment. . (Optional) Provide a config key, if required. For more information, see the https://pulsar.apache.org/functions-rest-api/#operation/registerFunction[Pulsar documentation]. diff --git a/modules/developing/pages/astream-kafka.adoc b/modules/developing/pages/astream-kafka.adoc index e207e96..7768d19 100644 --- a/modules/developing/pages/astream-kafka.adoc +++ b/modules/developing/pages/astream-kafka.adoc @@ -12,14 +12,6 @@ By integrating two popular event streaming ecosystems, {kafka_for_astra} unlocks This document will help you get started producing and consuming Kafka messages on a Pulsar cluster. -.Starlight for Kafka video -[%collapsible] -==== -This video from the *Five Minutes About Pulsar* series explains how to migrate from Kafka to Pulsar: - -video::Qy2ZlelLjXg[youtube, list=PL2g2h-wyI4SqeKH16czlcQ5x4Q_z-X7_m, height=445px,width=100%] -==== - == {kafka_for_astra} Quickstart :page-tag: starlight-kafka,quickstart,install,admin,dev,pulsar,kafka diff --git a/modules/developing/pages/clients/csharp-produce-consume.adoc b/modules/developing/pages/clients/csharp-produce-consume.adoc index 72a6c58..5145407 100644 --- a/modules/developing/pages/clients/csharp-produce-consume.adoc +++ b/modules/developing/pages/clients/csharp-produce-consume.adoc @@ -14,8 +14,8 @@ Go to the https://github.com/datastax/astra-streaming-examples[examples repo] fo == Create a console project -* Create a new console project, and then add a reference to the https://www.nuget.org/packages/DotPulsar/[dotpulsar client]: -+ +Create a new console project, and then add a reference to the https://www.nuget.org/packages/DotPulsar/[dotpulsar client]: + [source,shell] ---- include::{astra-streaming-examples-repo}/csharp/simple-producer-consumer/create-project.sh[] @@ -49,7 +49,7 @@ Messages can also be in formats like JSON, byte, and AVRO. include::{astra-streaming-examples-repo}/csharp/simple-producer-consumer/SimpleProducerConsumer/Program.cs[tag=build-producer] ---- -Asynchronously send a single message and wait for acknowledgment: +. Asynchronously send a single message and wait for acknowledgment: + .Program.cs [source,csharp] @@ -80,8 +80,8 @@ include::{astra-streaming-examples-repo}/csharp/simple-producer-consumer/SimpleP == Run the script -* In your project directory, run the script: -+ +In your project directory, run the script: + [source,shell] ---- dotnet run diff --git a/modules/developing/pages/clients/golang-produce-consume.adoc b/modules/developing/pages/clients/golang-produce-consume.adoc index 2935420..1c3344b 100644 --- a/modules/developing/pages/clients/golang-produce-consume.adoc +++ b/modules/developing/pages/clients/golang-produce-consume.adoc @@ -16,10 +16,17 @@ Go to the https://github.com/datastax/astra-streaming-examples[examples repo] fo == Create a project . Run the following script to create a project folder, change directory into it, and then initialize a new Go project: - ++ [source,shell] ---- -include::{astra-streaming-examples-repo}/go/simple-producer-consumer/create-project.sh[] +sudo apt install -y golang-go +mkdir SimpleProducerConsumer && cd SimpleProducerConsumer + +go mod init SimpleProducerConsumer + +touch main.go + +go get -u github.com/apache/pulsar-client-go ---- + The new project includes a `main` file and the retrieved Pulsar client package. @@ -81,8 +88,8 @@ include::{astra-streaming-examples-repo}/go/simple-producer-consumer/SimpleProdu == Run the script -* In your project directory, run your Go app: -+ +In your project directory, run your Go app: + [source,shell] ---- go run main.go diff --git a/modules/developing/pages/clients/java-produce-consume.adoc b/modules/developing/pages/clients/java-produce-consume.adoc index cd4451d..5fbffac 100644 --- a/modules/developing/pages/clients/java-produce-consume.adoc +++ b/modules/developing/pages/clients/java-produce-consume.adoc @@ -138,8 +138,8 @@ include::{astra-streaming-examples-repo}/java/simple-producer-consumer/SimplePro == Run the script -. Build and run the app: -+ +Build and run the app: + [source,shell] ---- mvn clean package assembly:single diff --git a/modules/developing/pages/clients/nodejs-produce-consume.adoc b/modules/developing/pages/clients/nodejs-produce-consume.adoc index 6eb0089..de49c84 100644 --- a/modules/developing/pages/clients/nodejs-produce-consume.adoc +++ b/modules/developing/pages/clients/nodejs-produce-consume.adoc @@ -17,10 +17,10 @@ Go to the https://github.com/datastax/astra-streaming-examples[examples repo] fo == Set up the environment -. Install the C++ Pulsar library dependency required by the Node.js Pulsar client npm package. -+ +Install the C++ Pulsar library dependency required by the Node.js Pulsar client npm package. + Pulsar Node client versions 1.8 and later do not require installation of the C++ Pulsar library dependency. -+ + [tabs] ====== Ubuntu-based Debian:: @@ -50,7 +50,7 @@ sudo ldconfig == Create a project -. In a terminal, run the following script to create a new Node.js project. +In a terminal, run the following script to create a new Node.js project. If NPM asks for project values, use the automatically suggested defaults. [source,shell] @@ -77,7 +77,7 @@ include::developing:partial$client-variables-table.adoc[] . Use the client to create a producer. + -There are many configuation options for producers. +There are many configuration options for producers. For this example, declare the topic where messages should go. + .index.js @@ -112,7 +112,7 @@ At this point, the script produces a message that waits to be consumed and ackno include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleProducerConsumer/index.js[tag=create-consumer] ---- -. Receive messages, write them to console, and acknowledge receipt with the broker: +. Receive messages, write them to the console, and acknowledge receipt with the broker: + .index.js [source,javascript] @@ -130,8 +130,8 @@ include::{astra-streaming-examples-repo}/nodejs/simple-producer-consumer/SimpleP == Run the script -* In your Node.js project, run the script: -+ +In your Node.js project, run the script: + [source,shell] ---- node index.js diff --git a/modules/developing/pages/clients/python-produce-consume.adoc b/modules/developing/pages/clients/python-produce-consume.adoc index 4b60244..aed1ae5 100644 --- a/modules/developing/pages/clients/python-produce-consume.adoc +++ b/modules/developing/pages/clients/python-produce-consume.adoc @@ -85,8 +85,8 @@ include::{astra-streaming-examples-repo}/python/simple-producer-consumer/SimpleP == Run the script -* In your Python project directory, run the script: -+ +In your Python project directory, run the script: + [source,shell] ---- python3 index.py diff --git a/modules/developing/pages/clients/spring-produce-consume.adoc b/modules/developing/pages/clients/spring-produce-consume.adoc index c38bde6..4c46fd2 100644 --- a/modules/developing/pages/clients/spring-produce-consume.adoc +++ b/modules/developing/pages/clients/spring-produce-consume.adoc @@ -29,7 +29,7 @@ image::spring-initializr.png[Spring Initializr] . Click *Generate Project*, download the zip file, and then extract it. . Navigate to `src/main/java`, and then open the `DemoApplication.java` file. -This file contains the main method that will run your application with the dependencies specified. +This file contains the main method that will run your application with the specified dependencies. + .DemoApplication.java [%collapsible] diff --git a/modules/developing/pages/using-curl.adoc b/modules/developing/pages/using-curl.adoc index 9197f0e..4900993 100644 --- a/modules/developing/pages/using-curl.adoc +++ b/modules/developing/pages/using-curl.adoc @@ -1,5 +1,5 @@ -= {product} HTTP requests -:navtitle: Form requests += Form {product} HTTP requests +:navtitle: Form HTTP requests :description: Interact with {product} over HTTP, such as with curl commands. You can use the xref:apis:index.adoc[{product} APIs] and https://pulsar.apache.org/docs/reference-rest-api-overview/[Pulsar REST APIs] to programmatically interact with your tenants and related {product} configurations. @@ -62,7 +62,7 @@ curl -sS --location -X GET "$WEB_SERVICE_URL/admin/v3/sinks/builtinsinks" \ The default response is a single JSON string. -You can use modifications like ` | jq .` or ` | python3 -mjson.tool` to format the output for easier reading. +You can use modifications like `| jq .` or `| python3 -mjson.tool` to format the output for easier reading. == See also diff --git a/modules/operations/pages/astream-pricing.adoc b/modules/operations/pages/astream-pricing.adoc index a1c8b09..c96692d 100644 --- a/modules/operations/pages/astream-pricing.adoc +++ b/modules/operations/pages/astream-pricing.adoc @@ -4,7 +4,7 @@ {product} is a fully-managed, software-as-a-service (SaaS) offering embedded in {astra_db}. {product} offers three subscription plans, as well as pricing for dedicated clusters. -A {product} subscription is associated with an {astra_db} organization, but {product} subscription plans are separate from {astra_db} organization subscription plans. +An {product} subscription is associated with an {astra_db} organization, but {product} subscription plans are separate from {astra_db} organization subscription plans. For {product} plans and pricing details, see https://www.datastax.com/pricing/astra-streaming[{product} pricing]. For information about {astra_db} plans, billing, and usage, see xref:astra-db-serverless:administration:subscription-plans.adoc[]. diff --git a/modules/operations/pages/astream-token-gen.adoc b/modules/operations/pages/astream-token-gen.adoc index cfecbe2..6134a7b 100644 --- a/modules/operations/pages/astream-token-gen.adoc +++ b/modules/operations/pages/astream-token-gen.adoc @@ -95,4 +95,4 @@ curl -sS --location -X GET "https://api.astra.datastax.com/v2/stats/namespaces/* == See also -* *xref:getting-started:index.adoc[] \ No newline at end of file +* xref:getting-started:index.adoc[] \ No newline at end of file diff --git a/modules/operations/pages/monitoring/new-relic.adoc b/modules/operations/pages/monitoring/new-relic.adoc index 7b1fa9e..1029496 100644 --- a/modules/operations/pages/monitoring/new-relic.adoc +++ b/modules/operations/pages/monitoring/new-relic.adoc @@ -1,6 +1,6 @@ = Integrate with New Relic -While there are multiple ways to https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic[send external Prometheus data to New Relic], one the https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] option is relevant to {product}. +While there are multiple ways to https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic[send external Prometheus data to New Relic], only the https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/get-started/send-prometheus-metric-data-new-relic/#remote-write[Prometheus remote write integration] option is relevant to {product}. == Prerequisites @@ -10,7 +10,7 @@ While there are multiple ways to https://docs.newrelic.com/docs/infrastructure/p == Prepare the extra Prometheus server -* xref:monitoring/integration.adoc[Install an extra Prometheus server] to act as a bridge to forward scraped {product} metrics to New Relic. +xref:monitoring/integration.adoc[Install an extra Prometheus server] to act as a bridge to forward scraped {product} metrics to New Relic. This is required because {product} is a managed service, and you can't modify the {product} Prometheus server as required by the New Relic Prometheus write integration. diff --git a/modules/operations/pages/monitoring/topic-dashboard.adoc b/modules/operations/pages/monitoring/topic-dashboard.adoc index 2d87aee..32da40f 100644 --- a/modules/operations/pages/monitoring/topic-dashboard.adoc +++ b/modules/operations/pages/monitoring/topic-dashboard.adoc @@ -38,7 +38,7 @@ Subscription displays the time series metrics chart summarized at the subscripti * Total subscription delayed messages divided by individual subscriptions * Total subscription message dispatch rate (msg/s) divided by individual subscriptions * Total subscription message throughput rate (byte/s) divided by individual subscriptions -* Total subscription message acknowledgement rate (msg/s) divided by individual subscriptions +* Total subscription message acknowledgment rate (msg/s) divided by individual subscriptions * Total subscription message redelivery rate (msg/s) divided by individual subscriptions * Total subscription message expired rate (msg/s) divided by individual subscriptions * Total subscription message dropped rate (msg/s) divided by individual subscriptions diff --git a/modules/operations/pages/onboarding-faq.adoc b/modules/operations/pages/onboarding-faq.adoc index bcbee45..e503ceb 100644 --- a/modules/operations/pages/onboarding-faq.adoc +++ b/modules/operations/pages/onboarding-faq.adoc @@ -8,7 +8,7 @@ This page answers some common questions about getting started with {product}. == Why does {company} call {product} "serverless"? -Running a production grade Pulsar cluster that can handle at-scale workloads is not a trivial task. +Running a - Pulsar cluster that can handle at-scale workloads is not a trivial task. It requires many (virtual) machines to be configured in a very particular way. In traditional cloud environments, you would pay hourly for every machine whether they are being used for workloads or not, and you would carry the burden of maintaining the server infrastructure. @@ -34,7 +34,7 @@ For more information, see xref:operations:astream-pricing.adoc[]. == Does {product} support single-sign on? -If your {astra_db} organization is on the *Pay As You Go* and *Enterprise* {astra_db} subscription plan, your users can use SSO to sign in to the {astra_ui}. +If your {astra_db} organization is on the *Pay As You Go* or *Enterprise* {astra_db} subscription plan, your users can use SSO to sign in to the {astra_ui}. For more information, see xref:astra-db-serverless:administration:configure-sso.adoc[]. == How do {astra_db} roles and permissions map to Pulsar roles and permissions? @@ -71,7 +71,7 @@ For migration assistance, contact {support_url}[{company} Support]. Every tenant in {product} comes with custom ports for Kafka and RabbitMQ workloads. {company} also offers a fully-compatible JMS implementation for your Java workloads. -xref:streaming-learning:use-cases-architectures:starlight/index.adoc[Learn more] +For more information, see xref:streaming-learning:use-cases-architectures:starlight/index.adoc[]. === How do I separate messaging traffic? @@ -80,7 +80,7 @@ The configurations of middleware and platforms supporting the app should be kept By Tenant:: To support the hierarchy of development environments, {company} recommends creating separate tenants for each development environment. -This gives you the greatest flexibility to balance separation of roles with consistent service configuration. +This gives you the greatest flexibility to balance the separation of roles with consistent service configuration. + All tokens created within a tenant are limited to that tenant. + @@ -99,7 +99,7 @@ All tokens would have access to all namespaces. {product} is actively maintained to keep parity with the official https://pulsar.apache.org[Apache Pulsar project]. The notable differences arise from accessibility and security. Naturally, you have less control in a managed, serverless cluster than you do in a cluster running in your own environment. -Beyond those differences, the effort to develop locally and then move to {product} should not be significant, but it is recommended to develop directly in {product}. +Beyond those differences, the effort to develop locally and then move to {product} should not be significant, but {company} recommends that you develop directly in {product}. If you are trying to reduce costs, use the free tier of {product} and then switch when you are ready to stage your production services. === Can I use {product} with my existing Kafka or RabbitMQ applications?