remoteRF-server 1.7.22__tar.gz → 1.7.23__tar.gz

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 (232) hide show
  1. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/PKG-INFO +2 -2
  2. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/pyproject.toml +3 -2
  3. remoterf_server-1.7.23/src/remoteRF_server/adapters/__init__.py +0 -0
  4. remoterf_server-1.7.23/src/remoteRF_server/adapters/oai/__init__.py +115 -0
  5. remoterf_server-1.7.23/src/remoteRF_server/adapters/oai/integration.json +1 -0
  6. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/device_manager.py +130 -19
  7. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/diag.py +12 -0
  8. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/dynamic_v2.py +411 -75
  9. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/grpc_server.py +138 -66
  10. remoterf_server-1.7.23/src/remoteRF_server/server/integration_controller.py +367 -0
  11. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/rpc_manager.py +8 -0
  12. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server.egg-info/PKG-INFO +2 -2
  13. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server.egg-info/SOURCES.txt +11 -0
  14. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server.egg-info/requires.txt +1 -1
  15. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_default_usrp_schema.py +1 -1
  16. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_dynamic_v2.py +7 -3
  17. remoterf_server-1.7.23/tests/test_integration_controller.py +664 -0
  18. remoterf_server-1.7.23/tests/test_integration_ownership.py +121 -0
  19. remoterf_server-1.7.23/tests/test_integration_routes.py +241 -0
  20. remoterf_server-1.7.23/tests/test_integration_startup.py +117 -0
  21. remoterf_server-1.7.23/tests/test_native_lifetime.py +330 -0
  22. remoterf_server-1.7.23/tests/test_oai_adapter.py +138 -0
  23. remoterf_server-1.7.23/tests/test_server_signals.py +154 -0
  24. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_startup_check.py +31 -7
  25. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_client_server_hardware.py +4 -4
  26. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_client_server_localhost.py +6 -13
  27. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_n210_client_server_hardware.py +4 -4
  28. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/LICENSE +0 -0
  29. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/README.md +0 -0
  30. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/setup.cfg +0 -0
  31. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/__init__.py +0 -0
  32. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/__init__.py +0 -0
  33. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  34. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/deployment_capabilities.proto +0 -0
  35. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2.py +0 -0
  36. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
  37. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/federation.proto +0 -0
  38. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/federation_pb2.py +0 -0
  39. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/federation_pb2_grpc.py +0 -0
  40. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  41. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  42. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  43. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  44. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
  45. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/idl/__init__.py +0 -0
  46. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  47. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
  48. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
  49. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/idl/schema.py +0 -0
  50. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/idl/v2.py +0 -0
  51. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/__init__.py +0 -0
  52. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  53. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/api_token.py +0 -0
  54. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  55. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/db_location.py +0 -0
  56. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/list_string.py +0 -0
  57. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/panel.py +0 -0
  58. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  59. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/__init__.py +0 -0
  60. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  61. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/adalm_pluto/pluto_diag.py +0 -0
  62. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
  63. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  64. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +0 -0
  65. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/adalm_pluto/poe.py +0 -0
  66. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/bluetooth/__init__.py +0 -0
  67. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/bluetooth/bluetooth_schema.py +0 -0
  68. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/bluetooth_diag.py +0 -0
  69. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/gps/__init__.py +0 -0
  70. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/gps/gps_schema.py +0 -0
  71. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/gps_diag.py +0 -0
  72. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/hackrf/__init__.py +0 -0
  73. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/hackrf/hackrf_schema.py +0 -0
  74. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/krakensdr/__init__.py +0 -0
  75. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/krakensdr/krakensdr_schema.py +0 -0
  76. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/rfbeam_kmd7/__init__.py +0 -0
  77. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/rfbeam_kmd7/rfbeam_kmd7_schema.py +0 -0
  78. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/rtl_sdr/__init__.py +0 -0
  79. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_diag.py +0 -0
  80. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_schema.py +0 -0
  81. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/ti_awr6843/__init__.py +0 -0
  82. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/ti_awr6843/ti_awr6843_schema.py +0 -0
  83. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/ti_mmwave/__init__.py +0 -0
  84. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/ti_mmwave/ti_mmwave_schema.py +0 -0
  85. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
  86. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
  87. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp/profiles.py +0 -0
  88. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
  89. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp/usrp_diag.py +0 -0
  90. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp/usrp_schema.py +0 -0
  91. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp_phaser/__init__.py +0 -0
  92. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/usrp_phaser/usrp_phaser_schema.py +0 -0
  93. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/wifi/__init__.py +0 -0
  94. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/drivers/wifi/wifi_schema.py +0 -0
  95. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/host/__init__.py +0 -0
  96. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/host/host_auth_token.py +0 -0
  97. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/host/host_directory_store.py +0 -0
  98. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  99. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/remote_admin_shell.py +0 -0
  100. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/__init__.py +0 -0
  101. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/acc_perms.py +0 -0
  102. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/cert_provider.py +0 -0
  103. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/commands.py +0 -0
  104. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/console.py +0 -0
  105. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/control.py +0 -0
  106. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/deployment/__init__.py +0 -0
  107. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/deployment/capabilities.py +0 -0
  108. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/direct_path.py +0 -0
  109. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/dynamic_v2_bridge.py +0 -0
  110. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/__init__.py +0 -0
  111. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/admin.py +0 -0
  112. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/cli.py +0 -0
  113. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/cluster.py +0 -0
  114. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/contracts.py +0 -0
  115. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/destination.py +0 -0
  116. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/devices.py +0 -0
  117. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/enrollment.py +0 -0
  118. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/global_home.py +0 -0
  119. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/home.py +0 -0
  120. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/home_devices.py +0 -0
  121. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/identity.py +0 -0
  122. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/legacy_state.py +0 -0
  123. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/links.py +0 -0
  124. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/migrations.py +0 -0
  125. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/peers.py +0 -0
  126. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/permissions.py +0 -0
  127. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/principals.py +0 -0
  128. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/runtime.py +0 -0
  129. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/federation/trust.py +0 -0
  130. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/global_onboarding.py +0 -0
  131. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/host_direct.py +0 -0
  132. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/host_dynamic_v2.py +0 -0
  133. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/json_output.py +0 -0
  134. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/remote_admin.py +0 -0
  135. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/remote_admin_ssh.py +0 -0
  136. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/reservation.py +0 -0
  137. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/schema_registry.py +0 -0
  138. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/startup_check.py +0 -0
  139. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/transport_counters.py +0 -0
  140. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/user_group_cli.py +0 -0
  141. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/user_group_handler.py +0 -0
  142. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/server/v2_errors.py +0 -0
  143. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/serverrf_cli.py +0 -0
  144. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/tools/__init__.py +0 -0
  145. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/tools/gen_certs.py +0 -0
  146. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
  147. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  148. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  149. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/remoteRF_server.egg-info/top_level.txt +0 -0
  150. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/rssi/__init__.py +0 -0
  151. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/rssi/cli.py +0 -0
  152. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/rssi/dongle.py +0 -0
  153. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/rssi/safety.py +0 -0
  154. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/src/rssi/scanner.py +0 -0
  155. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_account_hardening.py +0 -0
  156. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_account_names.py +0 -0
  157. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_account_removal.py +0 -0
  158. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_activity_log.py +0 -0
  159. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_availability.py +0 -0
  160. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_bind_host.py +0 -0
  161. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_cert_provider.py +0 -0
  162. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_client_sessions.py +0 -0
  163. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_cluster.py +0 -0
  164. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_commands.py +0 -0
  165. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_console.py +0 -0
  166. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_contrib_drivers.py +0 -0
  167. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_default_rtl_sdr_schema.py +0 -0
  168. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_default_wifi_schema.py +0 -0
  169. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_deployment_capabilities.py +0 -0
  170. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_detached_device_rpc.py +0 -0
  171. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_device_core_aliases.py +0 -0
  172. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_device_handles.py +0 -0
  173. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_device_init.py +0 -0
  174. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_devices_add_wizard.py +0 -0
  175. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_devices_list.py +0 -0
  176. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_diag.py +0 -0
  177. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_direct_fixed_port.py +0 -0
  178. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_direct_path_server.py +0 -0
  179. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_federated_direct_path.py +0 -0
  180. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_federated_dynamic_v2.py +0 -0
  181. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_federation_edges.py +0 -0
  182. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_first_run.py +0 -0
  183. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_d_contracts.py +0 -0
  184. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_e_federation.py +0 -0
  185. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_e_proto.py +0 -0
  186. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_f_enrollment.py +0 -0
  187. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_g1_logging.py +0 -0
  188. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_g_permissions.py +0 -0
  189. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_h_devices.py +0 -0
  190. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gate_i_direct_path.py +0 -0
  191. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_gen_certs.py +0 -0
  192. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_global_accounts_migration.py +0 -0
  193. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_global_home.py +0 -0
  194. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_global_onboarding.py +0 -0
  195. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_global_v1_disposal.py +0 -0
  196. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_hackrf_schema.py +0 -0
  197. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_home_device_plane.py +0 -0
  198. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_host_direct.py +0 -0
  199. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_host_directory_store.py +0 -0
  200. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_host_dynamic_v2.py +0 -0
  201. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_idl_schema.py +0 -0
  202. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_json_commands.py +0 -0
  203. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_lab_logins.py +0 -0
  204. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_native_drivers.py +0 -0
  205. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_panel.py +0 -0
  206. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_peer_transport.py +0 -0
  207. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_pluto_compat.py +0 -0
  208. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_pluto_poe.py +0 -0
  209. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_process_arg.py +0 -0
  210. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_proxy_lifecycle.py +0 -0
  211. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_remote_admin.py +0 -0
  212. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_reservation_cancel.py +0 -0
  213. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_reservation_handoff.py +0 -0
  214. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_reservation_policy_b.py +0 -0
  215. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_reservation_timestamps.py +0 -0
  216. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_rpc_manager_batch.py +0 -0
  217. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_rpc_manager_calln.py +0 -0
  218. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_rpc_manager_host_legacy.py +0 -0
  219. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_rpc_manager_rx_n.py +0 -0
  220. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_rtl_sdr_schema.py +0 -0
  221. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_schema_registry.py +0 -0
  222. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_soak_federation.py +0 -0
  223. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_ti_mmwave_hardware.py +0 -0
  224. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_ti_mmwave_schema.py +0 -0
  225. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_transport_counters.py +0 -0
  226. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_user_group_cli.py +0 -0
  227. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_b205mini_uhd410_hardware.py +0 -0
  228. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_preload.py +0 -0
  229. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_profiles.py +0 -0
  230. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
  231. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_usrp_uhd410_hardware.py +0 -0
  232. {remoterf_server-1.7.22 → remoterf_server-1.7.23}/tests/test_wipe.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.7.22
