Skip to content

Commit

Permalink
Added a random-traffic-generator for the Python Sample App (#31)
Browse files Browse the repository at this point in the history
*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.
  • Loading branch information
AsakerMohd authored Feb 1, 2024
1 parent 353770b commit 0934954
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
path("", views.vehicle, name="vehicle"),
path("<int:vehicle_id>", views.get_vehicle_by_id, name="get_vehicle_by_id"),
path("<int:vehicle_id>/image", views.get_vehicle_image, name="get_vehicle_image"),
path("image/<str:image_name>", views.get_image_by_name, name="image_by_name"),
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# SPDX-License-Identifier: Apache-2.0
import json
import os
import time

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

def get_vehicle_by_id(request, vehicle_id):
if request.method == "GET":
throttle_time = request.GET.get("throttle")
if throttle_time:
print("going to throttle for " + throttle_time + " seconds")
time.sleep(int(throttle_time))

vehicle_objects = Vehicle.objects.filter(id=vehicle_id).values()
if not vehicle_objects:
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
return HttpResponse(vehicle_objects)
return HttpResponseNotAllowed("Only GET requests are allowed!")


def get_vehicle_image(request, vehicle_id):
if request.method == "GET":
vehicle_object = Vehicle.objects.filter(id=vehicle_id).first()
if not vehicle_object:
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
image_name = getattr(vehicle_object, "image_name")
return HttpResponse(requests.get(get_image_endpoint() + "/images/name/" + image_name, timeout=10))
return HttpResponse(requests.get(build_image_url(image_name), timeout=10))
return HttpResponseNotAllowed("Only GET requests are allowed!")


@csrf_exempt
def get_image_by_name(request, image_name):
print(image_name)
if request.method == "GET":
response = requests.get(build_image_url(image_name), timeout=10)
if response.ok:
return HttpResponse(response)
return HttpResponseNotFound("Image with name: " + image_name + " is not found")
return HttpResponseNotAllowed("Only GET requests are allowed!")


def build_image_url(image_name):
return get_image_endpoint() + "/images/name/" + image_name
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Use the official lightweight Node.js 16 image.
# https://hub.docker.com/_/node
FROM node:16-slim

# Create and change to the app directory.
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (if available).
# Copying this first prevents re-running npm install on every code change.
COPY package*.json ./

# Install production dependencies.
# If you have native dependencies, you'll need additional tools.
# For a full list of package.json changes, see:
# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md
RUN npm install --only=production

# Copy local code to the container image.
COPY . .

# Run the web service on container startup.
CMD [ "node", "index.js" ]

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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."

## Running locally
1. Run `npm install`
2. Run locally:
- 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.
- If you want to run against the application on EKS, before running the `node index.js`, run `export <EKS_URL>`.

## Deploying to EKS
Run `bash build.sh <account_id> <region>`. This does the following:
1. This will retrieve the endpoint from EKS ingress-nginx pod
2. Build docker image of the traffic
3. Push the docker image to ECR
4. Deploy the image to EKS

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
account=$1
region=$2

# Save the endpoint URL to a variable
endpoint=$(kubectl get svc -n ingress-nginx | grep "ingress-nginx" | awk '{print $4}')

# Print the endpoint
echo "Endpoint: $endpoint"

export REPOSITORY_PREFIX=${account}.dkr.ecr.${region}.amazonaws.com
aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${REPOSITORY_PREFIX}
aws ecr create-repository --repository-name random-traffic-generator --region ${region} || true
docker build -t random-traffic-generator:latest .
docker tag random-traffic-generator:latest ${REPOSITORY_PREFIX}/random-traffic-generator:latest
docker push ${REPOSITORY_PREFIX}/random-traffic-generator:latest

sed -e 's#\${REPOSITORY_PREFIX}'"#${REPOSITORY_PREFIX}#g" -e 's#\${URL}'"#$endpoint#g" deployment.yaml | kubectl apply -f -


Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: random-traffic-generator
labels:
app: random-traffic-generator
spec:
replicas: 1
selector:
matchLabels:
app: random-traffic-generator
template:
metadata:
labels:
app: random-traffic-generator
spec:
containers:
- name: random-traffic-generator
image: ${REPOSITORY_PREFIX}/random-traffic-generator:latest
env:
- name: URL
value: "${URL}"

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
const axios = require('axios');
const cron = require('node-cron');

const vehicleURL = process.env.URL ? `${process.env.URL}/vehicle-inventory` : 'http://0.0.0.0:8001/vehicle-inventory'

function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}


