Skip to content

Commit 1b91248

Browse files
Add payment failure rate problem pattern - unsupported cards.
1 parent e7861c0 commit 1b91248

File tree

13 files changed

+25529
-355
lines changed

13 files changed

+25529
-355
lines changed

.env.override

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
# DO NOT PUSH CHANGES OF THIS FILE TO opentelemetry/opentelemetry-demo
22
# PLACE YOUR .env ENVIRONMENT VARIABLES OVERRIDES IN THIS FILE
3+
4+
IMAGE_NAME=europe-docker.pkg.dev/dynatrace-demoability/docker/astroshop-rds-and-payment-merge
5+
DEMO_VERSION=2.0.2
6+
7+
# CART_DOCKERFILE=./dynatrace/Dockerfile.cart
8+
CHECKOUT_DOCKERFILE=./dynatrace/Dockerfile.checkout
9+
LOAD_GENERATOR_DOCKERFILE=./dynatrace/Dockerfile.loadgenerator
10+
PRODUCT_CATALOG_DOCKERFILE=./dynatrace/Dockerfile.productcatalog

dynatrace/Dockerfile.checkout

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
3+
FROM golang:1.24-bookworm AS builder
4+
5+
WORKDIR /usr/src/app/
6+
7+
COPY ./src/checkout/go.mod go.mod
8+
COPY ./src/checkout/go.sum go.sum
9+
10+
RUN go mod download
11+
12+
COPY ./src/checkout/genproto/oteldemo/ genproto/oteldemo/
13+
COPY ./src/checkout/kafka/ kafka/
14+
COPY ./src/checkout/money/ money/
15+
COPY ./src/checkout/main.go main.go
16+
17+
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o checkout main.go
18+
19+
FROM gcr.io/distroless/static-debian12:nonroot
20+
21+
WORKDIR /usr/src/app/
22+
23+
COPY --from=builder /usr/src/app/checkout/ ./
24+
25+
EXPOSE ${CHECKOUT_PORT}
26+
27+
# ENTRYPOINT [ "./checkout" ]
28+
ENTRYPOINT ["/bin/sh", "-c", "'./checkout'"]

