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.
Files changed (96) hide show
  1. bmcgo/__init__.py +1 -1
  2. bmcgo/bmcgo.py +9 -3
  3. bmcgo/bmcgo_config.py +16 -0
  4. bmcgo/cli/cli.py +72 -21
  5. bmcgo/codegen/__init__.py +1 -1
  6. bmcgo/codegen/lua/codegen.py +2 -2
  7. bmcgo/codegen/lua/script/check_intfs.py +1 -0
  8. bmcgo/codegen/lua/script/dto/options.py +1 -0
  9. bmcgo/codegen/lua/script/gen_db_json.py +4 -3
  10. bmcgo/codegen/lua/script/gen_rpc_msg_json.py +78 -11
  11. bmcgo/codegen/lua/script/model_consistency_check.py +1 -1
  12. bmcgo/codegen/lua/script/render_utils/db_lua.py +5 -6
  13. bmcgo/codegen/lua/script/render_utils/model_lua.py +5 -1
  14. bmcgo/codegen/lua/script/template.py +5 -0
  15. bmcgo/codegen/lua/script/utils.py +50 -8
  16. bmcgo/codegen/lua/templates/apps/Makefile +2 -2
  17. bmcgo/codegen/lua/templates/apps/client.lua.mako +1 -1
  18. bmcgo/codegen/lua/templates/apps/model.lua.mako +4 -3
  19. bmcgo/codegen/lua/templates/apps/service.lua.mako +1 -1
  20. bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +4 -0
  21. bmcgo/codegen/lua/templates/new_app_v2/CMakeLists.txt.mako +26 -0
  22. bmcgo/codegen/lua/templates/new_app_v2/conanfile.py.mako +9 -0
  23. bmcgo/codegen/lua/v1/script/render_utils/db_lua.py +5 -6
  24. bmcgo/codegen/lua/v1/script/render_utils/model_lua.py +13 -1
  25. bmcgo/codegen/lua/v1/templates/apps/client.lua.mako +1 -1
  26. bmcgo/codegen/lua/v1/templates/apps/local_db.lua.mako +0 -4
  27. bmcgo/codegen/lua/v1/templates/apps/message.lua.mako +3 -0
  28. bmcgo/codegen/lua/v1/templates/apps/model.lua.mako +3 -0
  29. bmcgo/codegen/lua/v1/templates/apps/utils/mdb_intf.lua.mako +6 -4
  30. bmcgo/component/analysis/analysis.py +9 -4
  31. bmcgo/component/analysis/dep-rules.json +20 -8
  32. bmcgo/component/analysis/dep_node.py +2 -0
  33. bmcgo/component/analysis/intf_validation.py +8 -7
  34. bmcgo/component/analysis/sr_validation.py +5 -4
  35. bmcgo/component/busctl_log_parse/busctl_log_parser.py +809 -0
  36. bmcgo/component/busctl_log_parse/mock_data_save.py +170 -0
  37. bmcgo/component/busctl_log_parse/test_data_save.py +49 -0
  38. bmcgo/component/component_helper.py +29 -0
  39. bmcgo/component/coverage/incremental_cov.py +5 -0
  40. bmcgo/component/fixture/__init__.py +29 -0
  41. bmcgo/component/fixture/auto_case_generator.py +490 -0
  42. bmcgo/component/fixture/busctl_type_converter.py +1081 -0
  43. bmcgo/component/fixture/common_config.py +15 -0
  44. bmcgo/component/fixture/dbus_gateway.py +669 -0
  45. bmcgo/component/fixture/dbus_library.py +250 -0
  46. bmcgo/component/fixture/dbus_mock_utils.py +514 -0
  47. bmcgo/component/fixture/dbus_response_handler.py +138 -0
  48. bmcgo/component/fixture/dbus_signature.py +110 -0
  49. bmcgo/component/template_v2/conanbase.py.mako +1 -5
  50. bmcgo/component/test.py +69 -10
  51. bmcgo/error_analyzer/__init__.py +0 -0
  52. bmcgo/error_analyzer/case_matcher.py +114 -0
  53. bmcgo/error_analyzer/log_parser.py +128 -0
  54. bmcgo/error_analyzer/unified_error_analyzer.py +359 -0
  55. bmcgo/error_cases/cases.yml +59 -0
  56. bmcgo/error_cases/cases_template_valid.json +71 -0
  57. bmcgo/error_cases/conanfile.py +58 -0
  58. bmcgo/frame.py +0 -4
  59. bmcgo/functional/analysis.py +18 -12
  60. bmcgo/functional/bmc_studio_action.py +21 -10
  61. bmcgo/functional/check.py +86 -42
  62. bmcgo/functional/conan_index_build.py +1 -1
  63. bmcgo/functional/config.py +22 -18
  64. bmcgo/functional/csr_build.py +63 -34
  65. bmcgo/functional/deploy.py +4 -3
  66. bmcgo/functional/diff.py +51 -34
  67. bmcgo/functional/full_component.py +16 -5
  68. bmcgo/functional/hpm_signer.py +484 -0
  69. bmcgo/functional/new.py +8 -2
  70. bmcgo/functional/schema_valid.py +111 -15
  71. bmcgo/functional/upgrade.py +6 -6
  72. bmcgo/misc.py +1 -0
  73. bmcgo/tasks/task_build_conan.py +27 -6
  74. bmcgo/tasks/task_build_rootfs_img.py +120 -83
  75. bmcgo/tasks/task_buildgppbin.py +30 -13
  76. bmcgo/tasks/task_buildhpm_ext4.py +5 -3
  77. bmcgo/tasks/task_download_buildtools.py +20 -11
  78. bmcgo/tasks/task_download_dependency.py +29 -20
  79. bmcgo/tasks/task_hpm_envir_prepare.py +32 -53
  80. bmcgo/tasks/task_packet_to_supporte.py +12 -4
  81. bmcgo/tasks/task_prepare.py +1 -1
  82. bmcgo/tasks/task_sign_and_pack_hpm.py +15 -7
  83. bmcgo/utils/component_version_check.py +4 -4
  84. bmcgo/utils/config.py +3 -0
  85. bmcgo/utils/fetch_component_code.py +148 -17
  86. bmcgo/utils/install_manager.py +2 -2
  87. bmcgo/utils/installations/base_installer.py +10 -27
  88. bmcgo/utils/installations/install_plans/studio.yml +3 -0
  89. bmcgo/utils/mapping_config_patch.py +5 -4
  90. bmcgo/utils/tools.py +49 -7
  91. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/METADATA +1 -1
  92. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/RECORD +95 -74
  93. bmcgo/tasks/download_buildtools_hm.py +0 -124
  94. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/WHEEL +0 -0
  95. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/entry_points.txt +0 -0
  96. {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())