function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

console.log(vehicleURL)

// sends two requests every minute, 1 POST request and 1 GET request
const postGetCarsTrafficTask = cron.schedule('* * * * *', async () => {
console.log('add 1 car every 1 minutes');
const carData = {"make": "BMW", "model": "M340", "year": 2022, "image_name": "newCar.jpg"}
axios.post(`http://${vehicleURL}/`, carData, { timeout: 10000 })
.catch(err => {
console.error(err.response && err.response.data);
});

// gets image from image service through the vehicle service
axios.get(`http://${vehicleURL}/1/image`, { timeout: 10000 })
.catch(err => {
console.error(`${err.response}, ${err.response.data}`);
}); // Catch and log errors

axios.get(`http://${vehicleURL}/1`, { timeout: 10000 })
.catch(err => {
console.error(err.response && err.response.data);
}); // Catch and log errors
}, { scheduled: false });
postGetCarsTrafficTask.start();

// sends a burst of traffic sending 10 requests every 15 mins:
// 5 POST requests and 5 GET requests.
const postGetCarsTrafficBurstTask = cron.schedule('*/15 * * * *', async () => {
console.log('add 5 cars within 1 minutes');
const carData = {"make": "BMW", "model": "M340", "year": 2022, "image_name": "newCar.jpg"}
for (let i = 0; i < 5; i++) {
axios.post(`http://${vehicleURL}/`, carData, { timeout: 10000 })
.catch(err => {
console.error(err.response && err.response.data);
}); // Catch and log errors

// gets image from image service through the vehicle service
axios.get(`http://${vehicleURL}/1/image`, { timeout: 10000 })
.catch(err => {
console.error(err.response && err.response.data);
}); // Catch and log errors
}
}, { scheduled: false });
postGetCarsTrafficBurstTask.start();

// sends a GET request with custom throttle parameter in the body that mimics being throttled. The throttle time
// is going to be random between 2 - 5 secs.
const getCarThrottle = cron.schedule('*/5 * * * *', async () => {
sleepSecs = getRandomNumber(30,60);
console.log(`sleep ${sleepSecs} seconds`);
await sleep(sleepSecs*1000);
throttleSecs = getRandomNumber(2,5);
console.log(`request will be throttled for ${throttleSecs} seconds`)
axios.get(`http://${vehicleURL}/1`, {params: {"throttle": throttleSecs}}, { timeout: 10000 })
.catch(err => {
console.error(err.response && err.response.data);
}); // Catch and log errors
}, { scheduled: false });
getCarThrottle.start();

// sends an invalid GET request with a non existent car id to trigger 404 error
const getInvalidRequest = cron.schedule('*/5 * * * *', async () => {
sleepSecs = getRandomNumber(30,120);
console.log(`sleep ${sleepSecs} seconds`);
await sleep(sleepSecs*1000);
console.log("getting non existent car to trigger 404");
axios.get(`http://${vehicleURL}/123456789`, { timeout: 10000 })
.catch(err => {
console.error(err.response && err.response.data);
}); // Catch and log errors
}, { scheduled: false });
getInvalidRequest.start();

// sends an invalid GET request 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."
// The vehicle service will then return 404.
const getNonExistentImage = cron.schedule('*/5 * * * *', async () => {
sleepSecs = getRandomNumber(30,120);
console.log(`sleep ${sleepSecs} seconds`);
await sleep(sleepSecs*1000);
console.log('get an non existent image to trigger aws error');
axios.get(`http://${vehicleURL}/image/doesnotexist.jpeg`)
.catch(err => {
console.error(err.response && err.response.data);
}); // Catch and log errors
}, { scheduled: false });
getNonExistentImage.start();

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0934954

Please sign in to comment.