Skip to content

Commit 0934954

Browse files
authored
Added a random-traffic-generator for the Python Sample App (#31)
*Description of changes:* Added a random-traffic-generator for the Python Sample App. More details can be found in the README under `aws-otel-python-instrumentation/sample-applications/vehicle-dealership-sample-app/random-traffic-generator`. The traffic generator generates the following traffic: 1. Every minute, sends a single POST request to the VehicleInventoryApp and sends a single GET request. 2. Every hour, sends a burst of requests: 5 POST requests to the VehicleInventoryApp and 5 GET requests. 3. Every 5 minutes, sleeps for random amount of time between 30-60 seconds and then sends a GET request to the VehicleInventoryApp with a random throttle param between 5-20 seconds. The backend reads that throttle param and simulates throttling for that amount of time before responding to the request. 4. Every 5 minutes, sleeps for random amount of time between 30-60 seconds and then sends a GET request to the VehicleInventoryApp with an invalid car id to trigger 404 error. 5. Every 5 minutes, sleeps for random amount of time between 30-60 seconds and then sends a GET request to the ImageServiceApp with a non existent image name to trigger 500 error due to S3 Error: "An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist." By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 353770b commit 0934954

File tree

11 files changed

+368
-5
lines changed

11 files changed

+368
-5
lines changed

sample-applications/vehicle-dealership-sample-app/VehicleInventoryApp/MainService/url.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
path("", views.vehicle, name="vehicle"),
99
path("<int:vehicle_id>", views.get_vehicle_by_id, name="get_vehicle_by_id"),
1010
path("<int:vehicle_id>/image", views.get_vehicle_image, name="get_vehicle_image"),
11+
path("image/<str:image_name>", views.get_image_by_name, name="image_by_name"),
1112
]

sample-applications/vehicle-dealership-sample-app/VehicleInventoryApp/MainService/views.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
# SPDX-License-Identifier: Apache-2.0
33
import json
44
import os
5+
import time
56

67
import requests
7-
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed
8+
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, HttpResponseNotFound
89
from django.views.decorators.csrf import csrf_exempt
910
from dotenv import load_dotenv
1011
from MainService.models import Vehicle
@@ -27,8 +28,7 @@ def vehicle(request):
2728
make=body["make"], model=body["model"], year=body["year"], image_name=body["image_name"]
2829
)
2930
vehicle_object.save()
30-
print(get_image_endpoint() + "/images/name/" + body["image_name"])
31-
requests.post(get_image_endpoint() + "/images/name/" + body["image_name"], timeout=10)
31+
requests.post(build_image_url(body["image_name"]), timeout=10)
3232
return HttpResponse("VehicleId = " + str(vehicle_object.id))
3333
except KeyError as exception:
3434
return HttpResponseBadRequest("Missing key: " + str(exception))
@@ -40,14 +40,38 @@ def vehicle(request):
4040

4141
def get_vehicle_by_id(request, vehicle_id):
4242
if request.method == "GET":
43+
throttle_time = request.GET.get("throttle")
44+
if throttle_time:
45+
print("going to throttle for " + throttle_time + " seconds")
46+
time.sleep(int(throttle_time))
47+
4348
vehicle_objects = Vehicle.objects.filter(id=vehicle_id).values()
49+
if not vehicle_objects:
50+
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
4451
return HttpResponse(vehicle_objects)
4552
return HttpResponseNotAllowed("Only GET requests are allowed!")
4653

4754

