openubmc-bingo 0.6.45__py3-none-any.whl → 0.6.99__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bmcgo/__init__.py +1 -1
- bmcgo/bmcgo.py +9 -3
- bmcgo/bmcgo_config.py +16 -0
- bmcgo/cli/cli.py +72 -21
- bmcgo/codegen/__init__.py +1 -1
- bmcgo/codegen/lua/codegen.py +2 -2
- bmcgo/codegen/lua/script/check_intfs.py +1 -0
- bmcgo/codegen/lua/script/dto/options.py +1 -0
- bmcgo/codegen/lua/script/gen_db_json.py +4 -3
- bmcgo/codegen/lua/script/gen_rpc_msg_json.py +78 -11
- bmcgo/codegen/lua/script/model_consistency_check.py +1 -1
- bmcgo/codegen/lua/script/render_utils/db_lua.py +5 -6
- bmcgo/codegen/lua/script/render_utils/model_lua.py +5 -1
- bmcgo/codegen/lua/script/template.py +5 -0
- bmcgo/codegen/lua/script/utils.py +50 -8
- bmcgo/codegen/lua/templates/apps/Makefile +2 -2
- bmcgo/codegen/lua/templates/apps/client.lua.mako +1 -1
- bmcgo/codegen/lua/templates/apps/model.lua.mako +4 -3
- bmcgo/codegen/lua/templates/apps/service.lua.mako +1 -1
- bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +4 -0
- bmcgo/codegen/lua/templates/new_app_v2/CMakeLists.txt.mako +26 -0
- bmcgo/codegen/lua/templates/new_app_v2/conanfile.py.mako +9 -0
- bmcgo/codegen/lua/v1/script/render_utils/db_lua.py +5 -6
- bmcgo/codegen/lua/v1/script/render_utils/model_lua.py +13 -1
- bmcgo/codegen/lua/v1/templates/apps/client.lua.mako +1 -1
- bmcgo/codegen/lua/v1/templates/apps/local_db.lua.mako +0 -4
- bmcgo/codegen/lua/v1/templates/apps/message.lua.mako +3 -0
- bmcgo/codegen/lua/v1/templates/apps/model.lua.mako +3 -0
- bmcgo/codegen/lua/v1/templates/apps/utils/mdb_intf.lua.mako +6 -4
- bmcgo/component/analysis/analysis.py +9 -4
- bmcgo/component/analysis/dep-rules.json +20 -8
- bmcgo/component/analysis/dep_node.py +2 -0
- bmcgo/component/analysis/intf_validation.py +8 -7
- bmcgo/component/analysis/sr_validation.py +5 -4
- bmcgo/component/busctl_log_parse/busctl_log_parser.py +809 -0
- bmcgo/component/busctl_log_parse/mock_data_save.py +170 -0
- bmcgo/component/busctl_log_parse/test_data_save.py +49 -0
- bmcgo/component/component_helper.py +29 -0
- bmcgo/component/coverage/incremental_cov.py +5 -0
- bmcgo/component/fixture/__init__.py +29 -0
- bmcgo/component/fixture/auto_case_generator.py +490 -0
- bmcgo/component/fixture/busctl_type_converter.py +1081 -0
- bmcgo/component/fixture/common_config.py +15 -0
- bmcgo/component/fixture/dbus_gateway.py +669 -0
- bmcgo/component/fixture/dbus_library.py +250 -0
- bmcgo/component/fixture/dbus_mock_utils.py +514 -0
- bmcgo/component/fixture/dbus_response_handler.py +138 -0
- bmcgo/component/fixture/dbus_signature.py +110 -0
- bmcgo/component/template_v2/conanbase.py.mako +1 -5
- bmcgo/component/test.py +69 -10
- bmcgo/error_analyzer/__init__.py +0 -0
- bmcgo/error_analyzer/case_matcher.py +114 -0
- bmcgo/error_analyzer/log_parser.py +128 -0
- bmcgo/error_analyzer/unified_error_analyzer.py +359 -0
- bmcgo/error_cases/cases.yml +59 -0
- bmcgo/error_cases/cases_template_valid.json +71 -0
- bmcgo/error_cases/conanfile.py +58 -0
- bmcgo/frame.py +0 -4
- bmcgo/functional/analysis.py +18 -12
- bmcgo/functional/bmc_studio_action.py +21 -10
- bmcgo/functional/check.py +86 -42
- bmcgo/functional/conan_index_build.py +1 -1
- bmcgo/functional/config.py +22 -18
- bmcgo/functional/csr_build.py +63 -34
- bmcgo/functional/deploy.py +4 -3
- bmcgo/functional/diff.py +51 -34
- bmcgo/functional/full_component.py +16 -5
- bmcgo/functional/hpm_signer.py +484 -0
- bmcgo/functional/new.py +8 -2
- bmcgo/functional/schema_valid.py +111 -15
- bmcgo/functional/upgrade.py +6 -6
- bmcgo/misc.py +1 -0
- bmcgo/tasks/task_build_conan.py +27 -6
- bmcgo/tasks/task_build_rootfs_img.py +120 -83
- bmcgo/tasks/task_buildgppbin.py +30 -13
- bmcgo/tasks/task_buildhpm_ext4.py +5 -3
- bmcgo/tasks/task_download_buildtools.py +20 -11
- bmcgo/tasks/task_download_dependency.py +29 -20
- bmcgo/tasks/task_hpm_envir_prepare.py +32 -53
- bmcgo/tasks/task_packet_to_supporte.py +12 -4
- bmcgo/tasks/task_prepare.py +1 -1
- bmcgo/tasks/task_sign_and_pack_hpm.py +15 -7
- bmcgo/utils/component_version_check.py +4 -4
- bmcgo/utils/config.py +3 -0
- bmcgo/utils/fetch_component_code.py +148 -17
- bmcgo/utils/install_manager.py +2 -2
- bmcgo/utils/installations/base_installer.py +10 -27
- bmcgo/utils/installations/install_plans/studio.yml +3 -0
- bmcgo/utils/mapping_config_patch.py +5 -4
- bmcgo/utils/tools.py +49 -7
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/METADATA +1 -1
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/RECORD +95 -74
- bmcgo/tasks/download_buildtools_hm.py +0 -124
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/WHEEL +0 -0
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/entry_points.txt +0 -0
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
|
|
4
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
5
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
# See the Mulan PSL v2 for more details.
|
|
12
|
+
class CommonConfig:
|
|
13
|
+
# 常量(不需要配置)
|
|
14
|
+
DBUS_MOCK_DATA_FILE_NAME = 'mock_data.json'
|
|
15
|
+
TEST_DATA_FILE = 'test_data.json'
|
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
|
|
4
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
5
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
# See the Mulan PSL v2 for more details.
|
|
12
|
+
import asyncio
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import uuid
|
|
17
|
+
from collections import defaultdict
|
|
18
|
+
from typing import DefaultDict
|
|
19
|
+
from dbus_next.aio import MessageBus
|
|
20
|
+
from dbus_next.message import Message, MessageType
|
|
21
|
+
from dbus_next.service import ServiceInterface, method
|
|
22
|
+
from dbus_mock_utils import DBusMockUtils, set_runtime_mock, clear_runtime_mock
|
|
23
|
+
from dbus_signature import DBusSignature
|
|
24
|
+
from dbus_response_handler import DBusResponseHandler
|
|
25
|
+
from common_config import CommonConfig
|
|
26
|
+
|
|
27
|
+
# 常量定义
|
|
28
|
+
"""
|
|
29
|
+
优化 DBus 入口服务,支持通过命令行指定 mock_data 路径。
|
|
30
|
+
|
|
31
|
+
使用示例:
|
|
32
|
+
python dbus_gateway.py /opt/code/BMCITFramework/bmc_test_db/network_adapter_y/mock_data
|
|
33
|
+
"""
|
|
34
|
+
DBUS_BUS_ADDRESS = 'DBUS_SESSION_BUS_ADDRESS'
|
|
35
|
+
|
|
36
|
+
# 配置日志
|
|
37
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
# 存储不同服务的DBusMockUtils实例
|
|
41
|
+
_mock_utils_instances = {}
|
|
42
|
+
_dbus_default_mock_path = None # 全局mock数据路径
|
|
43
|
+
service_names = []
|
|
44
|
+
dependency_tracker = None
|
|
45
|
+
bus = None
|
|
46
|
+
|
|
47
|
+
MOCK_CONTROL_SERVICE = 'bmc.kepler.MockControl'
|
|
48
|
+
MOCK_CONTROL_OBJECT_PATH = '/bmc/kepler/MockControl'
|
|
49
|
+
MOCK_CONTROL_INTERFACE = 'bmc.kepler.MockControl'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DependencyTracker:
|
|
53
|
+
"""记录被测组件访问依赖服务的次数
|
|
54
|
+
|
|
55
|
+
测试端通过循环调用 get_dependency_count 来查询依赖调用次数,实现等待机制。
|
|
56
|
+
不再使用异步等待,简化了实现并提高了可控性。
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self._counts: DefaultDict[str, int] = defaultdict(int)
|
|
61
|
+
|
|
62
|
+
def get_count(self, lookup_key: str) -> int:
|
|
63
|
+
"""获取指定依赖的调用次数"""
|
|
64
|
+
return self._counts.get(lookup_key, 0)
|
|
65
|
+
|
|
66
|
+
async def record(self, lookup_key: str) -> None:
|
|
67
|
+
"""记录一次依赖调用"""
|
|
68
|
+
self._counts[lookup_key] = self._counts.get(lookup_key, 0) + 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MockControlInterface(ServiceInterface):
|
|
72
|
+
"""D-Bus 接口:提供依赖调用次数查询和运行时 mock 设置功能
|
|
73
|
+
|
|
74
|
+
测试端通过循环调用 get_dependency_count 来实现等待机制,不再使用 WaitForDependency。
|
|
75
|
+
通过 set_mock_response 和 clear_mock 可以在运行时设置 mock 数据。
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, tracker: DependencyTracker):
|
|
79
|
+
super().__init__(MOCK_CONTROL_INTERFACE)
|
|
80
|
+
self._tracker = tracker
|
|
81
|
+
|
|
82
|
+
@method()
|
|
83
|
+
async def get_dependency_count(self, lookup_key: 's') -> 'u':
|
|
84
|
+
"""获取指定依赖的调用次数
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
lookup_key: 依赖标识,格式:'service|path|interface|method'
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
该依赖的调用次数
|
|
91
|
+
"""
|
|
92
|
+
return self._tracker.get_count(lookup_key)
|
|
93
|
+
|
|
94
|
+
@method()
|
|
95
|
+
async def set_mock_response(
|
|
96
|
+
self,
|
|
97
|
+
service_name: 's',
|
|
98
|
+
lookup_key: 's',
|
|
99
|
+
response_json: 's',
|
|
100
|
+
args_json: 's' = '',
|
|
101
|
+
match_mode: 's' = 'exact'
|
|
102
|
+
) -> 'b':
|
|
103
|
+
"""设置运行时 mock 响应
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
service_name: 服务名称,如 'bmc.kepler.persistence'
|
|
107
|
+
lookup_key: 方法键,格式:'service|path|interface|method'
|
|
108
|
+
response_json: 响应数据的 JSON 字符串
|
|
109
|
+
args_json: 请求参数的 JSON 字符串(可选,默认为空字符串表示匹配所有参数)
|
|
110
|
+
match_mode: 匹配模式 'exact'(精确匹配)或 'any'(匹配任意参数)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
bool: 是否设置成功
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
import json
|
|
117
|
+
response = json.loads(response_json)
|
|
118
|
+
args = json.loads(args_json) if args_json else None
|
|
119
|
+
set_runtime_mock(service_name, lookup_key, response, args, match_mode)
|
|
120
|
+
logger.info(f"✅ 通过 D-Bus 接口设置 mock: {service_name} {lookup_key}")
|
|
121
|
+
return True
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"❌ 设置 mock 失败: {e}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
@method()
|
|
127
|
+
async def clear_mock(self, service_name: 's' = '', lookup_key: 's' = '') -> 'b':
|
|
128
|
+
"""清除运行时 mock 数据
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
service_name: 服务名称,如果为空字符串则清除所有服务的 mock
|
|
132
|
+
lookup_key: 方法键,如果为空字符串则清除该服务的所有 mock
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
bool: 是否清除成功
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
service_name = service_name if service_name else None
|
|
139
|
+
lookup_key = lookup_key if lookup_key else None
|
|
140
|
+
clear_runtime_mock(service_name, lookup_key)
|
|
141
|
+
|
|
142
|
+
# 清除调用计数器(调用计数器的键格式是 "method_key|call_index")
|
|
143
|
+
if service_name is None:
|
|
144
|
+
# 清除所有服务的调用计数器
|
|
145
|
+
for svc_name in list(_mock_utils_instances.keys()):
|
|
146
|
+
if svc_name in _mock_utils_instances:
|
|
147
|
+
_mock_utils_instances[svc_name].clear_call_counters()
|
|
148
|
+
logger.info("✅ 已清除所有服务的调用计数器")
|
|
149
|
+
elif lookup_key is None:
|
|
150
|
+
# 清除该服务的所有调用计数器
|
|
151
|
+
# 如果实例不存在,先创建它(这样调用计数器就会被初始化为空)
|
|
152
|
+
mock_utils = get_or_create_mock_utils(service_name)
|
|
153
|
+
mock_utils.clear_call_counters()
|
|
154
|
+
logger.info(f"✅ 已清除服务 {service_name} 的所有调用计数器")
|
|
155
|
+
else:
|
|
156
|
+
# 清除特定方法的调用计数器(counter_key 格式是 "method_key|call_index")
|
|
157
|
+
# 如果实例不存在,先创建它
|
|
158
|
+
mock_utils = get_or_create_mock_utils(service_name)
|
|
159
|
+
# 记录清除前的状态
|
|
160
|
+
all_keys = mock_utils.get_call_counter_keys()
|
|
161
|
+
logger.info(f"🔍 清除前,调用计数器中的所有键: {all_keys}")
|
|
162
|
+
# 使用公共方法清除特定方法的调用计数器
|
|
163
|
+
mock_utils.clear_call_counters(lookup_key)
|
|
164
|
+
# 记录清除后的状态
|
|
165
|
+
remaining_keys = mock_utils.get_call_counter_keys()
|
|
166
|
+
cleared_count = len(all_keys) - len(remaining_keys)
|
|
167
|
+
if cleared_count > 0:
|
|
168
|
+
logger.info(f"✅ 已清除方法 {lookup_key} 的调用计数器(共 {cleared_count} 个)")
|
|
169
|
+
else:
|
|
170
|
+
logger.info(f"✅ 方法 {lookup_key} 的调用计数器已为空或不存在(当前计数器键: {all_keys})")
|
|
171
|
+
|
|
172
|
+
logger.info(f"✅ 通过 D-Bus 接口清除 mock: service_name={service_name}, lookup_key={lookup_key}")
|
|
173
|
+
return True
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"❌ 清除 mock 失败: {e}")
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _convert_directory_to_service(dir_name):
|
|
180
|
+
"""
|
|
181
|
+
将目录名转换为服务名,规则:
|
|
182
|
+
bmc_kepler_xxx -> bmc.kepler.xxxx(仅转换前缀,下划线保持原样)
|
|
183
|
+
"""
|
|
184
|
+
prefix = 'bmc_kepler_'
|
|
185
|
+
if dir_name.startswith(prefix):
|
|
186
|
+
return 'bmc.kepler.' + dir_name[len(prefix):]
|
|
187
|
+
return dir_name
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_service_names_from_path(mock_data_root):
|
|
191
|
+
"""
|
|
192
|
+
根据mock_data目录下的子目录计算要注册的服务名
|
|
193
|
+
"""
|
|
194
|
+
names = []
|
|
195
|
+
if not mock_data_root:
|
|
196
|
+
return names
|
|
197
|
+
if not os.path.isdir(mock_data_root):
|
|
198
|
+
logger.warning(f"指定的 mock_data 路径不存在: {mock_data_root}")
|
|
199
|
+
return names
|
|
200
|
+
|
|
201
|
+
for entry in os.listdir(mock_data_root):
|
|
202
|
+
full_path = os.path.join(mock_data_root, entry)
|
|
203
|
+
if os.path.isdir(full_path):
|
|
204
|
+
service_name = _convert_directory_to_service(entry)
|
|
205
|
+
names.append(service_name)
|
|
206
|
+
return names
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_or_create_mock_utils(service_name):
|
|
210
|
+
"""获取或创建DBusMockUtils实例"""
|
|
211
|
+
if service_name not in _mock_utils_instances:
|
|
212
|
+
_mock_utils_instances[service_name] = DBusMockUtils(service_name, _dbus_default_mock_path)
|
|
213
|
+
return _mock_utils_instances[service_name]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def generate_signature(values):
|
|
217
|
+
"""自动生成D-Bus响应签名
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
values: 要生成签名的值列表
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
生成的D-Bus签名字符串
|
|
224
|
+
"""
|
|
225
|
+
if not values:
|
|
226
|
+
return '' # 空签名
|
|
227
|
+
|
|
228
|
+
# 直接拼接所有值的签名,不添加括号
|
|
229
|
+
return ''.join([DBusSignature.get_dbus_signature(v) for v in values])
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _handle_get_dependency_count(args, depen_tracker):
|
|
233
|
+
"""处理 get_dependency_count 方法
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
args: 方法参数列表
|
|
237
|
+
depen_tracker: 依赖追踪器实例
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
(signature, response_values) 或 None
|
|
241
|
+
"""
|
|
242
|
+
if depen_tracker is None:
|
|
243
|
+
logger.warning("MockControl 接口调用 get_dependency_count 但 depen_tracker 未初始化")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
if len(args) < 1:
|
|
247
|
+
logger.error("get_dependency_count 缺少参数 lookup_key")
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
lookup_key = args[0]
|
|
251
|
+
count = depen_tracker.get_count(lookup_key)
|
|
252
|
+
logger.info(f"查询依赖调用次数: {lookup_key} -> {count}")
|
|
253
|
+
return ('u', [count])
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _handle_set_mock_response(args):
|
|
257
|
+
"""处理 set_mock_response 方法
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
args: 方法参数列表
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
(signature, response_values)
|
|
264
|
+
"""
|
|
265
|
+
if len(args) < 3:
|
|
266
|
+
logger.error("set_mock_response 参数不足,需要至少 3 个参数")
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
service_name = args[0]
|
|
270
|
+
lookup_key = args[1]
|
|
271
|
+
response_json = args[2]
|
|
272
|
+
args_json = args[3] if len(args) > 3 else ''
|
|
273
|
+
match_mode = args[4] if len(args) > 4 else 'exact'
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
import json
|
|
277
|
+
response = json.loads(response_json)
|
|
278
|
+
args_list = json.loads(args_json) if args_json else None
|
|
279
|
+
set_runtime_mock(service_name, lookup_key, response, args_list, match_mode)
|
|
280
|
+
logger.info(f"✅ 通过 D-Bus 接口设置 mock: {service_name} {lookup_key}")
|
|
281
|
+
return ('b', [True])
|
|
282
|
+
except Exception as e:
|
|
283
|
+
logger.error(f"❌ 设置 mock 失败: {e}")
|
|
284
|
+
return ('b', [False])
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _clear_all_service_call_counters():
|
|
288
|
+
"""清除所有服务的调用计数器"""
|
|
289
|
+
for svc_name in list(_mock_utils_instances.keys()):
|
|
290
|
+
if svc_name in _mock_utils_instances:
|
|
291
|
+
_mock_utils_instances[svc_name].clear_call_counters()
|
|
292
|
+
logger.info("✅ 已清除所有服务的调用计数器")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _clear_service_call_counters(service_name):
|
|
296
|
+
"""清除指定服务的所有调用计数器
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
service_name: 服务名称
|
|
300
|
+
"""
|
|
301
|
+
mock_utils = get_or_create_mock_utils(service_name)
|
|
302
|
+
mock_utils.clear_call_counters()
|
|
303
|
+
logger.info(f"✅ 已清除服务 {service_name} 的所有调用计数器")
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _clear_method_call_counters(service_name, lookup_key):
|
|
307
|
+
"""清除特定方法的调用计数器
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
service_name: 服务名称
|
|
311
|
+
lookup_key: 方法键
|
|
312
|
+
"""
|
|
313
|
+
mock_utils = get_or_create_mock_utils(service_name)
|
|
314
|
+
# 记录清除前的状态
|
|
315
|
+
all_keys = mock_utils.get_call_counter_keys()
|
|
316
|
+
logger.info(f"🔍 清除前,调用计数器中的所有键: {all_keys}")
|
|
317
|
+
# 使用公共方法清除特定方法的调用计数器
|
|
318
|
+
mock_utils.clear_call_counters(lookup_key)
|
|
319
|
+
# 记录清除后的状态
|
|
320
|
+
remaining_keys = mock_utils.get_call_counter_keys()
|
|
321
|
+
cleared_count = len(all_keys) - len(remaining_keys)
|
|
322
|
+
if cleared_count > 0:
|
|
323
|
+
logger.info(f"✅ 已清除方法 {lookup_key} 的调用计数器(共 {cleared_count} 个)")
|
|
324
|
+
else:
|
|
325
|
+
logger.info(f"✅ 方法 {lookup_key} 的调用计数器已为空或不存在(当前计数器键: {all_keys})")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _handle_clear_mock(args):
|
|
329
|
+
"""处理 clear_mock 方法
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
args: 方法参数列表
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
(signature, response_values)
|
|
336
|
+
"""
|
|
337
|
+
service_name = args[0] if len(args) > 0 and args[0] else None
|
|
338
|
+
lookup_key = args[1] if len(args) > 1 and args[1] else None
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
clear_runtime_mock(service_name, lookup_key)
|
|
342
|
+
|
|
343
|
+
# 清除调用计数器(调用计数器的键格式是 "method_key|call_index")
|
|
344
|
+
if service_name is None:
|
|
345
|
+
_clear_all_service_call_counters()
|
|
346
|
+
elif lookup_key is None:
|
|
347
|
+
_clear_service_call_counters(service_name)
|
|
348
|
+
else:
|
|
349
|
+
_clear_method_call_counters(service_name, lookup_key)
|
|
350
|
+
|
|
351
|
+
logger.info(f"✅ 通过 D-Bus 接口清除 mock: service_name={service_name}, lookup_key={lookup_key}")
|
|
352
|
+
return ('b', [True])
|
|
353
|
+
except Exception as e:
|
|
354
|
+
logger.error(f"❌ 清除 mock 失败: {e}")
|
|
355
|
+
import traceback
|
|
356
|
+
logger.error(f"❌ 错误详情: {traceback.format_exc()}")
|
|
357
|
+
return ('b', [False])
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _handle_mock_control_interface(method_name, args, depen_tracker):
|
|
361
|
+
"""处理 MockControl 接口方法
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
method_name: 方法名
|
|
365
|
+
args: 方法参数列表
|
|
366
|
+
depen_tracker: 依赖追踪器实例
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
(signature, response_values) 或 None
|
|
370
|
+
"""
|
|
371
|
+
if method_name == 'get_dependency_count':
|
|
372
|
+
return _handle_get_dependency_count(args, depen_tracker)
|
|
373
|
+
elif method_name == 'set_mock_response':
|
|
374
|
+
return _handle_set_mock_response(args)
|
|
375
|
+
elif method_name == 'clear_mock':
|
|
376
|
+
return _handle_clear_mock(args)
|
|
377
|
+
# 其他 MockControl 方法可以在这里添加
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _handle_dbus_peer_interface(method_name):
|
|
382
|
+
"""处理 org.freedesktop.DBus.Peer 接口方法
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
method_name: 方法名
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
(signature, response_values) 或 None
|
|
389
|
+
"""
|
|
390
|
+
if method_name == 'Ping':
|
|
391
|
+
# Ping 方法:无参数,无返回值(空签名)
|
|
392
|
+
return ('', [])
|
|
393
|
+
elif method_name == 'GetMachineId':
|
|
394
|
+
# GetMachineId 方法:无参数,返回机器ID字符串
|
|
395
|
+
# 生成一个简单的机器ID(实际应该从 /etc/machine-id 读取)
|
|
396
|
+
machine_id = str(uuid.uuid4()).replace('-', '')
|
|
397
|
+
return ('s', [machine_id])
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def handle_standard_dbus_method(interface, method_name, args, depen_tracker=None):
|
|
402
|
+
"""处理标准 D-Bus 接口方法和 MockControl 接口方法
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
interface: D-Bus 接口名
|
|
406
|
+
method_name: 方法名
|
|
407
|
+
args: 方法参数列表
|
|
408
|
+
depen_tracker: 依赖追踪器实例(可选)
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
(signature, response_values) 或 None(如果不是标准方法)
|
|
412
|
+
"""
|
|
413
|
+
# MockControl 接口处理
|
|
414
|
+
if interface == MOCK_CONTROL_INTERFACE:
|
|
415
|
+
return _handle_mock_control_interface(method_name, args, depen_tracker)
|
|
416
|
+
|
|
417
|
+
# org.freedesktop.DBus.Peer 接口处理
|
|
418
|
+
if interface == 'org.freedesktop.DBus.Peer':
|
|
419
|
+
return _handle_dbus_peer_interface(method_name)
|
|
420
|
+
|
|
421
|
+
return None
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
async def process_method_call(message_bus, message):
|
|
425
|
+
"""处理方法调用请求"""
|
|
426
|
+
try:
|
|
427
|
+
service_name = message.destination
|
|
428
|
+
object_path = message.path
|
|
429
|
+
interface = message.interface
|
|
430
|
+
method_name = message.member
|
|
431
|
+
args = message.body
|
|
432
|
+
logger.info(f"📨 请求: {service_name} {object_path} {interface} {method_name} {args}")
|
|
433
|
+
# 首先检查是否是标准 D-Bus 接口方法或 MockControl 接口方法
|
|
434
|
+
standard_response = handle_standard_dbus_method(interface, method_name, args, dependency_tracker)
|
|
435
|
+
if standard_response is not None:
|
|
436
|
+
signature, response_values = standard_response
|
|
437
|
+
reply = Message.new_method_return(
|
|
438
|
+
message,
|
|
439
|
+
signature,
|
|
440
|
+
response_values
|
|
441
|
+
)
|
|
442
|
+
await message_bus.send(reply)
|
|
443
|
+
logger.info(f"✅ 标准方法响应: {interface}.{method_name} -> {response_values}")
|
|
444
|
+
return
|
|
445
|
+
# 构建lookup_key
|
|
446
|
+
lookup_key = f"{service_name}|{object_path}|{interface}|{method_name}"
|
|
447
|
+
# 获取响应
|
|
448
|
+
mock_utils = get_or_create_mock_utils(service_name)
|
|
449
|
+
response = mock_utils.match_params_and_get_response(lookup_key, args)
|
|
450
|
+
if response is not None:
|
|
451
|
+
response_type = response.get('type')
|
|
452
|
+
# 处理 no_reply 或 timeout 类型(不发送响应,模拟超时)
|
|
453
|
+
if response_type in ('no_reply', 'timeout'):
|
|
454
|
+
logger.info(f"🔇 模拟超时,不发送响应: {lookup_key}")
|
|
455
|
+
# 不发送响应,让调用端超时
|
|
456
|
+
return
|
|
457
|
+
# 处理 delay 类型(延迟响应)
|
|
458
|
+
if response_type == 'delay':
|
|
459
|
+
delay_seconds = response.get('delay_seconds', 0)
|
|
460
|
+
if delay_seconds > 0:
|
|
461
|
+
logger.info(f"⏳ 延迟响应 {delay_seconds} 秒: {lookup_key}")
|
|
462
|
+
await asyncio.sleep(delay_seconds)
|
|
463
|
+
# 继续处理延迟后的响应(如果有 values)
|
|
464
|
+
# 处理 error 或 interrupt 类型(发送错误响应)
|
|
465
|
+
if response_type in ('error', 'interrupt'):
|
|
466
|
+
error_name = response.get('error_name', 'org.freedesktop.DBus.Error.Failed')
|
|
467
|
+
error_message = response.get('error_message', 'Mock error response' if response_type == 'error' else 'Connection interrupted')
|
|
468
|
+
log_level = logger.warning if response_type == 'interrupt' else logger.error
|
|
469
|
+
log_level(f"{'⚠️ 模拟中断' if response_type == 'interrupt' else '❌ 发送错误响应'}: {error_name} - {error_message}")
|
|
470
|
+
try:
|
|
471
|
+
error_reply = Message(
|
|
472
|
+
destination=message.sender,
|
|
473
|
+
path=message.path,
|
|
474
|
+
interface=message.interface,
|
|
475
|
+
message_type=MessageType.ERROR,
|
|
476
|
+
error_name=error_name,
|
|
477
|
+
reply_serial=message.serial,
|
|
478
|
+
signature='s',
|
|
479
|
+
body=[error_message]
|
|
480
|
+
)
|
|
481
|
+
await message_bus.send(error_reply)
|
|
482
|
+
if response_type == 'interrupt':
|
|
483
|
+
logger.warning(f"⚠️ 已发送错误响应,模拟中断连接(注意:实际中断连接需要更复杂的处理)")
|
|
484
|
+
except Exception as e:
|
|
485
|
+
logger.error(f"⚠️ 发送错误回复时出错: {str(e)}")
|
|
486
|
+
return
|
|
487
|
+
# 处理正常的 method_return 类型响应
|
|
488
|
+
if dependency_tracker is not None:
|
|
489
|
+
await dependency_tracker.record(lookup_key)
|
|
490
|
+
|
|
491
|
+
# 使用 DBusResponseHandler 处理响应,确保 D-Bus 格式字符串被正确转换
|
|
492
|
+
processed_response = DBusResponseHandler.process_response(response)
|
|
493
|
+
|
|
494
|
+
if isinstance(processed_response, dict) and 'values' in processed_response:
|
|
495
|
+
response_values = processed_response['values']
|
|
496
|
+
response_signature = processed_response.get('signature')
|
|
497
|
+
else:
|
|
498
|
+
response_values = processed_response
|
|
499
|
+
response_signature = None
|
|
500
|
+
# 确保 response_values 是正确的类型
|
|
501
|
+
# 如果 response_values 是列表,确保列表中的元素类型正确
|
|
502
|
+
logger.info(f"🔍 处理响应值: response_values类型={type(response_values)}, response_values={response_values}")
|
|
503
|
+
if isinstance(response_values, list) and len(response_values) > 0:
|
|
504
|
+
# 检查列表中的每个元素
|
|
505
|
+
for idx, value in enumerate(response_values):
|
|
506
|
+
logger.debug("🔍 响应值[%s]: 类型=%s, 值=%s", idx, type(value), value)
|
|
507
|
+
if isinstance(value, dict):
|
|
508
|
+
# 检查字典中的值,确保嵌套的字典也是正确的类型
|
|
509
|
+
for key, val in value.items():
|
|
510
|
+
logger.debug(" 🔍 字典键 '%s': 类型=%s, 值=%s", key, type(val), val)
|
|
511
|
+
if isinstance(val, str) and val.startswith('{'):
|
|
512
|
+
# 可能是 JSON 字符串,尝试解析
|
|
513
|
+
try:
|
|
514
|
+
import json
|
|
515
|
+
parsed = json.loads(val)
|
|
516
|
+
value[key] = parsed
|
|
517
|
+
logger.info(f"✅ 成功将字典值 '{key}' 从字符串解析为 JSON: {type(parsed)}")
|
|
518
|
+
except Exception as e:
|
|
519
|
+
logger.warning(f"⚠️ 无法将字典值 '{key}' 解析为 JSON,保持原值")
|
|
520
|
+
elif isinstance(value, str):
|
|
521
|
+
# 如果第一个值是字符串,可能是 JSON 序列化的问题
|
|
522
|
+
logger.warning(f"⚠️ 响应值[{idx}] 是字符串类型: {type(value)}, 值: {value[:100] if len(str(value)) > 100 else value}")
|
|
523
|
+
# 尝试解析为 JSON
|
|
524
|
+
try:
|
|
525
|
+
import json
|
|
526
|
+
parsed = json.loads(value)
|
|
527
|
+
response_values[idx] = parsed
|
|
528
|
+
logger.info(f"✅ 成功将响应值[{idx}] 从字符串解析为 JSON: {type(parsed)}")
|
|
529
|
+
except Exception as e:
|
|
530
|
+
logger.error(f"❌ 无法将响应值[{idx}] 解析为 JSON,保持原值")
|
|
531
|
+
if response_signature is None:
|
|
532
|
+
response_signature = generate_signature(response_values)
|
|
533
|
+
logger.debug("📤 准备发送响应: signature=%s, values类型=%s, values=%s", response_signature, type(response_values), response_values)
|
|
534
|
+
# 创建并发送回复 - 使用dbus-next的Message类
|
|
535
|
+
reply = Message.new_method_return(
|
|
536
|
+
message,
|
|
537
|
+
response_signature,
|
|
538
|
+
response_values
|
|
539
|
+
)
|
|
540
|
+
await message_bus.send(reply)
|
|
541
|
+
logger.info(f"✅ 响应: {response_values}")
|
|
542
|
+
else:
|
|
543
|
+
# 发送一个默认的错误回复
|
|
544
|
+
logger.warning(f"❌ 未找到响应: {lookup_key}")
|
|
545
|
+
try:
|
|
546
|
+
error_reply = Message(
|
|
547
|
+
destination=message.sender,
|
|
548
|
+
path=message.path,
|
|
549
|
+
interface=message.interface,
|
|
550
|
+
message_type=MessageType.ERROR, # 移除member参数
|
|
551
|
+
error_name='org.freedesktop.DBus.Error.UnknownMethod',
|
|
552
|
+
reply_serial=message.serial,
|
|
553
|
+
signature='s', # 添加签名
|
|
554
|
+
body=[f'Method {method_name} not found or no response data available']
|
|
555
|
+
)
|
|
556
|
+
await message_bus.send(error_reply)
|
|
557
|
+
except Exception as e:
|
|
558
|
+
logger.error(f"⚠️ 发送错误回复时出错: {str(e)}")
|
|
559
|
+
except Exception as e:
|
|
560
|
+
import traceback
|
|
561
|
+
logger.error(f"⚠️ 处理请求时出错: {str(e)}")
|
|
562
|
+
logger.error(f"📋 错误详情:\n{traceback.format_exc()}")
|
|
563
|
+
try:
|
|
564
|
+
# 尝试发送错误响应
|
|
565
|
+
error_reply = Message(
|
|
566
|
+
destination=message.sender,
|
|
567
|
+
path=message.path,
|
|
568
|
+
interface=message.interface,
|
|
569
|
+
message_type=MessageType.ERROR, # 移除member参数
|
|
570
|
+
error_name='org.freedesktop.DBus.Error.Failed',
|
|
571
|
+
reply_serial=message.serial,
|
|
572
|
+
signature='s', # 添加签名
|
|
573
|
+
body=[str(e)]
|
|
574
|
+
)
|
|
575
|
+
await message_bus.send(error_reply)
|
|
576
|
+
except Exception as exce:
|
|
577
|
+
logger.info(f"发送错误响应时出错: {str(exce)}")
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
async def main():
|
|
581
|
+
"""主函数"""
|
|
582
|
+
global bus, service_names, dependency_tracker
|
|
583
|
+
|
|
584
|
+
if len(sys.argv) < 5:
|
|
585
|
+
logger.error("请提供所有必需参数: python dbus_gateway.py <test_db_name> <project_path> <bmc_test_db_path> <dbus_address>")
|
|
586
|
+
return
|
|
587
|
+
|
|
588
|
+
test_db_name = sys.argv[1]
|
|
589
|
+
project_path = sys.argv[2]
|
|
590
|
+
bmc_test_db_path = sys.argv[3]
|
|
591
|
+
dbus_address_arg = sys.argv[4]
|
|
592
|
+
|
|
593
|
+
# 根据 bmc_test_db_path 和 test_db_name 计算 mock_data_root
|
|
594
|
+
mock_data_root = os.path.join(bmc_test_db_path, test_db_name, 'mock_data')
|
|
595
|
+
|
|
596
|
+
# 计算dbus_default_mock_path(用于传递给DBusMockUtils)
|
|
597
|
+
dbus_default_mock_path = mock_data_root
|
|
598
|
+
|
|
599
|
+
# 保存全局配置,供get_or_create_mock_utils使用
|
|
600
|
+
global _dbus_default_mock_path
|
|
601
|
+
_dbus_default_mock_path = dbus_default_mock_path
|
|
602
|
+
|
|
603
|
+
service_names = load_service_names_from_path(mock_data_root)
|
|
604
|
+
if not service_names:
|
|
605
|
+
logger.warning(f"在 {mock_data_root} 下未找到任何 mock 服务目录,将不会注册业务服务名")
|
|
606
|
+
|
|
607
|
+
# 指定DBus地址,从环境变量获取或使用传入的值
|
|
608
|
+
dbus_address = os.environ.get(DBUS_BUS_ADDRESS, dbus_address_arg)
|
|
609
|
+
logger.info(f"使用DBus地址 ({DBUS_BUS_ADDRESS}): {dbus_address}")
|
|
610
|
+
|
|
611
|
+
# 修改连接方式,先设置环境变量再连接
|
|
612
|
+
old_address = os.environ.get(DBUS_BUS_ADDRESS)
|
|
613
|
+
os.environ[DBUS_BUS_ADDRESS] = dbus_address
|
|
614
|
+
# 创建并连接到DBus总线
|
|
615
|
+
bus = await MessageBus().connect()
|
|
616
|
+
logger.info(f"成功连接到DBus总线 ({DBUS_BUS_ADDRESS}): {dbus_address}")
|
|
617
|
+
|
|
618
|
+
# 关键修改:打印出当前进程的环境变量信息,帮助调试
|
|
619
|
+
logger.info(f"当前进程 {DBUS_BUS_ADDRESS}: {os.environ.get(DBUS_BUS_ADDRESS)}")
|
|
620
|
+
dependency_tracker = DependencyTracker()
|
|
621
|
+
# 导出 MockControl 接口(备用,实际处理在 handle_standard_dbus_method 中统一处理)
|
|
622
|
+
control_interface = MockControlInterface(dependency_tracker)
|
|
623
|
+
bus.export(MOCK_CONTROL_OBJECT_PATH, control_interface)
|
|
624
|
+
try:
|
|
625
|
+
await bus.request_name(MOCK_CONTROL_SERVICE)
|
|
626
|
+
logger.info(f"已注册依赖控制接口: {MOCK_CONTROL_SERVICE}")
|
|
627
|
+
except Exception as exc:
|
|
628
|
+
logger.error(f"注册依赖控制接口失败: {exc}")
|
|
629
|
+
|
|
630
|
+
def handler_wrapper(message):
|
|
631
|
+
if message.message_type == MessageType.METHOD_CALL:
|
|
632
|
+
logger.debug("拦截到方法调用: %s %s %s %s", message.destination, message.path, message.interface, message.member)
|
|
633
|
+
asyncio.create_task(process_method_call(bus, message))
|
|
634
|
+
return True # 返回True表示已处理消息,阻止消息继续传播
|
|
635
|
+
return False # 返回False允许消息继续传播
|
|
636
|
+
|
|
637
|
+
# 注册消息处理器
|
|
638
|
+
bus.add_message_handler(handler_wrapper)
|
|
639
|
+
logger.info("已注册消息处理器")
|
|
640
|
+
|
|
641
|
+
# 尝试注册多个服务名
|
|
642
|
+
|
|
643
|
+
for name in service_names:
|
|
644
|
+
try:
|
|
645
|
+
result = await bus.request_name(name)
|
|
646
|
+
logger.info(f"成功注册服务名: {name}")
|
|
647
|
+
except Exception as e:
|
|
648
|
+
logger.warning(f"无法注册服务名 {name}: {e}")
|
|
649
|
+
|
|
650
|
+
# 测试:列出当前注册的所有名称
|
|
651
|
+
try:
|
|
652
|
+
introspection = await bus.introspect('org.freedesktop.DBus', '/org/freedesktop/DBus')
|
|
653
|
+
proxy = bus.get_proxy_object('org.freedesktop.DBus', '/org/freedesktop/DBus', introspection)
|
|
654
|
+
dbus_interface = proxy.get_interface('org.freedesktop.DBus')
|
|
655
|
+
names = await dbus_interface.call_list_names()
|
|
656
|
+
logger.info(f"当前已注册的服务名列表: {names}")
|
|
657
|
+
except Exception as e:
|
|
658
|
+
logger.error(f"无法获取服务名列表: {e}")
|
|
659
|
+
|
|
660
|
+
logger.info("🟢 公共DBUS入口服务已启动")
|
|
661
|
+
logger.info("🟢 请在其他终端使用以下命令连接到此DBus会话:")
|
|
662
|
+
logger.info(f"🟢 export {DBUS_BUS_ADDRESS}={dbus_address}")
|
|
663
|
+
|
|
664
|
+
# 保持运行
|
|
665
|
+
await asyncio.get_event_loop().create_future()
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
if __name__ == "__main__":
|
|
669
|
+
asyncio.run(main())
|