From 283b00b20fc7d5c0b9fd5ccc813cd337f992504a Mon Sep 17 00:00:00 2001 From: zhu-yuncheng Date: Fri, 9 Sep 2022 18:26:14 +0800 Subject: [PATCH] update to v1.2.5-3 --- ...d-create-time-attribute-for-workflow.patch | 181 +++++ ...return-validation-of-config-analysis.patch | 90 +++ 0004-update-get-host-info.patch | 649 ++++++++++++++++++ 0005-web-fine-tuning.patch | 346 ++++++++++ A-Ops.spec | 16 +- 5 files changed, 1281 insertions(+), 1 deletion(-) create mode 100644 0002-add-create-time-attribute-for-workflow.patch create mode 100644 0003-fix-ragdoll-add-return-validation-of-config-analysis.patch create mode 100644 0004-update-get-host-info.patch create mode 100644 0005-web-fine-tuning.patch diff --git a/0002-add-create-time-attribute-for-workflow.patch b/0002-add-create-time-attribute-for-workflow.patch new file mode 100644 index 0000000..ba801ad --- /dev/null +++ b/0002-add-create-time-attribute-for-workflow.patch @@ -0,0 +1,181 @@ +From a9cdf00405a8c761b36c0dcef7eed6c19e367e86 Mon Sep 17 00:00:00 2001 +From: zhu-yuncheng +Date: Wed, 7 Sep 2022 15:32:20 +0800 +Subject: [PATCH] add create time attribute for workflow, bugfix for default + diagnose mode when assign model + +--- + .../controllers/workflow_controller.py | 2 ++ + .../aops_check/core/rule/default_workflow.py | 30 +++++-------------- + .../aops_check/database/dao/workflow_dao.py | 16 +++++++--- + .../aops_check/database/factory/table.py | 1 + + .../aops_check/utils/schema/workflow.py | 2 ++ + 5 files changed, 24 insertions(+), 27 deletions(-) + +diff --git a/aops-check/aops_check/controllers/workflow_controller.py b/aops-check/aops_check/controllers/workflow_controller.py +index 5f91c11..7f78d1a 100644 +--- a/aops-check/aops_check/controllers/workflow_controller.py ++++ b/aops-check/aops_check/controllers/workflow_controller.py +@@ -16,6 +16,7 @@ Author: + Description: + """ + import uuid ++import time + from typing import Dict, Tuple + from flask import jsonify, request + +@@ -75,6 +76,7 @@ class CreateWorkflow(BaseResponse): + args['step'] = args.get('step', 60) + args["period"] = args.get("period", 60) + args["alert"] = args.get("alert", {}) ++ args["create_time"] = int(time.time()) + args["status"] = "hold" + workflow_id = str(uuid.uuid1()).replace('-', '') + args['workflow_id'] = workflow_id +diff --git a/aops-check/aops_check/core/rule/default_workflow.py b/aops-check/aops_check/core/rule/default_workflow.py +index 3d82e5c..a5fb130 100644 +--- a/aops-check/aops_check/core/rule/default_workflow.py ++++ b/aops-check/aops_check/core/rule/default_workflow.py +@@ -45,11 +45,8 @@ class DefaultWorkflow: + step_detail = self.__app.info["detail"] + try: + if "singlecheck" in step_detail: +- failed_list, workflow_detail["singlecheck"] = DefaultWorkflow.__assign_single_item_model(hosts, +- step_detail["singlecheck"]) +- if failed_list: +- LOGGER.debug( +- "Query metric list of host '%s' failed when assign " % failed_list) ++ workflow_detail["singlecheck"] = DefaultWorkflow.__assign_single_item_model(hosts, ++ step_detail["singlecheck"]) + if "multicheck" in step_detail: + workflow_detail["multicheck"] = DefaultWorkflow.__assign_multi_item_model( + hosts) +@@ -170,7 +167,7 @@ class DefaultWorkflow: + return send_result + + @staticmethod +- def __assign_single_item_model(hosts_info: list, config: dict = None) -> Tuple[list, Dict[str, Dict[str, str]]]: ++ def __assign_single_item_model(hosts_info: list, config: dict = None) -> Dict[str, Dict[str, str]]: + """ + assign single item check model + Args: +@@ -182,25 +179,12 @@ class DefaultWorkflow: + Raises: + ValueError + """ +- data_proxy = DataDao(configuration) +- if not data_proxy.connect(): +- raise WorkflowModelAssignError("Connect to prometheus failed.") +- +- host_algo = {} +- failed_list = [] ++ host_model = {} + ++ kpi_model = ModelAssign.assign_kpi_model_by_name(config) + for host_ip in hosts_info: +- # query host's metric list +- status_code, metric_label_list = data_proxy.query_metric_list_of_host( +- host_ip) +- if status_code != SUCCEED: +- failed_list.append(host_ip) +- continue +- +- metric_list = DefaultWorkflow.__get_metric_names(metric_label_list) +- host_algo[host_ip] = ModelAssign.assign_kpi_model_by_name( +- metric_list, config) +- return failed_list, host_algo ++ host_model[host_ip] = kpi_model ++ return host_model + + @staticmethod + def __get_metric_names(metric_label_list: list) -> list: +diff --git a/aops-check/aops_check/database/dao/workflow_dao.py b/aops-check/aops_check/database/dao/workflow_dao.py +index 82b2a0c..75fb7cb 100644 +--- a/aops-check/aops_check/database/dao/workflow_dao.py ++++ b/aops-check/aops_check/database/dao/workflow_dao.py +@@ -303,8 +303,13 @@ class WorkflowDao(MysqlProxy, ElasticsearchProxy): + if not total_count: + return result + +- page, per_page = data.get('page'), data.get('per_page') +- processed_query, total_page = sort_and_page(workflow_query, None, None, per_page, page) ++ direction, page, per_page = data.get('direction'), data.get('page'), data.get('per_page') ++ if data.get("sort"): ++ sort_column = getattr(Workflow, data["sort"]) ++ else: ++ sort_column = None ++ processed_query, total_page = sort_and_page(workflow_query, sort_column, direction, ++ per_page, page) + + workflow_id_list = [row.workflow_id for row in processed_query] + workflow_host = self._get_workflow_hosts(workflow_id_list) +@@ -339,7 +344,7 @@ class WorkflowDao(MysqlProxy, ElasticsearchProxy): + filters.add(Workflow.app_name.in_(filter_dict["app"])) + + workflow_query = self.session.query(Workflow.workflow_name, Workflow.workflow_id, +- Workflow.description, Workflow.status, ++ Workflow.description, Workflow.create_time, Workflow.status, + Workflow.app_name, Workflow.app_id, Workflow.domain) \ + .filter(*filters) + return workflow_query +@@ -386,6 +391,7 @@ class WorkflowDao(MysqlProxy, ElasticsearchProxy): + workflow_info = { + "workflow_name": row.workflow_name, + "description": row.description, ++ "create_time": row.create_time, + "status": row.status, + "app_name": row.app_name, + "app_id": row.app_id, +@@ -458,6 +464,7 @@ class WorkflowDao(MysqlProxy, ElasticsearchProxy): + "workflow_id": "workflow_id1", + "workflow_name": "workflow_name1", + "description": "a long description", ++ "create_time": 1662533585, + "status": "running/hold/recommending", + "app_name": "app1", + "app_id": "app_id1", +@@ -538,7 +545,7 @@ class WorkflowDao(MysqlProxy, ElasticsearchProxy): + filters.add(Workflow.workflow_id == workflow_id) + + workflow_query = self.session.query(Workflow.workflow_name, Workflow.workflow_id, +- Workflow.description, Workflow.status, ++ Workflow.description, Workflow.create_time, Workflow.status, + Workflow.app_name, Workflow.app_id, Workflow.domain, + Workflow.step, Workflow.period) \ + .filter(*filters).one() +@@ -548,6 +555,7 @@ class WorkflowDao(MysqlProxy, ElasticsearchProxy): + workflow_info = { + "workflow_name": workflow_query.workflow_name, + "description": workflow_query.description, ++ "create_time": workflow_query.create_time, + "status": workflow_query.status, + "app_name": workflow_query.app_name, + "app_id": workflow_query.app_id, +diff --git a/aops-check/aops_check/database/factory/table.py b/aops-check/aops_check/database/factory/table.py +index bf64084..4f7d4e2 100644 +--- a/aops-check/aops_check/database/factory/table.py ++++ b/aops-check/aops_check/database/factory/table.py +@@ -44,6 +44,7 @@ class Workflow(Base, MyBase): + workflow_id = Column(String(32), primary_key=True, nullable=False) + workflow_name = Column(String(50), nullable=False) + description = Column(String(100), nullable=False) ++ create_time = Column(Integer, nullable=False) + status = Column(String(20), nullable=False) + app_name = Column(String(20), nullable=False) + app_id = Column(String(32), nullable=False) +diff --git a/aops-check/aops_check/utils/schema/workflow.py b/aops-check/aops_check/utils/schema/workflow.py +index 44a29b9..2cb3123 100644 +--- a/aops-check/aops_check/utils/schema/workflow.py ++++ b/aops-check/aops_check/utils/schema/workflow.py +@@ -52,6 +52,8 @@ class QueryWorkflowListSchema(Schema): + filter = fields.Nested(WorkflowListFilterSchema, required=False) + page = fields.Integer(required=False, validate=lambda s: s > 0) + per_page = fields.Integer(required=False, validate=lambda s: 0 < s < 50) ++ sort = fields.String(required=False, validate=validate.OneOf(["create_time"])) ++ direction = fields.String(required=False, validate=validate.OneOf(["asc", "desc"])) + + + class ExecuteWorkflowSchema(Schema): +-- +2.33.0 + diff --git a/0003-fix-ragdoll-add-return-validation-of-config-analysis.patch b/0003-fix-ragdoll-add-return-validation-of-config-analysis.patch new file mode 100644 index 0000000..94fbbbe --- /dev/null +++ b/0003-fix-ragdoll-add-return-validation-of-config-analysis.patch @@ -0,0 +1,90 @@ +From 0cb6b9681fcbb3bdd1e05a903fde36a89f66abc0 Mon Sep 17 00:00:00 2001 +From: algorithmofdish +Date: Thu, 8 Sep 2022 16:40:33 +0800 +Subject: [PATCH] fix(ragdoll): add return validation of config analysis + +--- + gala-ragdoll/ragdoll/controllers/domain_controller.py | 10 +--------- + .../ragdoll/controllers/management_controller.py | 4 ++-- + gala-ragdoll/ragdoll/utils/object_parse.py | 4 ++++ + gala-ragdoll/ragdoll/utils/yang_module.py | 2 +- + 4 files changed, 8 insertions(+), 12 deletions(-) + +diff --git a/gala-ragdoll/ragdoll/controllers/domain_controller.py b/gala-ragdoll/ragdoll/controllers/domain_controller.py +index 3c14dba..3f14e02 100644 +--- a/gala-ragdoll/ragdoll/controllers/domain_controller.py ++++ b/gala-ragdoll/ragdoll/controllers/domain_controller.py +@@ -115,12 +115,4 @@ def query_domain(): # noqa: E501 + domain = Domain(domain_name = d_ll) + domain_list.append(domain) + +- if len(domain_list) > 0: +- codeNum = 200 +- else: +- codeNum = 400 +- codeString = "The current configuration domain is empty. Add a configuration domain first" +- base_rsp = BaseResponse(codeNum, codeString) +- return base_rsp, codeNum +- +- return domain_list, codeNum ++ return domain_list, 200 +diff --git a/gala-ragdoll/ragdoll/controllers/management_controller.py b/gala-ragdoll/ragdoll/controllers/management_controller.py +index 9044a7d..4d0fc49 100644 +--- a/gala-ragdoll/ragdoll/controllers/management_controller.py ++++ b/gala-ragdoll/ragdoll/controllers/management_controller.py +@@ -95,7 +95,7 @@ def add_management_confs_in_domain(body=None): # noqa: E501 + "please check the input parameters.") + return base_rsp, codeNum + content_string = object_parse.parse_conf_to_json(d_conf.file_path, d_conf.contents) +- if not json.loads(content_string): ++ if not content_string or not json.loads(content_string): + codeNum = 400 + base_rsp = BaseResponse(codeNum, "Input configuration content verification failed, " + + "please check the config.") +@@ -163,7 +163,7 @@ def add_management_confs_in_domain(body=None): # noqa: E501 + content = d_file.get("content") + content_string = object_parse.parse_conf_to_json(file_path, content) + # create the file and expected value in domain +- if not json.loads(content_string): ++ if not content_string or not json.loads(content_string): + codeNum = 400 + base_rsp = BaseResponse(codeNum, "Input configuration content verification failed," + + "please check the config in the host.") +diff --git a/gala-ragdoll/ragdoll/utils/object_parse.py b/gala-ragdoll/ragdoll/utils/object_parse.py +index 04134fb..3ee1c6e 100644 +--- a/gala-ragdoll/ragdoll/utils/object_parse.py ++++ b/gala-ragdoll/ragdoll/utils/object_parse.py +@@ -40,6 +40,8 @@ class ObjectParse(object): + + def get_conf_type_by_conf_path(self, conf_path): + yang_model = self._yang_modules.getModuleByFilePath(conf_path) ++ if not yang_model: ++ return "" + _conf_type = self._yang_modules.getTypeInModdule([yang_model]) + conf_type = _conf_type[yang_model.name()] + return conf_type +@@ -62,6 +64,8 @@ class ObjectParse(object): + desc: parse the conf contents to the json accroding the yang file. + """ + conf_type = self.get_conf_type_by_conf_path(conf_path) ++ if not conf_type: ++ return "" + + # create conf model + conf_model = self.create_conf_model_by_type(conf_type) +diff --git a/gala-ragdoll/ragdoll/utils/yang_module.py b/gala-ragdoll/ragdoll/utils/yang_module.py +index 62e2d37..ec554a4 100644 +--- a/gala-ragdoll/ragdoll/utils/yang_module.py ++++ b/gala-ragdoll/ragdoll/utils/yang_module.py +@@ -315,7 +315,7 @@ class YangModule(object): + input: /etc/yum.repos.d/openEuler.repo + output: openEuler-logos-openEuler.repo + """ +- res = "" ++ res = None + if len(self._module_list) == 0: + return None + for d_module in self._module_list: +-- +2.33.0 + diff --git a/0004-update-get-host-info.patch b/0004-update-get-host-info.patch new file mode 100644 index 0000000..9433609 --- /dev/null +++ b/0004-update-get-host-info.patch @@ -0,0 +1,649 @@ +From 371c0a9157acdfed5397f21816a5cd51aefa1c1f Mon Sep 17 00:00:00 2001 +From: wenxin +Date: Thu, 8 Sep 2022 09:47:56 +0800 +Subject: [PATCH] update get host info + +--- + .../controllers/agent_controller.py | 38 ++++- + .../aops_agent/manages/command_manage.py | 159 ++++++++++++------ + aops-agent/aops_agent/swagger/swagger.yaml | 75 +++++++-- + .../controllers/test_agent_controller.py | 2 +- + .../tests/manages/test_command_manage.py | 134 ++++++++++++++- + .../tests/manages/test_plugin_manage.py | 91 ++++++++++ + 6 files changed, 431 insertions(+), 68 deletions(-) + create mode 100644 aops-agent/aops_agent/tests/manages/test_plugin_manage.py + +diff --git a/aops-agent/aops_agent/controllers/agent_controller.py b/aops-agent/aops_agent/controllers/agent_controller.py +index 213aa95..de7b4b8 100644 +--- a/aops-agent/aops_agent/controllers/agent_controller.py ++++ b/aops-agent/aops_agent/controllers/agent_controller.py +@@ -36,9 +36,43 @@ def get_host_info() -> Response: + get basic info about machine + + Returns: +- a dict which contains os info,bios version and kernel version ++ Response: e.g ++ { ++ "code": int, ++ "msg" : "xxx", ++ "resp":{ ++ "os":{ ++ 'os_version': os_version, ++ 'bios_version': bios_version, ++ 'kernel': kernel_version ++ }, ++ "cpu":{ ++ "architecture": string, ++ "core_count": string, ++ "model_name": string, ++ "vendor_id": string, ++ "l1d_cache": string, ++ "l1i_cache": string, ++ "l2_cache": string, ++ "l3_cache": string ++ }, ++ "memory":{ ++ "size": "xx GB", ++ "total": int, ++ "info": [ ++ { ++ "size": "xx GB", ++ "type": "DDR4", ++ "speed": "xxxx MT/s", ++ "manufacturer": "string" ++ } ++ ... ++ ] ++ } ++ } ++ } + """ +- return jsonify(StatusCode.make_response_body(Command.get_host_info())) ++ return jsonify(StatusCode.make_response_body((SUCCESS, Command.get_host_info()))) + + + @Command.validate_token +diff --git a/aops-agent/aops_agent/manages/command_manage.py b/aops-agent/aops_agent/manages/command_manage.py +index ab2713d..ffabcde 100644 +--- a/aops-agent/aops_agent/manages/command_manage.py ++++ b/aops-agent/aops_agent/manages/command_manage.py +@@ -11,7 +11,7 @@ + # See the Mulan PSL v2 for more details. + # ******************************************************************************/ + import re +-from typing import Any, Tuple, Dict, Union, List ++from typing import Any, Dict, Union, List + import json + + import connexion +@@ -26,8 +26,7 @@ from aops_agent.conf.status import ( + HTTP_CONNECT_ERROR, + SUCCESS, + StatusCode, +- TOKEN_ERROR, +- SERVER_ERROR ++ TOKEN_ERROR + ) + from aops_agent.log.log import LOGGER + from aops_agent.manages.token_manage import TokenManage as TOKEN +@@ -43,7 +42,7 @@ from aops_agent.tools.util import ( + class Command: + + @classmethod +- def get_host_info(cls) -> Tuple[int, dict]: ++ def get_host_info(cls) -> dict: + """ + get basic info about machine + +@@ -84,62 +83,124 @@ class Command: + } + } + """ ++ host_info = { ++ 'resp': { ++ 'os': { ++ 'os_version': cls.__get_system_info(), ++ 'bios_version': cls.__get_bios_version(), ++ 'kernel': cls.__get_kernel_version() ++ }, ++ 'cpu': cls.__get_cpu_info(), ++ 'memory': cls.__get_memory_info() ++ } ++ } ++ return host_info ++ ++ @staticmethod ++ def __get_system_info() -> str: ++ """ ++ get system name and its version ++ ++ Returns: ++ str: e.g openEuler 21.09 ++ """ + try: +- os_data = get_shell_data(['cat', '/etc/os-release'], key=False) +- os_version_info = get_shell_data(['grep', 'PRETTY_NAME'], stdin=os_data.stdout) +- os_data.stdout.close() ++ os_data = get_shell_data(['cat', '/etc/os-release']) ++ except InputError: ++ LOGGER.error('Get system info error,linux has no command!') ++ return '' + +- bios_data = get_shell_data(['dmidecode', '-t', '-0'], key=False) +- bios_version_info = get_shell_data(['grep', 'Version'], stdin=bios_data.stdout) +- bios_data.stdout.close() ++ res = re.search('(?=PRETTY_NAME=).+', os_data) ++ if res: ++ return res.group()[12:].strip('"') ++ LOGGER.warning('Get os version fail, please check file /etc/os-release and try it again') ++ return '' ++ ++ @staticmethod ++ def __get_bios_version() -> str: ++ """ ++ get bios version number ++ ++ Returns: ++ str ++ """ ++ try: ++ bios_data = get_shell_data(['dmidecode', '-t', 'bios']) ++ except InputError: ++ LOGGER.error('Get system info error,linux has no command dmidecode!') ++ return '' ++ ++ res = re.search('(?=Version:).+', bios_data) ++ ++ if res: ++ return res.group()[8:].strip() ++ LOGGER.warning('Get bios version fail, please check dmidecode and try it again') ++ return '' + ++ @staticmethod ++ def __get_kernel_version() -> str: ++ """ ++ get kernel version number ++ ++ Returns: ++ str ++ """ ++ try: + kernel_data = get_shell_data(['uname', '-r']) ++ except InputError: ++ LOGGER.error('Get system info error,linux has no command!') ++ return '' ++ res = re.search('[\d\.]+-[\d\.]+[\d]', kernel_data) ++ if res: ++ return res.group() ++ LOGGER.warning('Get kernel version fail, please check dmidecode and try it again') ++ return '' + +- lscpu_data = get_shell_data(['lscpu'], key=False, env={"LANG": "en_US.utf-8"}) +- cpu_data = get_shell_data(['grep', +- 'Architecture\|CPU(s)\|Model name\|Vendor ID\|L1d cache\|L1i cache\|L2 cache\|L3 cache'], +- stdin=lscpu_data.stdout) +- lscpu_data.stdout.close() ++ @staticmethod ++ def __get_cpu_info() -> Dict[str, str]: ++ """ ++ get cpu info by command lscpu + ++ Returns: ++ dict: e.g ++ { ++ "architecture": string, ++ "core_count": string, ++ "model_name": string, ++ "vendor_id": string, ++ "l1d_cache": string, ++ "l1i_cache": string, ++ "l2_cache": string, ++ "l3_cache": string ++ } ++ """ ++ try: ++ lscpu_data = get_shell_data(['lscpu'], env={"LANG": "en_US.utf-8"}) + except InputError: +- LOGGER.error('Get host info error,linux has no command!') +- return SERVER_ERROR, {} ++ LOGGER.error('Get cpu info error,linux has no command lscpu or grep.') ++ return {} + +- os_version = os_version_info.split("=")[1].replace('\n', '').replace('"', '') +- bios_version = bios_version_info.split(':')[1].replace('\n', '').replace(' ', '') +- kernel_version = kernel_data[:re.search('[a-zA-Z]+', kernel_data).span()[0] - 1] ++ info_list = re.findall('.+:.+', lscpu_data) ++ if not info_list: ++ LOGGER.warning('Failed to read cpu info by lscpu, please check it and try again.') + + cpu_info = {} +- for line in cpu_data.split('\n')[:-1]: +- tmp = line.split(":") +- if len(tmp) != 2: +- continue ++ for info in info_list: ++ tmp = info.split(":") + cpu_info[tmp[0]] = tmp[1].strip() + +- memory_info = cls.__get_memory_info() +- memory_info['size'] = cls.__get_total_online_memory() +- +- host_info = { +- 'resp': { +- 'os': { +- 'os_version': os_version, +- 'bios_version': bios_version, +- 'kernel': kernel_version +- }, +- 'cpu': { +- "architecture": cpu_info.get('Architecture'), +- "core_count": cpu_info.get('CPU(s)'), +- "model_name": cpu_info.get('Model name'), +- "vendor_id": cpu_info.get('Vendor ID'), +- "l1d_cache": cpu_info.get('L1d cache'), +- "l1i_cache": cpu_info.get('L1i cache'), +- "l2_cache": cpu_info.get('L2 cache'), +- "l3_cache": cpu_info.get('L3 cache') +- }, +- 'memory': memory_info +- } ++ res = { ++ "architecture": cpu_info.get('Architecture'), ++ "core_count": cpu_info.get('CPU(s)'), ++ "model_name": cpu_info.get('Model name'), ++ "vendor_id": cpu_info.get('Vendor ID'), ++ "l1d_cache": cpu_info.get('L1d cache'), ++ "l1i_cache": cpu_info.get('L1i cache'), ++ "l2_cache": cpu_info.get('L2 cache'), ++ "l3_cache": cpu_info.get('L3 cache') + } +- return SUCCESS, host_info ++ ++ return res + + @staticmethod + def __get_total_online_memory() -> str: +@@ -169,6 +230,7 @@ class Command: + Returns: + dict: e.g + { ++ "size": "xx GB", + "total": int, + "info": [ + { +@@ -183,6 +245,8 @@ class Command: + + """ + res = {} ++ if Command.__get_total_online_memory(): ++ res['size'] = Command.__get_total_online_memory() + + try: + memory_data = get_shell_data(['dmidecode', '-t', 'memory']).split('Memory Device') +@@ -234,6 +298,7 @@ class Command: + token = TOKEN.get_value() + access_token = connexion.request.headers.get('access_token') + if token == '' or access_token != token: ++ LOGGER.warning("token is incorrect when request %s" % connexion.request.path) + return jsonify(StatusCode.make_response_body(TOKEN_ERROR)) + return func(*arg, **kwargs) + +diff --git a/aops-agent/aops_agent/swagger/swagger.yaml b/aops-agent/aops_agent/swagger/swagger.yaml +index 055a140..7f8ff65 100644 +--- a/aops-agent/aops_agent/swagger/swagger.yaml ++++ b/aops-agent/aops_agent/swagger/swagger.yaml +@@ -13,7 +13,7 @@ paths: + /agent/basic/info: + get: + tags: +- - "aops_agent" ++ - "agent" + description: "获取主机基础信息" + operationId: "get_host_info" + parameters: +@@ -197,23 +197,64 @@ definitions: + get_host_info_rsp: + description: "获取主机基本信息" + properties: +- os: +- type: "object" +- description: "操作系统信息" ++ code: ++ type: integer ++ msg: ++ type: string ++ resp: + properties: +- os_version: +- type: "string" +- description: "操作系统版本" +- kernel: +- type: "string" +- description: "kernel版本" +- bios_version: +- type: "string" +- description: "bios版本" +- required: +- - "kernel" +- - "os_version" +- - "bios_version" ++ os: ++ type: "object" ++ description: "操作系统信息" ++ properties: ++ os_version: ++ type: "string" ++ description: "操作系统版本" ++ kernel: ++ type: "string" ++ description: "kernel版本" ++ bios_version: ++ type: "string" ++ description: "bios版本" ++ cpu: ++ description: cpu信息 ++ properties: ++ architecture: ++ type: string ++ core_count: ++ type: string ++ model_name: ++ type: string ++ vendor_id: ++ type: string ++ l13_cache: ++ type: string ++ l1i_cache: ++ type: string ++ l2_cache: ++ type: string ++ l3_cache: ++ type: string ++ memory: ++ description: 内存信息 ++ properties: ++ size : ++ type: string ++ total: ++ type: integer ++ info: ++ type: array ++ items: ++ type: object ++ properties: ++ size: ++ type: string ++ type: ++ type: string ++ speed: ++ type: string ++ manufacturer: ++ type: string + resp_collect_items_change: + description: 采集状态修改结果 + properties: +diff --git a/aops-agent/aops_agent/tests/controllers/test_agent_controller.py b/aops-agent/aops_agent/tests/controllers/test_agent_controller.py +index 0c4e5ba..9101da9 100644 +--- a/aops-agent/aops_agent/tests/controllers/test_agent_controller.py ++++ b/aops-agent/aops_agent/tests/controllers/test_agent_controller.py +@@ -35,7 +35,7 @@ class TestAgentController(BaseTestCase): + mock_token.return_value = 'hdahdahiudahud' + url = "v1/agent/basic/info" + response = self.client.get(url, headers=self.headers_with_token) +- self.assertEqual(SUCCESS, response.json.get('code'), response.text) ++ self.assertIn('resp', response.json.keys(), response.text) + + @mock.patch.object(TokenManage, 'get_value') + def test_get_host_info_should_return_400_when_with_no_token(self, mock_token): +diff --git a/aops-agent/aops_agent/tests/manages/test_command_manage.py b/aops-agent/aops_agent/tests/manages/test_command_manage.py +index bdd6212..1fbdcd0 100644 +--- a/aops-agent/aops_agent/tests/manages/test_command_manage.py ++++ b/aops-agent/aops_agent/tests/manages/test_command_manage.py +@@ -21,7 +21,7 @@ from aops_agent.manages.command_manage import Command + from aops_agent.models.custom_exception import InputError + + +-class TestRegister(unittest.TestCase): ++class TestCommandManage(unittest.TestCase): + + def setUp(self) -> None: + warnings.simplefilter('ignore', ResourceWarning) +@@ -367,4 +367,136 @@ class TestRegister(unittest.TestCase): + def test_get_memory_size_should_return_empty_str_when_get_shell_data_error(self, mock_shell_data): + mock_shell_data.side_effect = InputError('') + res = Command._Command__get_total_online_memory() ++ self.assertEqual('', res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_cpu_info_should_return_correct_info_when_execute_command_successful( ++ self, mock_shell_data): ++ mock_shell_data.return_value = 'Architecture: x86_64\n' \ ++ 'CPU(s): 1\n' \ ++ 'Model name: AMD Test\n' \ ++ 'Vendor ID: AuthenticAMD\n' \ ++ 'L1d cache: 32 KiB\n' \ ++ 'L1i cache: 32 KiB\n' \ ++ 'L2 cache: 512 KiB\n' \ ++ 'L3 cache: 8 MiB\n' ++ expect_res = { ++ "architecture": "x86_64", ++ "core_count": "1", ++ "model_name": "AMD Test", ++ "vendor_id": "AuthenticAMD", ++ "l1d_cache": "32 KiB", ++ "l1i_cache": "32 KiB", ++ "l2_cache": "512 KiB", ++ "l3_cache": "8 MiB" ++ } ++ res = Command._Command__get_cpu_info() ++ self.assertEqual(expect_res, res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_cpu_info_should_return_null_when_execute_command_successful_but_not_get_expected_information( ++ self, mock_shell_data): ++ mock_shell_data.return_value = '' ++ expect_res = { ++ "architecture": None, ++ "core_count": None, ++ "model_name": None, ++ "vendor_id": None, ++ "l1d_cache": None, ++ "l1i_cache": None, ++ "l2_cache": None, ++ "l3_cache": None ++ } ++ res = Command._Command__get_cpu_info() ++ self.assertEqual(expect_res, res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_cpu_info_should_return_empty_dict_when_host_has_no_command_lscpu( ++ self, mock_shell_data): ++ mock_shell_data.side_effect = InputError('') ++ res = Command._Command__get_cpu_info() ++ self.assertEqual({}, res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_kernel_version_should_return_cpu_info_when_execute_command_successfully( ++ self, mock_shell_data): ++ mock_shell_data.return_value = '5.10.0-5.10.0.24.oe1.x86_64' ++ expect_res = '5.10.0-5.10.0.24' ++ res = Command._Command__get_kernel_version() ++ self.assertEqual(expect_res, res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_kernel_version_should_return_empty_string_when_execute_command_successfully_but_not_get_expected_information( ++ self, mock_shell_data): ++ mock_shell_data.return_value = 'test_info' ++ res = Command._Command__get_kernel_version() ++ self.assertEqual('', res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_kernel_version_should_return_cpu_info_when_host_has_no_command_uname(self, mock_shell_data): ++ mock_shell_data.side_effect = InputError('') ++ res = Command._Command__get_kernel_version() ++ self.assertEqual('', res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_bios_version_should_return_cpu_info_when_execute_command_successfully(self, mock_shell_data): ++ mock_shell_data.return_value = """ ++ BIOS Information ++ Vendor: innotek GmbH ++ Version: VirtualBox ++ Release Date: 12/01/2006 ++ Address: 0xE0000 ++ Runtime Size: 128 kB ++ ROM Size: 128 kB ++ Characteristics: ++ ISA is supported ++ PCI is supported ++ Boot from CD is supported ++ Selectable boot is supported ++ 8042 keyboard services are supported (int 9h) ++ CGA/mono video services are supported (int 10h) ++ ACPI is supported ++ """ ++ expect_res = 'VirtualBox' ++ res = Command._Command__get_bios_version() ++ self.assertEqual(expect_res, res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_bios_version_should_return_empty_string_when_execute_command_successfully_but_not_get_expected_information( ++ self, mock_shell_data): ++ mock_shell_data.return_value = 'test_info' ++ res = Command._Command__get_bios_version() ++ self.assertEqual('', res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_bios_version_should_return_cpu_info_when_host_has_no_command_dmidecode(self, mock_shell_data): ++ mock_shell_data.side_effect = InputError('') ++ res = Command._Command__get_bios_version() ++ self.assertEqual('', res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_system_info_should_return_cpu_info_when_execute_command_successfully(self, mock_shell_data): ++ mock_shell_data.return_value = """ ++ NAME="openEuler" ++ VERSION="21.09" ++ ID="openEuler" ++ VERSION_ID="21.09" ++ PRETTY_NAME="openEuler 21.09" ++ ANSI_COLOR="0;31" ++ """ ++ expect_res = 'openEuler 21.09' ++ res = Command._Command__get_system_info() ++ self.assertEqual(expect_res, res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_system_info_should_return_empty_string_when_execute_command_successfully_but_not_get_expected_information( ++ self, mock_shell_data): ++ mock_shell_data.return_value = 'test_info' ++ res = Command._Command__get_system_info() ++ self.assertEqual('', res) ++ ++ @mock.patch('aops_agent.manages.command_manage.get_shell_data') ++ def test_get_system_info_should_return_cpu_info_when_host_has_no_command_cat(self, mock_shell_data): ++ mock_shell_data.side_effect = InputError('') ++ res = Command._Command__get_system_info() + self.assertEqual('', res) +\ No newline at end of file +diff --git a/aops-agent/aops_agent/tests/manages/test_plugin_manage.py b/aops-agent/aops_agent/tests/manages/test_plugin_manage.py +new file mode 100644 +index 0000000..4c2ff86 +--- /dev/null ++++ b/aops-agent/aops_agent/tests/manages/test_plugin_manage.py +@@ -0,0 +1,91 @@ ++#!/usr/bin/python3 ++# ****************************************************************************** ++# Copyright (c) Huawei Technologies Co., Ltd. 2022-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. ++# ******************************************************************************/ ++import subprocess ++import unittest ++from unittest import mock ++ ++from aops_agent.conf.status import SUCCESS, FILE_NOT_FOUND ++from aops_agent.manages.plugin_manage import Plugin ++from aops_agent.models.custom_exception import InputError ++ ++ ++class TestPluginManage(unittest.TestCase): ++ @mock.patch.object(subprocess, 'Popen') ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_start_service_should_return_success_when_plugin_is_already_running( ++ self, mock_shell_data, mock_popen): ++ mock_return_value = "test-res-running" ++ mock_popen.stdout.return_value = None ++ mock_shell_data.side_effect = [subprocess.Popen, mock_return_value] ++ res = Plugin('test').start_service() ++ self.assertEqual(SUCCESS, res) ++ ++ @mock.patch.object(subprocess, 'Popen') ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_start_service_should_return_success_when_make_plugin_running_successful( ++ self, mock_shell_data, mock_popen): ++ mock_popen.stdout.return_value = None ++ mock_shell_data.side_effect = [subprocess.Popen, '', 'active'] ++ res = Plugin('test').start_service() ++ self.assertEqual(SUCCESS, res) ++ ++ @mock.patch.object(subprocess, 'Popen') ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_start_service_should_return_file_not_found_when_plugin_is_not_installed( ++ self, mock_shell_data, mock_popen): ++ mock_popen.stdout.return_value = None ++ mock_shell_data.side_effect = [subprocess.Popen, '', 'service not found'] ++ res = Plugin('test').start_service() ++ self.assertEqual(FILE_NOT_FOUND, res) ++ ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_start_service_should_return_file_not_found_when_host_has_no_command( ++ self, mock_shell_data): ++ mock_shell_data.side_effect = InputError('') ++ res = Plugin('test').start_service() ++ self.assertEqual(FILE_NOT_FOUND, res) ++ ++ @mock.patch.object(subprocess, 'Popen') ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_stop_service_should_return_success_when_plugin_is_already_stopping( ++ self, mock_shell_data, mock_popen): ++ mock_return_value = "test-res-inactive" ++ mock_popen.stdout.return_value = None ++ mock_shell_data.side_effect = [subprocess.Popen, mock_return_value] ++ res = Plugin('test').stop_service() ++ self.assertEqual(SUCCESS, res) ++ ++ @mock.patch.object(subprocess, 'Popen') ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_stop_service_should_return_success_when_make_plugin_running_successful( ++ self, mock_shell_data, mock_popen): ++ mock_popen.stdout.return_value = None ++ mock_shell_data.side_effect = [subprocess.Popen, '', 'inactive'] ++ res = Plugin('test').stop_service() ++ self.assertEqual(SUCCESS, res) ++ ++ @mock.patch.object(subprocess, 'Popen') ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_stop_service_should_return_file_not_found_when_plugin_is_not_installed( ++ self, mock_shell_data, mock_popen): ++ mock_popen.stdout.return_value = None ++ mock_shell_data.side_effect = [subprocess.Popen, '', 'service not found'] ++ res = Plugin('test').stop_service() ++ self.assertEqual(FILE_NOT_FOUND, res) ++ ++ @mock.patch('aops_agent.manages.plugin_manage.get_shell_data') ++ def test_stop_service_should_return_file_not_found_when_host_has_no_command( ++ self, mock_shell_data): ++ mock_shell_data.side_effect = InputError('') ++ res = Plugin('test').stop_service() ++ self.assertEqual(FILE_NOT_FOUND, res) +\ No newline at end of file +-- +2.33.0 + diff --git a/0005-web-fine-tuning.patch b/0005-web-fine-tuning.patch new file mode 100644 index 0000000..49af13a --- /dev/null +++ b/0005-web-fine-tuning.patch @@ -0,0 +1,346 @@ +From 8026bf8b4ed5f38d47f8f863698c97afb101f627 Mon Sep 17 00:00:00 2001 +From: Oxide_xyn +Date: Wed, 7 Sep 2022 21:09:10 +0800 +Subject: [PATCH] =?UTF-8?q?update:=20=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=8F=AF?= + =?UTF-8?q?=E4=BB=A5=E6=8C=89=E5=88=9B=E5=BB=BA=E6=97=B6=E9=97=B4=E6=8E=92?= + =?UTF-8?q?=E5=BA=8F?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +update: 主机详情词条微调 +--- + aops-web/src/api/check.js | 4 + + .../assests/components/HostBasicInfo.vue | 16 ++-- + aops-web/src/views/diagnosis/Workflow.vue | 31 +++++- + .../src/views/diagnosis/WorkflowDetail.vue | 95 +++++++++++-------- + 4 files changed, 91 insertions(+), 55 deletions(-) + +diff --git a/aops-web/src/api/check.js b/aops-web/src/api/check.js +index 5048448..713f6a8 100644 +--- a/aops-web/src/api/check.js ++++ b/aops-web/src/api/check.js +@@ -187,6 +187,8 @@ export function getWorkFlowList({tableInfo}) { + const domain = (tableInfo.filters.domain && tableInfo.filters.domain[0]) ? tableInfo.filters.domain : undefined; + const app = (tableInfo.filters.app_name && tableInfo.filters.app_name[0]) ? tableInfo.filters.app_name : undefined; + const status = (tableInfo.filters.status && tableInfo.filters.status[0]) ? tableInfo.filters.status : undefined; ++ const sort = tableInfo.sorter.order && tableInfo.sorter.field; ++ const direction = directionMap[tableInfo.sorter.order]; + return request({ + url: api.getWorkflowList, + method: 'post', +@@ -196,6 +198,8 @@ export function getWorkFlowList({tableInfo}) { + app, + status + }, ++ sort, ++ direction, + page: tableInfo.pagination.current, + per_page: tableInfo.pagination.pageSize + } +diff --git a/aops-web/src/views/assests/components/HostBasicInfo.vue b/aops-web/src/views/assests/components/HostBasicInfo.vue +index 4394c8f..9c74af0 100644 +--- a/aops-web/src/views/assests/components/HostBasicInfo.vue ++++ b/aops-web/src/views/assests/components/HostBasicInfo.vue +@@ -51,7 +51,7 @@ +
+ + +- 系统名: ++ 操作系统版本: + + + +@@ -92,21 +92,21 @@ + + + +- 内核数: ++ 核数: + + + + + + +- verdorld: ++ 厂商: + + + + + + +- model名称: ++ 处理器: + + + +@@ -118,28 +118,28 @@ +
+ + +- L1d_cache: ++ L1d cache: + + + + + + +- L1i_cache: ++ L1i cache: + + + + + + +- L2_cache: ++ L2 cache: + + + + + + +- L3_cache: ++ L3 cache: + + + +diff --git a/aops-web/src/views/diagnosis/Workflow.vue b/aops-web/src/views/diagnosis/Workflow.vue +index a63fcab..5576241 100644 +--- a/aops-web/src/views/diagnosis/Workflow.vue ++++ b/aops-web/src/views/diagnosis/Workflow.vue +@@ -206,6 +206,7 @@ +