Browse Source

Рабочая версия

master
Дмитрий 3 years ago
parent
commit
484b2af669
  1. 36
      config_object.py
  2. 10
      server.py
  3. 176
      solution.py

36
config_object.py

@ -1,31 +1,55 @@
from dataclasses import dataclass from dataclasses import dataclass
import asyncio import asyncio
import os import os
import aiofiles
@dataclass
class ConfigObject: class ConfigObject:
host: str host: str
conf_body: str conf_body: str
path: str path: str
def __init__(self, host: str, conf_body: str, path: str):
self.host = host
self.conf_body = conf_body.replace(
"server_name _;", f"server_name {host}.server.com;"
)
self.path = os.path.abspath(path)
if not os.path.isdir(self.path):
os.mkdir(self.path)
self.full_path_to_file: str = os.path.join(self.path, f"{host}.conf")
@property @property
def existst(self) -> bool: def existst(self) -> bool:
"""Возвращает True, если файл конфига уже существует.""" """Возвращает True, если файл конфига уже существует."""
return path.isfile(path) return os.path.isfile(self.full_path_to_file)
async def write(self) -> bool: async def write(self) -> bool:
"""Записывает конфиг файл.""" """Записывает конфиг файл."""
_config_file_name: str = path.join(path, f"{host}.conf")
if not self.existst: if not self.existst:
pass async with aiofiles.open(self.full_path_to_file, mode="w") as file:
await file.write(self.conf_body)
return True return True
def __repr__(self):
return f"Hi, config for {self.host}"
def read_config_template_file(path_to_file: str) -> str:
class ConfigFactory:
def __init__(self, path_to_template: str, path_to_configs_dir: str):
self.templ = self.__read_config_template_file(path_to_template)
self.path = path_to_configs_dir
def create(self, host: str) -> ConfigObject:
return ConfigObject(host, self.templ, self.path)
def __read_config_template_file(self, path_to_file: str) -> str:
"""Прочесть шаблон конфига для сервера из файла.""" """Прочесть шаблон конфига для сервера из файла."""
template: str = "" template: str = ""
_full_path = os.path.abspath(path_to_file) _full_path = os.path.abspath(path_to_file)
with open(_full_path, mode="r", encoding="utf8") as file: with open(_full_path, mode="r", encoding="utf8") as file:
template = file.read() template = file.read()

10
server.py

@ -15,6 +15,8 @@ curl --request GET \
from flask import Flask, jsonify, request from flask import Flask, jsonify, request
import json import json
import secrets import secrets
from random import randint
import time
app = Flask(__name__) app = Flask(__name__)
@ -43,7 +45,15 @@ def index():
for host in range(0, limit): for host in range(0, limit):
hosts.append({"hostname": f"content-creator-{secrets.token_hex(4)}"}) hosts.append({"hostname": f"content-creator-{secrets.token_hex(4)}"})
time.sleep(randint(1, 6))
return jsonify({"result": hosts, "done": True}) return jsonify({"result": hosts, "done": True})
@app.route("/api/portal/count", methods=["GET"])
def count():
time.sleep(randint(1, 2))
return jsonify({"result": randint(20, 300), "done": True})
app.run() app.run()

176
solution.py