3
+ Version: 1.7.23
4
4
  Summary: RemoteRF server-side control package
5
5
  License: GPL-3.0-or-later
6
6
  Classifier: Programming Language :: Python :: 3
@@ -10,7 +10,7 @@ Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: remoterf-federation-core==0.22.0
13
- Requires-Dist: remoterf-device-core==0.1.6
13
+ Requires-Dist: remoterf-device-core==0.1.7
14
14
  Requires-Dist: grpcio<2.0.0,>=1.81.1
15
15
  Requires-Dist: protobuf<7.0.0,>=6.33.5
16
16
  Requires-Dist: numpy
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server"
7
- version = "1.7.22"
7
+ version = "1.7.23"
8
8
  description = "RemoteRF server-side control package"
9
9
  license = { text = "GPL-3.0-or-later" }
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
12
12
  "remoterf-federation-core==0.22.0",
13
- "remoterf-device-core==0.1.6",
13
+ "remoterf-device-core==0.1.7",
14
14
  "grpcio>=1.81.1,<2.0.0",
15
15
  "protobuf>=6.33.5,<7.0.0",
16
16
  "numpy",
@@ -60,3 +60,4 @@ where = ["src"]
60
60
 
61
61
  [tool.setuptools.package-data]
62
62
  "remoteRF_server.common.grpc" = ["*.proto"]
