diff --git a/deps/2_nginx/sysom.conf b/deps/2_nginx/sysom.conf index 8f2d30eb22f1f9633eaac636671cbc1c0e6ded9c..0af3f01c5ae3d2e23525271e8d7ae1d570e97418 100644 --- a/deps/2_nginx/sysom.conf +++ b/deps/2_nginx/sysom.conf @@ -180,6 +180,13 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + location /api/v1/configtrace/ { + proxy_pass http://127.0.0.1:7031; + proxy_read_timeout 180; + proxy_redirect off; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /api/ { proxy_pass http://127.0.0.1:7001; proxy_read_timeout 180s; diff --git a/docker/sysom_base_dockerfile b/docker/sysom_base_dockerfile index b75442ff694559e936c4c8b4cf9f37a3cd16d801..ec766ec0fde8e60ceb502c6bdf8b97e0266ec79a 100644 --- a/docker/sysom_base_dockerfile +++ b/docker/sysom_base_dockerfile @@ -42,7 +42,7 @@ COPY --from=web_builder /root/sysom_web/dist /usr/local/sysom/web RUN bash -x /root/sysom/script/sysom.sh install deps ALL RUN bash -x /root/sysom/script/sysom.sh install env ALL -RUN bash -x /root/sysom/script/sysom.sh install ms sysom_api,sysom_diagnosis,sysom_channel,sysom_monitor_server,sysom_log,sysom_alarm,sysom_cmg +RUN bash -x /root/sysom/script/sysom.sh install ms sysom_api,sysom_diagnosis,sysom_channel,sysom_monitor_server,sysom_log,sysom_alarm,sysom_cmg,sysom_configtrace RUN yum clean all diff --git a/docker/sysom_base_lite_dockerfile b/docker/sysom_base_lite_dockerfile index df9a713ca1ca91a89df9d7e4a45960319b56f1b1..742ae16f4c9dd7e58ff205a73f241190d29953c8 100644 --- a/docker/sysom_base_lite_dockerfile +++ b/docker/sysom_base_lite_dockerfile @@ -41,7 +41,7 @@ COPY --from=web_builder /root/sysom_web/dist /usr/local/sysom/web RUN bash -x /root/sysom/script/sysom.sh install deps nginx RUN bash -x /root/sysom/script/sysom.sh install env ALL -RUN bash -x /root/sysom/script/sysom.sh install ms sysom_api,sysom_diagnosis,sysom_channel,sysom_monitor_server,sysom_log,sysom_cmg +RUN bash -x /root/sysom/script/sysom.sh install ms sysom_api,sysom_diagnosis,sysom_channel,sysom_monitor_server,sysom_log,sysom_cmg,sysom_configtrace RUN yum clean all diff --git a/script/server/sysom_config_trace/clear.sh b/script/server/sysom_config_trace/clear.sh new file mode 100644 index 0000000000000000000000000000000000000000..f80ffae29d2e1a57796e498a17a3abf9c8f24eb7 --- /dev/null +++ b/script/server/sysom_config_trace/clear.sh @@ -0,0 +1,8 @@ +#!/bin/bash +SERVICE_NAME=sysom-configtrace +clear_app() { + rm -rf /etc/supervisord.d/${SERVICE_NAME}.ini + ###use supervisorctl update to stop and clear services### + supervisorctl update +} +clear_app diff --git a/script/server/sysom_config_trace/init.sh b/script/server/sysom_config_trace/init.sh new file mode 100644 index 0000000000000000000000000000000000000000..8f6efc0384ead3c4dba52dfa38b9599419bc137c --- /dev/null +++ b/script/server/sysom_config_trace/init.sh @@ -0,0 +1,40 @@ +#!/bin/bash +VIRTUALENV_HOME=$GLOBAL_VIRTUALENV_HOME +SERVICE_NAME=sysom-configtrace + +if [ "$UID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +source_virtualenv() { + echo "INFO: activate virtualenv..." + source ${VIRTUALENV_HOME}/bin/activate || exit 1 +} + +init_conf() { + cp ${SERVICE_NAME}.ini /etc/supervisord.d/ + ###change the install dir base on param $1### + sed -i "s;/usr/local/sysom;${APP_HOME};g" /etc/supervisord.d/${SERVICE_NAME}.ini +} + +start_app() { + ###if supervisor service started, we need use "supervisorctl update" to start new conf#### + supervisorctl update + supervisorctl status ${SERVICE_NAME} + if [ $? -eq 0 ] + then + echo "supervisorctl start ${SERVICE_NAME} success..." + return 0 + fi + echo "${SERVICE_NAME} service start fail, please check log" + exit 1 +} + +deploy() { + source_virtualenv + init_conf + start_app +} + +deploy diff --git a/script/server/sysom_config_trace/install.sh b/script/server/sysom_config_trace/install.sh new file mode 100644 index 0000000000000000000000000000000000000000..c6ca018afa028af2fd2a5a8fec6870b9b5ac745a --- /dev/null +++ b/script/server/sysom_config_trace/install.sh @@ -0,0 +1,29 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +SERVICE_SCRIPT_HOME=${MICROSERVICE_SCRIPT_HOME}/${SERVICE_SCRIPT_DIR} +VIRTUALENV_HOME=$GLOBAL_VIRTUALENV_HOME +SERVICE_NAME=sysom-configtrace + +if [ "$UID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +install_requirement() { + pushd ${SERVICE_SCRIPT_HOME} + pip install -r requirements.txt + popd +} + +source_virtualenv() { + echo "INFO: activate virtualenv..." + source ${VIRTUALENV_HOME}/bin/activate || exit 1 +} + +install_app() { + source_virtualenv + install_requirement +} + +install_app diff --git a/script/server/sysom_config_trace/requirements.txt b/script/server/sysom_config_trace/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b8b4af886ec020fd0bdd96f2c4f7b1b46660785 --- /dev/null +++ b/script/server/sysom_config_trace/requirements.txt @@ -0,0 +1,8 @@ +connexion >= 2.6.0 +connexion[swagger-ui] >= 2.6.0 +python_dateutil == 2.6.0 +setuptools >= 21.0.0 +swagger-ui-bundle >= 0.0.2 +clogger>=0.0.1 +bcc==0.1.10 +pytz \ No newline at end of file diff --git a/script/server/sysom_config_trace/start.sh b/script/server/sysom_config_trace/start.sh new file mode 100644 index 0000000000000000000000000000000000000000..553ed2553928aebd86e6b41be9a692547ee82c8e --- /dev/null +++ b/script/server/sysom_config_trace/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash +SERVICE_NAME=sysom-configtrace +start_app() { + supervisorctl start $SERVICE_NAME +} + +start_app diff --git a/script/server/sysom_config_trace/stop.sh b/script/server/sysom_config_trace/stop.sh new file mode 100644 index 0000000000000000000000000000000000000000..a12ba7c4d712d22ec0ed235489dc2ef1acfeb389 --- /dev/null +++ b/script/server/sysom_config_trace/stop.sh @@ -0,0 +1,7 @@ +#!/bin/bash +SERVICE_NAME=sysom-configtrace +stop_app() { + supervisorctl stop $SERVICE_NAME +} + +stop_app diff --git a/script/server/sysom_config_trace/sysom-configtrace.ini b/script/server/sysom_config_trace/sysom-configtrace.ini new file mode 100644 index 0000000000000000000000000000000000000000..b8ad6755ff97f046e5c3d89ae5ac3f82c6fd98f7 --- /dev/null +++ b/script/server/sysom_config_trace/sysom-configtrace.ini @@ -0,0 +1,9 @@ +[program:sysom-configtrace] +directory = /usr/local/sysom/server/sysom_config_trace +command=/usr/local/sysom/environment/virtualenv/bin/uvicorn config_trace.__main__:main --port 7031 +startsecs=3 +autostart=true +autorestart=true +environment=PATH=/usr/local/sysom/virtualenv/bin:%(ENV_PATH)s +stderr_logfile=/var/log/sysom/sysom-configtrace-error.log +stdout_logfile=/var/log/sysom/sysom-configtrace.log diff --git a/script/server/sysom_config_trace/test-requirements.txt b/script/server/sysom_config_trace/test-requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2640639a25a3bffe0f449788303300a364c55cb7 --- /dev/null +++ b/script/server/sysom_config_trace/test-requirements.txt @@ -0,0 +1,7 @@ +flask_testing==0.8.0 +coverage>=4.0.3 +nose>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 +tox==3.20.1 diff --git a/script/server/sysom_config_trace/uninstall.sh b/script/server/sysom_config_trace/uninstall.sh new file mode 100644 index 0000000000000000000000000000000000000000..0aac04c09286c7f24e98d8a20ce131cdbec36375 --- /dev/null +++ b/script/server/sysom_config_trace/uninstall.sh @@ -0,0 +1,9 @@ +#!/bin/bash +BaseDir=$(dirname $(readlink -f "$0")) + +uninstall_app() { + # do nothing + echo "" +} + +uninstall_app \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/README.md b/sysom_server/sysom_config_trace/README.md new file mode 100644 index 0000000000000000000000000000000000000000..263280ebbdad56e2def66b14b2429996a53e5937 --- /dev/null +++ b/sysom_server/sysom_config_trace/README.md @@ -0,0 +1,14 @@ +# configtrace设计及实现 + +## 设计 + +![UML时序图](docs/UML时序图.png) + +## 实现 + +### TODO + +1. 实现定时任务更新域配置 +2. 受控节点汇报ebpf文件变更信息到管控节点 +3. 写.changelog +4. 读.changelog \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config.yml b/sysom_server/sysom_config_trace/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..345cf1164131f3cbfbbe5468bb66cf781c45e10e --- /dev/null +++ b/sysom_server/sysom_config_trace/config.yml @@ -0,0 +1,57 @@ +vars: + SERVICE_NAME: &SERVICE_NAME sysom_config_trace + SERVICE_CONSUMER_GROUP: + !concat &SERVICE_CONSUMER_GROUP [*SERVICE_NAME, "_consumer_group"] + +sysom_server: + cec: + consumer_group: *SERVICE_CONSUMER_GROUP + channel_job: + target_topic: SYSOM_CEC_CHANNEL_TOPIC + listen_topic: SYSOM_CEC_CHANNEL_CONFIGTRACE_TOPIC + consumer_group: *SERVICE_CONSUMER_GROUP + +sysom_service: + service_name: *SERVICE_NAME + service_dir: *SERVICE_NAME + config_path: config_trace.conf + +# 节点测配置 +sysom_node: + version: 2.1 + # 节点分发配置 + delivery: + from_dir: scripts + to_dir: node + files: + comm: &code_delivery_files_comm + - local: node_init.sh + remote: + - local: node_clear.sh + remote: + - local: node_update.sh + remote: + - local: trace_file_change.py + remote: + el7: + amd64: *code_delivery_files_comm + x86_64: *code_delivery_files_comm + uelc20: + amd64: *code_delivery_files_comm + x86_64: *code_delivery_files_comm + arm64: *code_delivery_files_comm + aarch64: *code_delivery_files_comm + ky10sp1: + amd64: *code_delivery_files_comm + x86_64: *code_delivery_files_comm + arm64: *code_delivery_files_comm + aarch64: *code_delivery_files_comm + ky10sp2: + amd64: *code_delivery_files_comm + x86_64: *code_delivery_files_comm + arm64: *code_delivery_files_comm + aarch64: *code_delivery_files_comm + scripts: + init: node_init.sh + clear: node_clear.sh + update: node_update.sh diff --git a/sysom_server/sysom_config_trace/config_trace.conf b/sysom_server/sysom_config_trace/config_trace.conf new file mode 100644 index 0000000000000000000000000000000000000000..122c961e183739f3bbb9226bfeec7c205e054c45 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace.conf @@ -0,0 +1,4 @@ +[git] +git_dir = "/home/confTraceTest" +user_name = "user_name" +user_email = "user_email" \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/__init__.py b/sysom_server/sysom_config_trace/config_trace/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sysom_server/sysom_config_trace/config_trace/__main__.py b/sysom_server/sysom_config_trace/config_trace/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..92199b03f13fd01620abf0f3d90195b715934771 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/__main__.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import connexion +import os +import configparser +import ast +import sys +from channel_job import default_channel_job_executor +from config_trace.const.common import CHANNEL_JOB_URL, YAML_CONFIG, GCLIENT_PROTO, GCLIENT_HOST, GCLIENT_PORT +from sysom_utils import PluginEventExecutor +from config_trace.const.conf_handler_const import CONFIG +from config_trace.utils.prepare import Prepare +from config_trace.jobs.confs_job import start_job_schedule, stop_job_schedule +from gclient_base import dispatch_g_client + +def main(): + default_channel_job_executor.init_config(CHANNEL_JOB_URL, + g_client = dispatch_g_client(f"{GCLIENT_PROTO}://{GCLIENT_HOST}:{GCLIENT_PORT}")) + default_channel_job_executor.start() + PluginEventExecutor(YAML_CONFIG, default_channel_job_executor).start() + # prepare to load config + load_prepare() + app = connexion.App(__name__, specification_dir='./swagger/') + app.add_api('swagger.yaml', arguments={'title': 'Configration Tracability'}, pythonic_params=True) + app.run(host="0.0.0.0", port=int(sys.argv[3]) if len(sys.argv) > 3 else 8080) + + +def load_prepare(): + git_dir, git_user_name, git_user_email = load_conf() + prepare = Prepare(git_dir) + prepare.mkdir_git_warehose(git_user_name, git_user_email) + start_job_schedule() + + +def load_conf(): + cf = configparser.ConfigParser() + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + cf.read("../config_trace.conf", encoding="utf-8") + git_dir = ast.literal_eval(cf.get("git", "git_dir")) + git_user_name = ast.literal_eval(cf.get("git", "user_name")) + git_user_email = ast.literal_eval(cf.get("git", "user_email")) + return git_dir, git_user_name, git_user_email + + + +if __name__ == '__main__': + main() diff --git a/sysom_server/sysom_config_trace/config_trace/const/__init__.py b/sysom_server/sysom_config_trace/config_trace/const/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sysom_server/sysom_config_trace/config_trace/const/common.py b/sysom_server/sysom_config_trace/config_trace/const/common.py new file mode 100644 index 0000000000000000000000000000000000000000..ad5bd52471fbca19ef4744672f6a66e27ae77f68 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/const/common.py @@ -0,0 +1,66 @@ +from clogger import logger +import os +from pathlib import Path +from sysom_utils import ConfigParser, CecTarget, SysomFramework + +BASE_DIR = Path(__file__).resolve().parent.parent.parent + +################################################################## +# Load yaml config first +################################################################## +YAML_GLOBAL_CONFIG_PATH = f"{BASE_DIR.parent.parent}/conf/config.yml" + +if not os.path.exists(YAML_GLOBAL_CONFIG_PATH): + YAML_GLOBAL_CONFIG_PATH = f"/etc/sysom/config.yml" + +YAML_SERVICE_CONFIG_PATH = f"{BASE_DIR}/config.yml" + +YAML_CONFIG = ConfigParser(YAML_GLOBAL_CONFIG_PATH, YAML_SERVICE_CONFIG_PATH) +################################################################## +# Cec settings +################################################################## +SYSOM_CEC_PRODUCER_URL = YAML_CONFIG.get_cec_url(CecTarget.PRODUCER) +# 配置管理模块消费组 +SYSOM_CEC_CONFIGTRACE_CONSUMER_GROUP = "sysom_configtrace_consumer_group" +# 配置管理任务下发主题(由 View -> Executor) +SYSOM_CEC_CONFIGTRACE_TASK_DISPATCH_TOPIC = "SYSOM_CEC_CONFIGTRACE_TASK_DISPATCH_TOPIC" + +# channl_job SDK 需要的url +CHANNEL_JOB_URL = YAML_CONFIG.get_local_channel_job_url() + +################################################################## +# GClient Config +################################################################## +GCLIENT_PROTO = 'http' +GCLIENT_HOST = '127.0.0.1' +GCLIENT_PORT = '7003' + +########################################################################################## +# Web Config +########################################################################################## + +SysomFramework.init(YAML_CONFIG) + +DEBUG = True + +LANGUAGE_CODE = 'zh-hans' +TIME_ZONE = YAML_CONFIG.get_global_config().timezone +USE_TZ = True + + + +################################################################## +# Config settings +################################################################## +# Config log format +log_format = YAML_CONFIG.get_server_config().logger.format +log_level = YAML_CONFIG.get_server_config().logger.level +logger.set_format(log_format) +logger.set_level(log_level) + + +CONFIG_FILE_PATH = YAML_CONFIG.get_service_config().config_path +SERVICE_DIR = YAML_CONFIG.get_service_config().service_dir +SERVER_ROOT_PATH = YAML_CONFIG.get_server_config().path.root_path + +CONFIG_FILE_ABS_PATH = os.path.join(SERVER_ROOT_PATH, SERVICE_DIR, CONFIG_FILE_PATH) \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/const/conf_files.py b/sysom_server/sysom_config_trace/config_trace/const/conf_files.py new file mode 100644 index 0000000000000000000000000000000000000000..ad89b76e66c5525e580299861b341880623ce0cc --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/const/conf_files.py @@ -0,0 +1,27 @@ +# Author: Lay +# Description: default +# Date: 2023/6/7 9:06 + + +yang_conf_list = [ + "/etc/yum.repos.d/openEuler.repo", + "/etc/coremail/coremail.conf", + "/etc/ssh/sshd_config", + "/etc/ssh/ssh_config", + "/etc/sysctl.conf", + "/etc/ntp.conf", + "/etc/passwd", + "/etc/sudoers", + "/etc/shadow", + "/etc/group", + "/etc/hostname", + "/etc/fstab", + "/etc/ld.so.conf", + "/etc/security/limits.conf", + "/etc/resolv.conf", + "/etc/rc.local", + "/etc/bashrc", + "/etc/profile", + "/etc/hosts", + "/etc/pam.d" +] diff --git a/sysom_server/sysom_config_trace/config_trace/const/conf_handler_const.py b/sysom_server/sysom_config_trace/config_trace/const/conf_handler_const.py new file mode 100644 index 0000000000000000000000000000000000000000..efb1094e8c51406d6682c052c28a3ded818d6572 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/const/conf_handler_const.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# ****************************************************************************** +# Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved. +# licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# ******************************************************************************/ +""" +@FileName: conf_handler_const.py +@Time: 2023/7/26 15:24 +@Author: JiaoSiMao +Description: +""" +import re + +from config_trace.const.common import CONFIG_FILE_ABS_PATH + + +NOT_SYNCHRONIZE = "NOT SYNCHRONIZE" +SYNCHRONIZED = "SYNCHRONIZED" +LIMITS_DOMAIN_RE = re.compile('(^[*]$)|(^[@|0-9A-Za-z]+[0-9A-Za-z]$)') +LIMITS_TYPE_VALUE = "soft|hard|-|" +LIMITS_ITEM_VALUE = "core|data|fsize|memlock|nofile|rss|stack|cpu|nproc|as|maxlogins|maxsyslogins|priority|locks|sigpending|" \ + "msgqueue|nice|rtprio|" +RESOLV_KEY_VALUE = "nameserver|domain|search|sortlist|" +FSTAB_COLUMN_NUM = 6 +PAM_FILE_PATH = "/etc/pam.d" +DIRECTORY_FILE_PATH_LIST = ["/etc/pam.d"] +CONFIG = CONFIG_FILE_ABS_PATH \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/controllers/__init__.py b/sysom_server/sysom_config_trace/config_trace/controllers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sysom_server/sysom_config_trace/config_trace/controllers/authorization_controller.py b/sysom_server/sysom_config_trace/config_trace/controllers/authorization_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..2f7b0bb3e281b3fb9efb588dfc8ba4c8cc3a59ed --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/controllers/authorization_controller.py @@ -0,0 +1,6 @@ +from typing import List +""" +controller generated to handled auth operation described at: +https://connexion.readthedocs.io/en/latest/security.html +""" + diff --git a/sysom_server/sysom_config_trace/config_trace/controllers/confs_controller.py b/sysom_server/sysom_config_trace/config_trace/controllers/confs_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..084c7b33be2d4ac3539a3a3cd2bce25af3b1dcaf --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/controllers/confs_controller.py @@ -0,0 +1,356 @@ +import connexion +import six +import os +import datetime +from config_trace.models.confs import Confs # noqa: E501 +from config_trace.models.configration import Configration +from config_trace.utils.git_tools import GitTools +from config_trace.models.v1_confs_body import V1ConfsBody # noqa: E501 +from config_trace.models.v1_confs_body1 import V1ConfsBody1 # noqa: E501 +from config_trace import util +from clogger import logger +from config_trace.controllers.format import Format +from config_trace.const.conf_handler_const import DIRECTORY_FILE_PATH_LIST +from config_trace.utils.conf_tools import ConfTools +from config_trace.controllers.domain_controller import list_domain_hosts + +TARGETDIR = GitTools().target_dir + +def get_confs_by_domain(host_name=None, domain_name=None): # noqa: E501 + """list domain confs + + # noqa: E501 + + :param host_name: + :type host_name: str + :param domain_name: + :type domain_name: str + + :rtype: List[Confs] + """ + + if domain_name == None or domain_name == "": + return fetchAllConfs() + else: + return fetchConfsByDomain(domain_name, host_name) + +def fetchConfsByDomain(domain_name, host_name): + if not os.path.exists(os.path.join(TARGETDIR, domain_name)): + return "The current domain does not exist, please create the domain first.", 400 + domain_path = os.path.join(TARGETDIR, domain_name) + confs = Confs(domain_name=domain_name, conf_files=[]) + # Traverse all files in the source management repository + conf_tools = ConfTools() + for root, dirs, files in os.walk(domain_path): + # Domain also contains host cache files, so we need to add hierarchical judgment for root + if len(files) > 0: + for d_file in files: + if d_file == "hostRecord.txt" or d_file == "domaininfo.txt" or d_file == ".changelog": + continue + d_file_path = os.path.join(root, d_file) + git_message = GitTools(TARGETDIR).getLogMessageByPath(d_file_path) + with open(d_file_path, 'r') as f: + expect_content = f.read() + real_content = "" + conf_real_path = d_file_path.split(domain_path)[1] + if host_name != "" and host_name != None: + real_content_items = conf_tools.fetchRealConfs(host_name, conf_real_path) + if real_content_items == None or len(real_content_items) == 0: + continue + real_content = real_content_items[conf_real_path][0] + conf_base_info = Configration(file_path=conf_real_path,expect_content=expect_content,real_content=real_content,change_log=git_message) + confs.conf_files.append(conf_base_info) + + if not confs: + return "The current domain does not exist, please create the domain first.", 400 + + return confs.to_dict(), 200 + + +def fetchAllConfs(): + #conf_tools = ConfTools() + cmd = "/bin/ls {}".format(TARGETDIR) + git_tools = GitTools(TARGETDIR) + res_domain = git_tools.run_shell_return_output(cmd).decode().split() + + if len(res_domain) == 0: + return "The current domain does not exist, please create the domain first.", 400 + + confs = None + for d_domian in res_domain: + domain_path = os.path.join(TARGETDIR, d_domian) + confs = Confs(domain_name=d_domian, + conf_files=[]) + # Traverse all files in the source management repository + for root, dirs, files in os.walk(domain_path): + # Domain also contains host cache files, so we need to add hierarchical judgment for root + if len(files) > 0: + for d_file in files: + if d_file == "hostRecord.txt" or d_file == "domaininfo.txt" or d_file == ".changelog": + continue + d_file_path = os.path.join(root, d_file) + git_message = git_tools.getLogMessageByPath(d_file_path) + with open(d_file_path, 'r') as f: + expect_content = f.read() + # real_content = "" + conf_real_path = d_file_path.split(domain_path)[1] + # if host_name != "" and host_name != None: + # real_content_items = conf_tools.fetchRealConfs(host_name, conf_real_path) + # if real_content_items == None or len(real_content_items) == 0: + # continue + # real_content = real_content_items[conf_real_path][0] + conf_base_info = Configration(file_path=conf_real_path,expect_content=expect_content,change_log=git_message) + confs.conf_files.append(conf_base_info) + + if not confs: + return "The current domain does not exist, please create the domain first.", 400 + + return confs.to_dict(), 200 + + +def sync_confs(action, body=None): # noqa: E501 + """sync confs + + # noqa: E501 + + :param action: + :type action: str + :param body: + :type body: dict | bytes + + :rtype: None + """ + if action != "sync": + return "The current action is not supported.", 400 + + content = V1ConfsBody.from_dict(body) + + domain_name = content.domain_name + # check the input domain + checkRes = Format.domainCheck(domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + # TODO: 2023-03-07 + # 将期望配置更新到domain对应主机上 + hosts = content.hosts + if isEmpty(hosts): + # fetch domain managed hosts + listed_hosts, code = list_domain_hosts(domain_name) + if code / 100 > 2: + return listed_hosts, code + hosts = [h['host_id'] for h in listed_hosts] + conf_tools = ConfTools() + + # 获取当前domain的期望配置 + confs, code = get_confs_by_domain("", domain_name) + if code / 100 > 2: + return confs, code + # 向每台主机下发期望配置 + success = [] + failed = [] + parent_dir = '{}/{}'.format(TARGETDIR, domain_name) + for conf in confs['conf_files']: + ok, nok = conf_tools.sync_confs(hosts, conf, parent_dir) + if ok: + success.append(ok) + if nok: + failed.append(nok) + return { + "success": success, + "failed": failed + }, 202 + +def isEmpty(hosts): + if not hosts: + return True + if len(hosts) ==0: + return True + + for h in hosts: + if h is not "": + return False + return True + +def update_confs_by_domain(body=None, domain_name=None, host_name=None): # noqa: E501 + """update domain confs + + # noqa: E501 + + :param domain_name: + :type domain_name: str + :param host_name: + :type host_name: str + + :rtype: None + """ + content = V1ConfsBody1.from_dict(body) # noqa: E501 + + # check the input domain + checkRes = Format.domainCheck(domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + if len(content.confs) == 0: + return "The confs list is empty, please check the input parameters.", 400 + + # TODO: 如果用户没有传入host_name,需要从domain host中获取其中一个,然后执行后续操作 + return upload_or_add_confs(content, domain_name, host_name) + + +def upload_or_add_confs(body=None, domain_name=None, host_name=None): + confs = body.confs + success_confs = [] + for conf in confs: + if conf.expect_content is None: + # 使用add conf + real_conf = add_conf(conf, domain_name, host_name) + if real_conf is None: + continue + success_confs.append(real_conf) + else: + # 使用upload conf + real_conf = upload_conf(conf, domain_name, host_name) + success_confs.append(real_conf) + return V1ConfsBody1(confs=success_confs).to_dict(), 202 + + +def upload_conf(conf=None, domain_name=None, host_name=None): + conf_tools = ConfTools() + + # 获取真实配置并放入到git中管理 + real_config_file_content = conf.expect_content + + result_configuration = [] + + if len(real_config_file_content) == 0: + return None + # 放置到git管理中 + conf_tools.wirteFileInPath('{}/{}{}'.format(TARGETDIR, domain_name, conf.file_path), real_config_file_content) + result_configuration.append(Configration(conf.file_path, expect_content=real_config_file_content)) + # 返回confs + git_tools = GitTools(TARGETDIR) + commit_code = git_tools.gitCommit("Add the conf in {} domain, ".format(domain_name) + + "the path including : {}".format(conf.file_path)) + return Confs(domain_name=domain_name, conf_files=result_configuration) + + +def add_conf(conf=None, domain_name=None, host_name=None): + # 一种是从本地已经存储的文件里拉取,一种是去对比目标主机上拉取 + # 然后基于path返回expect和real,构成一个完整的 + + # TODO: 需要考虑用户指定输入配置与已经追踪配置的冲突处理问题 + + conf_tools = ConfTools() + + # 获取真实配置并放入到git中管理 + if host_name is not None: + real_config_file_content = conf_tools.fetchRealConfs(host_name, conf.file_path) + else: + return None + + result_configuration = [] + + if len(real_config_file_content) == 0: + return None + # 放置到git管理中 + succ_conf = "" + + for file_path, content in real_config_file_content.items(): + conf_tools.wirteFileInPath('{}/{}{}'.format(TARGETDIR, domain_name, file_path), content[0]) + result_configuration.append(Configration(file_path, expect_content=content[0])) + succ_conf = succ_conf + file_path + " " + # 返回confs + + git_tools = GitTools(TARGETDIR) + commit_code = git_tools.gitCommit("Add the conf in {} domain, ".format(domain_name) + + "the path including : {}".format(conf.file_path)) + + return Confs(domain_name=domain_name, conf_files=result_configuration) + + +def get_confs_alerts(action, domain_name=None): # noqa: E501 + """list confs alerts + + # noqa: E501 + + :param action: + :type action: str + :param domain_name: + :type domain_name: str + + :rtype: List[str] + """ + if action != 'alerts': + return 'Unknown Action', 400 + + # 读取domain下的.changelog文件内容,并返回 + if domain_name == None or domain_name == "": + return 'Unknown Domain', 400 + + cmd = "cat {}/.changelog".format(os.path.join(TARGETDIR, domain_name)) + git_tools = GitTools(TARGETDIR) + changelog_content = git_tools.run_shell_return_output(cmd).decode().strip() + result=[] + for line in changelog_content.split('\n'): + # format: parse bpftrace string to dict + res = parse_bpftrace_string(line) + if res is not None: + result.append(res) + return result, 200 + + +# format: 192.168.137.2,1715331497.896836,/etc/resolv.conf,vim,5,0 +def parse_bpftrace_string(input:str): + if input == None or input == "": + return None + content = input.split(",") + import pytz + system_timezone = pytz.timezone('Asia/Shanghai') + local_time = datetime.datetime.fromtimestamp(float(content[1]), tz=system_timezone) + return { + "ip": content[0], + "time": local_time.strftime('%Y-%m-%d %H:%M:%S.%f'), + "file": content[2], + "command": content[3] + } + +def delete_confs(domain_name=None, host_name=None, file_path=None): # noqa: E501 + """delete confs + + # noqa: E501 + + :param body: + :type body: dict | bytes + :param domain_name: + :type domain_name: str + :param host_name: + :type host_name: str + + :rtype: None + """ + + if domain_name == None or domain_name == "": + return 'Unknown Domain', 400 + + # 删除当前domain下的指定配置 + if not os.path.exists(os.path.join(TARGETDIR, domain_name)): + return "The current domain does not exist.", 400 + git_tools = GitTools(TARGETDIR) + domain_path = os.path.join(TARGETDIR, domain_name) + status = git_tools.run_shell_return_code("rm -r -f {}".format('{}{}'.format(domain_path, file_path))) + if status != 0: + return "Delete failed.", 400 + git_tools.gitCommit("Delete the conf in {} domain, ".format(domain_name) + + "the path including : {}".format(file_path)) + return 'Accepted', 202 \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/controllers/domain_controller.py b/sysom_server/sysom_config_trace/config_trace/controllers/domain_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..6822b6a2ae7fdd3bf798de03159abca1682b1143 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/controllers/domain_controller.py @@ -0,0 +1,352 @@ +import ast +import connexion +import six +import os +import json +import shutil +from config_trace.models.domain import Domain # noqa: E501 +from config_trace.models.host import Host # noqa: E501 +from config_trace import util +from clogger import logger +from config_trace.controllers.format import Format +from config_trace.utils.git_tools import GitTools +from config_trace.models.domain_body import DomainBody + +TARGETDIR = GitTools().target_dir + +def create_domain(domain_name, body=None): # noqa: E501 + """create domains + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + :param body: + :type body: dict | bytes + + :rtype: None + """ + if body is not None: + body = DomainBody.from_dict(body) # noqa: E501 + + if domain_name == "": + return "Missing Param", 400 + isExist = Format.isDomainExist(domain_name) + if isExist: + return "Duplicated DomainName", 400 + else: + domainPath = os.path.join(TARGETDIR, domain_name) + os.umask(0o077) + os.mkdir(domainPath) + domain = Domain(domain_name=domain_name, priority=body.priority, enable_trace=body.enable_trace) + with open(os.path.join(domainPath, "domaininfo.txt"), 'w') as f: + f.write(json.dumps(domain.to_dict())) + + return "Create", 201 + +def create_domain_host(domain_name, host_name): # noqa: E501 + """create domains + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + :param host_name: host name + :type host_name: str + + :rtype: None + """ + + checkRes = Format.domainCheck(domainName=domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domainName=domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + domainPath = os.path.join(TARGETDIR, domain_name) + + # Check whether the current host exists in the domain. + hostPath = os.path.join(domainPath, "hostRecord.txt") + if os.path.isfile(hostPath): + isContained = Format.isContainedHostIdInfile(hostPath, host_name) + if isContained: + return "The all host already exists in the administrative scope of the domain.", 400 + host = Host(host_id=host_name, ip=host_name, ipv6="") + Format.addHostToFile(hostPath, host) + + return "Add host successfully", 201 + +def delete_domain(domain_name): # noqa: E501 + """delete domain + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + + :rtype: None + """ + if domain_name == "": + return "Missing Param", 400 + isExist = Format.isDomainExist(domain_name) + if not isExist: + return "Invalid DomainName", 400 + else: + domainPath = os.path.join(TARGETDIR, domain_name) + shutil.rmtree(domainPath) + return 'Accepted', 202 + +def delete_domain_host(domain_name, host_name): # noqa: E501 + """delete domain + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + :param host_name: host name + :type host_name: str + + :rtype: None + """ + + # check the input domain + checkRes = Format.domainCheck(domainName=domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domainName=domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + # Whether the host information added within the current domain is empty while ain exists + domainPath = os.path.join(TARGETDIR, domain_name) + hostPath = os.path.join(domainPath, "hostRecord.txt") + if not os.path.isfile(hostPath) or (os.path.isfile(hostPath) and os.stat(hostPath).st_size == 0): + return "The host information is not set in the current domain. Please add the host information first", 400 + + # If the domain exists, check whether the current input parameter host belongs to the corresponding + # domain. If the host is in the domain, the host is deleted. If the host is no longer in the domain, + # the host is added to the failure range + os.umask(0o077) + + isContained = False + try: + with open(hostPath, 'r') as d_file: + lines = d_file.readlines() + with open(hostPath, 'w') as w_file: + for line in lines: + line_host_id = json.loads(str(ast.literal_eval(line)).replace("'", "\""))["host_id"] + if host_name != line_host_id: + w_file.write(line) + else: + isContained = True + except OSError as err: + logger.error("OS error: {0}".format(err)) + return "OS error: {0}".format(err), 500 + + if isContained: + git_tools = GitTools(TARGETDIR) + commit_code = git_tools.gitCommit("Delete the host in {} domian, ".format(domain_name) + + "the host including : {}".format(host_name)) + return "Delete the host in {} domian, ".format(domain_name), 202 + else: + return "The host is not in the domain", 400 + +def get_domain(domain_name): # noqa: E501 + """get specific domain + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + + :rtype: Domain + """ + if domain_name == "": + return "Param Error", 400 + + cmd = "/bin/ls {}".format(TARGETDIR) + gitTools = GitTools(TARGETDIR) + ls_res = gitTools.run_shell_return_output(cmd).decode() + ll_list = ls_res.split('\n') + for d_ll in ll_list: + if d_ll == domain_name: + domainPath = os.path.join(TARGETDIR, d_ll) + domainInfo = os.path.join(domainPath, "domaininfo.txt") + if os.path.isfile(domainInfo): + content = '' + with open(domainInfo, 'r') as d_file: + content = json.load(d_file) + domain = Domain(domain_name=content["domain_name"], priority=content["priority"], enable_trace=content["enable_trace"]) + return domain.to_dict(), 200 + return "Not Existed", 400 + +def list_domain_hosts(domain_name): # noqa: E501 + """list domain hosts + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + + :rtype: List[Host] + """ + # check the input domain + checkRes = Format.domainCheck(domainName=domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domainName=domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + # The domain exists, but the host information is empty + domainPath = os.path.join(TARGETDIR, domain_name) + hostPath = os.path.join(domainPath, "hostRecord.txt") + if not os.path.isfile(hostPath) or (os.path.isfile(hostPath) and os.stat(hostPath).st_size == 0): + return [], 200 + + # The domain exists, and the host information exists and is not empty + hostlist = [] + logger.debug("hostPath is : {}".format(hostPath)) + try: + with open(hostPath, 'r') as d_file: + for line in d_file.readlines(): + json_str = json.loads(line) + host_json = ast.literal_eval(json_str) + hostId = host_json["host_id"] + ip = host_json["ip"] + ipv6 = host_json["ipv6"] + host = Host(host_id=hostId, ip=ip, ipv6=ipv6) + hostlist.append(host.to_dict()) + except OSError as err: + logger.error("OS error: {0}".format(err)) + return "OS error: {0}".format(err), 500 + + # Joining together the returned codenum codeMessag + if len(hostlist) == 0: + return "Some unknown problems.", 500 + else: + logger.debug("hostlist is : {}".format(hostlist)) + return hostlist, 200 + +def list_domains(): # noqa: E501 + """list domains + + # noqa: E501 + + + :rtype: List[Domain] + """ + domain_list = [] + cmd = "/bin/ls {}".format(TARGETDIR) + gitTools = GitTools(TARGETDIR) + ls_res = gitTools.run_shell_return_output(cmd).decode() + ll_list = ls_res.split('\n') + for d_ll in ll_list: + if d_ll: + # fetch domian infos + domainPath = os.path.join(TARGETDIR, d_ll) + domainInfo = os.path.join(domainPath, "domaininfo.txt") + if os.path.isfile(domainInfo): + content = '' + with open(domainInfo, 'r') as d_file: + content = json.load(d_file) + domain = Domain(domain_name=content["domain_name"], priority=content["priority"], enable_trace=content["enable_trace"]) + domain_list.append(domain.to_dict()) + + return domain_list, 200 + +def get_domain_host(domain_name, host_name): # noqa: E501 + """get specific domain + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + :param host_name: host name + :type host_name: str + + :rtype: Host + """ + # check the input domain + checkRes = Format.domainCheck(domainName=domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domainName=domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + # The domain exists, but the host information is empty + domainPath = os.path.join(TARGETDIR, domain_name) + hostPath = os.path.join(domainPath, "hostRecord.txt") + if not os.path.isfile(hostPath) or (os.path.isfile(hostPath) and os.stat(hostPath).st_size == 0): + return [], 200 + + # The domain exists, and the host information exists and is not empty + logger.debug("hostPath is : {}".format(hostPath)) + host = None + try: + with open(hostPath, 'r') as d_file: + for line in d_file.readlines(): + host_json = json.loads(str(ast.literal_eval(line)).replace("'", "\"")) + name = host_json["host_id"] + if name == host_name: + ip = host_json["ip"] + ipv6 = host_json["ipv6"] + host = Host(host_id=name, ip=ip, ipv6=ipv6) + except OSError as err: + logger.error("OS error: {0}".format(err)) + return "OS error: {0}".format(err), 500 + + # Joining together the returned codenum codeMessag + if not host: + return "Some unknown problems.", 500 + else: + logger.debug("host is : {}".format(host)) + return host.to_dict(), 200 + +def update_domain(domain_name, body=None): # noqa: E501 + """update domains + + # noqa: E501 + + :param domain_name: domain name + :type domain_name: str + :param body: + :type body: dict | bytes + + :rtype: None + """ + if body is not None: + body = DomainBody.from_dict(body) # noqa: E501 + + # check the input domain + checkRes = Format.domainCheck(domainName=domain_name) + if not checkRes: + return "Failed to verify the input parameter, please check the input parameters.", 400 + + # check whether the domain exists + isExist = Format.isDomainExist(domainName=domain_name) + if not isExist: + return "The current domain does not exist, please create the domain first.", 400 + + domainPath = os.path.join(TARGETDIR, domain_name) + + data = Domain(domain_name=domain_name, priority=body.priority, enable_trace=body.enable_trace) + with open(os.path.join(domainPath, "domaininfo.txt"), 'w') as f: + f.write(json.dumps(data.to_dict())) + + ## TODO: 根据enable_trace进行配置下发更新工作,对整个domain管理的所有主机生效 + if body.enable_trace: + from config_trace.jobs.confs_job import refresh_config_trace_conf_files + return refresh_config_trace_conf_files(domain_name) + + return 'Accepted', 202 \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/controllers/format.py b/sysom_server/sysom_config_trace/config_trace/controllers/format.py new file mode 100644 index 0000000000000000000000000000000000000000..9fbbb81de2d0b3d1acd1322c5a9b85ffddea3006 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/controllers/format.py @@ -0,0 +1,256 @@ +import os +import re +import json +import configparser +import ast +import requests +from clogger import logger +from config_trace.const.conf_handler_const import NOT_SYNCHRONIZE, SYNCHRONIZED, CONFIG, \ + DIRECTORY_FILE_PATH_LIST +from config_trace.models.configration import Configration +from config_trace.models.host import Host # noqa: E501 +from config_trace.utils.host_tools import HostTools + + +class Format(object): + + @staticmethod + def domainCheck(domainName): + res = True + if not re.match(r"^[A-Za-z0-9_\.-]*$", domainName) or domainName == "" or len(domainName) > 255: + res = False + return res + + @staticmethod + def isDomainExist(domainName): + TARGETDIR = Format.get_git_dir() + domainPath = os.path.join(TARGETDIR, domainName) + if os.path.exists(domainPath): + return True + + return False + + @staticmethod + def spliceAllSuccString(obj, operation, succDomain): + """ + docstring + """ + codeString = "All {obj} {oper} successfully, {succ} {obj} in total.".format( \ + obj=obj, oper=operation, succ=len(succDomain)) + return codeString + + @staticmethod + def splicErrorString(obj, operation, succDomain, failDomain): + """ + docstring + """ + codeString = "{succ} {obj} {oper} successfully, {fail} {obj} {oper} failed.".format( \ + succ=len(succDomain), obj=obj, oper=operation, fail=len(failDomain)) + + succString = "\n" + if len(succDomain) > 0: + succString = "These are successful: " + for succName in succDomain: + succString += succName + " " + succString += "." + + if len(failDomain) > 0: + failString = "These are failed: " + for failName in failDomain: + failString += failName + " " + return codeString + succString + failString + + return codeString + succString + + @staticmethod + def two_abs_join(abs1, abs2): + """ + Absolute path Joins two absolute paths together + :param abs1: main path + :param abs2: the spliced path + :return: together the path + """ + # 1. Format path (change \\ in path to \) + abs2 = os.fspath(abs2) + + # 2. Split the path file + abs2 = os.path.splitdrive(abs2)[1] + # 3. Remove the beginning '/' + abs2 = abs2.strip('\\/') or abs2 + return os.path.abspath(os.path.join(abs1, abs2)) + + @staticmethod + def isContainedHostIdInfile(f_file, content): + isContained = False + with open(f_file, 'r') as d_file: + for line in d_file.readlines(): + line_dict = json.loads(str(ast.literal_eval(line)).replace("'", "\"")) + if content == line_dict["host_id"]: + isContained = True + break + return isContained + + @staticmethod + def addHostToFile(d_file, host): + info_json = json.dumps(str(host), sort_keys=False, indent=4, separators=(',', ': ')) + os.umask(0o077) + with open(d_file, 'a+') as host_file: + host_file.write(info_json) + host_file.write("\n") + + @staticmethod + def getSubDirFiles(path): + """ + desc: Subdirectory records and files need to be logged to the successConf + """ + fileRealPathList = [] + fileXPathlist = [] + for root, dirs, files in os.walk(path): + if len(files) > 0: + preXpath = root.split('/', 3)[3] + for d_file in files: + xpath = os.path.join(preXpath, d_file) + fileXPathlist.append(xpath) + realPath = os.path.join(root, d_file) + fileRealPathList.append(realPath) + + return fileRealPathList, fileXPathlist + + @staticmethod + def isHostInDomain(domainName): + """ + desc: Query domain Whether host information is configured in the domain + """ + isHostInDomain = False + TARGETDIR = Format.get_git_dir() + domainPath = os.path.join(TARGETDIR, domainName) + hostPath = os.path.join(domainPath, "hostRecord.txt") + if os.path.isfile(hostPath): + isHostInDomain = True + + return isHostInDomain + + @staticmethod + def isHostIdExist(hostPath, hostId): + """ + desc: Query hostId exists within the current host domain management + """ + isHostIdExist = False + if os.path.isfile(hostPath) and os.stat(hostPath).st_size > 0: + with open(hostPath) as h_file: + for line in h_file.readlines(): + if hostId in line: + isHostIdExist = True + break + + return isHostIdExist + + @staticmethod + def get_file_content_by_readlines(d_file): + """ + desc: remove empty lines and comments from d_file + """ + res = [] + try: + with open(d_file, 'r') as s_f: + lines = s_f.readlines() + for line in lines: + tmp = line.strip() + if not len(tmp) or tmp.startswith("#"): + continue + res.append(line) + except FileNotFoundError: + logger.error(f"File not found: {d_file}") + except IOError as e: + logger.error(f"IO error: {e}") + except Exception as e: + logger.error(f"An error occurred: {e}") + return res + + @staticmethod + def get_file_content_by_read(d_file): + """ + desc: return a string after read the d_file + """ + if not os.path.exists(d_file): + return "" + with open(d_file, 'r') as s_f: + lines = s_f.read() + return lines + + @staticmethod + def rsplit(_str, seps): + """ + Splits _str by the first sep in seps that is found from the right side. + Returns a tuple without the separator. + """ + for idx, ch in enumerate(reversed(_str)): + if ch in seps: + return _str[0:-idx - 1], _str[-idx:] + + @staticmethod + def arch_sep(package_string): + """ + Helper method for finding if arch separator is '.' or '-' + + Args: + package_string (str): dash separated package string such as 'bash-4.2.39-3.el7'. + + Returns: + str: arch separator + """ + return '.' if package_string.rfind('.') > package_string.rfind('-') else '-' + + @staticmethod + def set_file_content_by_path(content, path): + res = 0 + if os.path.exists(path): + with open(path, 'w+') as d_file: + for d_cont in content: + d_file.write(d_cont) + d_file.write("\n") + res = 1 + return res + + @staticmethod + def get_git_dir(): + cf = configparser.ConfigParser() + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + parent = os.path.dirname(os.path.realpath(__file__)) + conf_path = os.path.join(parent, "../../config_trace.conf") + cf.read(conf_path, encoding="utf-8") + git_dir = ast.literal_eval(cf.get("git", "git_dir")) + return git_dir + + @staticmethod + def get_hostinfo_by_domain(domainName): + """ + desc: Query hostinfo by domainname + """ + logger.debug("Get hostinfo by domain : {}".format(domainName)) + TARGETDIR = Format.get_git_dir() + hostlist = [] + domainPath = os.path.join(TARGETDIR, domainName) + hostPath = os.path.join(domainPath, "hostRecord.txt") + if not os.path.isfile(hostPath) or os.stat(hostPath).st_size == 0: + return hostlist + try: + with open(hostPath, 'r') as d_file: + for line in d_file.readlines(): + json_str = json.loads(line) + host_json = ast.literal_eval(json_str) + hostId = host_json["host_id"] + ip = host_json["ip"] + ipv6 = host_json["ipv6"] + host = Host(host_id=hostId, ip=ip, ipv6=ipv6) + hostlist.append(host.to_dict()) + except OSError as err: + logger.error("OS error: {0}".format(err)) + return hostlist + if len(hostlist) == 0: + logger.debug("Hostlist is empty !") + else: + logger.debug("Hostlist is : {}".format(hostlist)) + return hostlist \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/jobs/confs_job.py b/sysom_server/sysom_config_trace/config_trace/jobs/confs_job.py new file mode 100644 index 0000000000000000000000000000000000000000..e3359f014c196ad89abe1f673e848c210fc2e8ab --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/jobs/confs_job.py @@ -0,0 +1,135 @@ +# coding=utf-8 +from config_trace.models.domain import Domain # noqa: E501 +from clogger import logger +import os +import schedule +import threading +import time +from config_trace.utils.git_tools import GitTools + +TARGETDIR = GitTools().target_dir +# TODO: execute scheduled jobs + +def refresh_config_trace_conf_files(domain_name): + # 根据domaain管理的配置列表刷新主机配置列表 + from config_trace.controllers.confs_controller import get_confs_by_domain + confs, code = get_confs_by_domain(domain_name=domain_name) + if code / 100 > 2: + return confs, code + + managedConfFileList = [] + if len(confs['conf_files']) > 0: + for conf in confs["conf_files"]: + managedConfFileList.append(conf["file_path"]) + + conflists = "[{}]\n".format(domain_name) + conflists += "\n".join(managedConfFileList) + + from config_trace.controllers.domain_controller import list_domain_hosts + managedHosts, code = list_domain_hosts(domain_name) + + from channel_job import default_channel_job_executor + for host_name in managedHosts: + job = default_channel_job_executor.dispatch_job( + channel_type="ssh", + channel_opt="cmd", + params={ + "instance": host_name['ip'], + # TODO: 优化写出路径问题 + "command": "echo '{}' > {}; if diff -q '{}' '{}' >/dev/null; then break; else systemctl restart sysom_configtrace_agent; mv {} {}; fi" + .format(conflists, "/var/run/configtrace/configlist.txt.tmp", "/var/run/configtrace/configlist.txt", "/var/run/configtrace/configlist.txt.tmp", "/var/run/configtrace/configlist.txt.tmp", "/var/run/configtrace/configlist.txt") + }, + timeout=10000, + auto_retry=False + ) + channel_result = job.execute() + if channel_result.code != 0: + return 'Failed', 400 + + return 'Accepted', 202 + + +def fetch_config_trace_changes(domain_name): + from config_trace.controllers.domain_controller import list_domain_hosts + managedHosts, code = list_domain_hosts(domain_name) + if code / 100 > 2: + return managedHosts, code + + alerts = [] + from channel_job import default_channel_job_executor + for host_name in managedHosts: + job = default_channel_job_executor.dispatch_job( + channel_type="ssh", + channel_opt="cmd", + params={ + "instance": host_name['ip'], + "command": "cat /var/run/configtrace/alert.log", + }, + timeout=10000, + auto_retry=False + ) + channel_result = job.execute() + if channel_result.code != 0: + return 'Failed', 400 + for line in channel_result.result.splitlines(): + alerts.append("{},{}".format(host_name['ip'], line)) + + # write to .changelog + + cmd = 'echo "{}" > {}/.changelog'.format('\n'.join(alerts), os.path.join(TARGETDIR, domain_name)) + gitTools = GitTools(TARGETDIR) + gitTools.run_shell_return_output(cmd) + return alerts, 202 + + +def refresh_config_trace_confs(): + logger.info("refresh confs") + from config_trace.controllers.domain_controller import list_domains + domains, code = list_domains() + logger.error("job: list domains failed") + if code / 100 > 2: + return + for d in domains: + domain = Domain(domain_name=d["domain_name"], priority=d["priority"], enable_trace=d["enable_trace"]) + if not domain.enable_trace: + continue + result, code = refresh_config_trace_conf_files(domain.domain_name) + if code / 100 > 2: + logger.error("refresh config trace conf files failed") + logger.info("run finished") + +def fetch_config_trace_changelists(): + logger.info("fetching changelist") + from config_trace.controllers.domain_controller import list_domains + domains, code = list_domains() + if code / 100 > 2: + logger.error("job: list domains failed") + return + + for d in domains: + domain = Domain(domain_name=d["domain_name"], priority=d["priority"], enable_trace=d["enable_trace"]) + if not domain.enable_trace: + continue + try: + result, code = fetch_config_trace_changes(domain.domain_name) + if code / 100 > 2: + logger.error("fetch config trace changes failed") + except Exception as e: + logger.error(e) + +def start_job_schedule(interval=5): + t = threading.Thread(target=start, args=[interval]) + t.start() + +def start(interval): + schedule.every(interval * 3).seconds.do(refresh_config_trace_confs).tag('confs') + schedule.every(interval * 3).seconds.do(fetch_config_trace_changelists).tag('changelists') + + while True: + schedule.run_pending() + time.sleep(1) + +def stop_job_schedule(): + logger.info("stop job schedule") + schedule.clear('confs') + schedule.clear('changelists') diff --git a/sysom_server/sysom_config_trace/config_trace/models/__init__.py b/sysom_server/sysom_config_trace/config_trace/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..22d061ceba9b2115de55c8d9c724a2eebaf55135 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/__init__.py @@ -0,0 +1,10 @@ +# coding: utf-8 + +# flake8: noqa +from __future__ import absolute_import +# import models into model package +from config_trace.models.configration import Configration +from config_trace.models.confs import Confs +from config_trace.models.domain import Domain +from config_trace.models.host import Host +from config_trace.models.v1_confs_body import V1ConfsBody diff --git a/sysom_server/sysom_config_trace/config_trace/models/base_model_.py b/sysom_server/sysom_config_trace/config_trace/models/base_model_.py new file mode 100644 index 0000000000000000000000000000000000000000..86204f1dc7fc567609f94bd00e4c41397cf0fb87 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/base_model_.py @@ -0,0 +1,69 @@ +import pprint + +import six +import typing + +from config_trace import util + +T = typing.TypeVar('T') + + +class Model(object): + # swaggerTypes: The key is attribute name and the + # value is attribute type. + swagger_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls: typing.Type[T], dikt) -> T: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/sysom_server/sysom_config_trace/config_trace/models/configration.py b/sysom_server/sysom_config_trace/config_trace/models/configration.py new file mode 100644 index 0000000000000000000000000000000000000000..0a9fb4b2e51b47a1e5b2626ca0558faaefe39633 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/configration.py @@ -0,0 +1,148 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace import util + + +class Configration(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, file_path: str=None, real_content: str=None, expect_content: str=None, change_log: str=None): # noqa: E501 + """Configration - a model defined in Swagger + + :param file_path: The file_path of this Configration. # noqa: E501 + :type file_path: str + :param real_content: The real_content of this Configration. # noqa: E501 + :type real_content: str + :param expect_content: The expect_content of this Configration. # noqa: E501 + :type expect_content: str + :param change_log: The change_log of this Configration. # noqa: E501 + :type change_log: str + """ + self.swagger_types = { + 'file_path': str, + 'real_content': str, + 'expect_content': str, + 'change_log': str + } + + self.attribute_map = { + 'file_path': 'filePath', + 'real_content': 'realContent', + 'expect_content': 'expectContent', + 'change_log': 'changeLog' + } + self._file_path = file_path + self._real_content = real_content + self._expect_content = expect_content + self._change_log = change_log + + @classmethod + def from_dict(cls, dikt) -> 'Configration': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The Configration of this Configration. # noqa: E501 + :rtype: Configration + """ + return util.deserialize_model(dikt, cls) + + @property + def file_path(self) -> str: + """Gets the file_path of this Configration. + + config file path # noqa: E501 + + :return: The file_path of this Configration. + :rtype: str + """ + return self._file_path + + @file_path.setter + def file_path(self, file_path: str): + """Sets the file_path of this Configration. + + config file path # noqa: E501 + + :param file_path: The file_path of this Configration. + :type file_path: str + """ + + self._file_path = file_path + + @property + def real_content(self) -> str: + """Gets the real_content of this Configration. + + the content of configuration file # noqa: E501 + + :return: The real_content of this Configration. + :rtype: str + """ + return self._real_content + + @real_content.setter + def real_content(self, real_content: str): + """Sets the real_content of this Configration. + + the content of configuration file # noqa: E501 + + :param real_content: The real_content of this Configration. + :type real_content: str + """ + + self._real_content = real_content + + @property + def expect_content(self) -> str: + """Gets the expect_content of this Configration. + + the expect content of configuration file # noqa: E501 + + :return: The expect_content of this Configration. + :rtype: str + """ + return self._expect_content + + @expect_content.setter + def expect_content(self, expect_content: str): + """Sets the change_log of this Configration. + + the expect content of configuration file # noqa: E501 + + :param change_log: The change_log of this Configration. + :type change_log: str + """ + + self._expect_content = expect_content + + @property + def change_log(self) -> str: + """Gets the change_log of this Configration. + + the expect content of configuration file # noqa: E501 + + :return: The change_log of this Configration. + :rtype: str + """ + return self._change_log + + @change_log.setter + def change_log(self, change_log: str): + """Sets the change_log of this Configration. + + the expect content of configuration file # noqa: E501 + + :param change_log: The change_log of this Configration. + :type change_log: str + """ + + self._change_log = change_log diff --git a/sysom_server/sysom_config_trace/config_trace/models/confs.py b/sysom_server/sysom_config_trace/config_trace/models/confs.py new file mode 100644 index 0000000000000000000000000000000000000000..899903f8540bd82c6cd013924a85c162add13f52 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/confs.py @@ -0,0 +1,89 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace.models.configration import Configration # noqa: F401,E501 +from config_trace import util + + +class Confs(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, domain_name: str=None, conf_files: List[Configration]=None): # noqa: E501 + """Confs - a model defined in Swagger + + :param domain_name: The domain_name of this Confs. # noqa: E501 + :type domain_name: str + :param conf_files: The conf_files of this Confs. # noqa: E501 + :type conf_files: List[Configration] + """ + self.swagger_types = { + 'domain_name': str, + 'conf_files': List[Configration] + } + + self.attribute_map = { + 'domain_name': 'domainName', + 'conf_files': 'confFiles' + } + self._domain_name = domain_name + self._conf_files = conf_files + + @classmethod + def from_dict(cls, dikt) -> 'Confs': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The Confs of this Confs. # noqa: E501 + :rtype: Confs + """ + return util.deserialize_model(dikt, cls) + + @property + def domain_name(self) -> str: + """Gets the domain_name of this Confs. + + + :return: The domain_name of this Confs. + :rtype: str + """ + return self._domain_name + + @domain_name.setter + def domain_name(self, domain_name: str): + """Sets the domain_name of this Confs. + + + :param domain_name: The domain_name of this Confs. + :type domain_name: str + """ + + self._domain_name = domain_name + + @property + def conf_files(self) -> List[Configration]: + """Gets the conf_files of this Confs. + + + :return: The conf_files of this Confs. + :rtype: List[Configration] + """ + return self._conf_files + + @conf_files.setter + def conf_files(self, conf_files: List[Configration]): + """Sets the conf_files of this Confs. + + + :param conf_files: The conf_files of this Confs. + :type conf_files: List[Configration] + """ + + self._conf_files = conf_files diff --git a/sysom_server/sysom_config_trace/config_trace/models/domain.py b/sysom_server/sysom_config_trace/config_trace/models/domain.py new file mode 100644 index 0000000000000000000000000000000000000000..c82b9455493b69007295cea0e8f00c193c3a3959 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/domain.py @@ -0,0 +1,120 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace import util + + +class Domain(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, domain_name: str=None, priority: int=None, enable_trace: bool=None): # noqa: E501 + """Domain - a model defined in Swagger + + :param domain_name: The domain_name of this Domain. # noqa: E501 + :type domain_name: str + :param priority: The priority of this Domain. # noqa: E501 + :type priority: int + :param enable_trace: The enable_trace of this Domain. # noqa: E501 + :type enable_trace: bool + """ + self.swagger_types = { + 'domain_name': str, + 'priority': int, + 'enable_trace': bool + } + + self.attribute_map = { + 'domain_name': 'domainName', + 'priority': 'priority', + 'enable_trace': 'enable_trace' + } + self._domain_name = domain_name + self._priority = priority + self._enable_trace = enable_trace + + @classmethod + def from_dict(cls, dikt) -> 'Domain': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The Domain of this Domain. # noqa: E501 + :rtype: Domain + """ + return util.deserialize_model(dikt, cls) + + @property + def domain_name(self) -> str: + """Gets the domain_name of this Domain. + + domain name # noqa: E501 + + :return: The domain_name of this Domain. + :rtype: str + """ + return self._domain_name + + @domain_name.setter + def domain_name(self, domain_name: str): + """Sets the domain_name of this Domain. + + domain name # noqa: E501 + + :param domain_name: The domain_name of this Domain. + :type domain_name: str + """ + + self._domain_name = domain_name + + @property + def priority(self) -> int: + """Gets the priority of this Domain. + + Priority of the current domain # noqa: E501 + + :return: The priority of this Domain. + :rtype: int + """ + return self._priority + + @priority.setter + def priority(self, priority: int): + """Sets the priority of this Domain. + + Priority of the current domain # noqa: E501 + + :param priority: The priority of this Domain. + :type priority: int + """ + + self._priority = priority + + @property + def enable_trace(self) -> bool: + """Gets the enable_trace of this Domain. + + If enable object trace or not for current domain # noqa: E501 + + :return: The enable_trace of this Domain. + :rtype: bool + """ + return self._enable_trace + + @enable_trace.setter + def enable_trace(self, enable_trace: bool): + """Sets the enable_trace of this Domain. + + If enable object trace or not for current domain # noqa: E501 + + :param enable_trace: The enable_trace of this Domain. + :type enable_trace: bool + """ + + self._enable_trace = enable_trace \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/config_trace/models/domain_body.py b/sysom_server/sysom_config_trace/config_trace/models/domain_body.py new file mode 100644 index 0000000000000000000000000000000000000000..2546521f7d48f3b60c2ff403abc5713a9865e725 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/domain_body.py @@ -0,0 +1,92 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace import util + + +class DomainBody(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, priority: int=None, enable_trace: bool=None): # noqa: E501 + """DomainBody - a model defined in Swagger + + :param priority: The priority of this DomainBody. # noqa: E501 + :type priority: int + :param enable_trace: The enable_trace of this DomainBody. # noqa: E501 + :type enable_trace: bool + """ + self.swagger_types = { + 'priority': int, + 'enable_trace': bool + } + + self.attribute_map = { + 'priority': 'priority', + 'enable_trace': 'enable_trace' + } + self._priority = priority + self._enable_trace = enable_trace + + @classmethod + def from_dict(cls, dikt) -> 'DomainBody': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The domains_domainName_body of this DomainBody. # noqa: E501 + :rtype: DomainBody + """ + return util.deserialize_model(dikt, cls) + + @property + def priority(self) -> int: + """Gets the priority of this DomainBody. + + Priority of the current domain # noqa: E501 + + :return: The priority of this DomainBody. + :rtype: int + """ + return self._priority + + @priority.setter + def priority(self, priority: int): + """Sets the priority of this DomainBody. + + Priority of the current domain # noqa: E501 + + :param priority: The priority of this DomainBody. + :type priority: int + """ + + self._priority = priority + + @property + def enable_trace(self) -> bool: + """Gets the enable_trace of this DomainBody. + + If Enable objective trace by eBPF # noqa: E501 + + :return: The enable_trace of this DomainBody. + :rtype: bool + """ + return self._enable_trace + + @enable_trace.setter + def enable_trace(self, enable_trace: bool): + """Sets the enable_trace of this DomainBody. + + If Enable objective trace by eBPF # noqa: E501 + + :param enable_trace: The enable_trace of this DomainBody. + :type enable_trace: bool + """ + + self._enable_trace = enable_trace diff --git a/sysom_server/sysom_config_trace/config_trace/models/git_log_message.py b/sysom_server/sysom_config_trace/config_trace/models/git_log_message.py new file mode 100644 index 0000000000000000000000000000000000000000..fc519621b07136875795ca98a7fc2c9ec357b448 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/git_log_message.py @@ -0,0 +1,194 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace import util + + +class GitLogMessage(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + + def __init__(self, _date: datetime=None, author: str=None, change_id: str=None, change_reason: str=None, pre_value: str=None, post_value: str=None): # noqa: E501 + """GitLogMessage - a model defined in Swagger + + :param _date: The _date of this GitLogMessage. # noqa: E501 + :type _date: datetime + :param author: The author of this GitLogMessage. # noqa: E501 + :type author: str + :param change_id: The change_id of this GitLogMessage. # noqa: E501 + :type change_id: str + :param change_reason: The change_reason of this GitLogMessage. # noqa: E501 + :type change_reason: str + :param pre_value: The pre_value of this GitLogMessage. # noqa: E501 + :type pre_value: str + :param post_value: The post_value of this GitLogMessage. # noqa: E501 + :type post_value: str + """ + self.swagger_types = { + '_date': datetime, + 'author': str, + 'change_id': str, + 'change_reason': str, + 'pre_value': str, + 'post_value': str + } + + self.attribute_map = { + '_date': 'date', + 'author': 'author', + 'change_id': 'changeId', + 'change_reason': 'changeReason', + 'pre_value': 'preValue', + 'post_value': 'postValue' + } + + self.__date = _date + self._author = author + self._change_id = change_id + self._change_reason = change_reason + self._pre_value = pre_value + self._post_value = post_value + + @classmethod + def from_dict(cls, dikt) -> 'GitLogMessage': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The GitLogMessage of this GitLogMessage. # noqa: E501 + :rtype: GitLogMessage + """ + return util.deserialize_model(dikt, cls) + + @property + def _date(self) -> datetime: + """Gets the _date of this GitLogMessage. + + + :return: The _date of this GitLogMessage. + :rtype: datetime + """ + return self.__date + + @_date.setter + def _date(self, _date: datetime): + """Sets the _date of this GitLogMessage. + + + :param _date: The _date of this GitLogMessage. + :type _date: datetime + """ + + self.__date = _date + + @property + def author(self) -> str: + """Gets the author of this GitLogMessage. + + + :return: The author of this GitLogMessage. + :rtype: str + """ + return self._author + + @author.setter + def author(self, author: str): + """Sets the author of this GitLogMessage. + + + :param author: The author of this GitLogMessage. + :type author: str + """ + + self._author = author + + @property + def change_id(self) -> str: + """Gets the change_id of this GitLogMessage. + + + :return: The change_id of this GitLogMessage. + :rtype: str + """ + return self._change_id + + @change_id.setter + def change_id(self, change_id: str): + """Sets the change_id of this GitLogMessage. + + + :param change_id: The change_id of this GitLogMessage. + :type change_id: str + """ + + self._change_id = change_id + + @property + def change_reason(self) -> str: + """Gets the change_reason of this GitLogMessage. + + + :return: The change_reason of this GitLogMessage. + :rtype: str + """ + return self._change_reason + + @change_reason.setter + def change_reason(self, change_reason: str): + """Sets the change_reason of this GitLogMessage. + + + :param change_reason: The change_reason of this GitLogMessage. + :type change_reason: str + """ + + self._change_reason = change_reason + + @property + def pre_value(self) -> str: + """Gets the pre_value of this GitLogMessage. + + + :return: The pre_value of this GitLogMessage. + :rtype: str + """ + return self._pre_value + + @pre_value.setter + def pre_value(self, pre_value: str): + """Sets the pre_value of this GitLogMessage. + + + :param pre_value: The pre_value of this GitLogMessage. + :type pre_value: str + """ + + self._pre_value = pre_value + + @property + def post_value(self) -> str: + """Gets the post_value of this GitLogMessage. + + + :return: The post_value of this GitLogMessage. + :rtype: str + """ + return self._post_value + + @post_value.setter + def post_value(self, post_value: str): + """Sets the post_value of this GitLogMessage. + + + :param post_value: The post_value of this GitLogMessage. + :type post_value: str + """ + + self._post_value = post_value diff --git a/sysom_server/sysom_config_trace/config_trace/models/host.py b/sysom_server/sysom_config_trace/config_trace/models/host.py new file mode 100644 index 0000000000000000000000000000000000000000..d090d6e01bf0d2b9278fdf0184fe26152f20eedc --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/host.py @@ -0,0 +1,120 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace import util + + +class Host(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, host_id: int=None, ip: str=None, ipv6: str=None): # noqa: E501 + """Host - a model defined in Swagger + + :param host_id: The host_id of this Host. # noqa: E501 + :type host_id: int + :param ip: The ip of this Host. # noqa: E501 + :type ip: str + :param ipv6: The ipv6 of this Host. # noqa: E501 + :type ipv6: str + """ + self.swagger_types = { + 'host_id': int, + 'ip': str, + 'ipv6': str + } + + self.attribute_map = { + 'host_id': 'hostId', + 'ip': 'ip', + 'ipv6': 'ipv6' + } + self._host_id = host_id + self._ip = ip + self._ipv6 = ipv6 + + @classmethod + def from_dict(cls, dikt) -> 'Host': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The Host of this Host. # noqa: E501 + :rtype: Host + """ + return util.deserialize_model(dikt, cls) + + @property + def host_id(self) -> int: + """Gets the host_id of this Host. + + the id of host # noqa: E501 + + :return: The host_id of this Host. + :rtype: int + """ + return self._host_id + + @host_id.setter + def host_id(self, host_id: int): + """Sets the host_id of this Host. + + the id of host # noqa: E501 + + :param host_id: The host_id of this Host. + :type host_id: int + """ + + self._host_id = host_id + + @property + def ip(self) -> str: + """Gets the ip of this Host. + + the ipv4 address of host # noqa: E501 + + :return: The ip of this Host. + :rtype: str + """ + return self._ip + + @ip.setter + def ip(self, ip: str): + """Sets the ip of this Host. + + the ipv4 address of host # noqa: E501 + + :param ip: The ip of this Host. + :type ip: str + """ + + self._ip = ip + + @property + def ipv6(self) -> str: + """Gets the ipv6 of this Host. + + the ipv6 address of host # noqa: E501 + + :return: The ipv6 of this Host. + :rtype: str + """ + return self._ipv6 + + @ipv6.setter + def ipv6(self, ipv6: str): + """Sets the ipv6 of this Host. + + the ipv6 address of host # noqa: E501 + + :param ipv6: The ipv6 of this Host. + :type ipv6: str + """ + + self._ipv6 = ipv6 diff --git a/sysom_server/sysom_config_trace/config_trace/models/v1_confs_body.py b/sysom_server/sysom_config_trace/config_trace/models/v1_confs_body.py new file mode 100644 index 0000000000000000000000000000000000000000..71e9e25dbe440920cfd4b22d91881fb34bd9a547 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/v1_confs_body.py @@ -0,0 +1,92 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace import util + + +class V1ConfsBody(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, domain_name: str=None, hosts: List[str]=None): # noqa: E501 + """V1ConfsBody - a model defined in Swagger + + :param domain_name: The domain_name of this V1ConfsBody. # noqa: E501 + :type domain_name: str + :param hosts: The hosts of this V1ConfsBody. # noqa: E501 + :type hosts: List[str] + """ + self.swagger_types = { + 'domain_name': str, + 'hosts': List[str] + } + + self.attribute_map = { + 'domain_name': 'domainName', + 'hosts': 'hosts' + } + self._domain_name = domain_name + self._hosts = hosts + + @classmethod + def from_dict(cls, dikt) -> 'V1ConfsBody': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The v1_confs_body of this V1ConfsBody. # noqa: E501 + :rtype: V1ConfsBody + """ + return util.deserialize_model(dikt, cls) + + @property + def domain_name(self) -> str: + """Gets the domain_name of this V1ConfsBody. + + domain name # noqa: E501 + + :return: The domain_name of this V1ConfsBody. + :rtype: str + """ + return self._domain_name + + @domain_name.setter + def domain_name(self, domain_name: str): + """Sets the domain_name of this V1ConfsBody. + + domain name # noqa: E501 + + :param domain_name: The domain_name of this V1ConfsBody. + :type domain_name: str + """ + + self._domain_name = domain_name + + @property + def hosts(self) -> List[str]: + """Gets the hosts of this V1ConfsBody. + + host lists # noqa: E501 + + :return: The hosts of this V1ConfsBody. + :rtype: List[str] + """ + return self._hosts + + @hosts.setter + def hosts(self, hosts: List[str]): + """Sets the hosts of this V1ConfsBody. + + host lists # noqa: E501 + + :param hosts: The hosts of this V1ConfsBody. + :type hosts: List[str] + """ + + self._hosts = hosts diff --git a/sysom_server/sysom_config_trace/config_trace/models/v1_confs_body1.py b/sysom_server/sysom_config_trace/config_trace/models/v1_confs_body1.py new file mode 100644 index 0000000000000000000000000000000000000000..7ade61db4f53b71048356083f660b3a1cf175bda --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/models/v1_confs_body1.py @@ -0,0 +1,63 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from config_trace.models.base_model_ import Model +from config_trace.models.configration import Configration # noqa: F401,E501 +from config_trace import util + + +class V1ConfsBody1(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, confs: List[Configration]=None): # noqa: E501 + """V1ConfsBody1 - a model defined in Swagger + + :param confs: The confs of this V1ConfsBody1. # noqa: E501 + :type confs: List[Configration] + """ + self.swagger_types = { + 'confs': List[Configration] + } + + self.attribute_map = { + 'confs': 'confs' + } + self._confs = confs + + @classmethod + def from_dict(cls, dikt) -> 'V1ConfsBody1': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The v1_confs_body_1 of this V1ConfsBody1. # noqa: E501 + :rtype: V1ConfsBody1 + """ + return util.deserialize_model(dikt, cls) + + @property + def confs(self) -> List[Configration]: + """Gets the confs of this V1ConfsBody1. + + + :return: The confs of this V1ConfsBody1. + :rtype: List[Configration] + """ + return self._confs + + @confs.setter + def confs(self, confs: List[Configration]): + """Sets the confs of this V1ConfsBody1. + + + :param confs: The confs of this V1ConfsBody1. + :type confs: List[Configration] + """ + + self._confs = confs diff --git a/sysom_server/sysom_config_trace/config_trace/swagger/swagger.yaml b/sysom_server/sysom_config_trace/config_trace/swagger/swagger.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d4ab582dd9b1584c17fc39885a58c8c3319712e5 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/swagger/swagger.yaml @@ -0,0 +1,484 @@ +openapi: 3.0.2 +info: + title: Configration Tracability + version: "1.0" +servers: + - url: http://localhost:8080 +tags: + - name: domain + description: configration domain + - name: host + description: host in domain + - name: confs + description: query the configration +paths: + /api/v1/configtrace/domains: + get: + tags: + - domain + summary: list domains + operationId: list_domains + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Domain" + x-content-type: application/json + x-openapi-router-controller: config_trace.controllers.domain_controller + /api/v1/configtrace/domains/{domainName}: + get: + tags: + - domain + summary: get specific domain + operationId: get_domain + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Domain" + x-openapi-router-controller: config_trace.controllers.domain_controller + post: + tags: + - domain + summary: create domains + operationId: create_domain + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + priority: + type: integer + description: Priority of the current domain + enable_trace: + type: boolean + description: If Enable objective trace by eBPF + responses: + "201": + description: Created + x-openapi-router-controller: config_trace.controllers.domain_controller + put: + tags: + - domain + summary: update domains + operationId: update_domain + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + priority: + type: integer + description: Priority of the current domain + enable_trace: + type: boolean + description: If Enable objective trace by eBPF + responses: + "202": + description: Accepted + x-openapi-router-controller: config_trace.controllers.domain_controller + delete: + tags: + - domain + summary: delete domain + operationId: delete_domain + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + responses: + "202": + description: Accepted + x-openapi-router-controller: config_trace.controllers.domain_controller + /api/v1/configtrace/domains/{domainName}/hosts: + get: + tags: + - domain + - host + summary: list domain hosts + operationId: list_domain_hosts + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Host" + x-content-type: application/json + x-openapi-router-controller: config_trace.controllers.domain_controller + /api/v1/configtrace/domains/{domainName}/hosts/{hostName}: + get: + summary: get specific domain + operationId: get_domain_host + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + - name: hostName + in: path + description: host name + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Host" + x-openapi-router-controller: config_trace.controllers.domain_controller + post: + tags: + - domain + summary: create domains + operationId: create_domain_host + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + - name: hostName + in: path + description: host name + required: true + style: simple + explode: false + schema: + type: string + responses: + "201": + description: Created + x-openapi-router-controller: config_trace.controllers.domain_controller + delete: + tags: + - domain + summary: delete domain + operationId: delete_domain_host + parameters: + - name: domainName + in: path + description: domain name + required: true + style: simple + explode: false + schema: + type: string + - name: hostName + in: path + description: host name + required: true + style: simple + explode: false + schema: + type: string + responses: + "202": + description: Accepted + x-openapi-router-controller: config_trace.controllers.domain_controller + /api/v1/configtrace/alerts: + get: + tags: + - confs + summary: list confs alerts + operationId: get_confs_alerts + parameters: + - name: domainName + in: query + required: false + style: form + explode: true + schema: + type: string + - name: action + in: query + required: true + style: form + explode: true + schema: + type: string + example: alerts + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: string + x-content-type: application/json + x-openapi-router-controller: config_trace.controllers.confs_controller + /api/v1/configtrace/confs: + get: + tags: + - confs + summary: list domain confs + operationId: get_confs_by_domain + parameters: + - name: domainName + in: query + required: false + style: form + explode: true + schema: + type: string + - name: hostName + in: query + required: false + style: form + explode: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Confs" + x-content-type: application/json + "400": + description: Client Error + content: + application/json: + schema: + type: string + x-content-type: application/json + x-openapi-router-controller: config_trace.controllers.confs_controller + put: + tags: + - confs + summary: update domain confs + operationId: update_confs_by_domain + parameters: + - name: domainName + in: query + required: false + style: form + explode: true + schema: + type: string + - name: hostName + in: query + required: false + style: form + explode: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + confs: + type: array + items: + $ref: "#/components/schemas/Configration" + responses: + "202": + description: Accepted + x-openapi-router-controller: config_trace.controllers.confs_controller + post: + tags: + - confs + summary: sync confs + operationId: sync_confs + parameters: + - name: action + in: query + required: true + style: form + explode: true + schema: + type: string + example: sync + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/v1_confs_body" + responses: + "202": + description: Accepted + x-openapi-router-controller: config_trace.controllers.confs_controller + delete: + tags: + - confs + summary: delete confs + operationId: delete_confs + parameters: + - name: domainName + in: query + required: false + style: form + explode: true + schema: + type: string + - name: hostName + in: query + required: false + style: form + explode: true + schema: + type: string + - name: filePath + in: query + required: false + style: form + explode: true + schema: + type: string + responses: + "202": + description: Accepted + x-openapi-router-controller: config_trace.controllers.confs_controller +components: + schemas: + Domain: + type: object + properties: + domainName: + type: string + description: domain name + priority: + type: integer + description: Priority of the current domain + format: int32 + enable_trace: + type: boolean + description: If enable object trace or not for current domain + format: boolean + example: + domainName: domainName + priority: 0 + enable_trace: false + Host: + type: object + properties: + hostId: + type: integer + description: the id of host + ip: + type: string + description: the ipv4 address of host + ipv6: + type: string + description: the ipv6 address of host + example: + ip: 1.2.3.4 + ipv6: ipv6 + hostId: hostId + Configration: + type: object + properties: + filePath: + type: string + description: config file path + realContent: + type: string + description: the content of configuration file + expectContent: + type: string + description: the expect content of configuration file + changeLog: + type: string + description: the configuration changelog + example: + realContent: realContent + filePath: filePath + expectContent: expectContent + Confs: + type: object + properties: + domainName: + type: string + confFiles: + type: array + items: + $ref: "#/components/schemas/Configration" + example: + confFiles: + - realContent: realContent + filePath: filePath + expectContent: expectContent + - realContent: realContent + filePath: filePath + expectContent: expectContent + domainName: domainName + v1_confs_body: + type: object + properties: + domainName: + type: string + description: domain name + hosts: + type: array + description: host lists + items: + type: string diff --git a/sysom_server/sysom_config_trace/config_trace/test/__init__.py b/sysom_server/sysom_config_trace/config_trace/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6d2543ec1e5c215e03b36e4f67c1fe34f60a99fb --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/test/__init__.py @@ -0,0 +1,16 @@ +import logging + +import connexion +from flask_testing import TestCase + +from config_trace.encoder import JSONEncoder + + +class BaseTestCase(TestCase): + + def create_app(self): + logging.getLogger('connexion.operation').setLevel('ERROR') + app = connexion.App(__name__, specification_dir='../swagger/') + app.app.json_encoder = JSONEncoder + app.add_api('swagger.yaml') + return app.app diff --git a/sysom_server/sysom_config_trace/config_trace/test/test_confs_controller.py b/sysom_server/sysom_config_trace/config_trace/test/test_confs_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..33bcbf4939d51a8cccb165005cf5da04c8e18768 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/test/test_confs_controller.py @@ -0,0 +1,63 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from config_trace.models.confs import Confs # noqa: E501 +from config_trace.models.v1_confs_body import V1ConfsBody # noqa: E501 +from config_trace.test import BaseTestCase + + +class TestConfsController(BaseTestCase): + """ConfsController integration test stubs""" + + def test_get_confs_by_domain(self): + """Test case for get_confs_by_domain + + list domain confs + """ + query_string = [('domain_name', 'domain_name_example'), + ('host_name', 'host_name_example')] + response = self.client.open( + '/api/v1/configtrace/confs', + method='GET', + query_string=query_string) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_sync_confs(self): + """Test case for sync_confs + + sync confs + """ + body = V1ConfsBody() + query_string = [('action', 'action_example')] + response = self.client.open( + '/api/v1/configtrace/confs', + method='POST', + data=json.dumps(body), + content_type='application/json', + query_string=query_string) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_update_confs_by_domain(self): + """Test case for update_confs_by_domain + + update domain confs + """ + query_string = [('domain_name', 'domain_name_example'), + ('host_name', 'host_name_example')] + response = self.client.open( + '/api/v1/configtrace/confs', + method='PUT', + query_string=query_string) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/sysom_server/sysom_config_trace/config_trace/test/test_default_controller.py b/sysom_server/sysom_config_trace/config_trace/test/test_default_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..bc6a8f64d70e46486b86659d2e2fb29521291168 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/test/test_default_controller.py @@ -0,0 +1,29 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from config_trace.models.host import Host # noqa: E501 +from config_trace.test import BaseTestCase + + +class TestDefaultController(BaseTestCase): + """DefaultController integration test stubs""" + + def test_get_domain_host(self): + """Test case for get_domain_host + + get specific domain + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}/hosts/{hostName}'.format(domain_name='domain_name_example', host_name='host_name_example'), + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/sysom_server/sysom_config_trace/config_trace/test/test_domain_controller.py b/sysom_server/sysom_config_trace/config_trace/test/test_domain_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..e46dcdf717e21f896eeb2c6679f10b53fbc8e0a5 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/test/test_domain_controller.py @@ -0,0 +1,96 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from config_trace.models.domain import Domain # noqa: E501 +from config_trace.models.host import Host # noqa: E501 +from config_trace.test import BaseTestCase + + +class TestDomainController(BaseTestCase): + """DomainController integration test stubs""" + + def test_create_domain(self): + """Test case for create_domain + + create domains + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}'.format(domain_name='domain_name_example'), + method='POST') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_create_domain_host(self): + """Test case for create_domain_host + + create domains + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}/hosts/{hostName}'.format(domain_name='domain_name_example', host_name='host_name_example'), + method='POST') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_delete_domain(self): + """Test case for delete_domain + + delete domain + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}'.format(domain_name='domain_name_example'), + method='DELETE') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_delete_domain_host(self): + """Test case for delete_domain_host + + delete domain + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}/hosts/{hostName}'.format(domain_name='domain_name_example', host_name='host_name_example'), + method='DELETE') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_get_domain(self): + """Test case for get_domain + + get specific domain + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}'.format(domain_name='domain_name_example'), + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_list_domain_hosts(self): + """Test case for list_domain_hosts + + list domain hosts + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}/hosts'.format(domain_name='domain_name_example'), + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_list_domains(self): + """Test case for list_domains + + list domains + """ + response = self.client.open( + '/api/v1/configtrace/domains', + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/sysom_server/sysom_config_trace/config_trace/test/test_host_controller.py b/sysom_server/sysom_config_trace/config_trace/test/test_host_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..e9cd63cc0799f893ddbff6ea8a9237fbc3d45951 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/test/test_host_controller.py @@ -0,0 +1,29 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from config_trace.models.host import Host # noqa: E501 +from config_trace.test import BaseTestCase + + +class TestHostController(BaseTestCase): + """HostController integration test stubs""" + + def test_list_domain_hosts(self): + """Test case for list_domain_hosts + + list domain hosts + """ + response = self.client.open( + '/api/v1/configtrace/domains/{domainName}/hosts'.format(domain_name='domain_name_example'), + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/sysom_server/sysom_config_trace/config_trace/type_util.py b/sysom_server/sysom_config_trace/config_trace/type_util.py new file mode 100644 index 0000000000000000000000000000000000000000..0563f81fd5345282a33705038dfa77fdcaa15872 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/type_util.py @@ -0,0 +1,32 @@ +# coding: utf-8 + +import sys + +if sys.version_info < (3, 7): + import typing + + def is_generic(klass): + """ Determine whether klass is a generic class """ + return type(klass) == typing.GenericMeta + + def is_dict(klass): + """ Determine whether klass is a Dict """ + return klass.__extra__ == dict + + def is_list(klass): + """ Determine whether klass is a List """ + return klass.__extra__ == list + +else: + + def is_generic(klass): + """ Determine whether klass is a generic class """ + return hasattr(klass, '__origin__') + + def is_dict(klass): + """ Determine whether klass is a Dict """ + return klass.__origin__ == dict + + def is_list(klass): + """ Determine whether klass is a List """ + return klass.__origin__ == list diff --git a/sysom_server/sysom_config_trace/config_trace/util.py b/sysom_server/sysom_config_trace/config_trace/util.py new file mode 100644 index 0000000000000000000000000000000000000000..ac61d394ff2773c1c7e2359bd7052afe0552e585 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/util.py @@ -0,0 +1,142 @@ +import datetime + +import six +import typing +from config_trace import type_util + + +def _deserialize(data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in six.integer_types or klass in (float, str, bool, bytearray): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif type_util.is_generic(klass): + if type_util.is_list(klass): + return _deserialize_list(data, klass.__args__[0]) + if type_util.is_dict(klass): + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = six.u(data) + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return an original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.swagger_types: + return data + + for attr, attr_type in six.iteritems(instance.swagger_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) + for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) + for k, v in six.iteritems(data)} diff --git a/sysom_server/sysom_config_trace/config_trace/utils/__init__.py b/sysom_server/sysom_config_trace/config_trace/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sysom_server/sysom_config_trace/config_trace/utils/conf_tools.py b/sysom_server/sysom_config_trace/config_trace/utils/conf_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..566a5f8a9acbb636e2f92fb8aa9acdd2cda02214 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/utils/conf_tools.py @@ -0,0 +1,492 @@ +import os +import json +import configparser +import ast +from enum import Enum + +from config_trace.const.conf_handler_const import CONFIG +from config_trace.utils.git_tools import GitTools +from config_trace.controllers.format import Format +from clogger import logger + +PATH = "path" +EXCEPTED_VALUE = "expectedValue" +STRIKETHROUGH = '-' +KNOWN_ARCHITECTURES = [ + # Common architectures + "x86_64", + "i686", + "aarch64" +] +STAT = "/usr/bin/stat" +LS = "/usr/bin/ls" +LL = "-l" +ACCESS = "Access" +UID = "Uid" +GID = "Gid" +TWOSPACE = " " +SPACE = " " +Colon = ":" +FS = "/" +LeftParen = "(" +RightParen = ")" +STRIKE = "-" +PERMISSION = 3 +R = "r" +W = "w" +X = "x" +RPERM = 4 +WPERM = 2 +XPERM = 1 +SPERM = 0 + +NOTFOUND = "NOT FOUND" +NOTSYNCHRONIZE = "NOT SYNCHRONIZE" +SYNCHRONIZED = "SYNCHRONIZED" + + +class SyncRes(Enum): + SUCCESS = "SUCCESS" + FAILED = "FAILED" + + +class ConfTools(object): + """ + desc: convert the configuration items controlled in the domain into dict storage + """ + + def __init__(self): + self._managementConfs = [] + self._target_dir = "/home/confBak" + + @property + def managementConfs(self): + return self._managementConfs + + @managementConfs.setter + def managementConfs(self, value): + self._managementConfs = value + + @property + def target_dir(self): + return self._target_dir + + @target_dir.setter + def target_dir(self, target_dir): + self._target_dir = target_dir + + def listToDict(self, manaConfs): + res = {} + logger.debug("manaConfs is : {}".format(manaConfs)) + for d_conf in manaConfs: + path = d_conf.get(PATH) + value = d_conf.get(EXCEPTED_VALUE).strip() + level = path.split("/") + d_res0 = {} + d_res0[level[len(level) - 1]] = value + + returnObject = res + returnCount = 0 + for count in range(0, len(level)): + d_level = level[count] + if returnObject.get(d_level): + returnObject = returnObject.get(d_level) + else: + returnCount = count + break + # to dict + for count in range(len(level) - 2, returnCount, -1): + d_res = {} + key = level[count] + d_res[key] = d_res0 + d_res0 = d_res + + # level less than 2 + if d_res0.get(level[returnCount]): + returnObject[level[returnCount]] = d_res0.get(level[returnCount]) + else: + returnObject[level[returnCount]] = d_res0 + + return res + + def getRpmInfo(self, path): + """ + desc: return the rpm_name\rpm_release\rpm_version for the package of path + example: + input: '/etc/yum.repos.d/openEuler.repo' + output: openEuler-repos 1.0 3.0.oe1.aarch64 + """ + if not os.path.exists(path): + return None + cmd = "/bin/rpm -qf {}".format(path) + gitTools = GitTools() + package_string = gitTools.run_shell_return_output(cmd).decode() + logger.debug("package_string is : {}".format(package_string)) + if 'not owned by any package' in package_string: + return None, None, None + pkg, arch = Format.rsplit(package_string, Format.arch_sep(package_string)) + if arch not in KNOWN_ARCHITECTURES: + pkg, arch = (package_string, None) + pkg, release = Format.rsplit(pkg, '-') + name, version = Format.rsplit(pkg, '-') + # If the value of epoch needs to be returned separately, + return name, release, version + + def getFileAttr(self, path): + """ + desc: return the fileAtrr and fileOwner of path. + if the /usr/bin/stat exists, we can use the case1: + command: /usr/bin/stat filename + output: + [root@openeuler-development-2-pafcm demo]# stat /etc/tcsd.conf + File: /etc/tcsd.conf + Size: 7046 Blocks: 16 IO Block: 4096 regular file + Device: fd00h/64768d Inode: 262026 Links: 1 + Access: (0600/-rw-------) Uid: ( 59/ tss) Gid: ( 59/ tss) + Context: system_u:object_r:etc_t:s0 + Access: 2021-06-18 14:43:15.413173879 +0800 + Modify: 2020-12-21 23:16:08.000000000 +0800 + Change: 2021-01-13 16:50:31.257896622 +0800 + Birth: 2021-01-13 16:50:31.257896622 +0800 + else, we use the case2: + command: ls -l filename + output: + [root@openeuler-development-2-pafcm demo]# ls -l /etc/tcsd.conf + -rw-------. 1 tss tss 6.9K Dec 21 23:16 /etc/tcsd.conf + + example: + input: '/etc/yum.repos.d/openEuler.repo' + output: 0644 (root root) + """ + if not os.path.exists(STAT): + fileAttr, fileOwner = self.getFileAttrByLl(path) + return fileAttr, fileOwner + + cmd = STAT + SPACE + path + gitTools = GitTools() + stat_rest = gitTools.run_shell_return_output(cmd).decode() + logger.debug("the stat_rest is : {}".format(stat_rest)) + fileAttr = "" + fileOwner = "" + lines = stat_rest.splitlines() + for line in lines: + if ACCESS in line and UID in line and GID in line: + d_lines = line.split(RightParen + TWOSPACE) + for d_line in d_lines: + d_line = d_line.lstrip() + if d_line.startswith(ACCESS): + fileAttr = d_line.split(FS)[0].split(LeftParen)[1] + elif d_line.startswith(UID): + fileUid = d_line.split(LeftParen)[1].split(FS)[1].lstrip() + elif d_line.startswith(GID): + fileGid = d_line.split(LeftParen)[1].split(FS)[1].lstrip().split(RightParen)[0] + else: + continue + fileOwner = LeftParen + fileUid + SPACE + fileGid + RightParen + + if not fileAttr or not fileOwner: + fileAttr, fileOwner = self.getFileAttrByLL(path) + logger.debug("fileAttr is : {}".format(fileAttr)) + logger.debug("fileOwner is : {}".format(fileOwner)) + return fileAttr, fileOwner + + def getFileAttrByLL(self, path): + """ + desc: we can use the command 'ls -l filename' to get the Attribute information of the path. + example: + command: ls -l filename + commandOutput: + [root@openeuler-development-2-pafcm demo]# ls -l /etc/tcsd.conf + -rw-------. 1 tss tss 6.9K Dec 21 23:16 /etc/tcsd.conf + calculate score: + the first digit indicates the type: [d]->directory, [-]->files + then every 3 are grouped, indicates read/write/execute + score: r->4 w->2 x->1 + """ + if not os.path.exists(LS): + return None, None + cmd = LS + SPACE + LL + SPACE + path + logger.debug("cmd is : {}".format(cmd)) + gitTools = GitTools() + ll_res = gitTools.run_shell_return_output(cmd).decode() + logger.debug("ll_res is : {}".format(ll_res)) + ll_res_list = ll_res.split(SPACE) + + fileType = ll_res_list[0] + permssions = "0" + for perm in range(0, PERMISSION): + items = fileType[1 + perm * PERMISSION: (perm + 1) * PERMISSION + 1] + value = 0 + for d_item in items: + d_item_value = self.switch_perm(d_item) + value = value + d_item_value + permssions = permssions + str(value) + logger.debug("the perssion is : {}".format(permssions)) + + fileOwner = LeftParen + ll_res_list[2] + SPACE + ll_res_list[3] + RightParen + logger.debug("the fileOwner is : {}".format(fileOwner)) + + return permssions, fileOwner + + def switch_perm(self, permValue): + if permValue == R: + return RPERM + elif permValue == W: + return WPERM + elif permValue == X: + return XPERM + else: + return SPERM + + def getXpathInManagerConfs(self, manageConfs): + """ + desc: generate the xpath list of configuration items. + """ + confXpath = [] + for d_conf in manageConfs: + path = d_conf.get('path') + confXpath.append(path) + + return confXpath + + def writeBakFileInPath(self, path, content): + """ + desc: Create the Path file, and write the content content, return the write result + """ + res = False + cwd = os.getcwd() + os.umask(0o077) + if not os.path.exists(self._target_dir): + os.mkdir(self._target_dir) + + os.chdir(self._target_dir) + path_git = Format.two_abs_join(self.target_dir, path) + paths = path_git.split('/') + path_git_delete_last = "" + for d_index in range(0, len(paths) - 1): + path_git_delete_last = path_git_delete_last + '/' + paths[d_index] + if not os.path.exists(path_git): + cmd = "/bin/mkdir -p " + path_git_delete_last + logger.debug("cmd is : {}".format(cmd)) + gitTools = GitTools() + ll_res = gitTools.run_shell_return_output(cmd).decode() + + if not os.path.exists(path_git_delete_last): + return res + + with open(path_git, 'w') as d_file: + d_file.write(content) + res = True + os.chdir(cwd) + + return res + + def getRealConfByPath(self, real_conf, path): + """ + desc: Returns the index and true value corresponding to the PATH in real_conf + exmaple: + input: + real_conf: [ + { + 'path': 'OS/yum/openEuler.repo/OS/name', + 'real_value': 'OS' + }, + { + 'path': 'OS/yum/openEuler.repo/OS/baseurl', + 'real_value': 'http://repo.openeuler.org/openEuler-20.03-LTS-SP1/OS/$basearch/' + }] + path: 'OS/yum/openEuler.repo/OS/name' + output: + index: 0 + value: 'OS' + """ + index = 0 + value = "" + for count in range(0, len(real_conf)): + d_real = real_conf[count] + if d_real.path == path: + index = count + value = d_real.real_value.strip() + break + + return index, value + + def syncConf(self, contents, path): + """ + desc: Put the new configuration into the environment with the path. + return: the result of effective + example: + input: + contents: [ + '[OS]', + 'name=OS', + 'baseurl=https://repo.huaweicloud.com/openeuler/openEuler-20.03-LTS-SP1/everything/x86_64/', + 'enabled=1', + 'gpgcheck=0', + 'gpgkey=http://repo.openeuler.org/openEuler-20.03-LTS-SP1/OS/$basearch/RPM-GPG-KEY-openEuler' + ] + path: '/etc/yum.repos.d/openEuler.repo' + output: + res : true or false + """ + res = 0 + res = Format.set_file_content_by_path(contents, path) + return res + + def wirteFileInPath(self, path, content): + """ + desc: Create the Path file, and write the content content, return the write result + """ + res = False + path_delete_last = "" + os.umask(0o077) + if not os.path.exists(path): + paths = path.split('/') + for d_index in range(0, len(paths) - 1): + path_delete_last = path_delete_last + '/' + paths[d_index] + if not os.path.exists(path_delete_last): + cmd = "/bin/mkdir -p " + path_delete_last + logger.debug("cmd is : {}".format(cmd)) + gitTools = GitTools() + ll_res = gitTools.run_shell_return_output(cmd).decode() + logger.debug("path_delete_last IS :{}".format(path_delete_last)) + if not os.path.exists(path_delete_last): + return res + + with open(path, 'w') as d_file: + d_file.writelines(content) + res = True + + return res + + def load_url_by_conf(self): + """ + desc: get the url of collect conf + """ + cf = configparser.ConfigParser() + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + parent = os.path.dirname(os.path.realpath(__file__)) + conf_path = os.path.join(parent, "../../config_trace.conf") + cf.read(conf_path, encoding="utf-8") + + collect_address = ast.literal_eval(cf.get("collect", "collect_address")) + collect_api = ast.literal_eval(cf.get("collect", "collect_api")) + collect_port = str(cf.get("collect", "collect_port")) + collect_url = "{address}:{port}{api}".format(address=collect_address, api=collect_api, port=collect_port) + + sync_address = ast.literal_eval(cf.get("sync", "sync_address")) + sync_api = ast.literal_eval(cf.get("sync", "sync_api")) + sync_port = str(cf.get("sync", "sync_port")) + sync_url = "{address}:{port}{api}".format(address=sync_address, api=sync_api, port=sync_port) + + object_file_address = ast.literal_eval(cf.get("objectFile", "object_file_address")) + object_file_api = ast.literal_eval(cf.get("objectFile", "object_file_api")) + object_file_port = str(cf.get("objectFile", "object_file_port")) + object_file_url = "{address}:{port}{api}".format(address=object_file_address, api=object_file_api, + port=object_file_port) + + url = {"collect_url": collect_url, "sync_url": sync_url, "object_file_url": object_file_url} + return url + + def load_port_by_conf(self): + """ + desc: get the password of collect conf + """ + cf = configparser.ConfigParser() + logger.debug("CONFIG is :{}".format(CONFIG)) + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + parent = os.path.dirname(os.path.realpath(__file__)) + conf_path = os.path.join(parent, "../../config_trace.conf") + cf.read(conf_path, encoding="utf-8") + port = cf.get("config_trace", "port") + return port + + @staticmethod + def load_log_conf(): + """ + desc: get the log configuration + """ + cf = configparser.ConfigParser() + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + parent = os.path.dirname(os.path.realpath(__file__)) + conf_path = os.path.join(parent, "../../config_trace.conf") + cf.read(conf_path, encoding="utf-8") + log_level = cf.get("log", "log_level") + log_dir = cf.get("log", "log_dir") + max_bytes = cf.get("log", "max_bytes") + backup_count = cf.get("log", "backup_count") + log_conf = {"log_level": log_level, "log_dir": log_dir, "max_bytes": int(max_bytes), + "backup_count": int(backup_count)} + return log_conf + + @staticmethod + def fetchRealConfs(host_name, file_path): + # 获取指定主机上的配置内容,需要考虑file_path为目录且存在子目录的情况 + # param: host_name: 主机名 + # param: file_path: 文件在主机上的路径,可以为目录 + # return: map[文件绝对路径][文件内容,文件元数据] + # 使用CEC框架实现交互访问 + from channel_job import default_channel_job_executor + job = default_channel_job_executor.dispatch_job( + channel_type="ssh", + channel_opt="cmd", + params={ + "instance": host_name, + "command": "cat " + file_path, + }, + auto_retry=False + ) + channel_result = job.execute() + + if channel_result.code != 0: + logger.warning("fetch config from host {} failed, err_msg: {} code: {}" + .format(host_name, channel_result.err_msg, channel_result.code)) + return {} + return { + # "/etc/hosts": ["127.0.0.1 localhost\n", "{\"mode\": \"0644\", \"owner\": \"root\", \"group\": \"root\"}"] + file_path: [channel_result.result] + } + + @staticmethod + def sync_confs(hosts, config, parent_dir): + # host_name: 入参主机名 + # config: 配置信息,包括: 路径及期望配置内容 + # Resp: 返回成功ip或失败ip, 例如: host_name, None + from channel_job import default_channel_job_executor + lpath = parent_dir + config["file_path"] + rpath = config["file_path"] + + job = default_channel_job_executor.dispatch_file_job( + params={ + "local_path": lpath, + "remote_path": rpath, + "instances": hosts + }, + opt="send-file" + ) + channel_result = job.execute() + logger.info(f'send file {lpath} to {rpath}') + logger.info(channel_result.__dict__) + if channel_result.code != 0: + return None, hosts + " " + channel_result.err_msg + return hosts, None + # job = default_channel_job_executor.dispatch_job( + # channel_type="ssh", + # channel_opt="cmd", + # params={ + # "instance": host_name, + # "command": "echo '" + config.expect_content + "' > " + config["path"], + # }, + # timeout=10000, + # auto_retry=False + # ) + # channel_result = job.execute() + # if channel_result.code != 0: + # return None, host_name + # return host_name, None diff --git a/sysom_server/sysom_config_trace/config_trace/utils/git_tools.py b/sysom_server/sysom_config_trace/config_trace/utils/git_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..03653f7e89c94f6d2a93cd20048d5b5f28195f45 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/utils/git_tools.py @@ -0,0 +1,183 @@ +import os +import subprocess +import sys +import configparser +import ast + +from config_trace.const.conf_handler_const import CONFIG +from clogger import logger +from config_trace.models.git_log_message import GitLogMessage +from config_trace.controllers.format import Format + + +class GitTools(object): + def __init__(self, target_dir=None): + if target_dir: + self._target_dir = target_dir + else: + self._target_dir = self.load_git_dir() + + def load_git_dir(self): + cf = configparser.ConfigParser() + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + parent = os.path.dirname(os.path.realpath(__file__)) + conf_path = os.path.join(parent, "../../config_trace.conf") + cf.read(conf_path, encoding="utf-8") + git_dir = ast.literal_eval(cf.get("git", "git_dir")) + return git_dir + + @property + def target_dir(self): + return self.load_git_dir() + + @target_dir.setter + def target_dir(self, target_dir): + self._target_dir = target_dir + + def gitInit(self): + cwdDir = os.getcwd() + os.chdir(self._target_dir) + shell = "/bin/git init" + returncode = self.run_shell_return_code(shell) + os.chdir(cwdDir) + return returncode + + def git_create_user(self, username, useremail): + """ + desc: Git initial configuration about add a user name and email + """ + returncode = 1 + cmd_add_user_name = "git config user.name {}".format(username) + cmd_name_code = self.run_shell_return_code(cmd_add_user_name) + cmd_add_user_email = "git config user.email {}".format(useremail) + cmd_email_code = self.run_shell_return_code(cmd_add_user_email) + if cmd_name_code and cmd_email_code: + returncode = 0 + return returncode + + def gitCommit(self, message): + cwdDir = os.getcwd() + os.chdir(self._target_dir) + cmd1 = "git add ." + returncode1 = self.run_shell_return_code(cmd1) + if returncode1 == 0: + cmd2 = "git commit -m '{}'".format(message) + returncode2 = self.run_shell_return_code(cmd2) + returncode = returncode2 + else: + returncode = returncode1 + os.chdir(cwdDir) + + return returncode + + def gitLog(self, path): + cwdDir = os.getcwd() + os.chdir(self._target_dir) + shell = ['git log {}'.format(path)] + output = self.run_shell_return_output(shell) + os.chdir(cwdDir) + return output + + # Execute the shell command and return the execution node + def run_shell_return_code(self, shell): + cmd = subprocess.Popen(shell, stdin=subprocess.PIPE, stderr=sys.stderr, close_fds=True, + stdout=sys.stdout, universal_newlines=True, shell=True, bufsize=1) + + output, err = cmd.communicate() + return cmd.returncode + + # Execute the shell command and return the execution node and output + def run_shell_return_output(self, shell): + cmd = subprocess.Popen(shell, stdout=subprocess.PIPE, shell=True) + logger.debug("subprocess.Popen({shell}, stdout=subprocess.PIPE, shell=True)".format(shell=shell)) + output, err = cmd.communicate() + return output + + def makeGitMessage(self, path, logMessage): + if len(logMessage) == 0: + return "the logMessage is null" + logger.debug("path is : {}".format(path)) + cwdDir = os.getcwd() + os.chdir(self._target_dir) + logger.debug(os.getcwd()) + logger.debug("logMessage is : {}".format(logMessage)) + gitLogMessageList = [] + singleLogLen = 6 + # the count is num of message + count = logMessage.count("commit") + lines = logMessage.split('\n') + + logger.debug("count is : {}".format(count)) + for index in range(0, count): + gitMessage = GitLogMessage() + for temp in range(0, singleLogLen): + line = lines[index * singleLogLen + temp] + value = line.split(" ", 1)[-1] + if "commit" in line: + gitMessage.change_id = value + if "Author" in line: + gitMessage.author = value + if "Date" in line: + gitMessage._date = value[2:] + gitMessage.change_reason = lines[index * singleLogLen + 4] + logger.debug("gitMessage is : {}".format(gitMessage)) + gitLogMessageList.append(gitMessage) + + logger.debug("################# gitMessage start ################") + if count == 1: + last_message = gitLogMessageList[0] + last_message.post_value = Format.get_file_content_by_read(path) + os.chdir(cwdDir) + return gitLogMessageList + + for index in range(0, count - 1): + logger.debug("index is : {}".format(index)) + message = gitLogMessageList[index] + next_message = gitLogMessageList[index + 1] + message.post_value = Format.get_file_content_by_read(path) + shell = ['/bin/git checkout {}'.format(next_message.change_id)] + output = self.run_shell_return_output(shell) + message.pre_value = Format.get_file_content_by_read(path) + # the last changlog + first_message = gitLogMessageList[count - 1] + first_message.post_value = Format.get_file_content_by_read(path) + + logger.debug("################# gitMessage end ################") + os.chdir(cwdDir) + return gitLogMessageList + + def gitCheckToHead(self): + """ + desc: git checkout to the HEAD in this git. + """ + cwdDir = os.getcwd() + os.chdir(self._target_dir) + cmd = ['/bin/git checkout master'] + output = self.run_shell_return_code(cmd) + os.chdir(cwdDir) + return output + + def getLogMessageByPath(self, confPath): + """ + :desc: Returns the Git change record under the current path. + :param : string + :rtype: confBaseInfo + """ + logMessage = self.gitLog(confPath) + gitMessage = self.makeGitMessage(confPath, logMessage.decode('utf-8')) + checoutResult = self.gitCheckToHead() + return gitMessage + + def gitDiff(self, confPath): + """ + :desc: 返回针对当前配置路径的期望配置和真实配置的差异 + """ + cwdDir = os.getcwd() + os.chdir(self._target_dir) + expected = self._target_dir + confPath + diff = self.run_shell_return_output(['/bin/git diff %s %s' % expected, confPath]) + os.chdir(cwdDir) + return diff + diff --git a/sysom_server/sysom_config_trace/config_trace/utils/host_tools.py b/sysom_server/sysom_config_trace/config_trace/utils/host_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..432ad8fd8c6250db971fd2406e6533d3696055b4 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/utils/host_tools.py @@ -0,0 +1,96 @@ +import os +import configparser +import ast + +from config_trace.const.conf_handler_const import CONFIG +from clogger import logger + + +class HostTools(object): + def __init__(self): + self._target_dir = self.load_git_dir() + self._host_file = "hostRecord.txt" + + @property + def target_dir(self): + return self._target_dir + + @target_dir.setter + def target_dir(self, target_dir): + self._target_dir = target_dir + + @property + def host_file(self): + return self._host_file + + @host_file.setter + def host_file(self, host_file): + self._host_file = host_file + + def isHostIdExist(self, hostPath, hostId): + """ + desc: 查询hostId是否存在当前的host域管理范围内 + """ + isHostIdExist = False + if os.path.isfile(hostPath) and os.stat(hostPath).st_size > 0: + with open(hostPath) as h_file: + for line in h_file.readlines(): + if str(hostId) in line: + isHostIdExist = True + break + + return isHostIdExist + + def getHostExistStatus(self, domain, hostList): + """ + desc: return two list about the status of the host exists + example: + input hostList: [{'host_id': '551d02da-7d8c-4357-b88d-15dc55ee22cc', + 'ip': '210.22.22.150', + 'ipv6': 'None'}] + output existHost:['551d02da-7d8c-4357-b88d-15dc55ee22cc'] + """ + if len(hostList) == 0: + return None, None + domainPath = os.path.join(self._target_dir, domain) + hostPath = os.path.join(domainPath, self._host_file) + existHost = [] + failedHost = [] + for d_host in hostList: + d_hostId = d_host.get('hostId') + isHostIdExist = self.isHostIdExist(hostPath, d_hostId) + if isHostIdExist: + existHost.append(d_hostId) + else: + failedHost.append(d_hostId) + return existHost, failedHost + + def getHostList(self, domainHost) -> []: + """ + desc:return a host list from the result of the /host/getHost + example: + domainHost is : [{'hostId': '551d02da-7d8c-4357-b88d-15dc55ee22cc', 'ip': '210.22.22.150', 'ipv6': 'None'}] + hostList is: [{ + "hostId" : '551d02da-7d8c-4357-b88d-15dc55ee22cc' + }] + """ + res = [] + logger.debug("The domainHost is : {}".format(domainHost)) + for d_host in domainHost: + hostId = int(d_host.get('host_id')) + d_host = {} + d_host["hostId"] = hostId + res.append(hostId) + + return res + + def load_git_dir(self): + cf = configparser.ConfigParser() + if os.path.exists(CONFIG): + cf.read(CONFIG, encoding="utf-8") + else: + parent = os.path.dirname(os.path.realpath(__file__)) + conf_path = os.path.join(parent, "../../config_trace.conf") + cf.read(conf_path, encoding="utf-8") + git_dir = ast.literal_eval(cf.get("git", "git_dir")) + return git_dir diff --git a/sysom_server/sysom_config_trace/config_trace/utils/prepare.py b/sysom_server/sysom_config_trace/config_trace/utils/prepare.py new file mode 100644 index 0000000000000000000000000000000000000000..691befad0855a9f985be9bb9f9d307efe5285ec1 --- /dev/null +++ b/sysom_server/sysom_config_trace/config_trace/utils/prepare.py @@ -0,0 +1,43 @@ +import os + +from clogger import logger +from config_trace.utils.git_tools import GitTools + +class Prepare(object): + def __init__(self, target_dir): + self._target_dir = target_dir + + @property + def target_dir(self): + return self._target_dir + + @target_dir.setter + def target_dir(self, target_dir): + self._target_dir = target_dir + + def mkdir_git_warehose(self, username, useremail): + res = True + logger.debug("self._target_dir is : {}".format(self._target_dir)) + if os.path.exists(self._target_dir): + rest = self.git_init(username, useremail) + return rest + os.umask(0o077) + cmd1 = "/bin/mkdir -p {}".format(self._target_dir) + git_tools = GitTools(self._target_dir) + mkdir_code = git_tools.run_shell_return_code(cmd1) + git_code = self.git_init(username, useremail) + if mkdir_code != 0 or not git_code: + res = False + return res + + def git_init(self, username, useremail): + res = False + cwd = os.getcwd() + os.chdir(self._target_dir) + git_tools = GitTools(self._target_dir) + res_init = git_tools.gitInit() + res_user = git_tools.git_create_user(username, useremail) + if res_init == 0 and res_user == 0: + res = True + os.chdir(cwd) + return res diff --git "a/sysom_server/sysom_config_trace/docs/UML\346\227\266\345\272\217\345\233\276.png" "b/sysom_server/sysom_config_trace/docs/UML\346\227\266\345\272\217\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..08e19468b0e7636b34f0f638f9d94c968b0545ee Binary files /dev/null and "b/sysom_server/sysom_config_trace/docs/UML\346\227\266\345\272\217\345\233\276.png" differ diff --git a/sysom_server/sysom_config_trace/scripts/node_clear.sh b/sysom_server/sysom_config_trace/scripts/node_clear.sh new file mode 100644 index 0000000000000000000000000000000000000000..8ffce5ab76244538b111118b73e2baaaa426fc9d --- /dev/null +++ b/sysom_server/sysom_config_trace/scripts/node_clear.sh @@ -0,0 +1,10 @@ +#!/bin/bash -x + +RESOURCE_DIR=${baseimage}/${SERVICE_NAME} + +systemctl stop sysom_configtrace_agent.service +systemctl disable sysom_configtrace_agent.service +rm -f /usr/lib/systemd/system/vmcore-collect.service +systemctl daemon-reload +rm -r -f ${RESOURCE_DIR} +exit 0 \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/scripts/node_init.sh b/sysom_server/sysom_config_trace/scripts/node_init.sh new file mode 100644 index 0000000000000000000000000000000000000000..54c1c28fdcab7b615107f9cfe2ea650949fe2eb3 --- /dev/null +++ b/sysom_server/sysom_config_trace/scripts/node_init.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +rpm -q --quiet bcc || yum install -y bcc +# TODO: 支持centos7,配置使用python3 +rpm -q --quiet python3-bcc || yum install -y python3-bcc + +RESOURCE_DIR=${baseimage}/${SERVICE_NAME} +mkdir -p ${RESOURCE_DIR} + +cp trace_file_change.py ${RESOURCE_DIR} + +# 写入service文件 + +cat << EOF > sysom_configtrace_agent.service +[Unit] +Description=SysOM ConfigTrace Agent +Documentation=SysOM ConfigTrace Agent +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=${RESOURCE_DIR}/trace_file_change.py --file /var/run/configtrace/configlist.txt +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +if [ ! -d ${RESOURCE_DIR} ]; then + mkdir -p ${RESOURCE_DIR} +fi +if [ ! -d /var/run/configtrace ]; then + mkdir -p /var/run/configtrace + touch /var/run/configtrace/configlist.txt +fi +mv sysom_configtrace_agent.service /usr/lib/systemd/system +systemctl daemon-reload +systemctl enable sysom_configtrace_agent +systemctl start sysom_configtrace_agent +ps -elf | grep "${RESOURCE_DIR}/trace_file_change.py" | grep -v grep 1>/dev/null + +if [ $? -ne 0 ] +then + exit 1 +fi \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/scripts/node_update.sh b/sysom_server/sysom_config_trace/scripts/node_update.sh new file mode 100644 index 0000000000000000000000000000000000000000..bd8888574ae9c10fb7526b959aff610d850209ed --- /dev/null +++ b/sysom_server/sysom_config_trace/scripts/node_update.sh @@ -0,0 +1 @@ +#!/bin/bash -x \ No newline at end of file diff --git a/sysom_server/sysom_config_trace/scripts/trace_file_change.py b/sysom_server/sysom_config_trace/scripts/trace_file_change.py new file mode 100644 index 0000000000000000000000000000000000000000..0e0656421f83fb30aa2f26865f18a48b86fe4cf8 --- /dev/null +++ b/sysom_server/sysom_config_trace/scripts/trace_file_change.py @@ -0,0 +1,251 @@ +#!/bin/python3 + +# 主机在部署完成后,由控制面程序对主机需要追踪的文件进行注入和配置,并控制进程重启,初始状态下默认为configfile的默认内容 + +# 使用方式: ./trace_file_change.py --file filename +# 通过bcc实现常态化对主机文件的变更追踪 +# 需要安装bcc和python3-bcc + +from __future__ import print_function +from bcc import BPF +from bcc.containers import filter_by_containers +from bcc.utils import printb +from datetime import datetime +import argparse + +# 定义BPF程序 +bpf_text = """ +#include +#include +#include + +struct val_t { + u64 id; + char comm[TASK_COMM_LEN]; + const char *fname; + int flags; +}; + +struct data_t { + u64 id; + u64 ts; + u32 uid; + int ret; + char comm[TASK_COMM_LEN]; + char fname[NAME_MAX]; + int flags; +}; + +BPF_PERF_OUTPUT(events); + +BPF_HASH(infotmp, u64, struct val_t); + +int trace_open(struct pt_regs *ctx, const char __user *filename, int flags) +{ + struct val_t val = {}; + u64 id = bpf_get_current_pid_tgid(); + u32 pid = id >> 32; // PID is higher part + u32 tid = id; // Cast and get the lower part + u32 uid = bpf_get_current_uid_gid(); + + + + if (!(flags & 3)) { return 0; } + + if (container_should_be_filtered()) { + return 0; + } + + if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) { + val.id = id; + val.fname = filename; + val.flags = flags; + infotmp.update(&id, &val); + } + + return 0; +}; + +int trace_openat(struct pt_regs *ctx, int dfd, const char __user *filename, int flags) +{ + struct val_t val = {}; + u64 id = bpf_get_current_pid_tgid(); + u32 pid = id >> 32; // PID is higher part + u32 tid = id; // Cast and get the lower part + u32 uid = bpf_get_current_uid_gid(); + + + + if (!(flags & 3)) { return 0; } + + if (container_should_be_filtered()) { + return 0; + } + + if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) { + val.id = id; + val.fname = filename; + val.flags = flags; // EXTENDED_STRUCT_MEMBER + infotmp.update(&id, &val); + } + + return 0; +}; + +#include +int trace_openat2(struct pt_regs *ctx, int dfd, const char __user *filename, struct open_how *how) +{ + int flags = how->flags; + + struct val_t val = {}; + u64 id = bpf_get_current_pid_tgid(); + u32 pid = id >> 32; // PID is higher part + u32 tid = id; // Cast and get the lower part + u32 uid = bpf_get_current_uid_gid(); + + + + if (!(flags & 3)) { return 0; } + + if (container_should_be_filtered()) { + return 0; + } + + if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) { + val.id = id; + val.fname = filename; + val.flags = flags; + infotmp.update(&id, &val); + } + + return 0; +}; + +int trace_return(struct pt_regs *ctx) +{ + u64 id = bpf_get_current_pid_tgid(); + struct val_t *valp; + struct data_t data = {}; + + u64 tsp = bpf_ktime_get_ns(); + + valp = infotmp.lookup(&id); + if (valp == 0) { + // missed entry + return 0; + } + bpf_probe_read_kernel(&data.comm, sizeof(data.comm), valp->comm); + bpf_probe_read_user(&data.fname, sizeof(data.fname), (void *)valp->fname); + data.id = valp->id; + data.ts = tsp / 1000; + data.uid = bpf_get_current_uid_gid(); + data.flags = valp->flags; + data.ret = PT_REGS_RC(ctx); + + events.perf_submit(ctx, &data, sizeof(data)); + infotmp.delete(&id); + + return 0; +} + +""" + +# 加载BPF程序 +# arguments +examples = """examples: + ./opensnoop --cgroupmap mappath # only trace cgroups in this BPF map + ./opensnoop --mntnsmap mappath # only trace mount namespaces in the map + ./opensnoop --config filepath # only trace out specified files change history +""" +parser = argparse.ArgumentParser( + description="Trace configfile syscalls", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=examples) +parser.add_argument("--cgroupmap", + help="trace cgroups in this BPF map only") +parser.add_argument("--mntnsmap", + help="trace mount namespaces in this BPF map only") +parser.add_argument("--file", + help="trace out specified files change history only") + +args = parser.parse_args() + + +bpf_text = filter_by_containers(args) + bpf_text + +b = BPF(text=bpf_text) + +b.attach_kprobe(event="do_sys_open", fn_name="trace_open") +b.attach_kretprobe(event="do_sys_open", fn_name="trace_return") + +# b.attach_kprobe(event="do_sys_openat", fn_name="trace_openat") +# b.attach_kretprobe(event="do_sys_openat", fn_name="trace_return") + +b.attach_kprobe(event="do_sys_openat2", fn_name="trace_openat2") +b.attach_kretprobe(event="do_sys_openat2", fn_name="trace_return") + +# fetch configfile list for host + +files = [] +confs = dict() +domain = '' +with open(args.file, 'r') as f: + for line in f.readlines(): + if line.startswith('#'): + continue + if line.startswith("["): + domain = line.strip().strip('[]') + confs[domain] = [] + continue + if line.strip() == '': + continue + confs[domain].append(line.strip()) + files.append(line.strip()) + +# process event +def print_event(cpu, data, size): + event = b["events"].event(data) + # split return value into FD and errno columns + if event.ret >= 0: + fd_s = event.ret + err = 0 + else: + fd_s = -1 + err = - event.ret + + if event.fname.decode("utf-8") not in files: + return + # byte to string + printb(b"%-6d %-16s %4d %3d " % + (boot_time_timestamp + int(event.ts / 1000000), + event.comm, fd_s, err), nl="") + + # printb(b'%s' % event.fname) + # TODO: 针对于在记录中的配置文件,进行告警提示 + # 根据主机取所有的待追踪的文件 + # 如果待追踪文件在列表中,则执行告警提示 + # 写出到文件 + with open('/var/run/configtrace/alert.log', 'a') as f: + f.write("%f,%s,%s,%d,%d\n" % + (boot_time_timestamp + float(event.ts / 1000000), + event.fname.decode("utf-8"), + event.comm.decode("utf-8"), fd_s, err)) + +def get_system_boot_time(): + with open('/proc/stat', 'r') as file: + for line in file: + if line.startswith('btime'): + boot_time = int(line.split()[1]) + return boot_time + +boot_time_timestamp = get_system_boot_time() +print(boot_time_timestamp) + + +# loop with callback to print_event +b["events"].open_perf_buffer(print_event, page_cnt=64) +while True: + try: + b.perf_buffer_poll() + except KeyboardInterrupt: + exit() diff --git a/sysom_server/sysom_config_trace/setup.py b/sysom_server/sysom_config_trace/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..b9bebe8fd297e1f6f4964c90652cf7718db24859 --- /dev/null +++ b/sysom_server/sysom_config_trace/setup.py @@ -0,0 +1,36 @@ +# coding: utf-8 + +import sys +from setuptools import setup, find_packages + +NAME = "config_trace" +VERSION = "1.0.0" +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = [ + "connexion", + "swagger-ui-bundle>=0.0.2" +] + +setup( + name=NAME, + version=VERSION, + description="Configration Tracability", + author_email="", + url="", + keywords=["Swagger", "Configration Tracability"], + install_requires=REQUIRES, + packages=find_packages(), + package_data={'': ['swagger/swagger.yaml']}, + include_package_data=True, + entry_points={ + 'console_scripts': ['config_trace=config_trace.__main__:main']}, + long_description="""\ + No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + """ +) diff --git a/sysom_server/sysom_config_trace/tox.ini b/sysom_server/sysom_config_trace/tox.ini new file mode 100644 index 0000000000000000000000000000000000000000..2751b218c1d2385c081c114c9094fc5e388126dc --- /dev/null +++ b/sysom_server/sysom_config_trace/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py38 + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= + nosetests \ + [] \ No newline at end of file diff --git a/sysom_web/config/routes.js b/sysom_web/config/routes.js index 3232d8ccd3538f0352fa2b5da7c6533f998dd040..9202e465db332947309e3c3f6cf5cb9f2bb34d2c 100644 --- a/sysom_web/config/routes.js +++ b/sysom_web/config/routes.js @@ -375,6 +375,35 @@ export default [ }, ] }, + { + path: "/configtrace", + name: "ConfigTrace", + routes: [ + { + path: '/configtrace', + redirect: '/configtrace/domains', + }, + { + path: '/configtrace/domains', + name: "Domains", + component: './configtrace/domains', + }, + { + path: '/configtrace/confs', + name: "Confs", + component: './configtrace/confs', + }, + { + path: '/configtrace/alerts', + name: "Alerts", + component: './configtrace/alerts', + }, + { + path: '/configtrace/confs/:host?', + component: './configtrace/confs', + }, + ] + }, { path: '/', redirect: '/welcome', diff --git a/sysom_web/package.json b/sysom_web/package.json index a361f2f06d727013961819753f7c9e78d5abb7b7..1c9cf0687aa1937b7d8ed06b01b0853053dba0b1 100644 --- a/sysom_web/package.json +++ b/sysom_web/package.json @@ -51,6 +51,7 @@ "@ant-design/pro-card": "^1.20.22", "@ant-design/pro-components": "^2.6.16", "@ant-design/pro-descriptions": "^1.12.6", + "@ant-design/pro-field": "^1.36.7", "@ant-design/pro-form": "^1.74.7", "@ant-design/pro-layout": "^6.5.0", "@ant-design/pro-table": "^2.80.8", @@ -61,12 +62,15 @@ "@uiw/codemirror-theme-tokyo-night": "^4.21.11", "@uiw/react-codemirror": "^4.21.11", "@umijs/route-utils": "^2.0.3", + "ahooks": "^2.0.0", "antd": "4.24.8", "browserslist": "^4.20.2", "caniuse-lite": "^1.0.30001320", "classnames": "^2.2.6", "js-export-excel": "^1.1.4", "moment": "^2.29.4", + "monaco-editor": "^0.36.0", + "monaco-editor-webpack-plugin": "^7.0.1", "node-fetch": "^3.3.1", "react": "17.x", "react-dev-inspector": "^1.8.4", diff --git a/sysom_web/src/pages/configtrace/alerts.jsx b/sysom_web/src/pages/configtrace/alerts.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1c807eccea7ec235090d9d54a7e3d462795b7e71 --- /dev/null +++ b/sysom_web/src/pages/configtrace/alerts.jsx @@ -0,0 +1,54 @@ +import { useRequest, useParams } from 'umi'; +import { useState, useRef, useEffect } from 'react'; +import { Statistic } from 'antd'; +import { getDomainList } from "./service"; +import ProCard from '@ant-design/pro-card'; +import DomainList from './components/DomainList'; +import AlertList from './components/AlertList'; + +const AlertsList = () => { + const { data, error, loading } = useRequest(getDomainList) + // 获取data中的第一个元素的值 + const { domain } = data?.[0]?.domain_name || {} + const [domainName, setDomainName] = useState(domain || '') + const [collapsed, setCollapsed] = useState(false) + + const onCollapsed = () => { + setCollapsed(!collapsed); + } + + return ( + <> + + + + + + + + + { + setDomainName(domainName); + }} + onLoad={dataSource => { + if (dataSource.length > 0 && !!dataSource[0].domain_name) { + setDomainName(dataSource[0].domain_name) + } + } + } /> + + + {collapsed ? + >>





+ : <<





+ } +
+ +
+ + ); +}; + +export default AlertsList; diff --git a/sysom_web/src/pages/configtrace/components/AlertList.jsx b/sysom_web/src/pages/configtrace/components/AlertList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..846f671be07eaf51515a48d765c55ffa3620a11e --- /dev/null +++ b/sysom_web/src/pages/configtrace/components/AlertList.jsx @@ -0,0 +1,66 @@ +import ProTable from '@ant-design/pro-table'; +import { useRef, useState, useEffect } from 'react' +import { getConfsAlerts } from '../service'; + +/** + * A Table components display Confs list + * @param {*} props + * @returns + */ +const AlertList = (props) => { + const [alerts, setAlerts] = useState([]); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await getConfsAlerts({ domainName: props.domainName }); + if (response.success) { + setAlerts(response.data); + } + } catch (error) { + console.error('Error fetching data:', error); + } + }; + if (props.domainName) { + fetchData(); + } + }, [props]); + + + // 表格包含四列分别是: ip、时间、涉及的文件和命令 + const columns = [ + { + title: 'IP', + dataIndex: 'ip', + key: 'ip', + }, + { + title: '时间', + dataIndex: 'time', + key: 'time', + }, + { + title: '变更的文件', + dataIndex: 'file', + key: 'file', + }, + { + title: '命令', + dataIndex: 'command', + key: 'command', + } + ] + + + return ( + + ); +} + +export default AlertList; \ No newline at end of file diff --git a/sysom_web/src/pages/configtrace/components/ConfsDiffView.jsx b/sysom_web/src/pages/configtrace/components/ConfsDiffView.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9ef51e5b0b284f8bb86d29e32a6c589620d89b08 --- /dev/null +++ b/sysom_web/src/pages/configtrace/components/ConfsDiffView.jsx @@ -0,0 +1,41 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { useEffect, forwardRef, useRef } from 'react'; + +let CodeDiffEditor = (props, ref) => { + const divRef = useRef(); + // 初始化编辑器 + useEffect(() => { + const editor = monaco.editor.createDiffEditor(divRef.current, { + readOnly: !props.editable, + renderSideBySide: true, + theme: 'vs-dark', + originalEditable: false, + }); + + const originalModel = monaco.editor.createModel(props.left, 'plaintext'); + const modifiedModel = monaco.editor.createModel(props.right, 'plaintext'); + + editor.setModel({ + original: originalModel, + modified: modifiedModel, + }); + + ref.current = modifiedModel; + + return () => { + originalModel.setValue(''); + modifiedModel.setValue(''); + originalModel.dispose(); + modifiedModel.dispose(); + editor.dispose(); + }; + }, []); + + return ( +
+ ); +}; + +CodeDiffEditor = forwardRef(CodeDiffEditor); + +export default CodeDiffEditor; \ No newline at end of file diff --git a/sysom_web/src/pages/configtrace/components/ConfsList.jsx b/sysom_web/src/pages/configtrace/components/ConfsList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..14f22a6b795ecf06707a21d7fefe6df9fad84093 --- /dev/null +++ b/sysom_web/src/pages/configtrace/components/ConfsList.jsx @@ -0,0 +1,339 @@ +import ProTable from '@ant-design/pro-table'; +import { PlusOutlined } from '@ant-design/icons'; +import { useRef, useState, useEffect } from 'react' +import { getConfsByDomainAndHost, updateDomainConfs, deleteDomainConfs, syncDomainConfs } from '../service'; +import { Popconfirm, Button, message, Modal } from 'antd'; +import ProCard from '@ant-design/pro-card'; +import { DrawerForm, ProFormList, ProFormSelect, ProFormText, ProFormTextArea, ProFormDependency } from '@ant-design/pro-form'; +import { getHostIP } from '@/pages/host/service'; +import CodeDiffEditor from './ConfsDiffView'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +/** + * A Table components display Confs list + * @param {*} props + * @returns + */ +const ConfsList = (props) => { + const actionRef = useRef(); + const editRef = useRef(); + const [loading, setLoading] = useState(false); + const [domainConfs, setDomainConfs] = useState([]); + const [domainConfsListModal, setDomainConfsListModal] = useState(false); + const [detailDiffView, setDetailDiffView] = useState(false); + const [compareFiles, setComapareFiles] = useState([]); + const [isEdit, setIsEdit] = useState(false); + + const [hostIP, setHostIP] = useState(props.hostId) || ''; + const [domain, setDomain] = useState(props.domainName) || ''; + + const onSubmit = async (e) => { + // 调用添加配置接口 + let query = { + "domainName": props.domainName, + } + + let params = []; + for (let i = 0; i < e.confs.length; i++) { + let param = {}; + param["filePath"] = e.confs[i]['filePath'] + if (e.confs[i]['host']) { + query['hostName'] = e.confs[i]['host']; + } else { + param["expectContent"] = e.confs[i]['expectContent']; + } + params.push(param); + } + + + let res = await updateDomainConfs(query, params); + if (res.success) { + message.success("添加成功!"); + actionRef.current?.reload(); + } else { + message.error("添加失败"); + } + return true; + + } + + const fetchData = async () => { + try { + const response = await getConfsByDomainAndHost({ domainName: props.domainName, hostName: props.hostId }); + if (response.success) { + setDomainConfs(response.data['conf_files']); + } + } catch (error) { + console.error('Error fetching data:', error); + } + }; + useEffect(() => { + setDomain(props.domainName); + setHostIP(props.hostId); + if (props.domainName) { + fetchData(); + } + }, [props]); + + + const ConfsListColumns = [ + { + title: "配置文件", + dataIndex: "file_path", + valueType: "text", + width: "10%", + }, + { + title: "期望配置详情", + dataIndex: "expect_content", + valueType: "textarea", + width: "30%", + render: (text, record) => ( +
+ {text} +
+ ), + }, + { + title: "实际配置详情", + dataIndex: "real_content", + valueType: "textarea", + width: "30%", + render: (text, record) => ( +
+ {text} +
+ ), + }, + { + title: "修改日志", + dataIndex: "change_log", + valueType: "textarea", + width: "15%", + }, + { + title: "操作", + valueType: "option", + dataIndex: "option", + width: "10%", + render: (dom, record) => [ + + { + // 左右对比视图给出git compare的画面 + setDetailDiffView(true); + setComapareFiles([record.file_path, record.expect_content, record.real_content]); + setIsEdit(false); + }}> 详情 + , + , + + { + // 左右对比视图给出git compare的画面 + setDetailDiffView(true); + setComapareFiles([record.file_path, record.expect_content, record.real_content]); + setIsEdit(true); + }}> 修改 + , + , + + { + const response = await deleteDomainConfs( + { domainName: props.domainName, hostName: props.hostId, filePath: record.file_path } + ); + if (response.success) { + message.success("删除成功"); + } + actionRef.current?.reload(); + }}> + 删除 + + + ], + } + ]; + + + return ( + <> + record.filePath} + toolBarRender={() => [ + { + const response = await syncDomainConfs( + { domainName: props.domainName, hosts: [props.hostId] } + ); + if (response.success) { + message.success("同步成功"); + } else { + message.error("同步失败"); + } + actionRef.current?.reload(); + }}> + + , + ] + } + pagination={{ + showQuickJumper: true, + pageSize: 10, + }} + defaultSize="small" + search={false} + onRefresh={() => { + console.log("refresh...."); + fetchData(); + }} + /> + + < DrawerForm + onVisibleChange={setDomainConfsListModal} + visible={domainConfsListModal} + title="添加配置" + onFinish={onSubmit} + drawerProps={{ + destroyOnClose: true, + }} + > + + { + return ( + + {listDom} + + ); + }} + > + + + + + {({ mode }) => { + if (mode === 'manual') { + return + } else { + return + } + }} + + + + { + setLoading(true); + + // 更新domain配置 + let query = { + "domainName": props.domainName, + }; + let param = [{ + "filePath": compareFiles[0], + "expectContent": editRef?.current?.getValue() + }]; + + let res = await updateDomainConfs(query, param); + setLoading(false); + if (res.success) { + setDetailDiffView(false); + message.success("修改成功!"); + } else { + message.error("修改失败!" + res.msg); + } + }} + onCancel={() => { setDetailDiffView(false); setComapareFiles([]); }} + okButtonProps={{ + disabled: !isEdit, + }} + > + + + + ); +} + +export default ConfsList; \ No newline at end of file diff --git a/sysom_web/src/pages/configtrace/components/DomainList.jsx b/sysom_web/src/pages/configtrace/components/DomainList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4ef28cbc00c2c15ea2cc9b1835f0a3d5fdbe73ea --- /dev/null +++ b/sysom_web/src/pages/configtrace/components/DomainList.jsx @@ -0,0 +1,219 @@ +import ProTable from '@ant-design/pro-table'; +import { PlusOutlined } from '@ant-design/icons'; +import { useRef, useState } from 'react' +import { getDomainList, getHostListSrv, deleteDomain, updateDomain, addDomain } from '../service'; +import { message, Popconfirm, Button } from 'antd'; +import DomainModalForm from './DomainModalForm'; + +/** + * A Table components display host list + * @param {*} props + * @returns + */ +let DomainList = (props, ref) => { + const [domainModalFormVisible, setDomainModalFormVisible] = useState(false); + const [domainModalFormMode, setDomainModalFormMode] = useState(0); + const [domainModalFormTitle, setDomainModalFormTitle] = useState('New Domain'); + const domainModalFormRef = useRef(); + const actionRef = useRef(); + const showDomainModal = () => { + setDomainModalFormVisible(true); + } + + const handleAddDomain = async (fields) => { + setDomainModalFormTitle('添加域'); + setDomainModalFormMode(0); + const hide = message.loading('正在添加'); + const token = localStorage.getItem('token'); + try { + let res = await addDomain({ ...fields }, token); + hide(); + if (res.success) { + message.success("添加成功"); + setDomainModalFormVisible(false); + return true; + } else { + message.error(res.data); + setDomainModalFormVisible(false); + return false; + } + } catch (error) { + hide(); + setDomainModalFormVisible(false); + } + actionRef.current.reload(); + }; + + const handleUpdateDomain = async (fields) => { + const hide = message.loading('正在修改'); + const token = localStorage.getItem('token'); + try { + let res = await updateDomain({ ...fields }, token); + hide(); + if (res.success) { + message.success("修改成功"); + setDomainModalFormVisible(false); + return true; + } else { + message.error(res.data); + setDomainModalFormVisible(false); + return false; + } + } catch (error) { + hide(); + setDomainModalFormVisible(false); + } + }; + + + const handleDeleteDomain = async (fields) => { + const hide = message.loading('正在删除'); + const token = localStorage.getItem('token'); + try { + let res = await deleteDomain({ ...fields }, token); + if (res.success) { + message.success("删除成功"); + hide(); + return true; + } else { + message.error(res.data); + hide(); + return false; + } + } catch (error) { + hide(); + } + }; + + const DomainListColumns = [ + { + title: "域名称", + key: "domain_name", + dataIndex: 'domain_name', + render: (dom, entity) => { + return ( + { + props?.onClick?.(entity.domain_name) + }} + > + {dom} + + ); + }, + }, + { + title: '优先级', + key: "priority", + dataIndex: 'priority', + initialValue: 'all', + filters: true, + onFilter: true, + }, + { + title: '开启追踪', + dataIndex: 'enable_trace', + key: 'enable_trace', + valueEnum: { + true: { + text: '是' + }, + false: { + text: '否' + } + }, + filters: true, + onFilter: true, + }, + { + title: '操作', + dataIndex: "operate", + valueType: "operate", + render: (_, record) => [ + + { + // 触发编辑主机信息 + setDomainModalFormTitle('修改域'); + setDomainModalFormMode(1); // 设置 HostModalForm 的模式为 “编辑主机信息模式” + setDomainModalFormVisible(true); // 显示模态框 + domainModalFormRef.current.setFieldsValue({ // 将当前选中的主机信息填充到模态框表单中 + ...record, + }); + }}>编辑 + , + , + + { + await handleDeleteDomain(record); + actionRef.current?.reload(); + }}> + 删除 + + + ], + }, + ]; + + + return ( + <> + record?.domain_name} + pagination={{ + showQuickJumper: true, + pageSize: 10, + }} + toolBarRender={() => [ + ]} + defaultSize="small" + search={false} + {...props} + /> + + { + let success = false; + // 针对不同的模式,执行不同的网络请求 + if (value.mode == 0) { + // 添加domain + success = await handleAddDomain(value); + } else { + // 编辑domain + success = await handleUpdateDomain(value); + } + + if (success) { + setDomainModalFormVisible(false); + if (actionRef.current) { + actionRef.current.reload(); + } + } + }} + /> + + + ); +} + +// DomainList = forwardRef(DomainList); + +export default DomainList \ No newline at end of file diff --git a/sysom_web/src/pages/configtrace/components/DomainModalForm.jsx b/sysom_web/src/pages/configtrace/components/DomainModalForm.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a3c47fc6d75e457290607b0fe440fb245115c314 --- /dev/null +++ b/sysom_web/src/pages/configtrace/components/DomainModalForm.jsx @@ -0,0 +1,163 @@ +import { ModalForm, ProFormText, ProFormTextArea, ProFormSelect, ProFormRadio } from '@ant-design/pro-form'; +import { useState } from 'react'; +import { useImperativeHandle, useRef, forwardRef } from 'react'; +import * as PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'umi'; +const MODE_ADD_DOMAIN = 0 +const MODE_EDIT_DOMAIN = 1 + +/** + * 主机信息模态表单 + * 1. 功能一:用于实现主机添加 + * 2. 功能二:用于实现主机信息编辑 + * @param {*} props + * props.mode => 模式: 0 => 添加主机 + * 1 => 修改主机信息 + * props.titl => 模态框顶部的标题 + * props.visible => 模态框是否可见 + * props.modalWidth => 模态框的宽度,默认为 440px + * props.onVisibleChange => 模态框可见性发生变动时触发 + * props.onFinish => 表单提交时触发 + */ +let DomainModalForm = (props, ref) => { + const { + mode, + title, + visible, + modalWidth, + onVisibleChange, + onFinish + } = props; + + const modalFormRef = useRef(); + const intl = useIntl(); + const [id, setId] = useState(-1); + + // https://zh-hans.reactjs.org/docs/hooks-reference.html#useimperativehandle + // 设置一些暴露给外部调用的函数,外部可以通过ref的方式调用 + useImperativeHandle(ref, () => ({ + // 填充表单的值 + setFieldsValue: (values) => { + if (!modalFormRef) { + modalFormRef = ref; + } + modalFormRef.current?.setFieldsValue(values); + }, + // getFieldValue: modalFormRef.getFieldValue, // 获取某个字段的值 + // getFieldsValue: modalFormRef.getFieldsValue, // 获取表单的当前值 + // getFieldsFormatValue: modalFormRef.getFieldsFormatValue, // 获取格式化之后所有数据 + // getFieldFormatValue: modalFormRef.getFieldFormatValue, // 获取格式化之后的单个数据 + // validateFieldsReturnFormatValue: modalFormRef.validateFieldsReturnFormatValue, // 校验字段后返回格式化之后的所有数据 + })); + + return ( + { + onFinish({ + mode: mode, + id: id, + ...value + }) + }} + > + + + ), + }, + ]} + width="md" + name="domain_name" + disabled={mode == MODE_EDIT_DOMAIN} + /> + + ), + }, + ]} + width="md" + name="priority" + /> + + ), + }, + ]} + options={[ + { + label: '是', + value: true, + }, + { + label: '否', + value: false, + }, + ]} + /> + + ) +} + +DomainModalForm = forwardRef(DomainModalForm); + +DomainModalForm.displayName = "DomainModalForm"; + +DomainModalForm.propTypes = { + mode: PropTypes.number, + title: PropTypes.string, + visible: PropTypes.bool, + modalWidth: PropTypes.string, + onVisibleChange: PropTypes.func, + onFinish: PropTypes.func +} + +// Props 参数默认值 +DomainModalForm.defaultProps = { + mode: 0, + title: "New Domain", + visible: false, + modalWidth: "440px", + onVisibleChange: () => { }, + onFinish: () => { }, +} + +export default DomainModalForm; \ No newline at end of file diff --git a/sysom_web/src/pages/configtrace/components/HostList.jsx b/sysom_web/src/pages/configtrace/components/HostList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..efc1aaa9883a4f929e1b62f4c0ed9ce47d1e389f --- /dev/null +++ b/sysom_web/src/pages/configtrace/components/HostList.jsx @@ -0,0 +1,164 @@ +import ProTable from '@ant-design/pro-table'; +import { PlusOutlined } from '@ant-design/icons'; +import { useIntl, FormattedMessage, history } from 'umi'; +import { useRef, useState, useEffect } from 'react' +import { getHostListSrv, addDomainHosts, deleteDomainHost } from '../service'; +import { message, Popconfirm, Button, Transfer } from 'antd'; +import { ModalForm } from '@ant-design/pro-form'; +import { getHost } from '../../host/service'; +import { isToken } from 'typescript'; +/** + * A Table components display host list + * @param {*} props + * @returns + */ +const HostList = (props) => { + const [domain, setDomain] = useState(props?.domainName) || ''; + const actionRef = useRef(); + const [showDomainHostListModal, setShowDomainListModal] = useState(false); + + const [allHosts, setAllHosts] = useState([]); + const [toAddHosts, setToAddHosts] = useState([]); + const [domainHosts, setDomainHosts] = useState([]); + + const showDomainHostList = () => { + setShowDomainListModal(true); + getHost().then((res) => { + setAllHosts(res.data.map(item => ({ ...item, key: item.ip }))); + }); + } + + const onSubmit = async (params) => { + const hide = message.loading('正在添加'); + const token = localStorage.getItem('token'); + try { + await addDomainHosts({ domain_name: props.domainName, hosts: toAddHosts }, token); + hide(); + setShowDomainListModal(false); + return true; + } catch (error) { + hide(); + setShowDomainListModal(false); + } + }; + + const HostListColumns = [ + { + title: "主机名", + dataIndex: "host_id", + valueType: "textarea", + }, + { + title: "IP", + dataIndex: "ip", + valueType: "textarea", + }, + { + title: "IPv6", + dataIndex: "ipv6", + valueType: "textarea", + }, + { + title: "操作", + valueType: "option", + dataIndex: "option", + render: (dom, record) => [ + + { + history.push('/configtrace/confs/' + record.host_id); + }}> 详情 + , + , + + { + const token = localStorage.getItem('token'); + await deleteDomainHost({ domain_name: props.domainName, host: record.host_id }, token); + actionRef.current?.reload(); + }}> + 删除 + + + ], + } + ]; + + useEffect(() => { + const fetchData = async () => { + try { + const response = await getHostListSrv(props.domainName); + setDomainHosts(response.data); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + + if (props.domainName) { + setDomain(props.domainName); + fetchData(); + } + }, [props]); + + return ( + <> + record.host_id} + toolBarRender={() => [ + ]} + pagination={{ + showQuickJumper: true, + pageSize: 10, + }} + defaultSize="small" + search={false} + {...props} + /> + + {/* 实现一个左右型的列表框,支持从左侧选择主机列表,放到右侧列表中,点击确认后,可以自动添加到主机列表中 */} + + item.ip} + /> + + + + ); +} + +export default HostList \ No newline at end of file diff --git a/sysom_web/src/pages/configtrace/confs.jsx b/sysom_web/src/pages/configtrace/confs.jsx new file mode 100644 index 0000000000000000000000000000000000000000..75af637f6f6f161471059adf6e74d9c898d01665 --- /dev/null +++ b/sysom_web/src/pages/configtrace/confs.jsx @@ -0,0 +1,57 @@ +import { useRequest, useParams } from 'umi'; +import { useState, useRef } from 'react'; +import ProCard from '@ant-design/pro-card'; +import { Statistic } from 'antd'; +import { getDomainList } from './service'; +import DomainList from './components/DomainList'; +import ConfsList from './components/ConfsList'; + +const Confs = () => { + + const { data, error, loading } = useRequest(getDomainList) + const { domain } = data?.[0]?.domain_name || {} + const [domainName, setDomainName] = useState(domain || '') + const [collapsed, setCollapsed] = useState(false) + const param = useParams(); + const hostId = param.host || ''; + + const onCollapsed = () => { + setCollapsed(!collapsed); + } + + return ( + <> + {/* 左右分布,左侧给出所有的domain列表,右侧给出当前domain下的所有配置列表,配置列表中包括期望配置、真实配置*/} + + + + + + + + + { + setDomainName(domainName) + }} + onLoad={dataSource => { + if (dataSource.length > 0 && !!dataSource[0].domain_name) { + setDomainName(dataSource[0].domain_name) + } + } + } /> + + + {collapsed ? + >>





+ : <<





+ } +
+ +
+ + ); +}; + +export default Confs; diff --git a/sysom_web/src/pages/configtrace/domain_hosts.jsx b/sysom_web/src/pages/configtrace/domain_hosts.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1dc4dac22d1b6a55274a4c228742dfdd50e5a257 --- /dev/null +++ b/sysom_web/src/pages/configtrace/domain_hosts.jsx @@ -0,0 +1,85 @@ +import React, { useState, useEffect } from 'react'; +import { GridContent, PageContainer } from '@ant-design/pro-layout'; +import ProTable from "@ant-design/pro-table"; +import { Button, Layout, Menu } from 'antd'; +import { useIntl, FormattedMessage, Link } from 'umi'; + +const { Sider, Content } = Layout; + +const DomainHostList = () => { + const [menuData, setMenuData] = useState([]); + const [selectedName, setSelectedName] = useState(''); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch('/api/v1/configtrace/domains'); + const data = await response.json(); + const sortedData = data.sort((a, b) => a.priority - b.priority); + setMenuData(sortedData); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + + fetchData(); + }, []); + + const handleMenuClick = (e) => { + setSelectedName(e.domain_name); + }; + + const filteredData = menuData.find(item => item.domain_name === selectedName); + + const columns = [ + { + title: , + dataIndex: "HostName", + valueType: "textarea" + }, + { + title: , + dataIndex: "IPv4", + valueType: "textarea", + }, + { + title: , + dataIndex: "IPv6", + valueType: "textarea", + }, + { + title: , + dataIndex: "option", + valueType: "option", + render: (_, record) => [ + + {} + + ], + } + ]; + return ( + + + + { + menuData.map(item => ( + {item.domain_name} + Delete + + )) + } + + + + + + ); +}; + +export default DomainHostList; diff --git a/sysom_web/src/pages/configtrace/domains.jsx b/sysom_web/src/pages/configtrace/domains.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1991a23dda6facd33434cc87c36b30e16e9072b4 --- /dev/null +++ b/sysom_web/src/pages/configtrace/domains.jsx @@ -0,0 +1,60 @@ +import { Statistic } from 'antd'; +import { useIntl, useRequest } from 'umi'; +import { useState, useRef } from 'react' +import { addDomain, updateDomain, deleteDomain, getDomainList } from './service'; +import ProCard from '@ant-design/pro-card'; +import DomainList from './components/DomainList'; +import HostList from './components/HostList'; + +const Dashboard = () => { + const intl = useIntl(); + const actionRef = useRef(); + const { data, error, loading } = useRequest(getDomainList) + // 获取data中的第一个元素的值 + const { domain } = data?.[0]?.domain_name || {} + const [domainName, setDomainName] = useState(domain || '') + const [collapsed, setCollapsed] = useState(false) + + const onCollapsed = () => { + setCollapsed(!collapsed); + } + + return ( + <> + + + + + + + {/* + + */} + + + { + setDomainName(domainName) + }} + onLoad={dataSource => { + if (dataSource.length > 0 && !!dataSource[0].domain_name) { + setDomainName(dataSource[0].domain_name) + } + } + } /> + + + {collapsed ? + >>





+ : <<





+ } +
+ +
+ + ); +}; + +export default Dashboard; diff --git a/sysom_web/src/pages/configtrace/service.js b/sysom_web/src/pages/configtrace/service.js new file mode 100644 index 0000000000000000000000000000000000000000..fa274927c97835187827023b9ba5b41e0372c702 --- /dev/null +++ b/sysom_web/src/pages/configtrace/service.js @@ -0,0 +1,311 @@ +// @ts-ignore + +/* eslint-disable */ +import { request } from 'umi'; +import _ from "lodash"; +import { async } from '@antv/x6/lib/registry/marker/async'; +import { message } from 'antd'; +import { OmitProps } from 'antd/lib/transfer/ListBody'; + +const ACCOUNT_URL = '/api/v1/configtrace/'; + +export async function getDomainList(params, options) { + try { + const msg = await request(ACCOUNT_URL + 'domains', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token') + }, + // params: { ...params }, + ...(options || {}), + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + console.error('Fetch domain list error. err:', e); + return { + success: false, + }; + } +} + +export async function getHostListSrv(domainName, params, options) { + try { + const msg = await request(ACCOUNT_URL + 'domains/' + domainName + '/hosts', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token') + }, + ...(options || {}), + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + console.error('Fetch host list error. err:', e); + return { + data: [], + success: true, + total: 0, + }; + } +} + +export async function addDomain(params, token, options) { + try { + const msg = await request(ACCOUNT_URL + 'domains/' + params.domain_name, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token, + }, + data: { + "priority": parseInt(params.priority), + "enable_trace": Boolean(params.enable_trace), + }, + ...(options || {}), + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: msg, + }; + } +} + +export async function updateDomain(params, token, options) { + try { + const msg = await request(ACCOUNT_URL + 'domains/' + params.domain_name, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token, + }, + data: { + "priority": parseInt(params.priority), + "enable_trace": Boolean(params.enable_trace), + }, + ...(options || {}), + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: msg, + }; + } +} + +export async function deleteDomain(params, token, options) { + try { + const msg = await request(ACCOUNT_URL + 'domains/' + params.domain_name, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token, + }, + ...(options || {}), + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: msg, + }; + } +} + +export async function addDomainHosts(params, token, options) { + for (let i = 0; i < params.hosts.length; i++) { + try { + const msg = await request(ACCOUNT_URL + 'domains/' + params.domain_name + "/hosts/" + params.hosts[i], { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token, + }, + ...(options || {}), + }); + message.success('添加成功'); + } catch (error) { + message.error("添加失败"); + } + } + return { + success: true, + data: [], + total: 0, + } +} + +export async function deleteDomainHost(params, token, options) { + try { + const msg = await request(ACCOUNT_URL + 'domains/' + params.domain_name + "/hosts/" + params.host, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token, + }, + ...(options || {}), + }); + message.success('删除成功'); + } catch (error) { + message.error("删除失败"); + } + return { + success: true, + total: 0, + data: [], + } +} + +export async function getConfsByDomainAndHost(params, options) { + console.log(params); + try { + const msg = await request(ACCOUNT_URL + 'confs', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token') + }, + params: { + domainName: params.domainName, + hostName: params.hostName, + }, + ...(options || {}), + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + console.error('Fetch confs list error. err:', e); + return { + success: true, + data: [], + total: 0, + }; + } +} + +export async function updateDomainConfs(params, data) { + try { + const msg = await request(ACCOUNT_URL + 'confs', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token'), + }, + params: params, + data: { + confs: data, + }, + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: e, + }; + } +} + +export async function deleteDomainConfs(params, options) { + try { + const msg = await request(ACCOUNT_URL + 'confs', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token'), + }, + params: params, + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: msg, + }; + } +} + +export async function syncDomainConfs(data, options) { + try { + const msg = await request(ACCOUNT_URL + 'confs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token'), + }, + params: { + action: "sync", + }, + data: data, + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: msg, + }; + } +} + +export async function getConfsAlerts(params, options) { + try { + const msg = await request(ACCOUNT_URL + 'alerts', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': localStorage.getItem('token'), + }, + params: { + domainName: params.domainName, + action: "alerts", + }, + }); + return { + data: msg, + success: true, + total: msg?.length, + }; + } catch (e) { + return { + success: false, + data: e, + }; + } +} \ No newline at end of file