diff --git a/shanygin_alexandr_lab_3/README.md b/shanygin_alexandr_lab_3/README.md new file mode 100644 index 0000000..f24080a --- /dev/null +++ b/shanygin_alexandr_lab_3/README.md @@ -0,0 +1,173 @@ +# Лабораторная работа №1 + +## Как запустить лабораторную работу +1. Переносим в Visual Studia Code нашу папку с docker-compose.yml и остальными файлами. +2. Открываем сочитанием Ctrl + Shift + ` терминал. +3. Вводим туда команду docker-compose up --build (Нужно подождать пока все пакеты загрузятся). При вводе команды нужно убедится что Docker Desktop запущен. +4. Далее в Docker Desktop мы можем запустить через порт наш сервис, например запустим порт 5001 и вывод будет вот таким: + +```json +{ +"category": "processor", +"name": "Intel Core i5", +"price": 25000, +"uuid": "comp-1" +}, +{ +"category": "graphics_card", +"name": "NVIDIA RTX 4070", +"price": 60000, +"uuid": "comp-2" +} +``` +5. Как тестировать и пользоваться данной программой будет в пенкту Тесты. + + +## Какие технологии использовали +1. Docker - контейнеризация приложений +2. Docker Compose - оркестрация многоконтейнерного приложения +3. Nginx - API gateway и reverse proxy +4. Flask - веб-фреймворк для создания REST API +5. Python - основной язык программирования +6. Requests - библиотека для HTTP-запросов +7. Postman - тестирование запросов программного продукта + + +## Что она делает +Это веб-приложение для сборки компьютеров, построенное на микросервисной архитектуре. Оно состоит из двух основных сервисов: один управляет комплектующими (процессоры, видеокарты, память), а другой отвечает за сборки ПК. + +Пользователь может создавать сборки компьютеров, добавляя в них различные компоненты по их идентификаторам. Когда запрашивается информация о конкретной сборке, система автоматически обращается к сервису комплектующих и подгружает полные данные о всех компонентах - их названия, категории и цены. + +Все запросы проходят через единый шлюз на Nginx, который перенаправляет их к нужному микросервису. + +## Тесты +Для тестирования программы использовался Postman + +**Нам нужно проверить 5 запросов у двух сервисов: component и build. Виды запросов:** +1. GET - получить список всех компонентов или сборок ПК +2. GET /{uuid} - получить элемент списка компонентов или сборок ПК по его id (в случии вызова сборки ПК так же будет выводится подробная информация о комплектующем) +3. POST - Создать элемент компонентов или сборок ПК +4. PUT /{uuid} - обновить элемент списка компонентов или сборок ПК по его id +5. DELETE /{uuid} - удалить элемент списка компонентов или сборок ПК по его id + +**Для начала тестирования нам нужно:** +1. Создать новую коллекцию(у нас их будет 2: build, component) +2. Добавить туда 5 requast и каждому из них присвоить соответствующие запросы +3. Далее в URL нужно добавить наш порт, и в зависимости он запроса, нужно добавить id в таком формате: http://localhost:5002/id +4. Если нужно изменить или создать элемент то нажимаеем на пункт Body, далее нажимаем на raw и после добавляем или изменяем запись в json формате + +**Разберем на примере build все запросы:** +1. Post +* Правильный запрос + + URL: http://localhost:5002/ + + ```json + { + "name": "PC1", + "component_id": [ + "comp-1" + ] + } + ``` + + Результат: 200 OK + +* Неправильный запрос + URL: http://localhost:5002/ + + ```json + { + "component_id": [ + "comp-1" + ] + } + ``` + Результат: Ошибка 500 (INTERNAL SERVER ERROR) + +2. Get +* Правильный запрос + + URL: http://localhost:5002/ + + ```json + { + "components": [ + "comp-1" + ], + "name": "PC1", + "uuid": "e32976c4-9e9d-44c8-bcf1-81b3238d23f0" + } + ``` + + Результат: 200 OK + +3. Get /{uuid} +* Правильный запрос + + URL: http://localhost:5002/e32976c4-9e9d-44c8-bcf1-81b3238d23f0 + + + + ```json + { + "components": [ + "comp-1" + ], + "components_details": [ + { + "category": "processor", + "name": "Intel Core i5", + "price": 25000, + "uuid": "comp-1" + } + ], + "name": "PC1", + "uuid": "e32976c4-9e9d-44c8-bcf1-81b3238d23f0" + } + ``` + Результат: 200 OK + +* Неправильный запрос + + URL: http://localhost:5002/e32 + + Результат: Ошибка 404 NOT FOUND + +4. PUT /{uuid} +* Правильный запрос + + URL: http://localhost:5002/e32976c4-9e9d-44c8-bcf1-81b3238d23f0 + + ```json + { + "components": [ + "comp-2" + ], + "name": "PC3" + } + ``` + + Результат: 200 OK + +* Неправильный запрос + + URL: http://localhost:5002/e32 + + Результат: Ошибка 404 NOT FOUND + +5. DELETE /{uuid} +* Правильный запрос + + URL: http://localhost:5002/e32976c4-9e9d-44c8-bcf1-81b3238d23f0 + + Результат: 200 OK + +* Неправильный запрос + + URL: http://localhost:5002/e32 + + Результат: Ошибка 404 NOT FOUND + +## Видеоотчет +https://rutube.ru/video/private/6e4e75bb7d1ede9f1fa45627d33fa73c/?p=QtwDp85zXK3jVJ77Tl3azw diff --git a/shanygin_alexandr_lab_3/docker-compose.yml b/shanygin_alexandr_lab_3/docker-compose.yml new file mode 100644 index 0000000..7e170ab --- /dev/null +++ b/shanygin_alexandr_lab_3/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.8' + +services: + nginx: + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf + depends_on: + - microservice_components + - microservice_builds + + microservice_components: + build: + context: ./microservice_components + dockerfile: Dockerfile + ports: + - "5001:5001" + + microservice_builds: + build: + context: ./microservice_builds + dockerfile: Dockerfile + ports: + - "5002:5002" \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/microservice_builds/Dockerfile b/shanygin_alexandr_lab_3/microservice_builds/Dockerfile new file mode 100644 index 0000000..efa57d7 --- /dev/null +++ b/shanygin_alexandr_lab_3/microservice_builds/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.9-alpine + +WORKDIR /app + +RUN apk add --no-cache gcc musl-dev + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY app.py . + +EXPOSE 5002 + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/microservice_builds/app.py b/shanygin_alexandr_lab_3/microservice_builds/app.py new file mode 100644 index 0000000..3bee104 --- /dev/null +++ b/shanygin_alexandr_lab_3/microservice_builds/app.py @@ -0,0 +1,73 @@ +from flask import Flask, jsonify, request +import requests +import uuid + +app = Flask(__name__) + +builds = {} +COMPONENTS_SERVICE = "http://microservice_components:5001" + +def get_component_details(component_id): + try: + response = requests.get(f"{COMPONENTS_SERVICE}/{component_id}") + if response.status_code == 200: + return response.json() + return None + except requests.exceptions.RequestException: + return None + +@app.route('/', methods=['GET']) +def get_builds(): + return jsonify(list(builds.values())) + +@app.route('/', methods=['GET']) +def get_build(build_id): + build = builds.get(build_id) + if not build: + return jsonify({"error": "Build not found"}), 404 + build_with_details = build.copy() + build_with_details["components_details"] = [] + + for component_uuid in build.get("components", []): + component_details = get_component_details(component_uuid) + if component_details: + build_with_details["components_details"].append(component_details) + else: + return jsonify({"error": "Build not found"}), 404 + + return jsonify(build_with_details) + +@app.route('/', methods=['POST']) +def create_build(): + data = request.get_json() + build_id = str(uuid.uuid4()) + + build = { + "uuid": build_id, + "name": data['name'], + "components": data['component_id'] + } + + builds[build_id] = build + return jsonify(build), 201 + +@app.route('/', methods=['PUT']) +def update_build(build_id): + build = builds.get(str(build_id)) + if not build: + return jsonify({'error': 'Not found'}), 404 + data = request.json + build['name'] = data.get('name', build['name']) + build["components"] = data.get('components', build['components']) + return jsonify(build) + +@app.route('/', methods=['DELETE']) +def delete_build(build_id): + if build_id not in builds: + return jsonify({"error": "Not found"}), 404 + + del builds[build_id] + return jsonify({"message": "Build deleted"}), 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5002, debug=True) \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/microservice_builds/requirements.txt b/shanygin_alexandr_lab_3/microservice_builds/requirements.txt new file mode 100644 index 0000000..f855e65 --- /dev/null +++ b/shanygin_alexandr_lab_3/microservice_builds/requirements.txt @@ -0,0 +1,2 @@ +flask==2.3.3 +requests==2.31.0 \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/microservice_components/Dockerfile b/shanygin_alexandr_lab_3/microservice_components/Dockerfile new file mode 100644 index 0000000..9f1b9ab --- /dev/null +++ b/shanygin_alexandr_lab_3/microservice_components/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.9-alpine + +WORKDIR /app + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY app.py . + +EXPOSE 5001 + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/microservice_components/app.py b/shanygin_alexandr_lab_3/microservice_components/app.py new file mode 100644 index 0000000..e4134d9 --- /dev/null +++ b/shanygin_alexandr_lab_3/microservice_components/app.py @@ -0,0 +1,72 @@ +from flask import Flask, jsonify, request +import uuid + +app = Flask(__name__) + +components = { + "comp-1": { + "uuid": "comp-1", + "name": "Intel Core i5", + "category": "processor", + "price": 25000 + }, + "comp-2": { + "uuid": "comp-2", + "name": "NVIDIA RTX 4070", + "category": "graphics_card", + "price": 60000 + } +} + +@app.route('/', methods=['GET']) +def get_components(): + return jsonify(list(components.values())) + +@app.route('/', methods=['GET']) +def get_component(component_id): + component = components.get(component_id) + if component: + return jsonify(component) + return jsonify({"error": "Not found"}), 404 + +@app.route('/', methods=['POST']) +def create_component(): + data = request.get_json() + component_id = str(uuid.uuid4()) + component = { + 'uuid': component_id, + "name": data['name'], + "category": data['category'], + "price": data['price'] + + } + components[component_id] = component + return jsonify(component), 201 + +@app.route('/', methods=['PUT']) +def update_component(component_id): + try: + if component_id not in components: + return jsonify({"error": "Not found"}), 404 + + data = request.get_json() + component = components[component_id] + + component['name'] = data.get('name', component['name']) + component['category'] = data.get('category', component['category']) + component['price'] = data.get('price', component['price']) + + return jsonify(component) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/', methods=['DELETE']) +def delete_component(component_id): + if component_id not in components: + return jsonify({"error": "Not found"}), 404 + + del components[component_id] + return jsonify({"message": "Component deleted"}), 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5001, debug=True) \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/microservice_components/requirements.txt b/shanygin_alexandr_lab_3/microservice_components/requirements.txt new file mode 100644 index 0000000..f855e65 --- /dev/null +++ b/shanygin_alexandr_lab_3/microservice_components/requirements.txt @@ -0,0 +1,2 @@ +flask==2.3.3 +requests==2.31.0 \ No newline at end of file diff --git a/shanygin_alexandr_lab_3/nginx.conf b/shanygin_alexandr_lab_3/nginx.conf new file mode 100644 index 0000000..8daa6aa --- /dev/null +++ b/shanygin_alexandr_lab_3/nginx.conf @@ -0,0 +1,33 @@ +events { + worker_connections 1024; +} + +http { + upstream components_service { + server microservice_components:5001; + } + + upstream builds_service { + server microservice_builds:5002; + } + + server { + listen 80; + + location /components/ { + proxy_pass http://components_service/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /builds/ { + proxy_pass http://builds_service/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file