63
+ "remoteRF_server.adapters.oai" = ["integration.json"]
@@ -0,0 +1,115 @@
1
+ """gNB-only OAI scaffold; this adapter TRANSMITS (gNB).
2
+
3
+ The operator-supplied executable and configuration are not managed by this
4
+ adapter; the operator installs and reviews them.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from pathlib import Path
10
+ import re
11
+ import time
12
+
13
+
14
+ SERIAL = re.compile(r"[A-Za-z0-9]{1,64}")
15
+ EXTRA_ARGS = ()
16
+ READY_MARKER = "ALL RUs ready"
17
+ STARTUP_TIMEOUT = 25.0
18
+
19
+
20
+ def serial_selector(value):
21
+ if type(value) is not dict or set(value) != {"serial"} or type(value["serial"]) is not str:
22
+ raise ValueError("one explicit USRP serial is required")
23
+ if not SERIAL.fullmatch(value["serial"]):
24
+ raise ValueError("invalid USRP serial")
25
+ return value["serial"]
26
+
27
+
28
+ def _operator_file(name):
29
+ value = os.environ.get(name)
30
+ path = Path(value) if value is not None else Path()
31
+ if value is None or not path.is_absolute() or not path.is_file():
32
+ raise RuntimeError(f"{name} must name an absolute existing regular file")
33
+ return str(path)
34
+
35
+
36
+ class OAI:
37
+ def __init__(self, context):
38
+ self._context = context
39
+ self._process_id = None
40
+ self._ready = False
41
+ self._stopped = False
42
+
43
+ def validate(self, profile, configuration):
44
+ if profile != "gnb" or type(configuration) is not dict or configuration:
45
+ raise ValueError("unsupported gNB configuration")
46
+ return {}
47
+
48
+ def _process(self):
49
+ rows = self._context.processes.snapshot()["processes"]
50
+ row = next((row for row in rows if row["id"] == self._process_id), None)
51
+ if row is None or row["returncode"] is not None:
52
+ raise RuntimeError("OAI gNB process is no longer running")
53
+ return row
54
+
55
+ def start(self, profile, configuration):
56
+ self.validate(profile, configuration)
57
+ serial = serial_selector(self._context.selector)
58
+ processes = self._context.processes
59
+ work_dir = Path(self._context.work_dir)
60
+ if processes is None or not work_dir.is_absolute():
61
+ raise RuntimeError("owned process supervision is required")
62
+ if self._process_id is not None or self._stopped:
63
+ raise RuntimeError("OAI gNB has already started or stopped")
64
+ if self._context.cancelled.is_set():
65
+ raise RuntimeError("OAI gNB was cancelled")
66
+
67
+ softmodem = _operator_file("REMOTERF_OAI_SOFTMODEM")
68
+ conf = _operator_file("REMOTERF_OAI_GNB_CONF")
69
+ env = {"LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"}
70
+ if "UHD_IMAGES_DIR" in os.environ:
71
+ env["UHD_IMAGES_DIR"] = os.environ["UHD_IMAGES_DIR"]
72
+
73
+ # EXTRA_ARGS, READY_MARKER, and sdr_addrs require verification against the pinned OAI build.
74
+ argv = [softmodem, "-O", conf, *EXTRA_ARGS,
75
+ "--RUs.[0].sdr_addrs", f"serial={serial}"]
76
+ self._process_id = processes.launch(
77
+ argv, env=env, cwd=work_dir, required_while_running=True)
78
+ deadline = time.monotonic() + STARTUP_TIMEOUT
79
+ while time.monotonic() < deadline:
80
+ if self._context.cancelled.is_set():
81
+ raise RuntimeError("OAI gNB was cancelled")
82
+ self._process()
83
+ tail = processes.log_tail(self._process_id, "stdout")[-65536:]
84
+ if READY_MARKER in tail.decode("utf-8", errors="replace"):
85
+ self._ready = True
86
+ return
87
+ time.sleep(0.1)
88
+ raise RuntimeError("OAI gNB did not reach readiness")
89
+
90
+ def status(self):
91
+ if self._stopped:
92
+ return {"ready": False, "stopped": True}
93
+ if not self._ready:
94
+ return {"ready": False}
95
+ self._process()
96
+ return {"ready": True}
97
+
98
+ def call(self, action, parameters):
99
+ if action != "health" or type(parameters) is not dict or parameters:
100
+ raise ValueError("health accepts no parameters")
101
+ return self.status()
102
+
103
+ def stop(self):
104
+ if self._stopped:
105
+ return
106
+ if self._context.processes is None:
107
+ self._stopped = True
108
+ return
109
+ if not self._context.processes.stop(timeout=10.0):
110
+ raise RuntimeError("OAI gNB cleanup could not be verified")
111
+ self._stopped = True
112
+
113
+
114
+ def create_adapter(context):
115
+ return OAI(context)
@@ -0,0 +1 @@
1
+ {"id":"org.remoterf.oai","version":"0.1.0","contract_version":"0.1","factory":"remoteRF_server.adapters.oai:create_adapter","device_types":["usrp"],"profiles":["gnb"],"actions":["health"]}
@@ -43,6 +43,9 @@ from ..host import host_tunnel_server as hts
43
43
 
44
44
  logger = logging.getLogger(__name__)
45
45
 
46
+ from remoterf_device_core.hardware_access import AccessTable
47
+ hardware_access = AccessTable()
48
+
46
49
  _state_lock = threading.RLock()
47
50
  _reservation_end_callbacks: list = []
48
51
  # token -> (gid, salt, hsh): see reservation_bridge_token()
@@ -926,23 +929,56 @@ def _start_host_reservation(device: VirtualDevice) -> None:
926
929
 
927
930
 
928
931
  def _prepare_for_holder(device_id: int, held: Tuple[str, str]) -> None:
929
- """A reservation started: ready the device (a PoE Pluto boots) before the
930
- holder's first call, which waits on io_lock for this same boot meanwhile."""
932
+ """Prepare only after verified previous release and for the current holder."""
933
+ from remoterf_device_core.integrations import IntegrationError
931
934
  with _state_lock:
