سلام!
به آزمون ورودی _بوتکمپ دوآپس ابرآروان_ خوش آمدید. توجه داشته باشید از آنجا که این مسابقه با مسابقات دوآپس پیشین کوئرا کمی متفاوت است، دانستن جزئیات زیر به پیشروی شما در این مسابقه کمک میکند:
+ این مسابقه دارای سناریو یک پارچه است، یعنی پروژه سوالات شبیه به هم و دنبالهدار هستند و روی یک فضا پیش میروند.
+ در این مسابقه تلاش شده تا مهارت شما هم در سطح توسعه دهنده یا همان *developer* و هم در سطح *operation* سنجیده شود. برای همین شما در برخی تمرینها مجبور به توسعه و پیادهسازی هستید. در این گونه سوالات تلاش شده تا شما را محدود به استفاده از زبان برنامهنویسی مخصوصی نکنیم و دست شما را باز بگذاریم. بنابراین میتوانید با زبان دلخواه خود به حل سوالات بپردازید.
## تمرین ورودی
پس از مطالعه شرایط بالا، پروژه اولیه را از [این لینک](/contest/assignments/35250/download_problem_initial_project/128257/) دریافت کنید. درون پروژه با ساختار درختی زیر مواجه میشوید:
```
agree
└── agree.sh
```
از شما میخواهیم تا اسکریپتی بنویسید که صرفا عبارت زیر را چاپ کند.
```
Seen!
```
به علامتهای نگارشی و بزرگی و کوچکی **توجه کنید.** خروجی اسکریپت شما باید دقیقا برابر مقدار خواسته شده باشد.
راهحل خود را درون فایل `agree.sh` موجود در پروژه اولیه، پیادهسازی کنید.
## توجه کنید
+ فراموش نکنید که اسکریپتی کامل را در پاسختان تحویل دهید.
+ فراموش نکنید که اسکریپت خود را درون فایلی با نام `agree.sh` وارد نمایید.
+ برای ارسال پاسخ خود کافیست فایل `agree.sh` را آپلود کنید.
مقدمه
شمارش درخواستهای یک کاربر به سامانهی غذای دانشگاه در چین یکی از سختترین کارهایی است که میتواند اتفاق بیافتد. امروزه چینیها بهدنبال ایجاد یک `API` برای شمارش اتوماتیک و نرمافزاری غذاهایی هستند که ملت بهصورت رایگان از سلف دانشگاه خود سفارش میدهند!
برای دانلود پروژهی اولیه روی [این لینک](/contest/assignments/35250/download_problem_initial_project/128198/) کلیک کنید.
**توجه کنید که شما باید از راهحل این سؤال در سؤالات بعدی نیز استفاده کنید.**
## ایپیآی
در ابتدا به یک `API` نیاز داریم که بتواند بر اساس کد ملی منحصر به هر فرد، تعداد درخواستهای غذای او را بشمرد. این `API` باید در **پورت** `80` و در **مسیر** `/` در دسترس باشد؛ بهشکلی که با ارسال یک ریکوئست به این آدرس به همراه یک هدر `CLIENT-KEY` باید تعداد ریکوئستهای او بهروزرسانی شده و در پاسخ، تعداد کل ریکوئستها توسط همهی کاربران را در کلیدی به نام `"state"` برگراند. بهعنوان مثال:
```bash
$ curl -H "CLIENT-KEY: one" localhost
{'state': {'one': 1}}
$ curl -H "CLIENT-KEY: one" localhost
{'state': {'one': 2}}
$ curl -H "CLIENT-KEY: two" localhost
{'state': {'one': 2, 'two': 1}}
```
## نصب نیازمندیها و اجرا
برای حل این سؤال میتوانید از هر زبان و هر تکنولوژیای که میخواهید استفاده کنید. بهصورتی که در یک پوشه به نام `api` کد برنامه را نوشته و در فایلی به نام `runner.sh` که توسط `sh` اجرا میشود، باید برنامهی خود را اجرا کنید. توجه کنید که حتماً باید `Dockerfile` مربوط به پروژهی خود را برای ما ارسال کنید.
در پروژهی اولیه، ۴ داکرفایل برای `php`، `python`، `golang` و `node` قرار دادیم که میتوانید از آنها مستقیماً استفاده کنید. در صورتی که از یکی از این زبانها برای حل سؤال استفاده میکنید، کافیست که `Dockerfile` مربوط به آن را در پوشهی `api` کپی کنید و طبق توضیحات داده شده، سؤال را حل کنید. برای نصب نیازمندیهای پایتون از `requirements.txt`، برای پیاچپی از `composer.json`، برای گولنگ از `go.mod` و برای نودجیاس از `package.json` استفاده کنید.
در صورتی که زبان مورد استفادهی شما، چیزی به جز این ۴ مورد است، باید خودتان داکرفایلی در پوشهی `api` بهشکلی بنویسید که بتواند نیازمندیهای پروژهی شما را نصب کرده و برنامهی شما را مانند داکرفایلهای موجود اجرا کند.
### تغییر `Dockerfile`
شما آزادید که `Dockerfile` را به هرصورتی که علاقمندید تغییر دهید. در صورتی که با داکر و داکرفایل آشنایی ندارید، هم کافیست که با استفاده از توضیحات داده شده صرفاً با پیادهسازی این `API` سؤال را حل کنید.
## فرد بیهویت
نکتهی مهمی که در این `API` وجود دارد این است که درصورتی که هدر `CLIENT-KEY` وجود نداشت، آخرین وضعیت باید در پاسخ برگردانده شود. درواقع تعداد ریکوئستهایی که فردی بیهویت به `API` میزند شمرده نمیشود.
```bash
$ curl localhost
{'state': {'one': 2, 'two': 1}}
```
## نحوه ارسال پاسخ
شما میتوانید تمامی محتوای موجود در پوشهی `api` را تغییر دهید و هر فایلی که میخواهید اضافه یا کم کنید.
```text
api
├── <mark class="purple" title="نام این فایل اهمیتی ندارد"> api.py </mark> # or main.go somefile.js anyfile.php name.any ...
├── Dockerfile
├── requirements.txt # or go.mod package.json composer.json
└── runner.sh
```
توجه کنید که نام فایل کد شما برای سیستم داوری اهمیتی ندارد و این خود شما هستید که در `runner.sh` از نام آن برای اجرای پروژه استفاده میکنید.
در نهایت این پوشه را _zip_ کرده و ارسال کنید. توجه کنید که پس از _extract_ کردن فایل _zip_ شما، باید پوشهی `api` را ببینیم که درون آن `Dockerfile` وجود دارد.
شمارنده
محمد برنامهنویسی است که برای سرعت اجرای نرمافزار اهمیت ویژهای قائل است. برای همین منظور او پیشنهاد داده که ذخیرهسازی اطلاعات مربوط به هدر `CLIENT-KEY`، در دیتابیس `Redis` انجام شود. در نتیجه شما باید پاسخی که برای [سوال دوم مسابقه](https://quera.ir/contest/assignments/35250/problems/128198) ارسال کردهاید را به گونهای تغییر دهید که از `Redis` برای خواندن و ذخیرهسازی اطلاعات استفاده کند.
برای دانلود پروژهی اولیه روی [این لینک](/contest/assignments/35250/download_problem_initial_project/128196/) کلیک کنید.
## کانتینرها
در ابتدای کار، برای پیاده سازی این سیستم، نیاز است که فایل `docker-compose.yml` را تکمیل کنید. توجه کنید که امکان *build* کردن یک *Image* جدید را دارید. برای دیتابیس `Redis` میتوانید از *image* زیر استفاده نمایید:
```
registry.gitlab.com/qio/standard/redis:latest
```
توجه داشته باشید که باید فایل `docker-compose.yml` را به شکلی تکمیل کنید که پس از `up` کردن آن، هر ۲ کانتینر (*Api*, *Redis*) به درستی اجرا شوند. سپس با توجه به درخواستهایی که از سمت سیستم داوری برای `api` ارسال میشود، باید کلیدهای `CLIENT-KEY` در دیتابیس `Redis` ذخیره شده باشد. در غیر این صورت نمره کامل به شما تعلق نمیگیرد.
### شرایط *containerها*
۱. نام containerها باید حتماً به شکل زیر باشد:
```
api
redis
```
۲. هریک از این containerها باید روی پورت مشخصی که مربوط به آنها میباشد قرار گیرند. دقیقاً به شکل زیر:
```
port of api = 80
port of redis = 6379
```
دقت کنید که تنظیمات پورتها به گونهای انجام شود که وقتی سیستم داوری دستور `docker-compose up` را اجرا کرد، بتواند به هر دو پورت ۸۰ و ۶۳۷۹ دسترسی داشته باشد.
۳. نام دیتابیس ردیس که ذخیره سازی باید در آن انجام شود، `0` است.
۴. هر کلید باید بصورت جداگانه در `Redis` ذخیره شود. به این صورت که اگر n بار درخواست با هدر `CLIENT-KEY=test` ارسال شد، دیتا باید به گونهای ذخیره شده باشد که اگر از `Redis` کلید `test` خوانده شود، مقدار n را برگرداند.
### ایجاد *Volume*
در صورتیکه نیاز دارید فایل یا مسیری را به درون هریک از *containerها Volume* کنید، میتوانید از پوشهای به نام `api` استفاده کنید.
```
services:
sample_container:
build: ./api
volumes:
- ./api/somefile:/somefile
```
فراموش نکنید که فایل `somefile` را درون پوشهی `api` قرارداده و آن را برای داوری ارسال نمایید. برای مثال، پوشهی ارسالی شما برای داوری کوئرا به شکل زیر میتواند باشد:
```
.
├── api
│ └── somefile
└── docker-compose.yml
```
\**توجه:** سیستم داوری کوئرا بهصورت خودکار فایل `docker-compose.yml` را با کامند `up` اجرا میکند. شما نیازی به کد یا اسکریپتی برای اجرای این کار ندارید.
## نحوه ارسال پاسخ
شما فقط میتوانید مسیرهای هایلایت شده را تغییر دهید:
```
.
├── <mark class="yellow" title="این پوشه را میتوانید تغییر دهید"> api </mark>
│ └── ...
└── <mark class="yellow" title="این فایل را میتوانید تغییر دهید"> docker-compose.yml </mark>
```
در نهایت این پوشه را *zip* کرده و ارسال کنید. توجه کنید که پس از *extract* کردن فایل *zip* شما، باید فایل `docker-compose.yml` را ببینیم.
ذخیرهساز سریع
مصطفی که در زمینه امنیت اطلاعات شخص بسیار حرفهای میباشد، پیشنهاد داده است که تعداد درخواستهای قابل ارسال به `api` که در سوال [سوال دوم مسابقه](https://quera.ir/contest/assignments/35250/problems/128198) نوشته شده است را به شکلی محدود کنیم که بتواند حملات احتمالی به سیستم را کنترل نماید. برای همین منظور شما باید پاسخ سوال [سوال دوم مسابقه](https://quera.ir/contest/assignments/35250/problems/128198) را به گونهای که در زیر توضیح داده شده است اصلاح نمایید.
برای دانلود پروژهی اولیه روی [این لینک](/contest/assignments/35250/download_problem_initial_project/128231/) کلیک کنید.
# محدودیت rate-limit
در این `api` هر `client-key` مجاز باشد
در هر دقیقه حداکثر ۱۰ request ارسال کند.
یعنی هر کلاینت اگر در ۶۰ ثانیهی اخیر ۱۰ request ارسال کرده باشد باید برای request بعدی خود ۶۰ ثانیه منتظر بماند.
در صورتی که تعداد request های یک `client-key` از این محدودیت بیشتر شد؛
پاسخها به درخواستهای آن `client-key` باید
**تا یک دقیقه** دارای مشخصات زیر باشد:
- Status code: `429`
- Body:
```json
{"message": "Too many request from <client-key>"}
```
**مقدار `<client-key>` در `message` باید بر اساس هدر `client-key` درخواست جایگذاری شود.**
برای rate limit **مجاز به استفاده از nginx نیستید** و باید با توجه به زبان مورد نظر خود، پیادهسازی را در آن زبان انجام دهید. (به نحوه ارسال پاسخ و فایلهای مجاز در انتهای سوال دقت نمایید.)
---
# نکات تکمیلی
<details class="blue">
<summary>
نصب نیازمندیها و اجرا
</summary>
برای حل این سؤال میتوانید از هر زبان و هر تکنولوژیای که میخواهید استفاده کنید. بهصورتی که در یک پوشه به نام `api` کد برنامه را نوشته و در فایلی به نام `runner.sh` که توسط `sh` اجرا میشود، باید برنامهی خود را اجرا کنید. توجه کنید که حتماً باید `Dockerfile` مربوط به پروژهی خود را برای ما ارسال کنید.
در پروژهی اولیه، ۴ داکرفایل برای `php`، `python`، `golang` و `node` قرار دادیم که میتوانید از آنها مستقیماً استفاده کنید. در صورتی که از یکی از این زبانها برای حل سؤال استفاده میکنید، کافیست که `Dockerfile` مربوط به آن را در پوشهی `api` کپی کنید و طبق توضیحات داده شده، سؤال را حل کنید. برای نصب نیازمندیهای پایتون از `requirements.txt`، برای پیاچپی از `composer.json`، برای گولنگ از `go.mod` و برای نودجیاس از `package.json` استفاده کنید.
در صورتی که زبان مورد استفادهی شما، چیزی به جز این ۴ مورد است، باید خودتان داکرفایلی در پوشهی `api` بهشکلی بنویسید که بتواند نیازمندیهای پروژهی شما را نصب کرده و برنامهی شما را مانند داکرفایلهای موجود اجرا کند.
</details>
<details class="green">
<summary>
تغییر `Dockerfile`
</summary>
شما آزادید که `Dockerfile` را به هرصورتی که علاقمندید تغییر دهید. در صورتی که با داکر و داکرفایل آشنایی ندارید، هم کافیست که با استفاده از توضیحات داده شده صرفاً با پیادهسازی این `API` سؤال را حل کنید.
</details>
<details class="red">
<summary>
فرد بیهویت
</summary>
نکتهی مهمی که در این `API` وجود دارد این است که درصورتی که هدر `CLIENT-KEY` وجود نداشت، آخرین وضعیت باید در پاسخ برگردانده شود. درواقع تعداد ریکوئستهایی که فردی بیهویت به `API` میزند شمرده نمیشود؛ ولی state کلی تا آن لحظه برگردانده میشود:
```json
{
"state": {
"client-key-arvancloud": 5,
"client-key-quera": 7,
"client-key-bood-key-bood-man-naboodam": 35
}
}
```
</details>
## نحوه ارسال پاسخ
شما میتوانید تمامی محتوای موجود در پوشهی `api` را تغییر دهید و هر فایلی که میخواهید اضافه یا کم کنید.
```text
api
├── <mark class="purple" title="نام این فایل اهمیتی ندارد"> api.py </mark> # or main.go somefile.js anyfile.php name.any ...
├── Dockerfile
├── requirements.txt # or go.mod package.json composer.json
└── runner.sh
```
توجه کنید که نام فایل کد شما برای سیستم داوری اهمیتی ندارد و این خود شما هستید که در `runner.sh` از نام آن برای اجرای پروژه استفاده میکنید.
در نهایت این پوشه را _zip_ کرده و ارسال کنید. توجه کنید که پس از _extract_ کردن فایل _zip_ شما، باید پوشهی `api` را ببینیم که درون آن `Dockerfile` وجود دارد.
شمارندهی محدود
یک سرویس http api روی یک docker container با نام `api` و روی پورت `8080` ران میشود.
#### از شما میخواهیم:
۱. با استفاده از `nginx` درخواستها به پورت `80` را به این سرویس **proxy** کنید.
۲. با استفاده از `nginx` یک `rate limit` اضافه کنید.
این rate limit باید بر اساس `header` با نام `CLIENT-KEY` کار کند
و هر client مجاز باشد در هر دقیقه ۱۰ request ارسال کند
و پس از آن به مدت ۱ دقیقه response با status code برابر `429` دریافت کند.
همچنین در تنظیم rate limit توجه داشته باشید که
یک `client-key` باید بتواند همه **۱۰ درخواست مجاز خود در دقیقه** را،
**در یک ثانیه** بزند و محدودیتی از این لحاظ نداشته باشد.
---
محتوای فایل `docker-compose.yml` که در داوری اجرا میشود:
```yaml docker-compose.yml
version: "3"
services:
api:
build: "./api"
container_name: "api"
volumes:
- ./api:/api
ports:
- "8080:8080"
nginx:
image: registry.gitlab.com/qio/standard/nginx:1.21-alpine
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
```
---
**شما تنها مجاز به ارسال فایل `nginx.conf` هستید
و هر فایل دیگری که ارسال کنید در داوری اثر نخواهد داشت.**
محدودیت با nginx
جف بزوس برای حل مشکلات وب سرورهایی که در آمازون استفاده میشود، نیازمند این است که بتواند لاگ هر وب سرور را بصورت مرتب بررسی نماید. برای حل این مشکل او میخواهد سیستمی پیاده کند که خروجی لاگ وب سرور مورد نظر او، داخل سیستم `Elasticsearch` ذخیره شود که هم به راحتی آنها را بخواند و هم سیستم لاگ عملکرد بهینهای داشته باشد. برای این منظور شما باید پاسخ سوالهای [سوال دوم مسابقه](https://quera.ir/contest/assignments/35250/problems/128198) و [سوال پنجم مسابقه](https://quera.ir/contest/assignments/35250/problems/128202) را خود را به این صورت با هم ترکیب کنید که هم `api` خود در آن اجرا شود و هم اینکه درخواست ها توسط `nginx` به سرور `api` پروکسی شود. سپس کانتینر `Filebeat` مسئولیت خواندن لاگهای وب سرور مورد نظر (`nginx`) را بر عهده داشته باشد و در صورت اضافه شدن هر لاگ جدید آنرا برای کانتینر `Elasticsearch` ارسال نماید.
برای دانلود پروژهی اولیه روی [این لینک](/contest/assignments/35250/download_problem_initial_project/128195/) کلیک کنید.
## کانتینرهای **Elasticsearch**
در ابتدای کار، برای پیاده سازی این سیستم، نیاز است که فایل `docker-compose.yml` را تکمیل کنید. توجه داشته باشید که شما اجازهی *build* کردن یک *Image* جدید **خواهید داشت**. برای این سوال شما میتوانید از لیست زیر استفاده نمایید:
```
registry.gitlab.com/qio/standard/elasticsearch:7.16.1
registry.gitlab.com/qio/standard/filebeat:7.16.1
registry.gitlab.com/qio/standard/nginx:1.21-alpine
```
توجه داشته باشید که باید فایل `docker-compose.yml` را به شکلی تکمیل کنید که پس از `up` کردن آن، هر ۴ کانتینر (*Elasticsearc*, *Filebeat* , *Nginx*, *Api*) به درستی اجرا شوند. سپس با توجه به درخواستهایی که از سمت سیستم داوری برای وب سرور `nginx` ارسال میشود، تعدادی لاگ در فایل مربوطه ذخیره میشود که در نهایت انتظار میرود با تنظیماتی که شما انجام دادهاید، این لاگهای جدید، به سیستم Elasticsearch منتقل شود.
### شرایط *containerها*
۱. نام containerها باید حتماً به شکل زیر باشد:
```
elasticsearch
filebeat
nginx
api
```
۲. هریک از این containerها باید روی پورت مشخصی که مربوط به آنها میباشد قرار گیرند. دقیقاً به شکل زیر:
```
port of elasticsearch = 9200
port of nginx = 80
```
دقت کنید که تنظیمات پورتها به گونهای انجام شود که وقتی سیستم داوری دستور `docker-compose up` را اجرا کرد، بتواند به هر دو پورت ۹۲۰۰ و ۸۰ دسترسی داشته باشد.
۳. نام ایندکس (`index`) که لاگها باید در آن ذخیره شود `log-index` میباشد.
۴. آدرسی که لاگهای `nginx` باید در آن ذخیره شود، آدرس `/var/log/nginx/access.log` میباشد.
۵. توجه کنید تمامی مواردی که برای `ratelimit` در سوال ۵ پیاده کرده بودید باید حذف کنید و محدودیتی از بابت ارسال درخواست به سیستم وجود نداشته باشد.
۶. محتویات فایل `docker-compose.yml` شما باید به گونهای باشد که در کمترین زمان ممکن کانتینرها آماده باشند و کار خود را انجام بدهند. برای این منظور حداکثر ۹۰ ثانیه در نظر گرفته شده است و در صورتی که زمان بیشتری طول بکشد، نمره کامل را نخواهید گرفت.
### ایجاد *Volume*
در صورتیکه نیاز دارید فایل یا مسیری را به درون هریک از *containerها Volume* کنید، میتوانید از پوشههای `api` و `data` استفاده کنید.
```
services:
sample_container:
image: "registry.gitlab.com/qio/standard/filebeat:7.16.1"
volumes:
- ./api/somefile:/somefile
- ./data/somefile2:/somefile2
```
فراموش نکنید که فایل `somefile` را درون پوشهی `api` و فایل `somefile2` را درون پوشهی `data` قرارداده و آن را برای داوری ارسال نمایید. برای مثال، پوشهی ارسالی شما برای داوری کوئرا به شکل زیر میتواند باشد:
```
.
├── api
│ └── somefile
├── data
│ └── somefile2
├── docker-compose.yml
└── requirements.txt
```
\**توجه:** سیستم داوری کوئرا بهصورت خودکار فایل `docker-compose.yml` را با کامند `up` اجرا میکند. شما نیازی به کد یا اسکریپتی برای اجرای این کار ندارید.
## شرایط نمرهدهی
نمرهای که شما دریافت میکنید، مربوط به اجرای درست و دقیق کلاستر خواهد بود. به این صورت که هر ۴ کانتینر مورد نظر باید با نامی که در بالا توضیح داده شده باید بدون مشکل اجرا شوند و همچنین لاگی که توسط وب سرور `nginx` ایجاد میشود، باید در ایندکس `log-index` کانتینر `Elasticsearch` ثبت شده باشد.
## نحوه ارسال پاسخ
شما فقط میتوانید مسیرهای هایلایت شده را تغییر دهید:
```
.
├── <mark class="yellow" title="این پوشه را میتوانید تغییر دهید"> data </mark>
│ └── ...
├── <mark class="yellow" title="این پوشه را میتوانید تغییر دهید"> api </mark>
│ └── ...
├── <mark class="yellow" title="این فایل را میتوانید تغییر دهید"> docker-compose.yml </mark>
└── requirements.txt
```
در نهایت این پوشه را *zip* کرده و ارسال کنید. توجه کنید که پس از *extract* کردن فایل *zip* شما، باید فایل `docker-compose.yml` را ببینیم.