2
0

linuxtest.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # Test class and utilities for functional Linux-based tests
  2. #
  3. # Copyright (c) 2018 Red Hat, Inc.
  4. #
  5. # Author:
  6. # Cleber Rosa <crosa@redhat.com>
  7. #
  8. # This work is licensed under the terms of the GNU GPL, version 2 or
  9. # later. See the COPYING file in the top-level directory.
  10. import os
  11. import shutil
  12. from avocado.utils import cloudinit, datadrainer, process, vmimage
  13. from . import LinuxSSHMixIn
  14. from . import QemuSystemTest
  15. if os.path.islink(os.path.dirname(os.path.dirname(__file__))):
  16. # The link to the avocado tests dir in the source code directory
  17. lnk = os.path.dirname(os.path.dirname(__file__))
  18. #: The QEMU root source directory
  19. SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk)))
  20. else:
  21. SOURCE_DIR = BUILD_DIR
  22. class LinuxDistro:
  23. """Represents a Linux distribution
  24. Holds information of known distros.
  25. """
  26. #: A collection of known distros and their respective image checksum
  27. KNOWN_DISTROS = {
  28. 'fedora': {
  29. '31': {
  30. 'x86_64':
  31. {'checksum': ('e3c1b309d9203604922d6e255c2c5d09'
  32. '8a309c2d46215d8fc026954f3c5c27a0'),
  33. 'pxeboot_url': ('https://archives.fedoraproject.org/'
  34. 'pub/archive/fedora/linux/releases/31/'
  35. 'Everything/x86_64/os/images/pxeboot/'),
  36. 'kernel_params': ('root=UUID=b1438b9b-2cab-4065-a99a-'
  37. '08a96687f73c ro no_timer_check '
  38. 'net.ifnames=0 console=tty1 '
  39. 'console=ttyS0,115200n8'),
  40. },
  41. 'aarch64':
  42. {'checksum': ('1e18d9c0cf734940c4b5d5ec592facae'
  43. 'd2af0ad0329383d5639c997fdf16fe49'),
  44. 'pxeboot_url': 'https://archives.fedoraproject.org/'
  45. 'pub/archive/fedora/linux/releases/31/'
  46. 'Everything/aarch64/os/images/pxeboot/',
  47. 'kernel_params': ('root=UUID=b6950a44-9f3c-4076-a9c2-'
  48. '355e8475b0a7 ro earlyprintk=pl011,0x9000000'
  49. ' ignore_loglevel no_timer_check'
  50. ' printk.time=1 rd_NO_PLYMOUTH'
  51. ' console=ttyAMA0'),
  52. },
  53. 'ppc64':
  54. {'checksum': ('7c3528b85a3df4b2306e892199a9e1e4'
  55. '3f991c506f2cc390dc4efa2026ad2f58')},
  56. 's390x':
  57. {'checksum': ('4caaab5a434fd4d1079149a072fdc789'
  58. '1e354f834d355069ca982fdcaf5a122d')},
  59. },
  60. '32': {
  61. 'aarch64':
  62. {'checksum': ('b367755c664a2d7a26955bbfff985855'
  63. 'adfa2ca15e908baf15b4b176d68d3967'),
  64. 'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/'
  65. 'releases/32/Server/aarch64/os/images/'
  66. 'pxeboot/'),
  67. 'kernel_params': ('root=UUID=3df75b65-be8d-4db4-8655-'
  68. '14d95c0e90c5 ro no_timer_check net.ifnames=0'
  69. ' console=tty1 console=ttyS0,115200n8'),
  70. },
  71. },
  72. '33': {
  73. 'aarch64':
  74. {'checksum': ('e7f75cdfd523fe5ac2ca9eeece68edc1'
  75. 'a81f386a17f969c1d1c7c87031008a6b'),
  76. 'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/'
  77. 'releases/33/Server/aarch64/os/images/'
  78. 'pxeboot/'),
  79. 'kernel_params': ('root=UUID=d20b3ffa-6397-4a63-a734-'
  80. '1126a0208f8a ro no_timer_check net.ifnames=0'
  81. ' console=tty1 console=ttyS0,115200n8'
  82. ' console=tty0'),
  83. },
  84. },
  85. }
  86. }
  87. def __init__(self, name, version, arch):
  88. self.name = name
  89. self.version = version
  90. self.arch = arch
  91. try:
  92. info = self.KNOWN_DISTROS.get(name).get(version).get(arch)
  93. except AttributeError:
  94. # Unknown distro
  95. info = None
  96. self._info = info or {}
  97. @property
  98. def checksum(self):
  99. """Gets the cloud-image file checksum"""
  100. return self._info.get('checksum', None)
  101. @checksum.setter
  102. def checksum(self, value):
  103. self._info['checksum'] = value
  104. @property
  105. def pxeboot_url(self):
  106. """Gets the repository url where pxeboot files can be found"""
  107. return self._info.get('pxeboot_url', None)
  108. @property
  109. def default_kernel_params(self):
  110. """Gets the default kernel parameters"""
  111. return self._info.get('kernel_params', None)
  112. class LinuxTest(LinuxSSHMixIn, QemuSystemTest):
  113. """Facilitates having a cloud-image Linux based available.
  114. For tests that intend to interact with guests, this is a better choice
  115. to start with than the more vanilla `QemuSystemTest` class.
  116. """
  117. distro = None
  118. username = 'root'
  119. password = 'password'
  120. smp = '2'
  121. memory = '1024'
  122. def _set_distro(self):
  123. distro_name = self.params.get(
  124. 'distro',
  125. default=self._get_unique_tag_val('distro'))
  126. if not distro_name:
  127. distro_name = 'fedora'
  128. distro_version = self.params.get(
  129. 'distro_version',
  130. default=self._get_unique_tag_val('distro_version'))
  131. if not distro_version:
  132. distro_version = '31'
  133. self.distro = LinuxDistro(distro_name, distro_version, self.arch)
  134. # The distro checksum behaves differently than distro name and
  135. # version. First, it does not respect a tag with the same
  136. # name, given that it's not expected to be used for filtering
  137. # (distro name versions are the natural choice). Second, the
  138. # order of precedence is: parameter, attribute and then value
  139. # from KNOWN_DISTROS.
  140. distro_checksum = self.params.get('distro_checksum',
  141. default=None)
  142. if distro_checksum:
  143. self.distro.checksum = distro_checksum
  144. def setUp(self, ssh_pubkey=None, network_device_type='virtio-net'):
  145. super().setUp()
  146. self.require_netdev('user')
  147. self._set_distro()
  148. self.vm.add_args('-smp', self.smp)
  149. self.vm.add_args('-m', self.memory)
  150. # The following network device allows for SSH connections
  151. self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22',
  152. '-device', '%s,netdev=vnet' % network_device_type)
  153. self.set_up_boot()
  154. if ssh_pubkey is None:
  155. ssh_pubkey, self.ssh_key = self.set_up_existing_ssh_keys()
  156. self.set_up_cloudinit(ssh_pubkey)
  157. def set_up_existing_ssh_keys(self):
  158. ssh_public_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa.pub')
  159. source_private_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa')
  160. ssh_dir = os.path.join(self.workdir, '.ssh')
  161. os.mkdir(ssh_dir, mode=0o700)
  162. ssh_private_key = os.path.join(ssh_dir,
  163. os.path.basename(source_private_key))
  164. shutil.copyfile(source_private_key, ssh_private_key)
  165. os.chmod(ssh_private_key, 0o600)
  166. return (ssh_public_key, ssh_private_key)
  167. def download_boot(self):
  168. # Set the qemu-img binary.
  169. # If none is available, the test will cancel.
  170. vmimage.QEMU_IMG = super().get_qemu_img()
  171. self.log.info('Downloading/preparing boot image')
  172. # Fedora 31 only provides ppc64le images
  173. image_arch = self.arch
  174. if self.distro.name == 'fedora':
  175. if image_arch == 'ppc64':
  176. image_arch = 'ppc64le'
  177. try:
  178. boot = vmimage.get(
  179. self.distro.name, arch=image_arch, version=self.distro.version,
  180. checksum=self.distro.checksum,
  181. algorithm='sha256',
  182. cache_dir=self.cache_dirs[0],
  183. snapshot_dir=self.workdir)
  184. except:
  185. self.cancel('Failed to download/prepare boot image')
  186. return boot.path
  187. def prepare_cloudinit(self, ssh_pubkey=None):
  188. self.log.info('Preparing cloudinit image')
  189. try:
  190. cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
  191. pubkey_content = None
  192. if ssh_pubkey:
  193. with open(ssh_pubkey) as pubkey:
  194. pubkey_content = pubkey.read()
  195. cloudinit.iso(cloudinit_iso, self.name,
  196. username=self.username,
  197. password=self.password,
  198. # QEMU's hard coded usermode router address
  199. phone_home_host='10.0.2.2',
  200. phone_home_port=self.phone_server.server_port,
  201. authorized_key=pubkey_content)
  202. except Exception:
  203. self.cancel('Failed to prepare the cloudinit image')
  204. return cloudinit_iso
  205. def set_up_boot(self):
  206. path = self.download_boot()
  207. self.vm.add_args('-drive', 'file=%s' % path)
  208. def set_up_cloudinit(self, ssh_pubkey=None):
  209. self.phone_server = cloudinit.PhoneHomeServer(('0.0.0.0', 0),
  210. self.name)
  211. cloudinit_iso = self.prepare_cloudinit(ssh_pubkey)
  212. self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
  213. def launch_and_wait(self, set_up_ssh_connection=True):
  214. self.vm.set_console()
  215. self.vm.launch()
  216. console_drainer = datadrainer.LineLogger(self.vm.console_socket.fileno(),
  217. logger=self.log.getChild('console'))
  218. console_drainer.start()
  219. self.log.info('VM launched, waiting for boot confirmation from guest')
  220. while not self.phone_server.instance_phoned_back:
  221. self.phone_server.handle_request()
  222. if set_up_ssh_connection:
  223. self.log.info('Setting up the SSH connection')
  224. self.ssh_connect(self.username, self.ssh_key)