932
935
  st = _devices.get(int(device_id))
933
936
  if st is None:
934
937
  return
938
+ retry = lambda: _prepare_for_holder(device_id, held)
935
939
  with st.io_lock:
936
- _reset_locked(st) # a back-to-back predecessor's power-down comes first
937
940
  with _state_lock:
938
941
  if _devices.get(int(device_id)) is not st or (st.salt, st.hsh) != held:
939
- return # the reservation is already over: leave the device off
942
+ return
943
+ if _native_cleanup_pending(device_id):
944
+ _after_native_cleanup(device_id, "prepare", retry)
945
+ return
940
946
  try:
941
- st.dev.prepare_for_holder()
947
+ with hardware_access.native(device_id):
948
+ _reset_locked(st)
949
+ st.dev.prepare_for_holder()
950
+ except IntegrationError as exc:
951
+ if exc.code != "device_owned":
952
+ raise
953
+ _after_native_cleanup(device_id, "prepare", retry)
942
954
  except Exception as exc:
943
955
  print(f"Warning: preparing {st.name or st.ident} for its reservation failed: {exc}")
944
956
 
945
957
 
958
+ _deferred_native_hooks = {}
959
+
960
+
961
+ def _after_native_cleanup(gid, kind, action):
962
+ from remoterf_device_core.native_lifetime import submit_cleanup
963
+ key = (int(gid), kind)
964
+ with _state_lock:
965
+ already = key in _deferred_native_hooks
966
+ _deferred_native_hooks[key] = action
967
+ if already:
968
+ return
969
+ def run():
970
+ with _state_lock:
971
+ latest = _deferred_native_hooks.pop(key)
972
+ latest()
973
+ submit_cleanup(lambda: not _native_cleanup_pending(gid), run)
974
+
975
+
976
+ def _native_cleanup_pending(gid):
977
+ # Import lazily: device inventory is loaded before the session runtime.
978
+ from .dynamic_v2 import session_manager
979
+ return session_manager.cleanup_pending(gid) or hardware_access.blocked(gid)
980
+
981
+
946
982
  def _reset_locked(st: _DeviceState) -> None:
