Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(infrastructure): Update Load Balancer project #207

Merged
merged 1 commit into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions infrastructure/beat-the-load-balancer/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,34 @@ The backend compute infra for BLB is distributed into 3 applications that work i
* User Application is the front for receiving HTTP traffic from Loader and
distributing it according to user inputs.
* Worker application is processing received HTTP traffic and generates score

## How to setup?

(Optional) If you want to have a dedicated folder

mkdir app
cd app
# (optional)
# sudo apt-get install git python3-pip python3.11-venv -y

clone the repository

git clone https://github.com/GoogleCloudPlatform/devrel-demos.git
cd devrel-demos/infrastructure/beat-the-load-balancer/backend

(Optional) If you need to install `pip`

python3 -m venv venv
source venv/bin/activate

Install requirements and running application in demo mode.

pip install -r requirements.txt




gcloud artifacts repositories create quickstart-docker-repo --repository-format=docker \
--location=us-west2 --description="Docker repository" --project=beat-the-load-balancer

gcloud builds submit --region=us-west2 --tag us-west2-docker.pkg.dev/beat-the-load-balancer/quickstart-docker-repo/quickstart-image:tag1 --project=beat-the-load-balancer
4 changes: 4 additions & 0 deletions infrastructure/beat-the-load-balancer/backend/lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@
elif HOSTNAME == "vm-loader2":
# GLB static IP
GAME_LOADER_TARGET_HOST = "http://10.10.10.1000:8000"


# Pubsub SA Account
pubsub_sa_account_file = "path-to-file.json"
17 changes: 16 additions & 1 deletion infrastructure/beat-the-load-balancer/backend/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,19 @@ purge:
# ps -eaf | grep supervisor | grep venv | awk '{print $2}' | xargs kill -9
# ps -eaf | grep celery | grep venv | awk '{print $2}' | xargs kill -9
# ps -eaf | grep python | grep venv | awk '{print $2}' | xargs kill -9
################################################################################
################################################################################

## ==> docker build

VERSION=01

build: ## build docker image
podman build --tag blb:${VERSION} --file ./Dockerfile

run:
podman rm -f main-vm && podman run -d --hostname main-vm --name main-vm blb:${VERSION}

game:
podman exec -it main-vm bash


36 changes: 36 additions & 0 deletions infrastructure/beat-the-load-balancer/frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
88 changes: 88 additions & 0 deletions infrastructure/beat-the-load-balancer/frontend/app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use server'

import { main_vm_address } from '@/lbb.config'

const QUEUE_CAPACITY = 100;

const getRequest = async (url: string) => {
try {
const response = await fetch(url);
const parsedResponse = await response.json();
return parsedResponse;
} catch (error) {
console.error(`Failure making request to ${url}`)
console.error(error);
throw error;
}
}

function checkSecretPassword(secretPassword: string) {
if (process.env.SECRET_PASSWORD && secretPassword !== process.env.SECRET_PASSWORD) {
throw new Error('Incorrect secret password');
}
}

export async function checkSecretPasswordExists() {
return !!process.env.SECRET_PASSWORD;
}

export async function setVm(vmSelected: { vmId: number }, secretPassword: string) {
checkSecretPassword(secretPassword);
return getRequest(`${main_vm_address}/vm/select/vm-wh0${vmSelected.vmId}`);
}

export async function stopGame(secretPassword: string) {
checkSecretPassword(secretPassword);
return getRequest(`${main_vm_address}/game/stop`);
}

export async function resetGame(secretPassword: string) {
checkSecretPassword(secretPassword);
await stopGame(secretPassword);
return getRequest(`${main_vm_address}/game/reset`);
}

export async function startLoader(secretPassword: string) {
checkSecretPassword(secretPassword);
return getRequest(`${main_vm_address}/game/start`);
}

export async function getRawStats(secretPassword: string) {
checkSecretPassword(secretPassword);
return getRequest(`${main_vm_address}/vm/all/stats`);
}

export async function getVmAllStats(secretPassword: string) {
checkSecretPassword(secretPassword);
const parsedResponse = await getRawStats(secretPassword);
const emptyObjectArray = [{}, {}, {}, {}, {}, {}, {}, {}];
const statusArray = emptyObjectArray.map((status, index) => {
const utilization = Math.max(Math.min(parsedResponse.QUEUE[index] / QUEUE_CAPACITY, 1), 0);
const atMaxCapacity = parsedResponse.QUEUE[index] >= QUEUE_CAPACITY;
return {
cpu: parsedResponse.ALL_CPU[index],
memory: parsedResponse.ALL_MEM[index],
score: parsedResponse.ALL_SCORES[index],
tasksCompleted: parsedResponse.ALL_TASKS_COMPLETED[index],
tasksRegistered: parsedResponse.ALL_TASKS_REGISTERED[index],
hostName: parsedResponse.ALL_VMS[index],
crashCount: parsedResponse.ALL_VMS_CRASHES_COUNT[index],
queue: Math.max(parsedResponse.QUEUE[index], 0),
utilization,
atMaxCapacity,
}
});
const gameVmSelection = parsedResponse.GAME_VM_SELECTION;
const gameVmSelectionIndex = parsedResponse.ALL_VMS.findIndex((vmName: string) => vmName === gameVmSelection);
const vmStats = {
statusArray,
playerName: parsedResponse.GAME_PLAYER_NAME,
gameVmSelection,
gameVmSelectionIndex,
gameVmSelectionUpdates: parsedResponse.GAME_VM_SELECTION_UPDATES,
playerOneScore: statusArray.slice(0, 4).reduce((agg, vmStatus) => (agg + Number(vmStatus.score)), 0),
playerTwoScore: statusArray.slice(4).reduce((agg, vmStatus) => (agg + Number(vmStatus.score)), 0),
};
return vmStats;
}

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