4855
def get_vehicle_image(request, vehicle_id):
4956
if request.method == "GET":
5057
vehicle_object = Vehicle.objects.filter(id=vehicle_id).first()
58+
if not vehicle_object:
59+
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
5160
image_name = getattr(vehicle_object, "image_name")
52-
return HttpResponse(requests.get(get_image_endpoint() + "/images/name/" + image_name, timeout=10))
61+
return HttpResponse(requests.get(build_image_url(image_name), timeout=10))
62+
return HttpResponseNotAllowed("Only GET requests are allowed!")
63+
64+
65+
@csrf_exempt
66+
def get_image_by_name(request, image_name):
67+
print(image_name)
68+
if request.method == "GET":
69+
response = requests.get(build_image_url(image_name), timeout=10)
70+
if response.ok:
71+
return HttpResponse(response)
72+
return HttpResponseNotFound("Image with name: " + image_name + " is not found")
5373
return HttpResponseNotAllowed("Only GET requests are allowed!")
74+
75+
76+
def build_image_url(image_name):
77+
return get_image_endpoint() + "/images/name/" + image_name
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Use the official lightweight Node.js 16 image.
4+
# https://hub.docker.com/_/node
5+
FROM node:16-slim
6+
7+
# Create and change to the app directory.
8+
WORKDIR /usr/src/app
9+
10+
# Copy application dependency manifests to the container image.
11+
# A wildcard is used to ensure copying both package.json AND package-lock.json (if available).
12+
# Copying this first prevents re-running npm install on every code change.
13+
COPY package*.json ./
14+
15+
# Install production dependencies.
16+
# If you have native dependencies, you'll need additional tools.
17+
# For a full list of package.json changes, see:
18+
# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md
19+
RUN npm install --only=production
20+
21+
# Copy local code to the container image.
22+
COPY . .
23+
24+
# Run the web service on container startup.
25+
CMD [ "node", "index.js" ]
26+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Random Traffic Generator
2+
The traffic generator generates the following traffic:
3+
1. Every minute, sends a single POST request to the VehicleInventoryApp and sends a single GET request.
4+
2. Every hour, sends a burst of requests: 5 POST requests to the VehicleInventoryApp and 5 GET requests.
5+
3. Every 5 minutes, sleeps for random amount of time between 30-60 seconds and then sends a GET request to the VehicleInventoryApp with a random throttle param between 5-20 seconds. The backend reads that throttle param and simulates throttling for that amount of time before responding to the request.
6+
4. Every 5 minutes, sleeps for random amount of time between 30-60 seconds and then sends a GET request to the VehicleInventoryApp with an invalid car id to trigger 404 error.
7+
5. Every 5 minutes, sleeps for random amount of time between 30-60 seconds and then sends a GET request to the ImageServiceApp with a non existent image name to trigger 500 error due to S3 Error: "An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist."
8+
9+
## Running locally
10+
1. Run `npm install`
11+
2. Run locally:
12+
- If you are running against you application locally, just run `node index.js`. The default endpoint is `0.0.0.0:8000` for the ImageServiceApp and `0.0.0.0:8001` for VehicleInventoryApp.
13+
- If you want to run against the application on EKS, before running the `node index.js`, run `export <EKS_URL>`.
14+
15+
## Deploying to EKS
16+
Run `bash build.sh <account_id> <region>`. This does the following:
17+
1. This will retrieve the endpoint from EKS ingress-nginx pod
18+
2. Build docker image of the traffic
19+
3. Push the docker image to ECR
20+
4. Deploy the image to EKS
21+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
account=$1
5+
region=$2
6+
7+
# Save the endpoint URL to a variable
8+
endpoint=$(kubectl get svc -n ingress-nginx | grep "ingress-nginx" | awk '{print $4}')
9+
10+
# Print the endpoint
11+
echo "Endpoint: $endpoint"
12+
13+
export REPOSITORY_PREFIX=${account}.dkr.ecr.${region}.amazonaws.com
14+
aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${REPOSITORY_PREFIX}
15+
aws ecr create-repository --repository-name random-traffic-generator --region ${region} || true
16+
docker build -t random-traffic-generator:latest .
17+
docker tag random-traffic-generator:latest ${REPOSITORY_PREFIX}/random-traffic-generator:latest
18+
docker push ${REPOSITORY_PREFIX}/random-traffic-generator:latest
19+
20+
sed -e 's#\${REPOSITORY_PREFIX}'"#${REPOSITORY_PREFIX}#g" -e 's#\${URL}'"#$endpoint#g" deployment.yaml | kubectl apply -f -
21+
22+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
apiVersion: apps/v1
4+
kind: Deployment
5+
metadata:
6+
name: random-traffic-generator
7+
labels:
8+
app: random-traffic-generator
9+
spec:
10+
replicas: 1
11+
selector:
12+
matchLabels:
13+
app: random-traffic-generator
14+
template:
15+
metadata:
16+
labels:
17+
app: random-traffic-generator
18+
spec:
19+
containers:
20+
- name: random-traffic-generator
21+
image: ${REPOSITORY_PREFIX}/random-traffic-generator:latest
22+
env:
23+
- name: URL
24+
value: "${URL}"
25+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
const axios = require('axios');
4+
const cron = require('node-cron');
5+
6+
const vehicleURL = process.env.URL ? `${process.env.URL}/vehicle-inventory` : 'http://0.0.0.0:8001/vehicle-inventory'
7+
8+
function getRandomNumber(min, max) {
9+
return Math.floor(Math.random() * (max - min + 1)) + min;
10+
}
11+
12+
13+
function sleep(ms) {
14+
return new Promise(resolve => setTimeout(resolve, ms));
15+
}
16+
17+
console.log(vehicleURL)
18+
19+
// sends two requests every minute, 1 POST request and 1 GET request
20+
const postGetCarsTrafficTask = cron.schedule('* * * * *', async () => {
21+
console.log('add 1 car every 1 minutes');
22+
const carData = {"make": "BMW", "model": "M340", "year": 2022, "image_name": "newCar.jpg"}
23+
axios.post(`http://${vehicleURL}/`, carData, { timeout: 10000 })
24+
.catch(err => {
25+
console.error(err.response && err.response.data);
26+
});
27+
28+
// gets image from image service through the vehicle service
29+
axios.get(`http://${vehicleURL}/1/image`, { timeout: 10000 })
30+
.catch(err => {
31+
console.error(`${err.response}, ${err.response.data}`);
32+
}); // Catch and log errors
33+
34+
axios.get(`http://${vehicleURL}/1`, { timeout: 10000 })
35+
.catch(err => {
36+
console.error(err.response && err.response.data);
37+
}); // Catch and log errors
38+
}, { scheduled: false });
39+
postGetCarsTrafficTask.start();
40+
41+
// sends a burst of traffic sending 10 requests every 15 mins:
42+
// 5 POST requests and 5 GET requests.
43+
const postGetCarsTrafficBurstTask = cron.schedule('*/15 * * * *', async () => {
44+
console.log('add 5 cars within 1 minutes');
45+
const carData = {"make": "BMW", "model": "M340", "year": 2022, "image_name": "newCar.jpg"}
46+
for (let i = 0; i < 5; i++) {
47+
axios.post(`http://${vehicleURL}/`, carData, { timeout: 10000 })
48+
.catch(err => {
49+
console.error(err.response && err.response.data);
50+
}); // Catch and log errors
51+
52+
// gets image from image service through the vehicle service
53+
axios.get(`http://${vehicleURL}/1/image`, { timeout: 10000 })
54+
.catch(err => {
55+
console.error(err.response && err.response.data);
56+
}); // Catch and log errors
57+
}
58+
}, { scheduled: false });
59+
postGetCarsTrafficBurstTask.start();
60+
61+
// sends a GET request with custom throttle parameter in the body that mimics being throttled. The throttle time
62+
// is going to be random between 2 - 5 secs.
63+
const getCarThrottle = cron.schedule('*/5 * * * *', async () => {
64+
sleepSecs = getRandomNumber(30,60);
65+
console.log(`sleep ${sleepSecs} seconds`);
66+
await sleep(sleepSecs*1000);
67+
throttleSecs = getRandomNumber(2,5);
68+
console.log(`request will be throttled for ${throttleSecs} seconds`)
69+
axios.get(`http://${vehicleURL}/1`, {params: {"throttle": throttleSecs}}, { timeout: 10000 })
70+
.catch(err => {
71+
console.error(err.response && err.response.data);
72+
}); // Catch and log errors
73+
}, { scheduled: false });
74+
getCarThrottle.start();
75+
76+
// sends an invalid GET request with a non existent car id to trigger 404 error
77+
const getInvalidRequest = cron.schedule('*/5 * * * *', async () => {
78+
sleepSecs = getRandomNumber(30,120);
79+
console.log(`sleep ${sleepSecs} seconds`);
80+
await sleep(sleepSecs*1000);
81+
console.log("getting non existent car to trigger 404");
82+
axios.get(`http://${vehicleURL}/123456789`, { timeout: 10000 })
83+
.catch(err => {
84+
console.error(err.response && err.response.data);
85+
}); // Catch and log errors
86+
}, { scheduled: false });
87+
getInvalidRequest.start();
88+
89+
// sends an invalid GET request with a non existent image name to trigger 500 error due to S3 Error:
90+
// "An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist."
91+
// The vehicle service will then return 404.
92+
const getNonExistentImage = cron.schedule('*/5 * * * *', async () => {
93+
sleepSecs = getRandomNumber(30,120);
94+
console.log(`sleep ${sleepSecs} seconds`);
95+
await sleep(sleepSecs*1000);
96+
console.log('get an non existent image to trigger aws error');
97+
axios.get(`http://${vehicleURL}/image/doesnotexist.jpeg`)
98+
.catch(err => {
99+
console.error(err.response && err.response.data);
100+
}); // Catch and log errors
101+
}, { scheduled: false });
102+
getNonExistentImage.start();

sample-applications/vehicle-dealership-sample-app/random-traffic-generator/package-lock.json

Lines changed: 126 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)