2022-11-16 06:27:50 +00:00
|
|
|
import asyncio
|
2019-10-10 04:47:21 +00:00
|
|
|
import os
|
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
import aiofiles
|
2024-03-18 10:53:22 +00:00
|
|
|
from asgiref.sync import sync_to_async
|
2022-11-16 06:27:50 +00:00
|
|
|
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
|
|
|
|
2021-11-18 10:47:01 +00:00
|
|
|
from common.db.utils import close_old_connections
|
2022-11-16 06:27:50 +00:00
|
|
|
from common.utils import get_logger
|
2020-12-29 13:05:18 +00:00
|
|
|
from .ansible.utils import get_ansible_task_log_path
|
2022-11-16 06:27:50 +00:00
|
|
|
from .celery.utils import get_celery_task_log_path
|
2023-01-16 11:02:09 +00:00
|
|
|
from .const import CELERY_LOG_MAGIC_MARK
|
2024-03-18 10:53:22 +00:00
|
|
|
from .models import CeleryTaskExecution
|
2019-09-26 11:22:17 +00:00
|
|
|
|
2019-10-21 11:28:33 +00:00
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
2019-09-26 11:22:17 +00:00
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
class TaskLogWebsocket(AsyncJsonWebsocketConsumer):
|
2019-09-26 11:22:17 +00:00
|
|
|
disconnected = False
|
2024-03-18 10:53:22 +00:00
|
|
|
user_tasks = (
|
|
|
|
'ops.tasks.run_ops_job',
|
|
|
|
'ops.tasks.run_ops_job_execution',
|
|
|
|
)
|
2019-09-26 11:22:17 +00:00
|
|
|
|
2020-12-29 13:05:18 +00:00
|
|
|
log_types = {
|
|
|
|
'celery': get_celery_task_log_path,
|
|
|
|
'ansible': get_ansible_task_log_path
|
|
|
|
}
|
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
async def connect(self):
|
2021-01-14 02:10:10 +00:00
|
|
|
user = self.scope["user"]
|
2021-01-19 06:36:41 +00:00
|
|
|
if user.is_authenticated:
|
2022-11-16 06:27:50 +00:00
|
|
|
await self.accept()
|
2021-01-14 02:10:10 +00:00
|
|
|
else:
|
2022-11-16 06:27:50 +00:00
|
|
|
await self.close()
|
2019-09-26 11:22:17 +00:00
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
def get_log_path(self, task_id, log_type):
|
|
|
|
func = self.log_types.get(log_type)
|
2020-12-29 13:05:18 +00:00
|
|
|
if func:
|
|
|
|
return func(task_id)
|
|
|
|
|
2024-03-18 10:53:22 +00:00
|
|
|
@sync_to_async
|
|
|
|
def get_task(self, task_id):
|
|
|
|
task = CeleryTaskExecution.objects.filter(id=task_id).first()
|
|
|
|
# task.creator 是 foreign key, 会异步去查询的,在下面的 if task.creator 会报错, 所以这里先取出来
|
|
|
|
if task and task.creator != ' ':
|
|
|
|
return task
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
async def receive_json(self, content, **kwargs):
|
|
|
|
task_id = content.get('task')
|
2024-03-18 10:53:22 +00:00
|
|
|
task = await self.get_task(task_id)
|
|
|
|
if not task:
|
|
|
|
await self.send_json({'message': 'Task not found', 'task': task_id})
|
|
|
|
return
|
|
|
|
if task.name in self.user_tasks and task.creator != self.scope['user']:
|
|
|
|
await self.send_json({'message': 'No permission', 'task': task_id})
|
|
|
|
return
|
|
|
|
|
|
|
|
task_type = content.get('type', 'celery')
|
|
|
|
log_path = self.get_log_path(task_id, task_type)
|
2022-11-16 06:27:50 +00:00
|
|
|
await self.async_handle_task(task_id, log_path)
|
2019-10-10 04:47:21 +00:00
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
async def async_handle_task(self, task_id, log_path):
|
|
|
|
logger.info("Task id: {}".format(task_id))
|
2024-04-11 11:06:02 +00:00
|
|
|
timeout = 0
|
2019-10-23 02:14:07 +00:00
|
|
|
while not self.disconnected:
|
2024-04-11 11:06:02 +00:00
|
|
|
if timeout >= 60:
|
|
|
|
await self.send_json({'message': '\r\n', 'task': task_id})
|
|
|
|
await self.send_json({'message': 'Task log was not found, the directory may not be shared.',
|
|
|
|
'task': task_id})
|
|
|
|
break
|
2019-10-23 02:14:07 +00:00
|
|
|
if not os.path.exists(log_path):
|
2022-11-16 06:27:50 +00:00
|
|
|
await self.send_json({'message': '.', 'task': task_id})
|
2024-04-11 11:06:02 +00:00
|
|
|
timeout += 0.5
|
2022-11-16 06:27:50 +00:00
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
else:
|
|
|
|
await self.send_task_log(task_id, log_path)
|
2019-10-23 02:14:07 +00:00
|
|
|
break
|
2019-09-26 11:22:17 +00:00
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
async def send_task_log(self, task_id, log_path):
|
|
|
|
await self.send_json({'message': '\r\n'})
|
|
|
|
try:
|
|
|
|
logger.debug('Task log path: {}'.format(log_path))
|
|
|
|
async with aiofiles.open(log_path, 'rb') as task_log_f:
|
|
|
|
while not self.disconnected:
|
|
|
|
data = await task_log_f.read(4096)
|
|
|
|
if data:
|
|
|
|
data = data.replace(b'\n', b'\r\n')
|
|
|
|
await self.send_json(
|
|
|
|
{'message': data.decode(errors='ignore'), 'task': task_id}
|
|
|
|
)
|
2023-01-16 11:02:09 +00:00
|
|
|
if data.find(CELERY_LOG_MAGIC_MARK) != -1:
|
|
|
|
await self.send_json(
|
|
|
|
{'event': 'end', 'task': task_id, 'message': ''}
|
|
|
|
)
|
|
|
|
logger.debug("Task log file magic mark found")
|
|
|
|
break
|
2022-11-16 06:27:50 +00:00
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
except OSError as e:
|
|
|
|
logger.warn('Task log path open failed: {}'.format(e))
|
2019-10-10 04:47:21 +00:00
|
|
|
|
2022-11-16 06:27:50 +00:00
|
|
|
async def disconnect(self, close_code):
|
2019-10-10 04:47:21 +00:00
|
|
|
self.disconnected = True
|
2021-11-18 10:47:01 +00:00
|
|
|
close_old_connections()
|