26 changes: 26 additions & 0 deletions infrastructure/beat-the-load-balancer/frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import AudioButton from "@/components/AudioButton";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Load Balancing Blitz",
description: "Race against Google Cloud load balancer to see who can route internet traffic faster in this timed showdown!",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<AudioButton />
{children}
</body>
</html>
);
}
103 changes: 103 additions & 0 deletions infrastructure/beat-the-load-balancer/frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client'

import { useEffect, useState } from "react";
import { checkSecretPasswordExists } from "./actions";
import Play from "@/components/Play";
import { Canvas } from '@react-three/fiber'
import MessagePretty from "@/components/MessagePretty";
import StartButton from "@/components/StartButton";

export default function Home() {
const [secretPassword, setSecretPassword] = useState<string>((typeof window !== 'undefined' && localStorage.getItem("secretPassword")) || '');
const [secretPasswordExists, setSecretPasswordExists] = useState<boolean>(false);
const [showPassword, setShowPassword] = useState<boolean>(secretPasswordExists && !secretPassword);
const [showPlayComponent, setShowPlayComponent] = useState<boolean>(false);

const setPassword = (event: { target: { value: string; }; }) => {
const newValue = event.target.value;
localStorage.setItem('secretPassword', newValue);
setSecretPassword(newValue);
}

const handleShowPasswordClick = () => {
setSecretPassword('');
setShowPassword(true);
}

useEffect(() => {
const checkPasswordExists = async () => {
const passwordExists = await checkSecretPasswordExists();
setSecretPasswordExists(passwordExists);
}
checkPasswordExists();
}, []);

if (showPlayComponent) {
return (
<Play />
);
}

const colors = ['#EA4335', '#34A853','#FBBC04', '#4285F4'];
const playerOneBlocks = Array.from(Array(100).keys()).map((index) => {
const color = colors[Math.floor(Math.random() * colors.length)];
const randomNumber = Math.random() - 0.5;
const xPosition = randomNumber * 10;
const yPosition = (index / 5) + randomNumber;
const uuid = crypto.randomUUID();
return { randomNumber, xPosition, yPosition, uuid, color }
});


return (
<>
<div className="absolute min-h-screen top-0 bottom-0 left-0 right-0 -z-10">
<Canvas>
<ambientLight intensity={Math.PI / 2} />
<pointLight position={[-10, -10, -10]} decay={0} intensity={2} />
<pointLight position={[5, 5, 5]} decay={0} intensity={3} />
{playerOneBlocks.map(({ randomNumber, xPosition, yPosition, uuid, color }) => {
Dismissed Show dismissed Hide dismissed
return (
<MessagePretty
key={uuid}
position={[xPosition, yPosition, -3]}
endPoint={[randomNumber,0,0]}
color={color}
/>
)
})}
</Canvas>
</div>
<main className="flex flex-col min-h-screen items-center justify-between p-24">
<h1 className={`mb-3 text-7xl font-mono -z-20`}>
Load Balancing Blitz
</h1>
<StartButton
onClick={() => setShowPlayComponent(true)}
showPassword={showPassword}
/>
<div>
<p>
Press 1, 2, 3, and 4 to distribute the incoming requests
across the four virtual machines.
</p>
<p>
Hint: Keep moving! Be careful not to direct all of the requests to a single machine.
</p>
</div>
{secretPasswordExists && <>
{showPassword ? <div>
<input value={secretPassword} onChange={setPassword} className="border border-black border-1" />
<button onClick={() => setShowPassword(false)}>
Hide Password
</button>
</div>
: <button onClick={handleShowPasswordClick}>
Enter Password
</button>
}
</>}
</main>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client'

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import Stats from "@/components/Stats";

export default function ResultsPage() {
const router = useRouter()

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent): void => {
switch (event.code) {
case 'KeyS':
router.push('/');
}
};
window.addEventListener("keydown", handleKeyDown);

return () => window.removeEventListener("keydown", handleKeyDown);
}, [router]);



return (
<main className="min-h-screen items-center justify-between p-24">
<div className="flex items-center justify-between">
<Link
href="/"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Press S to return to Home{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
</Link>
</div>
<Stats />
</main>
);
}
Loading
Loading