947
983
  """Under st.io_lock: drop what the last reservation left on the device
948
984
  (buffers, streamers, a running transmitter) before anyone else drives it."""
@@ -958,11 +994,26 @@ def _reset_locked(st: _DeviceState) -> None:
958
994
 
959
995
 
960
996
  def _reset_if_pending(device_id: int) -> None:
997
+ from remoterf_device_core.integrations import IntegrationError
961
998
  with _state_lock:
962
999
  st = _devices.get(int(device_id))
963
- if st is not None:
964
- with st.io_lock:
965
- _reset_locked(st)
1000
+ if st is None:
1001
+ return
1002
+ retry = lambda: _reset_if_pending(device_id)
1003
+ with st.io_lock:
1004
+ with _state_lock:
1005
+ if _devices.get(int(device_id)) is not st:
1006
+ return
1007
+ if _native_cleanup_pending(device_id):
1008
+ _after_native_cleanup(device_id, "reset", retry)
1009
+ return
1010
+ try:
1011
+ with hardware_access.native(device_id):
1012
+ _reset_locked(st)
1013
+ except IntegrationError as exc:
1014
+ if exc.code != "device_owned":
1015
+ raise
1016
+ _after_native_cleanup(device_id, "reset", retry)
966
1017
 
967
1018
 