@ -1,140 +1,80 @@
from dataclasses import dataclass import argparse
import asyncio
import os
from asyncio import Task from asyncio import Task
from dataclasses import dataclass
from typing import List from typing import List
from aiohttp.client_exceptions import ClientConnectorError
import asyncio
import os
import aiohttp import aiohttp
import aiofiles from aiohttp.client_exceptions import ClientConnectorError
from loguru import logger
from config_object import ConfigObject, read_config_template_file
from app_config import AppConfig from app_config import AppConfig
from request_builder import RequestBulder from config_object import ConfigFactory, ConfigObject
from request_builder import Json, RequestBulder
async def write_config_files(hosts: list, cfg: AppConfig, template: str) -> None:
"""Записываем конфиги для списка hosts."""
full_path_to_config_dir: str = os.path.abspath(cfg.path_for_config)
if not os.path.isdir(full_path_to_config_dir):
os.mkdir(full_path_to_config_dir)
for host in hosts:
config_filename: str = f"{host}.conf"
config_file = os.path.join(full_path_to_config_dir, config_filename)
if not os.path.isfile(config_file):
config_body: str = template.replace(
"server_name _;", f"server_name {host}.server.com;"
)
async with aiofiles.open(config_file, mode="w") as file:
await file.write(config_body)
def _get_template(templ_file: str) -> str:
"""Возвращает шаблон конфига для хоста из файла `templ_file`."""
template: str = ""
with open(templ_file, mode="r", encoding="utf8") as file:
template = file.read()
return template
async def get_records_count(session, server) -> int:
"""Возвращает количество записей в базе."""
async with session.get(f"{server}/count") as resp:
resp = await resp.json()
count: int = int(resp["result"])
return count
async def get_tasks( async def get_records_count(rb: RequestBulder) -> int:
url: str, portion: int, count: int, session: aiohttp.ClientSession, body: dict resp: Json | bool = await rb.send_request("count", json_body={})
) -> List[Task]: records_count = 0
"""Вернет список задач с запросами к API. if resp:
records_count = resp.get("result")
Функция не ограничивает кол-во запросов, это нужно сделать до
вызова, чтобы передать корректный `session`.
Parameters
----------
url : str
Куда слать запрос. Ожидается "server/get"
portion : int
Сколько записей запрашивать за раз
count : int
Общее количество записей
session : aiohttp.ClientSession
Объект сессии, создается в уровне выше, с одним объектом
меньше накладных расходов на каждый запрос.
body : json
Json для запроса.
"""
tasks: List[Task] = []
for offset in range(0, count, portion):
tasks.append(asyncio.create_task(session.get(url, json=body)))
body["offset"] = offset
return tasks
async def send_async_request(cfg: AppConfig, json_body: dict) -> None:
"""Начать серию запросов."""
template: str = _get_template(cfg.template)
# ограничим одновременное число запросов
conn = aiohttp.TCPConnector(limit=cfg.requests_count)
url = cfg.central_host_url
portion = cfg.request_portion
try: return records_count
async with aiohttp.ClientSession(connector=conn) as session:
# получаем количесвто записей
count = await get_records_count(session, url)
tasks = await get_tasks(f"{url}/get", portion, count, session, json_body)
responses = await asyncio.gather(*tasks) async def custom_wrapper(
config_factory: ConfigFactory,
rb: RequestBulder,
usr: str,
json: Json,
) -> None:
resp: Json | bool = await rb.send_request("get", json)
# Пройдемся по ответам на запросы, запишем файлы конфига для # Если мы получили валидный ответ, то разбираем пачку хостов из
# каждого respone. Каждый response содержит portion или меньше хостов # resp['result'] с созданием для каждой строки кофнига через
for response in responses: # фабрику
resp = await response.json() if resp:
hosts = [i["hostname"] for i in resp.get("result")] conf_list = [config_factory.create(host["hostname"]) for host in resp["result"]]
await write_config_files(hosts, cfg, template) await asyncio.gather(*[c.write() for c in conf_list])
except ClientConnectorError: else:
print(f"Невозможно подключиться к серверу {url}") logger.error(f"Сервер вернул ошибку")
async def main() -> None: async def main(config_path: str) -> None:
"""Точка входа.""" """Точка входа."""
cfg: AppConfig = AppConfig("service.conf") cfg: AppConfig = AppConfig(config_path)
print(cfg.configs_path)
rb = RequestBulder(cfg) rb = RequestBulder(cfg)
resp = await rb.send_request("count", json_body={}) while True:
records_count = await get_records_count(rb)
if resp:
records_count = resp.get("result") json_boby_list = []
for offset in range(0, records_count, cfg.request_portion):
print(records_count) body_json = {
"columns": "['hostname']",
body_json = {"columns": "['hostname']", "limit": cfg.request_portion, "offset": 0} "limit": cfg.request_portion,
"offset": offset,
template = read_config_template_file(cfg.template) }
json_boby_list.append(body_json)
print(template)
# while True: conf_factory = ConfigFactory(cfg.template, cfg.path_for_config)
await asyncio.gather(
*[custom_wrapper(conf_factory, rb, "get", jb) for jb in json_boby_list]
)
# await send_async_request(cfg, json_body=body) await rb.wait()
# await asyncio.sleep(cfg.frequency_sec) # закрываем сессию
await rb.close()
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) parser = argparse.ArgumentParser(description="Service for nginx config creation.")
parser.add_argument(
"--config_path", required=True, type=str, help="path for conifg file"
)
args = parser.parse_args()
config_path = os.path.abspath(args.config_path)
asyncio.run(main(config_path))

Loading…
Cancel
Save