dynatrace/Dockerfile.loadgenerator

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright The OpenTelemetry Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
5+
FROM python:3.12-slim-bookworm AS base
6+
7+
FROM base AS builder
8+
RUN apt-get -qq update \
9+
&& apt-get install -y --no-install-recommends g++ \
10+
&& rm -rf /var/lib/apt/lists/*
11+
12+
COPY ./src/load-generator/requirements.txt .
13+
RUN pip install --prefix="/reqs" -r requirements.txt
14+
15+
FROM base
16+
WORKDIR /usr/src/app/
17+
COPY --from=builder /reqs /usr/local
18+
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers
19+
RUN playwright install --with-deps chromium
20+
21+
# COPY ./src/load-generator/locustfile.py .
22+
# COPY ./src/load-generator/people.json .
23+
COPY ./dynatrace/locustfile.py .
24+
COPY ./dynatrace/people.json .
25+
26+
ENTRYPOINT ["locust", "--skip-log-setup"]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright The OpenTelemetry Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
FROM golang:1.24-bookworm AS builder
5+
6+
WORKDIR /usr/src/app/
7+
8+
COPY ./src/product-catalog/go.mod ./src/product-catalog/go.sum ./
9+
10+
RUN go mod download
11+
12+
COPY ./src/product-catalog/genproto/oteldemo/ genproto/oteldemo/
13+
COPY ./src/product-catalog/main.go main.go
14+
15+
RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -ldflags "-s -w" -o product-catalog main.go
16+
17+
FROM gcr.io/distroless/static-debian12:nonroot
18+
19+
WORKDIR /usr/src/app/
20+
21+
COPY --from=builder /usr/src/app/product-catalog/ ./
22+
23+
EXPOSE ${PRODUCT_CATALOG_PORT}
24+
# ENTRYPOINT [ "./product-catalog" ]
25+
ENTRYPOINT ["/bin/sh", "-c", "'./product-catalog'"]

dynatrace/locustfile.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#!/usr/bin/python
2+
3+
# Copyright The OpenTelemetry Authors
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
7+
import json
8+
import os
9+
import random
10+
import uuid
11+
import logging
12+
13+
from locust import HttpUser, task, between
14+
from locust_plugins.users.playwright import PlaywrightUser, pw, PageWithRetry, event
15+
16+
from opentelemetry import context, baggage, trace
17+
from opentelemetry.metrics import set_meter_provider
18+
from opentelemetry.sdk.metrics import MeterProvider
19+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
20+
from opentelemetry.sdk.trace import TracerProvider
21+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
22+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
23+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
24+
from opentelemetry.instrumentation.jinja2 import Jinja2Instrumentor
25+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
26+
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
27+
from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor
28+
from opentelemetry._logs import set_logger_provider
29+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
30+
OTLPLogExporter,
31+
)
32+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
33+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
34+
from opentelemetry.sdk.resources import Resource
35+
36+
from openfeature import api
37+
from openfeature.contrib.provider.ofrep import OFREPProvider
38+
from openfeature.contrib.hook.opentelemetry import TracingHook
39+
40+
from playwright.async_api import Route, Request
41+
42+
logger_provider = LoggerProvider(resource=Resource.create(
43+
{
44+
"service.name": "load-generator",
45+
}
46+
),)
47+
set_logger_provider(logger_provider)
48+
49+
exporter = OTLPLogExporter(insecure=True)
50+
logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
51+
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
52+
53+
# Attach OTLP handler to locust logger
54+
logging.getLogger().addHandler(handler)
55+
logging.getLogger().setLevel(logging.INFO)
56+
57+
exporter = OTLPMetricExporter(insecure=True)
58+
set_meter_provider(MeterProvider([PeriodicExportingMetricReader(exporter)]))
59+
60+
tracer_provider = TracerProvider()
61+
trace.set_tracer_provider(tracer_provider)
62+
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
63+
64+
# Instrumenting manually to avoid error with locust gevent monkey
65+
Jinja2Instrumentor().instrument()
66+
RequestsInstrumentor().instrument()
67+
SystemMetricsInstrumentor().instrument()
68+
URLLib3Instrumentor().instrument()
69+
logging.info("Instrumentation complete")
70+
71+
# Initialize Flagd provider
72+
base_url = f"http://{os.environ.get('FLAGD_HOST', 'localhost')}:{os.environ.get('FLAGD_OFREP_PORT', 8016)}"
73+
api.set_provider(OFREPProvider(base_url=base_url))
74+
api.add_hooks([TracingHook()])
75+
76+
def get_flagd_value(FlagName):
77+
# Initialize OpenFeature
78+
client = api.get_client()
79+
return client.get_integer_value(FlagName, 0)
80+
81+
categories = [
82+
"binoculars",
83+
"telescopes",
84+
"accessories",
85+
"assembly",
86+
"travel",
87+
"books",
88+
None,
89+
]
90+
91+
products = [
92+
"0PUK6V6EV0",
93+
"1YMWWN1N4O",
94+
"2ZYFJ3GM2N",
95+
"66VCHSJNUP",
96+
"6E92ZMYYFZ",
97+
"9SIQT8TOJO",
98+
"L9ECAV7KIM",
99+
"LS4PSXUNUM",
100+
"OLJCESPC7Z",
101+
"HQTGWGPNH4",
102+
]
103+
104+
people_file = open('people.json')
105+
people = json.load(people_file)
106+
107+
class WebsiteBrowserUser(PlaywrightUser):
108+
weight = 2
109+
headless = True # to use a headless browser, without a GUI
110+
111+
@task(2)
112+
@pw
113+
async def open_cart_page_and_change_currency(self, page: PageWithRetry):
114+
try:
115+
page.on("console", lambda msg: print(msg.text))
116+
await page.route('**/*', add_baggage_header)
117+
await page.goto("/", wait_until="domcontentloaded")
118+
119+
# Open the Shopping cart flyout
120+
await page.click('a[data-cy="cart-icon"]')
121+
# Click the go to shopping cart button
122+
await page.click('button:has-text("Go to Shopping Cart")')
123+
124+
# select a random user from the people.json file and checkout
125+
checkout_details = random.choice(people)
126+
await page.select_option('[name="currency_code"]', value=str(checkout_details['userCurrency']))
127+
128+
await page.wait_for_timeout(2000) # giving the browser time to export the traces
129+
except Exception as e:
130+
traceback.print_exc(file=sys.stdout)
131+
raise RescheduleTask(e)
132+
133+
@task(2)
134+
@pw
135+
async def add_product_to_cart(self, page: PageWithRetry):
136+
try:
137+
page.on("console", lambda msg: print(msg.text))
138+
await page.route('**/*', add_baggage_header)
139+
await page.goto("/", wait_until="domcontentloaded")
140+
141+
# Add 1-4 products to the cart
142+
for i in range(random.choice([1, 2, 3, 4])):
143+
# Get a random product link and click on it
144+
product_id = random.choice(products)
145+
await page.click(f"a[href='/product/{product_id}']")
146+
147+
# Add a random number of products to the cart
148+
product_count = random.choice([1, 2, 3, 4, 5, 10])
149+
await page.select_option('select[data-cy="product-quantity"]', value=str(product_count))
150+
151+
# add the product to our cart
152+
await page.click('button:has-text("Add To Cart")')
153+
154+
# Continue Shopping
155+
await page.click('button:has-text("Continue Shopping")')
156+
157+
# Open the Shopping cart flyout
158+
await page.click('a[data-cy="cart-icon"]')
159+
# Click the go to shopping cart button
160+
await page.click('button:has-text("Go to Shopping Cart")')
161+
162+
await page.wait_for_timeout(2000) # giving the browser time to export the traces
163+
except Exception as e:
164+
traceback.print_exc(file=sys.stdout)
165+
raise RescheduleTask(e)
166+
167+
@task(4)
168+
@pw
169+
async def add_product_to_cart_and_checkout(self, page: PageWithRetry):
170+
try:
171+
page.on("console", lambda msg: print(msg.text))
172+
await page.route('**/*', add_baggage_header)
173+
await page.goto("/", wait_until="domcontentloaded")
174+
175+
# Add 1-4 products to the cart
176+
for i in range(random.choice([1, 2, 3, 4])):
177+
# Get a random product link and click on it
178+
product_id = random.choice(products)
179+
await page.click(f"a[href='/product/{product_id}']")
180+
181+
# Add a random number of products to the cart
182+
product_count = random.choice([1, 2, 3, 4, 5, 10])
183+
await page.select_option('select[data-cy="product-quantity"]', value=str(product_count))
184+
185+
# add the product to our cart
186+
await page.click('button:has-text("Add To Cart")')
187+
188+
# Continue Shopping
189+
await page.click('button:has-text("Continue Shopping")')
190+
191+
# Open the Shopping cart flyout
192+
await page.click('a[data-cy="cart-icon"]')
193+
# Click the go to shopping cart button
194+
await page.click('button:has-text("Go to Shopping Cart")')
195+
196+
# select a random user from the people.json file and checkout
197+
checkout_details = random.choice(people)
198+
await page.select_option('select[name="currency_code"]', value=str(checkout_details['userCurrency']))
199+
200+
await page.locator('input#email').fill(checkout_details['email'])
201+
await page.locator('input#street_address').fill(checkout_details['address']['streetAddress'])
202+
await page.locator('input#zip_code').fill(str(checkout_details['address']['zipCode']))
203+
await page.locator('input#city').fill(checkout_details['address']['city'])
204+
await page.locator('input#state').fill(checkout_details['address']['state'])
205+
await page.locator('input#country').fill(checkout_details['address']['country'])
206+
await page.locator('input#credit_card_number').fill(str(checkout_details['creditCard']['creditCardNumber']))
207+
await page.select_option('select#credit_card_expiration_month', value=str(checkout_details['creditCard']['creditCardExpirationMonth']))
208+
await page.select_option('select#credit_card_expiration_year', value=str(checkout_details['creditCard']['creditCardExpirationYear']))
209+
await page.locator('input#credit_card_cvv').fill(str(checkout_details['creditCard']['creditCardCvv']))
210+
211+
# Complete the order
212+
await page.click('button:has-text("Place Order")')
213+
await page.wait_for_timeout(2000) # giving the browser time to export the traces
214+
except Exception as e:
215+
traceback.print_exc(file=sys.stdout)
216+
raise RescheduleTask(e)
217+
218+
219+
async def add_baggage_header(route: Route, request: Request):
220+
existing_baggage = request.headers.get('baggage', '')
221+
headers = {
222+
**request.headers,
223+
'baggage': ', '.join(filter(None, (existing_baggage, 'synthetic_request=true')))
224+
}
225+
await route.continue_(headers=headers)

0 commit comments

Comments
 (0)