968
1019
  def reservation_bridge_token(device_id: int, expect=None) -> str:
@@ -1013,6 +1064,8 @@ def get_local_device_context(device_id: int) -> dict[str, Any] | None:
1013
1064
  "ident": st.ident,
1014
1065
  "name": st.name,
1015
1066
  "reserved": bool(st.salt or st.hsh),
1067
+ "io_lock": st.io_lock,
1068
+ "prepare": lambda: _reset_if_pending(int(device_id)),
1016
1069
  }
1017
1070
 
1018
1071
 
@@ -1086,7 +1139,8 @@ def device_is_available(device_id: int) -> bool:
1086
1139
  st = _devices.get(int(device_id))
1087
1140
  if not st or not _is_connected(st):
1088
1141
  return False
1089
- return (st.salt == "") and (st.hsh == "")
1142
+ available = (st.salt == "") and (st.hsh == "")
1143
+ return available and not hardware_access.blocked(device_id) and not _native_cleanup_pending(device_id)
1090
1144
 
1091
1145
  def get_device_by_id(device_id: int):
1092
1146
  _sync_host_devices()
@@ -1370,8 +1424,11 @@ def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
1370
1424
 
1371
1425
  lock.acquire()
1372
1426
  try:
1373
- _reset_locked(st)
1374
- yield int(gid), dev
1427
+ with hardware_access.native(gid):
1428
+ if _native_cleanup_pending(gid):
1429
+ raise RuntimeError("previous native cleanup is still in progress")
1430
+ _reset_locked(st)
1431
+ yield int(gid), dev
1375
1432
  finally:
1376
1433
  lock.release()
1377
1434
 
@@ -1398,8 +1455,11 @@ def acquire_device_by_gid(gid: int) -> Iterator[Tuple[int, object]]:
1398
1455
 
1399
1456
  lock.acquire()
1400
1457
  try:
1401
- _reset_locked(st)
1402
- yield int(gid), dev
1458
+ with hardware_access.native(gid):
1459
+ if _native_cleanup_pending(gid):
1460
+ raise RuntimeError("previous native cleanup is still in progress")
1461
+ _reset_locked(st)
1462
+ yield int(gid), dev
1403
1463
  finally:
1404
1464
  lock.release()
1405
1465
 
@@ -1476,6 +1536,19 @@ _reload_lock = threading.Lock()
1476
1536
 
1477
1537
 
1478
1538
  def _reload_devices_locked(on_device=None) -> None:
1539
+ from .dynamic_v2 import session_manager
1540
+ session_manager.integrations.validate_inventory(_load_device_records())
1541
+ with _state_lock:
1542
+ gids = [gid for gid, st in _devices.items() if st.origin == "local" and st.dev is not None]
1543
+ with hardware_access.maintenance(gids):
1544
+ for gid in gids:
1545
+ session_manager.close_gid(gid)
1546
+ if any(session_manager.cleanup_pending(gid) for gid in gids):
1547
+ raise RuntimeError("native cleanup is still in progress; retry reload after it finishes")
1548
+ _reload_devices_unfenced(on_device)
1549
+
1550
+
1551
+ def _reload_devices_unfenced(on_device=None) -> None:
1479
1552
  # ponytail: reconnects every local device, not just the changed one;
1480
1553
  # diff against the current table if a reload mid-session ever hurts.
1481
1554
  records = _load_device_records()
@@ -1510,7 +1583,10 @@ def _reload_devices_locked(on_device=None) -> None:
1510
1583
 
1511
1584
  if on_device is not None:
1512
1585
  on_device(int(gid), name)
1513
- dev, ident2, dtype2 = _connect_from_record(rec)
1586
+ if hardware_access.gate(gid).snapshot()['owner'] is not None:
1587
+ dev, ident2, dtype2 = _connect_from_record(rec, connect=False)
1588
+ else:
1589
+ dev, ident2, dtype2 = _connect_from_record(rec)
1514
1590
 
1515
1591
  tmp_info[int(gid)] = name
1516
1592
  if ident:
@@ -1573,14 +1649,49 @@ def _close_schema(schema) -> bool:
1573
1649
 
1574
1650
 
