linux.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. #
  2. # Copyright (c) 2021 Project CHIP Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. """
  17. Handles linux-specific functionality for running test cases
  18. """
  19. import logging
  20. import os
  21. import subprocess
  22. import sys
  23. import time
  24. from .test_definition import ApplicationPaths
  25. test_environ = os.environ.copy()
  26. def EnsureNetworkNamespaceAvailability():
  27. if os.getuid() == 0:
  28. logging.debug("Current user is root")
  29. logging.warn("Running as root and this will change global namespaces.")
  30. return
  31. os.execvpe(
  32. "unshare", ["unshare", "--map-root-user", "-n", "-m", "python3",
  33. sys.argv[0], '--internal-inside-unshare'] + sys.argv[1:],
  34. test_environ)
  35. def EnsurePrivateState():
  36. logging.info("Ensuring /run is privately accessible")
  37. logging.debug("Making / private")
  38. if os.system("mount --make-private /") != 0:
  39. logging.error("Failed to make / private")
  40. logging.error("Are you using --privileged if running in docker?")
  41. sys.exit(1)
  42. logging.debug("Remounting /run")
  43. if os.system("mount -t tmpfs tmpfs /run") != 0:
  44. logging.error("Failed to mount /run as a temporary filesystem")
  45. logging.error("Are you using --privileged if running in docker?")
  46. sys.exit(1)
  47. def CreateNamespacesForAppTest():
  48. """
  49. Creates appropriate namespaces for a tool and app binaries in a simulated
  50. isolated network.
  51. """
  52. COMMANDS = [
  53. # 2 virtual hosts: for app and for the tool
  54. "ip netns add app",
  55. "ip netns add tool",
  56. # create links for switch to net connections
  57. "ip link add eth-app type veth peer name eth-app-switch",
  58. "ip link add eth-tool type veth peer name eth-tool-switch",
  59. "ip link add eth-ci type veth peer name eth-ci-switch",
  60. # link the connections together
  61. "ip link set eth-app netns app",
  62. "ip link set eth-tool netns tool",
  63. "ip link add name br1 type bridge",
  64. "ip link set br1 up",
  65. "ip link set eth-app-switch master br1",
  66. "ip link set eth-tool-switch master br1",
  67. "ip link set eth-ci-switch master br1",
  68. # mark connections up
  69. "ip netns exec app ip addr add 10.10.10.1/24 dev eth-app",
  70. "ip netns exec app ip link set dev eth-app up",
  71. "ip netns exec app ip link set dev lo up",
  72. "ip link set dev eth-app-switch up",
  73. "ip netns exec tool ip addr add 10.10.10.2/24 dev eth-tool",
  74. "ip netns exec tool ip link set dev eth-tool up",
  75. "ip netns exec tool ip link set dev lo up",
  76. "ip link set dev eth-tool-switch up",
  77. # Force IPv6 to use ULAs that we control
  78. "ip netns exec tool ip -6 addr flush eth-tool",
  79. "ip netns exec app ip -6 addr flush eth-app",
  80. "ip netns exec tool ip -6 a add fd00:0:1:1::2/64 dev eth-tool",
  81. "ip netns exec app ip -6 a add fd00:0:1:1::3/64 dev eth-app",
  82. # create link between virtual host 'tool' and the test runner
  83. "ip addr add 10.10.10.5/24 dev eth-ci",
  84. "ip link set dev eth-ci up",
  85. "ip link set dev eth-ci-switch up",
  86. ]
  87. for command in COMMANDS:
  88. logging.debug("Executing '%s'" % command)
  89. if os.system(command) != 0:
  90. logging.error("Failed to execute '%s'" % command)
  91. logging.error("Are you using --privileged if running in docker?")
  92. sys.exit(1)
  93. # IPv6 does Duplicate Address Detection even though
  94. # we know ULAs provided are isolated. Wait for 'tenative'
  95. # address to be gone.
  96. logging.info('Waiting for IPv6 DaD to complete (no tentative addresses)')
  97. for i in range(100): # wait at most 10 seconds
  98. output = subprocess.check_output(['ip', 'addr'])
  99. if b'tentative' not in output:
  100. logging.info('No more tentative addresses')
  101. break
  102. time.sleep(0.1)
  103. else:
  104. logging.warn("Some addresses look to still be tentative")
  105. def RemoveNamespaceForAppTest():
  106. """
  107. Removes namespaces for a tool and app binaries previously created to simulate an
  108. isolated network. This tears down what was created in CreateNamespacesForAppTest.
  109. """
  110. COMMANDS = [
  111. "ip link set dev eth-ci down",
  112. "ip link set dev eth-ci-switch down",
  113. "ip addr del 10.10.10.5/24 dev eth-ci",
  114. "ip link set br1 down",
  115. "ip link delete br1",
  116. "ip link delete eth-ci-switch",
  117. "ip link delete eth-tool-switch",
  118. "ip link delete eth-app-switch",
  119. "ip netns del tool",
  120. "ip netns del app",
  121. ]
  122. for command in COMMANDS:
  123. logging.debug("Executing '%s'" % command)
  124. if os.system(command) != 0:
  125. breakpoint()
  126. logging.error("Failed to execute '%s'" % command)
  127. sys.exit(1)
  128. def PrepareNamespacesForTestExecution(in_unshare: bool):
  129. if not in_unshare:
  130. EnsureNetworkNamespaceAvailability()
  131. elif in_unshare:
  132. EnsurePrivateState()
  133. CreateNamespacesForAppTest()
  134. def ShutdownNamespaceForTestExecution():
  135. RemoveNamespaceForAppTest()
  136. def PathsWithNetworkNamespaces(paths: ApplicationPaths) -> ApplicationPaths:
  137. """
  138. Returns a copy of paths with updated command arrays to invoke the
  139. commands in an appropriate network namespace.
  140. """
  141. return ApplicationPaths(
  142. chip_tool='ip netns exec tool'.split() + paths.chip_tool,
  143. all_clusters_app='ip netns exec app'.split() + paths.all_clusters_app,
  144. lock_app='ip netns exec app'.split() + paths.lock_app,
  145. ota_provider_app='ip netns exec app'.split() + paths.ota_provider_app,
  146. ota_requestor_app='ip netns exec app'.split() + paths.ota_requestor_app,
  147. tv_app='ip netns exec app'.split() + paths.tv_app,
  148. bridge_app='ip netns exec app'.split() + paths.bridge_app,
  149. chip_repl_yaml_tester_cmd='ip netns exec tool'.split() + paths.chip_repl_yaml_tester_cmd,
  150. chip_tool_with_python_cmd='ip netns exec tool'.split() + paths.chip_tool_with_python_cmd,
  151. )