mirror of https://github.com/jumpserver/jumpserver
perf: ansible runner in isolated mode (#13434)
perf: use new ansible runner perf: change lock Co-authored-by: ibuler <ibuler@qq.com>pull/13474/head
parent
9be77cf58f
commit
165d030c8e
|
@ -94,6 +94,7 @@ ARG TOOLS=" \
|
||||||
sshpass \
|
sshpass \
|
||||||
telnet \
|
telnet \
|
||||||
vim \
|
vim \
|
||||||
|
bubblewrap \
|
||||||
wget"
|
wget"
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
|
|
|
@ -2,16 +2,18 @@ import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class DefaultCallback:
|
class DefaultCallback:
|
||||||
STATUS_MAPPER = {
|
STATUS_MAPPER = {
|
||||||
'successful': 'success',
|
"successful": "success",
|
||||||
'failure': 'failed',
|
"failure": "failed",
|
||||||
'failed': 'failed',
|
"failed": "failed",
|
||||||
'running': 'running',
|
"running": "running",
|
||||||
'pending': 'pending',
|
"pending": "pending",
|
||||||
'timeout': 'timeout',
|
"timeout": "timeout",
|
||||||
'unknown': 'unknown'
|
"unknown": "unknown",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -28,7 +30,7 @@ class DefaultCallback:
|
||||||
dark={},
|
dark={},
|
||||||
skipped=[],
|
skipped=[],
|
||||||
)
|
)
|
||||||
self.status = 'running'
|
self.status = "running"
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.local_pid = 0
|
self.local_pid = 0
|
||||||
self.private_data_dir = None
|
self.private_data_dir = None
|
||||||
|
@ -42,61 +44,63 @@ class DefaultCallback:
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def is_success(self):
|
def is_success(self):
|
||||||
return self.status != 'success'
|
return self.status != "success"
|
||||||
|
|
||||||
def event_handler(self, data, **kwargs):
|
def event_handler(self, data, **kwargs):
|
||||||
event = data.get('event', None)
|
event = data.get("event", None)
|
||||||
if not event:
|
if not event:
|
||||||
return
|
return
|
||||||
|
|
||||||
pid = data.get('pid', None)
|
pid = data.get("pid", None)
|
||||||
if pid:
|
if pid:
|
||||||
self.write_pid(pid)
|
self.write_pid(pid)
|
||||||
|
|
||||||
event_data = data.get('event_data', {})
|
event_data = data.get("event_data", {})
|
||||||
host = event_data.get('remote_addr', '')
|
host = event_data.get("remote_addr", "")
|
||||||
task = event_data.get('task', '')
|
task = event_data.get("task", "")
|
||||||
res = event_data.get('res', {})
|
res = event_data.get("res", {})
|
||||||
handler = getattr(self, event, self.on_any)
|
handler = getattr(self, event, self.on_any)
|
||||||
handler(event_data, host=host, task=task, res=res)
|
handler(event_data, host=host, task=task, res=res)
|
||||||
|
|
||||||
def runner_on_ok(self, event_data, host=None, task=None, res=None):
|
def runner_on_ok(self, event_data, host=None, task=None, res=None):
|
||||||
detail = {
|
detail = {
|
||||||
'action': event_data.get('task_action', ''),
|
"action": event_data.get("task_action", ""),
|
||||||
'res': res,
|
"res": res,
|
||||||
'rc': res.get('rc', 0),
|
"rc": res.get("rc", 0),
|
||||||
'stdout': res.get('stdout', ''),
|
"stdout": res.get("stdout", ""),
|
||||||
}
|
}
|
||||||
self.result['ok'][host][task] = detail
|
self.result["ok"][host][task] = detail
|
||||||
|
|
||||||
def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
|
def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
|
||||||
detail = {
|
detail = {
|
||||||
'action': event_data.get('task_action', ''),
|
"action": event_data.get("task_action", ""),
|
||||||
'res': {},
|
"res": {},
|
||||||
'rc': 0,
|
"rc": 0,
|
||||||
}
|
}
|
||||||
self.result['skipped'][host][task] = detail
|
self.result["skipped"][host][task] = detail
|
||||||
|
|
||||||
def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
|
def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
|
||||||
detail = {
|
detail = {
|
||||||
'action': event_data.get('task_action', ''),
|
"action": event_data.get("task_action", ""),
|
||||||
'res': res,
|
"res": res,
|
||||||
'rc': res.get('rc', 0),
|
"rc": res.get("rc", 0),
|
||||||
'stdout': res.get('stdout', ''),
|
"stdout": res.get("stdout", ""),
|
||||||
'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';')
|
"stderr": ";".join([res.get("stderr", ""), res.get("msg", "")]).strip(";"),
|
||||||
}
|
}
|
||||||
ignore_errors = event_data.get('ignore_errors', False)
|
ignore_errors = event_data.get("ignore_errors", False)
|
||||||
error_key = 'ignored' if ignore_errors else 'failures'
|
error_key = "ignored" if ignore_errors else "failures"
|
||||||
self.result[error_key][host][task] = detail
|
self.result[error_key][host][task] = detail
|
||||||
|
|
||||||
def runner_on_unreachable(self, event_data, host=None, task=None, res=None, **kwargs):
|
def runner_on_unreachable(
|
||||||
|
self, event_data, host=None, task=None, res=None, **kwargs
|
||||||
|
):
|
||||||
detail = {
|
detail = {
|
||||||
'action': event_data.get('task_action', ''),
|
"action": event_data.get("task_action", ""),
|
||||||
'res': res,
|
"res": res,
|
||||||
'rc': 255,
|
"rc": 255,
|
||||||
'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';')
|
"stderr": ";".join([res.get("stderr", ""), res.get("msg", "")]).strip(";"),
|
||||||
}
|
}
|
||||||
self.result['dark'][host][task] = detail
|
self.result["dark"][host][task] = detail
|
||||||
|
|
||||||
def runner_on_start(self, event_data, **kwargs):
|
def runner_on_start(self, event_data, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
@ -117,21 +121,26 @@ class DefaultCallback:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def playbook_on_stats(self, event_data, **kwargs):
|
def playbook_on_stats(self, event_data, **kwargs):
|
||||||
error_func = lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};"
|
error_func = (
|
||||||
for tp in ['dark', 'failures']:
|
lambda err, task_detail: err
|
||||||
|
+ f"{task_detail[0]}: {task_detail[1]['stderr']};"
|
||||||
|
)
|
||||||
|
for tp in ["dark", "failures"]:
|
||||||
for host, tasks in self.result[tp].items():
|
for host, tasks in self.result[tp].items():
|
||||||
error = reduce(error_func, tasks.items(), '').strip(';')
|
error = reduce(error_func, tasks.items(), "").strip(";")
|
||||||
self.summary[tp][host] = error
|
self.summary[tp][host] = error
|
||||||
failures = list(self.result['failures'].keys())
|
failures = list(self.result["failures"].keys())
|
||||||
dark_or_failures = list(self.result['dark'].keys()) + failures
|
dark_or_failures = list(self.result["dark"].keys()) + failures
|
||||||
|
|
||||||
for host, tasks in self.result.get('ignored', {}).items():
|
for host, tasks in self.result.get("ignored", {}).items():
|
||||||
ignore_errors = reduce(error_func, tasks.items(), '').strip(';')
|
ignore_errors = reduce(error_func, tasks.items(), "").strip(";")
|
||||||
if host in failures:
|
if host in failures:
|
||||||
self.summary['failures'][host] += ignore_errors
|
self.summary["failures"][host] += ignore_errors
|
||||||
|
|
||||||
self.summary['ok'] = list(set(self.result['ok'].keys()) - set(dark_or_failures))
|
self.summary["ok"] = list(set(self.result["ok"].keys()) - set(dark_or_failures))
|
||||||
self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(dark_or_failures))
|
self.summary["skipped"] = list(
|
||||||
|
set(self.result["skipped"].keys()) - set(dark_or_failures)
|
||||||
|
)
|
||||||
|
|
||||||
def playbook_on_include(self, event_data, **kwargs):
|
def playbook_on_include(self, event_data, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
@ -151,6 +160,13 @@ class DefaultCallback:
|
||||||
def playbook_on_no_hosts_remaining(self, event_data, **kwargs):
|
def playbook_on_no_hosts_remaining(self, event_data, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def playbook_on_start(self, event_data, **kwargs):
|
||||||
|
if settings.DEBUG_DEV:
|
||||||
|
print("DEBUG: delete inventory: ", os.path.join(self.private_data_dir, 'inventory'))
|
||||||
|
inventory_path = os.path.join(self.private_data_dir, 'inventory', 'hosts')
|
||||||
|
if os.path.exists(inventory_path):
|
||||||
|
os.remove(inventory_path)
|
||||||
|
|
||||||
def warning(self, event_data, **kwargs):
|
def warning(self, event_data, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -158,11 +174,11 @@ class DefaultCallback:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def status_handler(self, data, **kwargs):
|
def status_handler(self, data, **kwargs):
|
||||||
status = data.get('status', '')
|
status = data.get("status", "")
|
||||||
self.status = self.STATUS_MAPPER.get(status, 'unknown')
|
self.status = self.STATUS_MAPPER.get(status, "unknown")
|
||||||
self.private_data_dir = data.get("private_data_dir", None)
|
self.private_data_dir = data.get("private_data_dir", None)
|
||||||
|
|
||||||
def write_pid(self, pid):
|
def write_pid(self, pid):
|
||||||
pid_filepath = os.path.join(self.private_data_dir, 'local.pid')
|
pid_filepath = os.path.join(self.private_data_dir, "local.pid")
|
||||||
with open(pid_filepath, 'w') as f:
|
with open(pid_filepath, "w") as f:
|
||||||
f.write(str(pid))
|
f.write(str(pid))
|
||||||
|
|
|
@ -4,9 +4,10 @@ import uuid
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils._os import safe_join
|
from django.utils._os import safe_join
|
||||||
from .interface import interface
|
|
||||||
from .callback import DefaultCallback
|
from .callback import DefaultCallback
|
||||||
from .exception import CommandInBlackListException
|
from .exception import CommandInBlackListException
|
||||||
|
from .interface import interface
|
||||||
from ..utils import get_ansible_log_verbosity
|
from ..utils import get_ansible_log_verbosity
|
||||||
|
|
||||||
__all__ = ['AdHocRunner', 'PlaybookRunner', 'SuperPlaybookRunner', 'UploadFileRunner']
|
__all__ = ['AdHocRunner', 'PlaybookRunner', 'SuperPlaybookRunner', 'UploadFileRunner']
|
||||||
|
@ -44,7 +45,7 @@ class AdHocRunner:
|
||||||
|
|
||||||
def set_local_connection(self):
|
def set_local_connection(self):
|
||||||
if self.job_module in self.need_local_connection_modules_choices:
|
if self.job_module in self.need_local_connection_modules_choices:
|
||||||
self.envs.update({"LOCAL_CONNECTION_ENABLED": "1"})
|
self.envs.update({"ANSIBLE_SUPER_MODE": "1"})
|
||||||
|
|
||||||
def run(self, verbosity=0, **kwargs):
|
def run(self, verbosity=0, **kwargs):
|
||||||
self.check_module()
|
self.check_module()
|
||||||
|
@ -84,6 +85,7 @@ class PlaybookRunner:
|
||||||
if not callback:
|
if not callback:
|
||||||
callback = DefaultCallback()
|
callback = DefaultCallback()
|
||||||
self.cb = callback
|
self.cb = callback
|
||||||
|
self.isolate = True
|
||||||
self.envs = {}
|
self.envs = {}
|
||||||
|
|
||||||
def copy_playbook(self):
|
def copy_playbook(self):
|
||||||
|
@ -101,6 +103,11 @@ class PlaybookRunner:
|
||||||
if os.path.exists(private_env):
|
if os.path.exists(private_env):
|
||||||
shutil.rmtree(private_env)
|
shutil.rmtree(private_env)
|
||||||
|
|
||||||
|
kwargs = dict(kwargs)
|
||||||
|
if self.isolate:
|
||||||
|
kwargs['process_isolation'] = True
|
||||||
|
kwargs['process_isolation_executable'] = 'bwrap'
|
||||||
|
|
||||||
interface.run(
|
interface.run(
|
||||||
private_data_dir=self.project_dir,
|
private_data_dir=self.project_dir,
|
||||||
inventory=self.inventory,
|
inventory=self.inventory,
|
||||||
|
@ -118,7 +125,8 @@ class PlaybookRunner:
|
||||||
class SuperPlaybookRunner(PlaybookRunner):
|
class SuperPlaybookRunner(PlaybookRunner):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.envs = {"LOCAL_CONNECTION_ENABLED": "1"}
|
self.envs = {"ANSIBLE_SUPER_MODE": "1"}
|
||||||
|
self.isolate = False
|
||||||
|
|
||||||
|
|
||||||
class UploadFileRunner:
|
class UploadFileRunner:
|
||||||
|
|
|
@ -480,7 +480,7 @@ description = "Radically simple IT automation"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "v2.14.1.4.zip", hash = "sha256:1805e06391223ac6198229a18f501f0e001e66c3b334cb7c5061e0ac810297d6"},
|
{file = "v2.14.1.5.zip", hash = "sha256:3f05961f2902bc5228ebd44875aadc18cff02f3190791f214a40b2adb07735e1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -492,17 +492,16 @@ resolvelib = ">=0.5.3,<0.9.0"
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "url"
|
type = "url"
|
||||||
url = "https://github.com/jumpserver/ansible/archive/refs/tags/v2.14.1.4.zip"
|
url = "https://github.com/jumpserver/ansible/archive/refs/tags/v2.14.1.5.zip"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansible-runner"
|
name = "ansible-runner"
|
||||||
version = "2.3.3"
|
version = "2.4.0.2"
|
||||||
description = "\"Consistent Ansible Python API and CLI with container and process isolation runtime capabilities\""
|
description = "\"Consistent Ansible Python API and CLI with container and process isolation runtime capabilities\""
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "ansible-runner-2.3.3.tar.gz", hash = "sha256:38ff635e4b94791de2956c81e265836ec4965b30e9ee35d72fcf3271dc46b98b"},
|
{file = "2.4.0.2.zip", hash = "sha256:e8c995ab977a8cb354b64ac84c238699dbfcd28f7157bd169572a05d2edffe6c"},
|
||||||
{file = "ansible_runner-2.3.3-py3-none-any.whl", hash = "sha256:c57ae0d096760d66b2897b0f9009856c7b83fd5428dcb831f470cba348346396"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -510,12 +509,10 @@ packaging = "*"
|
||||||
pexpect = ">=4.5"
|
pexpect = ">=4.5"
|
||||||
python-daemon = "*"
|
python-daemon = "*"
|
||||||
pyyaml = "*"
|
pyyaml = "*"
|
||||||
six = "*"
|
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "legacy"
|
type = "url"
|
||||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
url = "https://github.com/jumpserver-dev/ansible-runner/archive/refs/tags/2.4.0.2.zip"
|
||||||
reference = "tsinghua"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -2877,6 +2874,7 @@ googleapis-common-protos = ">=1.56.2,<2.0.dev0"
|
||||||
grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
|
grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
|
||||||
grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
|
grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
|
||||||
proto-plus = ">=1.22.3,<2.0.0dev"
|
proto-plus = ">=1.22.3,<2.0.0dev"
|
||||||
|
|
||||||
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
|
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
|
||||||
requests = ">=2.18.0,<3.0.0.dev0"
|
requests = ">=2.18.0,<3.0.0.dev0"
|
||||||
|
|
||||||
|
@ -8023,3 +8021,4 @@ reference = "tsinghua"
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "de1b3ef35e2119f1ea8a2a2aee7a9586d055ed04837bb9f6f84083aa24c85b85"
|
content-hash = "de1b3ef35e2119f1ea8a2a2aee7a9586d055ed04837bb9f6f84083aa24c85b85"
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,9 @@ python = "^3.11"
|
||||||
cython = "3.0.0"
|
cython = "3.0.0"
|
||||||
aiofiles = "23.1.0"
|
aiofiles = "23.1.0"
|
||||||
amqp = "5.1.1"
|
amqp = "5.1.1"
|
||||||
ansible-core = { url = "https://github.com/jumpserver/ansible/archive/refs/tags/v2.14.1.4.zip" }
|
ansible-core = { url = "https://github.com/jumpserver-dev/ansible/archive/refs/tags/v2.14.1.6.zip" }
|
||||||
ansible = "7.1.0"
|
ansible = "7.1.0"
|
||||||
ansible-runner = "2.3.3"
|
ansible-runner = { url = "https://github.com/jumpserver-dev/ansible-runner/archive/refs/tags/2.4.0.1.zip" }
|
||||||
asn1crypto = "1.5.1"
|
asn1crypto = "1.5.1"
|
||||||
bcrypt = "4.0.1"
|
bcrypt = "4.0.1"
|
||||||
billiard = "4.1.0"
|
billiard = "4.1.0"
|
||||||
|
|
4
receptor
4
receptor
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from apps.libs.process.ssh import kill_ansible_ssh_process
|
from apps.libs.process.ssh import kill_ansible_ssh_process
|
||||||
|
|
Loading…
Reference in New Issue