1575
1651
  def close_local_devices() -> int:
1576
- """Release server-owned native handles during an orderly shutdown."""
1652
+ """Request inventory cleanup, returning the number completed within 0.5s.
1653
+
1654
+ A stuck driver must not prevent attempts for other devices or close an
1655
+ inventory handle while a native call/application still owns the hardware.
1656
+ """
1657
+ from remoterf_device_core.native_lifetime import submit_cleanup
1658
+ from remoterf_device_core.integrations import IntegrationError
1577
1659
  with _state_lock:
1578
- schemas = [
1579
- state.dev
1580
- for state in _devices.values()
1660
+ states = [
1661
+ (gid, state)
1662
+ for gid, state in _devices.items()
1581
1663
  if state.origin == "local" and state.dev is not None
1582
1664
  ]
1583
- return sum(_close_schema(schema) for schema in schemas)
1665
+ completed = []
1666
+ jobs = []
1667
+ def close(gid, state):
1668
+ try:
1669
+ with state.io_lock:
1670
+ if _native_cleanup_pending(gid):
1671
+ _after_native_cleanup(gid, "shutdown", lambda: close(gid, state))
1672
+ return
1673
+ with hardware_access.gate(gid).maintenance():
1674
+ completed.append(_close_schema(state.dev))
1675
+ except IntegrationError:
1676
+ # Admission changed between readiness and execution. Retain
1677
+ # this schema and retry only after the new owner has drained.
1678
+ _after_native_cleanup(gid, "shutdown", lambda: close(gid, state))
1679
+
1680
+ for gid, state in states:
1681
+ if getattr(state.dev, "device", None) is None:
1682
+ continue
1683
+ def schedule(gid=gid, state=state):
1684
+ return submit_cleanup(lambda: not _native_cleanup_pending(gid)
1685
+ and not hardware_access.gate(gid).snapshot()["active_native"],
1686
+ lambda: close(gid, state))
1687
+ try:
1688
+ jobs.append(schedule())
1689
+ except Exception:
1690
+ logger.exception("could not schedule inventory cleanup for device %s", gid)
1691
+ deadline = time.monotonic() + 0.5
1692
+ for job in jobs:
1693
+ job.done.wait(max(0.0, deadline - time.monotonic()))
1694
+ return sum(completed)
1584
1695
 
1585
1696
  def set_pluto(ip: str = "192.168.2.1"):
1586
1697
  # kept for compatibility (no-op)
@@ -162,6 +162,13 @@ def _run_one(row: dict, init: dict, display: _Display, log_fd: int, left_after:
162
162
  typical = [_typical(t[1], st.dev) for t in mod.TESTS] + ([restore_s[0]] if teardown else [])
163
163
  started_at = time.monotonic()
164
164
  ran = False
165
+ access = []
166
+ access_lock = threading.Lock()
167
+
168
+ def release_access():
169
+ with access_lock:
170
+ if access:
171
+ access.pop().__exit__(None, None, None)
165
172
 
166
173
  def step(i: int, name: str, timeout: float, fn, step_ctx: dict) -> bool:
167
174
  """Run one test; True when it timed out."""
@@ -204,6 +211,9 @@ def _run_one(row: dict, init: dict, display: _Display, log_fd: int, left_after:
204
211
  if reason:
205
212
  row["status"] = f"skipped: {reason}"
206
213
  return
214
+ ticket = dm.hardware_access.native(gid)
215
+ ticket.__enter__()
216
+ access.append(ticket)
207
217
  ran = True
208
218
  dm._reset_locked(st)
209
219
  halted = None
@@ -232,6 +242,7 @@ def _run_one(row: dict, init: dict, display: _Display, log_fd: int, left_after:
232
242
 
233
243
  def _release(_f, fs=wedged):
234
244
  if all(f.done() for f in fs):
245
+ release_access()
235
246
  dm.diag_release(gid)
236
247
 
237
248
  for f in wedged:
@@ -239,6 +250,7 @@ def _run_one(row: dict, init: dict, display: _Display, log_fd: int, left_after:
239
250
  row["tests"].append({"name": "release", "status": "fail",
240
251
  "detail": "a test is still running; clients stay refused until it ends"})
241
252
  else:
253
+ release_access()
242
254
  dm.diag_release(gid)
243
255
  st.io_lock.release()
244
256
  _flush_all()