__init__.py 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493
  1. # -*- coding: utf-8 -*-
  2. """
  3. Set up the Salt integration test suite
  4. """
  5. from __future__ import absolute_import, print_function
  6. import atexit
  7. import copy
  8. import errno
  9. import logging
  10. import multiprocessing
  11. import os
  12. import pprint
  13. import re
  14. import shutil
  15. import signal
  16. import socket
  17. import stat
  18. import subprocess
  19. import sys
  20. import tempfile
  21. import threading
  22. import time
  23. from datetime import datetime, timedelta
  24. import salt
  25. import salt.config
  26. import salt.log.setup as salt_log_setup
  27. import salt.master
  28. import salt.minion
  29. import salt.output
  30. import salt.runner
  31. import salt.utils.color
  32. import salt.utils.files
  33. import salt.utils.msgpack
  34. import salt.utils.path
  35. import salt.utils.platform
  36. import salt.utils.process
  37. import salt.utils.stringutils
  38. import salt.utils.yaml
  39. import salt.version
  40. from salt.exceptions import SaltClientError
  41. from salt.ext import six
  42. from salt.utils.immutabletypes import freeze
  43. from salt.utils.verify import verify_env
  44. from tests.support.cli_scripts import ScriptPathMixin
  45. from tests.support.helpers import RedirectStdStreams, requires_sshd_server
  46. from tests.support.mixins import (
  47. AdaptedConfigurationTestCaseMixin,
  48. CheckShellBinaryNameAndVersionMixin,
  49. SaltClientTestCaseMixin,
  50. SaltMinionEventAssertsMixin,
  51. SaltReturnAssertsMixin,
  52. ShellCaseCommonTestsMixin,
  53. )
  54. from tests.support.parser import PNUM, SaltTestcaseParser, print_header
  55. from tests.support.paths import * # pylint: disable=wildcard-import
  56. from tests.support.processes import * # pylint: disable=wildcard-import
  57. from tests.support.processes import SaltMaster, SaltMinion, SaltSyndic
  58. from tests.support.runtests import RUNTIME_VARS
  59. from tests.support.unit import TestCase
  60. try:
  61. import pwd
  62. except ImportError:
  63. pass
  64. try:
  65. import salt.ext.six.moves.socketserver as socketserver # pylint: disable=no-name-in-module
  66. except ImportError:
  67. import socketserver
  68. log = logging.getLogger(__name__)
  69. _RUNTESTS_PORTS = {}
  70. def get_unused_localhost_port():
  71. """
  72. Return a random unused port on localhost
  73. """
  74. usock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
  75. usock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  76. usock.bind(("127.0.0.1", 0))
  77. port = usock.getsockname()[1]
  78. if port in (54505, 54506, 64505, 64506, 64507, 64508, 64510, 64511, 64520, 64521):
  79. # These ports are hardcoded in the test configuration
  80. port = get_unused_localhost_port()
  81. usock.close()
  82. return port
  83. DARWIN = True if sys.platform.startswith("darwin") else False
  84. BSD = True if "bsd" in sys.platform else False
  85. AIX = True if sys.platform.startswith("aix") else False
  86. if (AIX or DARWIN) and port in _RUNTESTS_PORTS:
  87. port = get_unused_localhost_port()
  88. usock.close()
  89. return port
  90. _RUNTESTS_PORTS[port] = usock
  91. if DARWIN or BSD or AIX:
  92. usock.close()
  93. return port
  94. def close_open_sockets(sockets_dict):
  95. for port in list(sockets_dict):
  96. sock = sockets_dict.pop(port)
  97. sock.close()
  98. atexit.register(close_open_sockets, _RUNTESTS_PORTS)
  99. SALT_LOG_PORT = get_unused_localhost_port()
  100. class ThreadingMixIn(socketserver.ThreadingMixIn):
  101. daemon_threads = False
  102. class ThreadedSocketServer(ThreadingMixIn, socketserver.TCPServer, object):
  103. allow_reuse_address = True
  104. def server_activate(self):
  105. self.shutting_down = threading.Event()
  106. super(ThreadedSocketServer, self).server_activate()
  107. def server_close(self):
  108. if hasattr(self, "shutting_down"):
  109. self.shutting_down.set()
  110. super(ThreadedSocketServer, self).server_close()
  111. class SocketServerRequestHandler(socketserver.StreamRequestHandler):
  112. def handle(self):
  113. unpacker = salt.utils.msgpack.Unpacker(encoding="utf-8")
  114. while not self.server.shutting_down.is_set():
  115. try:
  116. wire_bytes = self.request.recv(1024)
  117. if not wire_bytes:
  118. break
  119. unpacker.feed(wire_bytes)
  120. for record_dict in unpacker:
  121. record = logging.makeLogRecord(record_dict)
  122. logger = logging.getLogger(record.name)
  123. logger.handle(record)
  124. del record_dict
  125. except (EOFError, KeyboardInterrupt, SystemExit):
  126. break
  127. except socket.error as exc:
  128. try:
  129. if exc.errno == errno.WSAECONNRESET:
  130. # Connection reset on windows
  131. break
  132. except AttributeError:
  133. # We're not on windows
  134. pass
  135. log.exception(exc)
  136. except Exception as exc: # pylint: disable=broad-except
  137. log.exception(exc)
  138. class TestDaemonStartFailed(Exception):
  139. """
  140. Simple exception to signal that a test daemon failed to start
  141. """
  142. class TestDaemon(object):
  143. """
  144. Set up the master and minion daemons, and run related cases
  145. """
  146. MINIONS_CONNECT_TIMEOUT = MINIONS_SYNC_TIMEOUT = 600
  147. def __init__(self, parser):
  148. self.parser = parser
  149. self.colors = salt.utils.color.get_colors(
  150. self.parser.options.no_colors is False
  151. )
  152. if salt.utils.platform.is_windows():
  153. # There's no shell color support on windows...
  154. for key in self.colors:
  155. self.colors[key] = ""
  156. def __enter__(self):
  157. """
  158. Start a master and minion
  159. """
  160. # Setup the multiprocessing logging queue listener
  161. salt_log_setup.setup_multiprocessing_logging_listener(self.master_opts)
  162. # Set up PATH to mockbin
  163. self._enter_mockbin()
  164. self.minion_targets = set(["minion", "sub_minion"])
  165. if self.parser.options.transport == "zeromq":
  166. self.start_zeromq_daemons()
  167. elif self.parser.options.transport == "tcp":
  168. self.start_tcp_daemons()
  169. self.pre_setup_minions()
  170. self.setup_minions()
  171. if getattr(self.parser.options, "ssh", False):
  172. self.prep_ssh()
  173. self.wait_for_minions(time.time(), self.MINIONS_CONNECT_TIMEOUT)
  174. if self.parser.options.sysinfo:
  175. try:
  176. print_header(
  177. "~~~~~~~ Versions Report ",
  178. inline=True,
  179. width=getattr(self.parser.options, "output_columns", PNUM),
  180. )
  181. except TypeError:
  182. print_header("~~~~~~~ Versions Report ", inline=True)
  183. print("\n".join(salt.version.versions_report()))
  184. try:
  185. print_header(
  186. "~~~~~~~ Minion Grains Information ",
  187. inline=True,
  188. width=getattr(self.parser.options, "output_columns", PNUM),
  189. )
  190. except TypeError:
  191. print_header("~~~~~~~ Minion Grains Information ", inline=True)
  192. grains = self.client.cmd("minion", "grains.items")
  193. minion_opts = self.minion_opts.copy()
  194. minion_opts["color"] = self.parser.options.no_colors is False
  195. salt.output.display_output(grains, "grains", minion_opts)
  196. try:
  197. print_header(
  198. "=",
  199. sep="=",
  200. inline=True,
  201. width=getattr(self.parser.options, "output_columns", PNUM),
  202. )
  203. except TypeError:
  204. print_header("", sep="=", inline=True)
  205. try:
  206. return self
  207. finally:
  208. self.post_setup_minions()
  209. def start_zeromq_daemons(self):
  210. """
  211. Fire up the daemons used for zeromq tests
  212. """
  213. self.log_server = ThreadedSocketServer(
  214. ("localhost", SALT_LOG_PORT), SocketServerRequestHandler
  215. )
  216. self.log_server_process = threading.Thread(target=self.log_server.serve_forever)
  217. self.log_server_process.start()
  218. try:
  219. sys.stdout.write(
  220. " * {LIGHT_YELLOW}Starting salt-master ... {ENDC}".format(**self.colors)
  221. )
  222. sys.stdout.flush()
  223. self.master_process = start_daemon(
  224. daemon_name="salt-master",
  225. daemon_id=self.master_opts["id"],
  226. daemon_log_prefix="salt-master/{}".format(self.master_opts["id"]),
  227. daemon_cli_script_name="master",
  228. daemon_config=self.master_opts,
  229. daemon_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  230. daemon_class=SaltMaster,
  231. bin_dir_path=SCRIPT_DIR,
  232. fail_hard=True,
  233. event_listener_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  234. start_timeout=120,
  235. )
  236. sys.stdout.write(
  237. "\r{0}\r".format(
  238. " " * getattr(self.parser.options, "output_columns", PNUM)
  239. )
  240. )
  241. sys.stdout.write(
  242. " * {LIGHT_GREEN}Starting salt-master ... STARTED!\n{ENDC}".format(
  243. **self.colors
  244. )
  245. )
  246. sys.stdout.flush()
  247. except (RuntimeWarning, RuntimeError):
  248. sys.stdout.write(
  249. "\r{0}\r".format(
  250. " " * getattr(self.parser.options, "output_columns", PNUM)
  251. )
  252. )
  253. sys.stdout.write(
  254. " * {LIGHT_RED}Starting salt-master ... FAILED!\n{ENDC}".format(
  255. **self.colors
  256. )
  257. )
  258. sys.stdout.flush()
  259. raise TestDaemonStartFailed()
  260. try:
  261. sys.stdout.write(
  262. " * {LIGHT_YELLOW}Starting salt-minion ... {ENDC}".format(**self.colors)
  263. )
  264. sys.stdout.flush()
  265. self.minion_process = start_daemon(
  266. daemon_name="salt-minion",
  267. daemon_id=self.master_opts["id"],
  268. daemon_log_prefix="salt-minion/{}".format(self.minion_opts["id"]),
  269. daemon_cli_script_name="minion",
  270. daemon_config=self.minion_opts,
  271. daemon_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  272. daemon_class=SaltMinion,
  273. bin_dir_path=SCRIPT_DIR,
  274. fail_hard=True,
  275. event_listener_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  276. start_timeout=120,
  277. )
  278. sys.stdout.write(
  279. "\r{0}\r".format(
  280. " " * getattr(self.parser.options, "output_columns", PNUM)
  281. )
  282. )
  283. sys.stdout.write(
  284. " * {LIGHT_GREEN}Starting salt-minion ... STARTED!\n{ENDC}".format(
  285. **self.colors
  286. )
  287. )
  288. sys.stdout.flush()
  289. except (RuntimeWarning, RuntimeError):
  290. sys.stdout.write(
  291. "\r{0}\r".format(
  292. " " * getattr(self.parser.options, "output_columns", PNUM)
  293. )
  294. )
  295. sys.stdout.write(
  296. " * {LIGHT_RED}Starting salt-minion ... FAILED!\n{ENDC}".format(
  297. **self.colors
  298. )
  299. )
  300. sys.stdout.flush()
  301. raise TestDaemonStartFailed()
  302. try:
  303. sys.stdout.write(
  304. " * {LIGHT_YELLOW}Starting sub salt-minion ... {ENDC}".format(
  305. **self.colors
  306. )
  307. )
  308. sys.stdout.flush()
  309. self.sub_minion_process = start_daemon(
  310. daemon_name="sub salt-minion",
  311. daemon_id=self.master_opts["id"],
  312. daemon_log_prefix="sub-salt-minion/{}".format(
  313. self.sub_minion_opts["id"]
  314. ),
  315. daemon_cli_script_name="minion",
  316. daemon_config=self.sub_minion_opts,
  317. daemon_config_dir=RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR,
  318. daemon_class=SaltMinion,
  319. bin_dir_path=SCRIPT_DIR,
  320. fail_hard=True,
  321. event_listener_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  322. start_timeout=120,
  323. )
  324. sys.stdout.write(
  325. "\r{0}\r".format(
  326. " " * getattr(self.parser.options, "output_columns", PNUM)
  327. )
  328. )
  329. sys.stdout.write(
  330. " * {LIGHT_GREEN}Starting sub salt-minion ... STARTED!\n{ENDC}".format(
  331. **self.colors
  332. )
  333. )
  334. sys.stdout.flush()
  335. except (RuntimeWarning, RuntimeError):
  336. sys.stdout.write(
  337. "\r{0}\r".format(
  338. " " * getattr(self.parser.options, "output_columns", PNUM)
  339. )
  340. )
  341. sys.stdout.write(
  342. " * {LIGHT_RED}Starting sub salt-minion ... FAILED!\n{ENDC}".format(
  343. **self.colors
  344. )
  345. )
  346. sys.stdout.flush()
  347. raise TestDaemonStartFailed()
  348. try:
  349. sys.stdout.write(
  350. " * {LIGHT_YELLOW}Starting syndic salt-master ... {ENDC}".format(
  351. **self.colors
  352. )
  353. )
  354. sys.stdout.flush()
  355. self.prep_syndic()
  356. self.smaster_process = start_daemon(
  357. daemon_name="salt-smaster",
  358. daemon_id=self.syndic_master_opts["id"],
  359. daemon_log_prefix="salt-smaster/{}".format(
  360. self.syndic_master_opts["id"]
  361. ),
  362. daemon_cli_script_name="master",
  363. daemon_config=self.syndic_master_opts,
  364. daemon_config_dir=RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR,
  365. daemon_class=SaltMaster,
  366. bin_dir_path=SCRIPT_DIR,
  367. fail_hard=True,
  368. event_listener_config_dir=RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR,
  369. start_timeout=120,
  370. )
  371. sys.stdout.write(
  372. "\r{0}\r".format(
  373. " " * getattr(self.parser.options, "output_columns", PNUM)
  374. )
  375. )
  376. sys.stdout.write(
  377. " * {LIGHT_GREEN}Starting syndic salt-master ... STARTED!\n{ENDC}".format(
  378. **self.colors
  379. )
  380. )
  381. sys.stdout.flush()
  382. except (RuntimeWarning, RuntimeError):
  383. sys.stdout.write(
  384. "\r{0}\r".format(
  385. " " * getattr(self.parser.options, "output_columns", PNUM)
  386. )
  387. )
  388. sys.stdout.write(
  389. " * {LIGHT_RED}Starting syndic salt-master ... FAILED!\n{ENDC}".format(
  390. **self.colors
  391. )
  392. )
  393. sys.stdout.flush()
  394. raise TestDaemonStartFailed()
  395. try:
  396. sys.stdout.write(
  397. " * {LIGHT_YELLOW}Starting salt-syndic ... {ENDC}".format(**self.colors)
  398. )
  399. sys.stdout.flush()
  400. self.syndic_process = start_daemon(
  401. daemon_name="salt-syndic",
  402. daemon_id=self.syndic_opts["id"],
  403. daemon_log_prefix="salt-syndic/{}".format(self.syndic_opts["id"]),
  404. daemon_cli_script_name="syndic",
  405. daemon_config=self.syndic_opts,
  406. daemon_config_dir=RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR,
  407. daemon_class=SaltSyndic,
  408. bin_dir_path=SCRIPT_DIR,
  409. fail_hard=True,
  410. event_listener_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  411. start_timeout=120,
  412. )
  413. sys.stdout.write(
  414. "\r{0}\r".format(
  415. " " * getattr(self.parser.options, "output_columns", PNUM)
  416. )
  417. )
  418. sys.stdout.write(
  419. " * {LIGHT_GREEN}Starting salt-syndic ... STARTED!\n{ENDC}".format(
  420. **self.colors
  421. )
  422. )
  423. sys.stdout.flush()
  424. except (RuntimeWarning, RuntimeError):
  425. sys.stdout.write(
  426. "\r{0}\r".format(
  427. " " * getattr(self.parser.options, "output_columns", PNUM)
  428. )
  429. )
  430. sys.stdout.write(
  431. " * {LIGHT_RED}Starting salt-syndic ... FAILED!\n{ENDC}".format(
  432. **self.colors
  433. )
  434. )
  435. sys.stdout.flush()
  436. raise TestDaemonStartFailed()
  437. if self.parser.options.proxy:
  438. self.minion_targets.add(self.proxy_opts["id"])
  439. try:
  440. sys.stdout.write(
  441. " * {LIGHT_YELLOW}Starting salt-proxy ... {ENDC}".format(
  442. **self.colors
  443. )
  444. )
  445. sys.stdout.flush()
  446. self.proxy_process = start_daemon(
  447. daemon_name="salt-proxy",
  448. daemon_id=self.proxy_opts["id"],
  449. daemon_log_prefix="salt-proxy/{}".format(self.proxy_opts["id"]),
  450. daemon_cli_script_name="proxy",
  451. daemon_config=self.proxy_opts,
  452. daemon_config_dir=RUNTIME_VARS.TMP_CONF_DIR,
  453. daemon_class=SaltProxy,
  454. bin_dir_path=SCRIPT_DIR,
  455. fail_hard=True,
  456. start_timeout=120,
  457. )
  458. sys.stdout.write(
  459. "\r{0}\r".format(
  460. " " * getattr(self.parser.options, "output_columns", PNUM)
  461. )
  462. )
  463. sys.stdout.write(
  464. " * {LIGHT_GREEN}Starting salt-proxy ... STARTED!\n{ENDC}".format(
  465. **self.colors
  466. )
  467. )
  468. sys.stdout.flush()
  469. except (RuntimeWarning, RuntimeError):
  470. sys.stdout.write(
  471. "\r{0}\r".format(
  472. " " * getattr(self.parser.options, "output_columns", PNUM)
  473. )
  474. )
  475. sys.stdout.write(
  476. " * {LIGHT_RED}Starting salt-proxy ... FAILED!\n{ENDC}".format(
  477. **self.colors
  478. )
  479. )
  480. sys.stdout.flush()
  481. raise TestDaemonStartFailed()
  482. start_tcp_daemons = start_zeromq_daemons
  483. def prep_syndic(self):
  484. """
  485. Create a roster file for salt's syndic
  486. """
  487. roster_path = os.path.join(FILES, "conf/_ssh/roster")
  488. shutil.copy(roster_path, RUNTIME_VARS.TMP_CONF_DIR)
  489. shutil.copy(roster_path, RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR)
  490. def prep_ssh(self):
  491. """
  492. Generate keys and start an ssh daemon on an alternate port
  493. """
  494. sys.stdout.write(
  495. " * {LIGHT_GREEN}Starting {0} ... {ENDC}".format(
  496. "SSH server", **self.colors
  497. )
  498. )
  499. keygen = salt.utils.path.which("ssh-keygen")
  500. sshd = salt.utils.path.which("sshd")
  501. if not (keygen and sshd):
  502. print(
  503. "WARNING: Could not initialize SSH subsystem. Tests for salt-ssh may break!"
  504. )
  505. return
  506. if not os.path.exists(RUNTIME_VARS.TMP_CONF_DIR):
  507. os.makedirs(RUNTIME_VARS.TMP_CONF_DIR)
  508. # Generate client key
  509. pub_key_test_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "key_test.pub")
  510. priv_key_test_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "key_test")
  511. if os.path.exists(pub_key_test_file):
  512. os.remove(pub_key_test_file)
  513. if os.path.exists(priv_key_test_file):
  514. os.remove(priv_key_test_file)
  515. keygen_process = subprocess.Popen(
  516. [
  517. keygen,
  518. "-t",
  519. "ecdsa",
  520. "-b",
  521. "521",
  522. "-C",
  523. '"$(whoami)@$(hostname)-$(date -I)"',
  524. "-f",
  525. "key_test",
  526. "-P",
  527. "",
  528. ],
  529. stdout=subprocess.PIPE,
  530. stderr=subprocess.PIPE,
  531. close_fds=True,
  532. cwd=RUNTIME_VARS.TMP_CONF_DIR,
  533. )
  534. _, keygen_err = keygen_process.communicate()
  535. if keygen_err:
  536. print(
  537. "ssh-keygen had errors: {0}".format(
  538. salt.utils.stringutils.to_str(keygen_err)
  539. )
  540. )
  541. sshd_config_path = os.path.join(FILES, "conf/_ssh/sshd_config")
  542. shutil.copy(sshd_config_path, RUNTIME_VARS.TMP_CONF_DIR)
  543. auth_key_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "key_test.pub")
  544. # Generate server key
  545. server_key_dir = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "server")
  546. if not os.path.exists(server_key_dir):
  547. os.makedirs(server_key_dir)
  548. server_dsa_priv_key_file = os.path.join(server_key_dir, "ssh_host_dsa_key")
  549. server_dsa_pub_key_file = os.path.join(server_key_dir, "ssh_host_dsa_key.pub")
  550. server_ecdsa_priv_key_file = os.path.join(server_key_dir, "ssh_host_ecdsa_key")
  551. server_ecdsa_pub_key_file = os.path.join(
  552. server_key_dir, "ssh_host_ecdsa_key.pub"
  553. )
  554. server_ed25519_priv_key_file = os.path.join(
  555. server_key_dir, "ssh_host_ed25519_key"
  556. )
  557. server_ed25519_pub_key_file = os.path.join(
  558. server_key_dir, "ssh_host.ed25519_key.pub"
  559. )
  560. for server_key_file in (
  561. server_dsa_priv_key_file,
  562. server_dsa_pub_key_file,
  563. server_ecdsa_priv_key_file,
  564. server_ecdsa_pub_key_file,
  565. server_ed25519_priv_key_file,
  566. server_ed25519_pub_key_file,
  567. ):
  568. if os.path.exists(server_key_file):
  569. os.remove(server_key_file)
  570. keygen_process_dsa = subprocess.Popen(
  571. [
  572. keygen,
  573. "-t",
  574. "dsa",
  575. "-b",
  576. "1024",
  577. "-C",
  578. '"$(whoami)@$(hostname)-$(date -I)"',
  579. "-f",
  580. "ssh_host_dsa_key",
  581. "-P",
  582. "",
  583. ],
  584. stdout=subprocess.PIPE,
  585. stderr=subprocess.PIPE,
  586. close_fds=True,
  587. cwd=server_key_dir,
  588. )
  589. _, keygen_dsa_err = keygen_process_dsa.communicate()
  590. if keygen_dsa_err:
  591. print(
  592. "ssh-keygen had errors: {0}".format(
  593. salt.utils.stringutils.to_str(keygen_dsa_err)
  594. )
  595. )
  596. keygen_process_ecdsa = subprocess.Popen(
  597. [
  598. keygen,
  599. "-t",
  600. "ecdsa",
  601. "-b",
  602. "521",
  603. "-C",
  604. '"$(whoami)@$(hostname)-$(date -I)"',
  605. "-f",
  606. "ssh_host_ecdsa_key",
  607. "-P",
  608. "",
  609. ],
  610. stdout=subprocess.PIPE,
  611. stderr=subprocess.PIPE,
  612. close_fds=True,
  613. cwd=server_key_dir,
  614. )
  615. _, keygen_escda_err = keygen_process_ecdsa.communicate()
  616. if keygen_escda_err:
  617. print(
  618. "ssh-keygen had errors: {0}".format(
  619. salt.utils.stringutils.to_str(keygen_escda_err)
  620. )
  621. )
  622. keygen_process_ed25519 = subprocess.Popen(
  623. [
  624. keygen,
  625. "-t",
  626. "ed25519",
  627. "-b",
  628. "521",
  629. "-C",
  630. '"$(whoami)@$(hostname)-$(date -I)"',
  631. "-f",
  632. "ssh_host_ed25519_key",
  633. "-P",
  634. "",
  635. ],
  636. stdout=subprocess.PIPE,
  637. stderr=subprocess.PIPE,
  638. close_fds=True,
  639. cwd=server_key_dir,
  640. )
  641. _, keygen_ed25519_err = keygen_process_ed25519.communicate()
  642. if keygen_ed25519_err:
  643. print(
  644. "ssh-keygen had errors: {0}".format(
  645. salt.utils.stringutils.to_str(keygen_ed25519_err)
  646. )
  647. )
  648. with salt.utils.files.fopen(
  649. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "sshd_config"), "a"
  650. ) as ssh_config:
  651. ssh_config.write("AuthorizedKeysFile {0}\n".format(auth_key_file))
  652. if not keygen_dsa_err:
  653. ssh_config.write("HostKey {0}\n".format(server_dsa_priv_key_file))
  654. if not keygen_escda_err:
  655. ssh_config.write("HostKey {0}\n".format(server_ecdsa_priv_key_file))
  656. if not keygen_ed25519_err:
  657. ssh_config.write("HostKey {0}\n".format(server_ed25519_priv_key_file))
  658. self.sshd_pidfile = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "sshd.pid")
  659. self.sshd_process = subprocess.Popen(
  660. [sshd, "-f", "sshd_config", "-o", "PidFile={0}".format(self.sshd_pidfile)],
  661. stdout=subprocess.PIPE,
  662. stderr=subprocess.PIPE,
  663. close_fds=True,
  664. cwd=RUNTIME_VARS.TMP_CONF_DIR,
  665. )
  666. _, sshd_err = self.sshd_process.communicate()
  667. if sshd_err:
  668. print(
  669. "sshd had errors on startup: {0}".format(
  670. salt.utils.stringutils.to_str(sshd_err)
  671. )
  672. )
  673. else:
  674. os.environ["SSH_DAEMON_RUNNING"] = "True"
  675. self.prep_syndic()
  676. with salt.utils.files.fopen(
  677. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "roster"), "a"
  678. ) as roster:
  679. roster.write(" user: {0}\n".format(RUNTIME_VARS.RUNNING_TESTS_USER))
  680. roster.write(
  681. " priv: {0}/{1}\n".format(RUNTIME_VARS.TMP_CONF_DIR, "key_test")
  682. )
  683. if salt.utils.platform.is_darwin():
  684. roster.write(" set_path: $PATH:/usr/local/bin/\n")
  685. sys.stdout.write(" {LIGHT_GREEN}STARTED!\n{ENDC}".format(**self.colors))
  686. @classmethod
  687. def config(cls, role):
  688. """
  689. Return a configuration for a master/minion/syndic.
  690. Currently these roles are:
  691. * master
  692. * minion
  693. * syndic
  694. * syndic_master
  695. * sub_minion
  696. * proxy
  697. """
  698. return RUNTIME_VARS.RUNTIME_CONFIGS[role]
  699. @classmethod
  700. def config_location(cls):
  701. return RUNTIME_VARS.TMP_CONF_DIR
  702. @property
  703. def client(self):
  704. """
  705. Return a local client which will be used for example to ping and sync
  706. the test minions.
  707. This client is defined as a class attribute because its creation needs
  708. to be deferred to a latter stage. If created it on `__enter__` like it
  709. previously was, it would not receive the master events.
  710. """
  711. if "runtime_client" not in RUNTIME_VARS.RUNTIME_CONFIGS:
  712. RUNTIME_VARS.RUNTIME_CONFIGS[
  713. "runtime_client"
  714. ] = salt.client.get_local_client(mopts=self.master_opts)
  715. return RUNTIME_VARS.RUNTIME_CONFIGS["runtime_client"]
  716. @classmethod
  717. def transplant_configs(cls, transport="zeromq"):
  718. if os.path.isdir(RUNTIME_VARS.TMP):
  719. shutil.rmtree(RUNTIME_VARS.TMP)
  720. os.makedirs(RUNTIME_VARS.TMP)
  721. os.makedirs(RUNTIME_VARS.TMP_ROOT_DIR)
  722. os.makedirs(RUNTIME_VARS.TMP_CONF_DIR)
  723. os.makedirs(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR)
  724. os.makedirs(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR)
  725. os.makedirs(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR)
  726. print(
  727. " * Transplanting configuration files to '{0}'".format(
  728. RUNTIME_VARS.TMP_CONF_DIR
  729. )
  730. )
  731. tests_known_hosts_file = os.path.join(
  732. RUNTIME_VARS.TMP_CONF_DIR, "salt_ssh_known_hosts"
  733. )
  734. with salt.utils.files.fopen(tests_known_hosts_file, "w") as known_hosts:
  735. known_hosts.write("")
  736. # This master connects to syndic_master via a syndic
  737. master_opts = salt.config._read_conf_file(
  738. os.path.join(RUNTIME_VARS.CONF_DIR, "master")
  739. )
  740. master_opts["known_hosts_file"] = tests_known_hosts_file
  741. master_opts["cachedir"] = "cache"
  742. master_opts["user"] = RUNTIME_VARS.RUNNING_TESTS_USER
  743. master_opts["root_dir"] = os.path.join(TMP_ROOT_DIR)
  744. master_opts["pki_dir"] = "pki"
  745. master_opts["syndic_master"] = "localhost"
  746. pytest_stop_sending_events_file = os.path.join(
  747. TMP_ROOT_DIR, "pytest_stop_sending_events_file_master"
  748. )
  749. with salt.utils.files.fopen(pytest_stop_sending_events_file, "w") as wfh:
  750. wfh.write("")
  751. master_opts["pytest_stop_sending_events_file"] = pytest_stop_sending_events_file
  752. file_tree = {
  753. "root_dir": os.path.join(FILES, "pillar", "base", "file_tree"),
  754. "follow_dir_links": False,
  755. "keep_newline": True,
  756. }
  757. master_opts["ext_pillar"].append({"file_tree": file_tree})
  758. # Config settings to test `event_return`
  759. if "returner_dirs" not in master_opts:
  760. master_opts["returner_dirs"] = []
  761. master_opts["returner_dirs"].append(
  762. os.path.join(RUNTIME_VARS.FILES, "returners")
  763. )
  764. master_opts["event_return"] = "runtests_noop"
  765. # Under windows we can't seem to properly create a virtualenv off of another
  766. # virtualenv, we can on linux but we will still point to the virtualenv binary
  767. # outside the virtualenv running the test suite, if that's the case.
  768. try:
  769. real_prefix = sys.real_prefix
  770. # The above attribute exists, this is a virtualenv
  771. if salt.utils.platform.is_windows():
  772. virtualenv_binary = os.path.join(
  773. real_prefix, "Scripts", "virtualenv.exe"
  774. )
  775. else:
  776. # We need to remove the virtualenv from PATH or we'll get the virtualenv binary
  777. # from within the virtualenv, we don't want that
  778. path = os.environ.get("PATH")
  779. if path is not None:
  780. path_items = path.split(os.pathsep)
  781. for item in path_items[:]:
  782. if item.startswith(sys.base_prefix):
  783. path_items.remove(item)
  784. os.environ["PATH"] = os.pathsep.join(path_items)
  785. virtualenv_binary = salt.utils.path.which("virtualenv")
  786. if path is not None:
  787. # Restore previous environ PATH
  788. os.environ["PATH"] = path
  789. if not virtualenv_binary.startswith(real_prefix):
  790. virtualenv_binary = None
  791. if virtualenv_binary and not os.path.exists(virtualenv_binary):
  792. # It doesn't exist?!
  793. virtualenv_binary = None
  794. except AttributeError:
  795. # We're not running inside a virtualenv
  796. virtualenv_binary = None
  797. # This minion connects to master
  798. minion_opts = salt.config._read_conf_file(
  799. os.path.join(RUNTIME_VARS.CONF_DIR, "minion")
  800. )
  801. minion_opts["cachedir"] = "cache"
  802. minion_opts["user"] = RUNTIME_VARS.RUNNING_TESTS_USER
  803. minion_opts["root_dir"] = os.path.join(TMP_ROOT_DIR)
  804. minion_opts["pki_dir"] = "pki"
  805. minion_opts["hosts.file"] = os.path.join(TMP_ROOT_DIR, "hosts")
  806. minion_opts["aliases.file"] = os.path.join(TMP_ROOT_DIR, "aliases")
  807. if virtualenv_binary:
  808. minion_opts["venv_bin"] = virtualenv_binary
  809. # This sub_minion also connects to master
  810. sub_minion_opts = salt.config._read_conf_file(
  811. os.path.join(RUNTIME_VARS.CONF_DIR, "sub_minion")
  812. )
  813. sub_minion_opts["cachedir"] = "cache"
  814. sub_minion_opts["user"] = RUNTIME_VARS.RUNNING_TESTS_USER
  815. sub_minion_opts["root_dir"] = os.path.join(TMP, "rootdir-sub-minion")
  816. sub_minion_opts["pki_dir"] = "pki"
  817. sub_minion_opts["hosts.file"] = os.path.join(TMP_ROOT_DIR, "hosts")
  818. sub_minion_opts["aliases.file"] = os.path.join(TMP_ROOT_DIR, "aliases")
  819. if virtualenv_binary:
  820. sub_minion_opts["venv_bin"] = virtualenv_binary
  821. # This is the master of masters
  822. syndic_master_opts = salt.config._read_conf_file(
  823. os.path.join(RUNTIME_VARS.CONF_DIR, "syndic_master")
  824. )
  825. syndic_master_opts["cachedir"] = "cache"
  826. syndic_master_opts["user"] = RUNTIME_VARS.RUNNING_TESTS_USER
  827. syndic_master_opts["root_dir"] = os.path.join(TMP, "rootdir-syndic-master")
  828. syndic_master_opts["pki_dir"] = "pki"
  829. pytest_stop_sending_events_file = os.path.join(
  830. TMP_ROOT_DIR, "pytest_stop_sending_events_file_syndic_master"
  831. )
  832. with salt.utils.files.fopen(pytest_stop_sending_events_file, "w") as wfh:
  833. wfh.write("")
  834. syndic_master_opts[
  835. "pytest_stop_sending_events_file"
  836. ] = pytest_stop_sending_events_file
  837. # This is the syndic for master
  838. # Let's start with a copy of the syndic master configuration
  839. syndic_opts = copy.deepcopy(syndic_master_opts)
  840. # Let's update with the syndic configuration
  841. syndic_opts.update(
  842. salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, "syndic"))
  843. )
  844. syndic_opts["cachedir"] = "cache"
  845. syndic_opts["root_dir"] = os.path.join(TMP_ROOT_DIR)
  846. # This is the syndic for master
  847. # Let's start with a copy of the syndic master configuration
  848. syndic_opts = copy.deepcopy(syndic_master_opts)
  849. # Let's update with the syndic configuration
  850. syndic_opts.update(
  851. salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, "syndic"))
  852. )
  853. syndic_opts["config_dir"] = RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR
  854. syndic_opts["cachedir"] = os.path.join(TMP, "rootdir", "cache")
  855. syndic_opts["root_dir"] = os.path.join(TMP, "rootdir")
  856. # This proxy connects to master
  857. proxy_opts = salt.config._read_conf_file(os.path.join(CONF_DIR, "proxy"))
  858. proxy_opts["cachedir"] = "cache"
  859. # proxy_opts['user'] = running_tests_user
  860. proxy_opts["root_dir"] = os.path.join(TMP, "rootdir-proxy")
  861. proxy_opts["pki_dir"] = "pki"
  862. proxy_opts["hosts.file"] = os.path.join(TMP, "rootdir-proxy", "hosts")
  863. proxy_opts["aliases.file"] = os.path.join(TMP, "rootdir-proxy", "aliases")
  864. if transport == "tcp":
  865. master_opts["transport"] = "tcp"
  866. minion_opts["transport"] = "tcp"
  867. sub_minion_opts["transport"] = "tcp"
  868. syndic_master_opts["transport"] = "tcp"
  869. proxy_opts["transport"] = "tcp"
  870. # This is the syndic for master
  871. # Let's start with a copy of the syndic master configuration
  872. syndic_opts = copy.deepcopy(master_opts)
  873. # Let's update with the syndic configuration
  874. syndic_opts.update(
  875. salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, "syndic"))
  876. )
  877. syndic_opts["cachedir"] = os.path.join(TMP, "rootdir", "cache")
  878. syndic_opts["config_dir"] = RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR
  879. # Set up config options that require internal data
  880. master_opts["pillar_roots"] = syndic_master_opts["pillar_roots"] = {
  881. "base": [
  882. RUNTIME_VARS.TMP_PILLAR_TREE,
  883. os.path.join(FILES, "pillar", "base"),
  884. ]
  885. }
  886. minion_opts["pillar_roots"] = {
  887. "base": [
  888. RUNTIME_VARS.TMP_PILLAR_TREE,
  889. os.path.join(FILES, "pillar", "base"),
  890. ]
  891. }
  892. master_opts["file_roots"] = syndic_master_opts["file_roots"] = {
  893. "base": [
  894. # Let's support runtime created files that can be used like:
  895. # salt://my-temp-file.txt
  896. RUNTIME_VARS.TMP_STATE_TREE,
  897. os.path.join(FILES, "file", "base"),
  898. ],
  899. # Alternate root to test __env__ choices
  900. "prod": [
  901. os.path.join(FILES, "file", "prod"),
  902. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  903. ],
  904. }
  905. minion_opts["file_roots"] = {
  906. "base": [
  907. # Let's support runtime created files that can be used like:
  908. # salt://my-temp-file.txt
  909. RUNTIME_VARS.TMP_STATE_TREE,
  910. os.path.join(FILES, "file", "base"),
  911. ],
  912. # Alternate root to test __env__ choices
  913. "prod": [
  914. os.path.join(FILES, "file", "prod"),
  915. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  916. ],
  917. }
  918. master_opts.setdefault("reactor", []).append(
  919. {"salt/minion/*/start": [os.path.join(FILES, "reactor-sync-minion.sls")]}
  920. )
  921. master_opts.setdefault("reactor", []).append(
  922. {"salt/test/reactor": [os.path.join(FILES, "reactor-test.sls")]}
  923. )
  924. for opts_dict in (master_opts, syndic_master_opts):
  925. if "ext_pillar" not in opts_dict:
  926. opts_dict["ext_pillar"] = []
  927. if salt.utils.platform.is_windows():
  928. opts_dict["ext_pillar"].append(
  929. {"cmd_yaml": "type {0}".format(os.path.join(FILES, "ext.yaml"))}
  930. )
  931. else:
  932. opts_dict["ext_pillar"].append(
  933. {"cmd_yaml": "cat {0}".format(os.path.join(FILES, "ext.yaml"))}
  934. )
  935. # all read, only owner write
  936. autosign_file_permissions = (
  937. stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  938. )
  939. for opts_dict in (master_opts, syndic_master_opts):
  940. # We need to copy the extension modules into the new master root_dir or
  941. # it will be prefixed by it
  942. new_extension_modules_path = os.path.join(
  943. opts_dict["root_dir"], "extension_modules"
  944. )
  945. if not os.path.exists(new_extension_modules_path):
  946. shutil.copytree(
  947. os.path.join(INTEGRATION_TEST_DIR, "files", "extension_modules"),
  948. new_extension_modules_path,
  949. )
  950. opts_dict["extension_modules"] = os.path.join(
  951. opts_dict["root_dir"], "extension_modules"
  952. )
  953. # Copy the autosign_file to the new master root_dir
  954. new_autosign_file_path = os.path.join(
  955. opts_dict["root_dir"], "autosign_file"
  956. )
  957. shutil.copyfile(
  958. os.path.join(INTEGRATION_TEST_DIR, "files", "autosign_file"),
  959. new_autosign_file_path,
  960. )
  961. os.chmod(new_autosign_file_path, autosign_file_permissions)
  962. # Point the config values to the correct temporary paths
  963. for name in ("hosts", "aliases"):
  964. optname = "{0}.file".format(name)
  965. optname_path = os.path.join(TMP, name)
  966. master_opts[optname] = optname_path
  967. minion_opts[optname] = optname_path
  968. sub_minion_opts[optname] = optname_path
  969. syndic_opts[optname] = optname_path
  970. syndic_master_opts[optname] = optname_path
  971. proxy_opts[optname] = optname_path
  972. master_opts["runtests_conn_check_port"] = get_unused_localhost_port()
  973. minion_opts["runtests_conn_check_port"] = get_unused_localhost_port()
  974. sub_minion_opts["runtests_conn_check_port"] = get_unused_localhost_port()
  975. syndic_opts["runtests_conn_check_port"] = get_unused_localhost_port()
  976. syndic_master_opts["runtests_conn_check_port"] = get_unused_localhost_port()
  977. proxy_opts["runtests_conn_check_port"] = get_unused_localhost_port()
  978. for conf in (
  979. master_opts,
  980. minion_opts,
  981. sub_minion_opts,
  982. syndic_opts,
  983. syndic_master_opts,
  984. proxy_opts,
  985. ):
  986. if "engines" not in conf:
  987. conf["engines"] = []
  988. conf["engines"].append({"salt_runtests": {}})
  989. if "engines_dirs" not in conf:
  990. conf["engines_dirs"] = []
  991. conf["engines_dirs"].insert(0, ENGINES_DIR)
  992. if "log_handlers_dirs" not in conf:
  993. conf["log_handlers_dirs"] = []
  994. conf["log_handlers_dirs"].insert(0, LOG_HANDLERS_DIR)
  995. conf["runtests_log_port"] = SALT_LOG_PORT
  996. conf["runtests_log_level"] = (
  997. os.environ.get("TESTS_MIN_LOG_LEVEL_NAME") or "debug"
  998. )
  999. # ----- Transcribe Configuration ---------------------------------------------------------------------------->
  1000. for entry in os.listdir(RUNTIME_VARS.CONF_DIR):
  1001. if entry in (
  1002. "master",
  1003. "minion",
  1004. "sub_minion",
  1005. "syndic",
  1006. "syndic_master",
  1007. "proxy",
  1008. ):
  1009. # These have runtime computed values and will be handled
  1010. # differently
  1011. continue
  1012. entry_path = os.path.join(RUNTIME_VARS.CONF_DIR, entry)
  1013. if os.path.isfile(entry_path):
  1014. shutil.copy(entry_path, os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry))
  1015. elif os.path.isdir(entry_path):
  1016. shutil.copytree(
  1017. entry_path, os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry)
  1018. )
  1019. for entry in (
  1020. "master",
  1021. "minion",
  1022. "sub_minion",
  1023. "syndic",
  1024. "syndic_master",
  1025. "proxy",
  1026. ):
  1027. computed_config = copy.deepcopy(locals()["{0}_opts".format(entry)])
  1028. with salt.utils.files.fopen(
  1029. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry), "w"
  1030. ) as fp_:
  1031. salt.utils.yaml.safe_dump(
  1032. computed_config, fp_, default_flow_style=False
  1033. )
  1034. sub_minion_computed_config = copy.deepcopy(sub_minion_opts)
  1035. with salt.utils.files.fopen(
  1036. os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, "minion"), "w"
  1037. ) as wfh:
  1038. salt.utils.yaml.safe_dump(
  1039. sub_minion_computed_config, wfh, default_flow_style=False
  1040. )
  1041. shutil.copyfile(
  1042. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "master"),
  1043. os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, "master"),
  1044. )
  1045. syndic_master_computed_config = copy.deepcopy(syndic_master_opts)
  1046. with salt.utils.files.fopen(
  1047. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR, "master"), "w"
  1048. ) as wfh:
  1049. salt.utils.yaml.safe_dump(
  1050. syndic_master_computed_config, wfh, default_flow_style=False
  1051. )
  1052. syndic_computed_config = copy.deepcopy(syndic_opts)
  1053. with salt.utils.files.fopen(
  1054. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, "minion"), "w"
  1055. ) as wfh:
  1056. salt.utils.yaml.safe_dump(
  1057. syndic_computed_config, wfh, default_flow_style=False
  1058. )
  1059. shutil.copyfile(
  1060. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "master"),
  1061. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, "master"),
  1062. )
  1063. # <---- Transcribe Configuration -----------------------------------------------------------------------------
  1064. # ----- Verify Environment ---------------------------------------------------------------------------------->
  1065. master_opts = salt.config.master_config(
  1066. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "master")
  1067. )
  1068. minion_opts = salt.config.minion_config(
  1069. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "minion")
  1070. )
  1071. syndic_opts = salt.config.syndic_config(
  1072. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, "master"),
  1073. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, "minion"),
  1074. )
  1075. sub_minion_opts = salt.config.minion_config(
  1076. os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, "minion")
  1077. )
  1078. syndic_master_opts = salt.config.master_config(
  1079. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR, "master")
  1080. )
  1081. proxy_opts = salt.config.proxy_config(
  1082. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "proxy")
  1083. )
  1084. RUNTIME_VARS.RUNTIME_CONFIGS["master"] = freeze(master_opts)
  1085. RUNTIME_VARS.RUNTIME_CONFIGS["minion"] = freeze(minion_opts)
  1086. RUNTIME_VARS.RUNTIME_CONFIGS["syndic"] = freeze(syndic_opts)
  1087. RUNTIME_VARS.RUNTIME_CONFIGS["sub_minion"] = freeze(sub_minion_opts)
  1088. RUNTIME_VARS.RUNTIME_CONFIGS["syndic_master"] = freeze(syndic_master_opts)
  1089. RUNTIME_VARS.RUNTIME_CONFIGS["proxy"] = freeze(proxy_opts)
  1090. verify_env(
  1091. [
  1092. os.path.join(master_opts["pki_dir"], "minions"),
  1093. os.path.join(master_opts["pki_dir"], "minions_pre"),
  1094. os.path.join(master_opts["pki_dir"], "minions_rejected"),
  1095. os.path.join(master_opts["pki_dir"], "minions_denied"),
  1096. os.path.join(master_opts["cachedir"], "jobs"),
  1097. os.path.join(master_opts["root_dir"], "cache", "tokens"),
  1098. os.path.join(syndic_master_opts["pki_dir"], "minions"),
  1099. os.path.join(syndic_master_opts["pki_dir"], "minions_pre"),
  1100. os.path.join(syndic_master_opts["pki_dir"], "minions_rejected"),
  1101. os.path.join(syndic_master_opts["cachedir"], "jobs"),
  1102. os.path.join(syndic_master_opts["root_dir"], "cache", "tokens"),
  1103. os.path.join(master_opts["pki_dir"], "accepted"),
  1104. os.path.join(master_opts["pki_dir"], "rejected"),
  1105. os.path.join(master_opts["pki_dir"], "pending"),
  1106. os.path.join(syndic_master_opts["pki_dir"], "accepted"),
  1107. os.path.join(syndic_master_opts["pki_dir"], "rejected"),
  1108. os.path.join(syndic_master_opts["pki_dir"], "pending"),
  1109. os.path.join(minion_opts["pki_dir"], "accepted"),
  1110. os.path.join(minion_opts["pki_dir"], "rejected"),
  1111. os.path.join(minion_opts["pki_dir"], "pending"),
  1112. os.path.join(sub_minion_opts["pki_dir"], "accepted"),
  1113. os.path.join(sub_minion_opts["pki_dir"], "rejected"),
  1114. os.path.join(sub_minion_opts["pki_dir"], "pending"),
  1115. os.path.dirname(master_opts["log_file"]),
  1116. minion_opts["extension_modules"],
  1117. sub_minion_opts["extension_modules"],
  1118. sub_minion_opts["pki_dir"],
  1119. master_opts["sock_dir"],
  1120. syndic_master_opts["sock_dir"],
  1121. sub_minion_opts["sock_dir"],
  1122. minion_opts["sock_dir"],
  1123. RUNTIME_VARS.TMP_STATE_TREE,
  1124. RUNTIME_VARS.TMP_PILLAR_TREE,
  1125. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  1126. TMP,
  1127. ],
  1128. RUNTIME_VARS.RUNNING_TESTS_USER,
  1129. root_dir=master_opts["root_dir"],
  1130. )
  1131. cls.master_opts = master_opts
  1132. cls.minion_opts = minion_opts
  1133. # cls.proxy_opts = proxy_opts
  1134. cls.sub_minion_opts = sub_minion_opts
  1135. cls.syndic_opts = syndic_opts
  1136. cls.syndic_master_opts = syndic_master_opts
  1137. cls.proxy_opts = proxy_opts
  1138. # <---- Verify Environment -----------------------------------------------------------------------------------
  1139. def __exit__(self, type, value, traceback):
  1140. """
  1141. Kill the minion and master processes
  1142. """
  1143. try:
  1144. if hasattr(self.sub_minion_process, "terminate"):
  1145. self.sub_minion_process.terminate()
  1146. else:
  1147. log.error("self.sub_minion_process can't be terminate.")
  1148. except AttributeError:
  1149. pass
  1150. try:
  1151. if hasattr(self.minion_process, "terminate"):
  1152. self.minion_process.terminate()
  1153. else:
  1154. log.error("self.minion_process can't be terminate.")
  1155. except AttributeError:
  1156. pass
  1157. if hasattr(self, "proxy_process"):
  1158. self.proxy_process.terminate()
  1159. try:
  1160. if hasattr(self.master_process, "terminate"):
  1161. self.master_process.terminate()
  1162. else:
  1163. log.error("self.master_process can't be terminate.")
  1164. except AttributeError:
  1165. pass
  1166. try:
  1167. self.syndic_process.terminate()
  1168. except AttributeError:
  1169. pass
  1170. try:
  1171. self.smaster_process.terminate()
  1172. except AttributeError:
  1173. pass
  1174. self._exit_mockbin()
  1175. self._exit_ssh()
  1176. # Shutdown the multiprocessing logging queue listener
  1177. salt_log_setup.shutdown_multiprocessing_logging()
  1178. salt_log_setup.shutdown_multiprocessing_logging_listener(daemonizing=True)
  1179. # Shutdown the log server
  1180. self.log_server.shutdown()
  1181. self.log_server.server_close()
  1182. self.log_server_process.join()
  1183. def pre_setup_minions(self):
  1184. """
  1185. Subclass this method for additional minion setups.
  1186. """
  1187. def setup_minions(self):
  1188. """
  1189. Minions setup routines
  1190. """
  1191. def post_setup_minions(self):
  1192. """
  1193. Subclass this method to execute code after the minions have been setup
  1194. """
  1195. def _enter_mockbin(self):
  1196. path = os.environ.get("PATH", "")
  1197. path_items = path.split(os.pathsep)
  1198. if MOCKBIN not in path_items:
  1199. path_items.insert(0, MOCKBIN)
  1200. os.environ["PATH"] = os.pathsep.join(path_items)
  1201. def _exit_ssh(self):
  1202. if hasattr(self, "sshd_process"):
  1203. try:
  1204. self.sshd_process.kill()
  1205. except OSError as exc:
  1206. if exc.errno != 3:
  1207. raise
  1208. with salt.utils.files.fopen(self.sshd_pidfile) as fhr:
  1209. try:
  1210. os.kill(int(fhr.read()), signal.SIGKILL)
  1211. except OSError as exc:
  1212. if exc.errno != 3:
  1213. raise
  1214. def _exit_mockbin(self):
  1215. path = os.environ.get("PATH", "")
  1216. path_items = path.split(os.pathsep)
  1217. try:
  1218. path_items.remove(MOCKBIN)
  1219. except ValueError:
  1220. pass
  1221. os.environ["PATH"] = os.pathsep.join(path_items)
  1222. @classmethod
  1223. def clean(cls):
  1224. """
  1225. Clean out the tmp files
  1226. """
  1227. def remove_readonly(func, path, excinfo):
  1228. if os.path.exists(path):
  1229. # Give full permissions to owner
  1230. os.chmod(path, stat.S_IRWXU)
  1231. func(path)
  1232. for dirname in (
  1233. TMP,
  1234. RUNTIME_VARS.TMP_STATE_TREE,
  1235. RUNTIME_VARS.TMP_PILLAR_TREE,
  1236. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  1237. ):
  1238. if os.path.isdir(dirname):
  1239. try:
  1240. shutil.rmtree(six.text_type(dirname), onerror=remove_readonly)
  1241. except Exception: # pylint: disable=broad-except
  1242. log.exception("Failed to remove directory: %s", dirname)
  1243. def wait_for_jid(self, targets, jid, timeout=120):
  1244. time.sleep(1) # Allow some time for minions to accept jobs
  1245. now = datetime.now()
  1246. expire = now + timedelta(seconds=timeout)
  1247. job_finished = False
  1248. while now <= expire:
  1249. running = self.__client_job_running(targets, jid)
  1250. sys.stdout.write(
  1251. "\r{0}\r".format(
  1252. " " * getattr(self.parser.options, "output_columns", PNUM)
  1253. )
  1254. )
  1255. if not running and job_finished is False:
  1256. # Let's not have false positives and wait one more seconds
  1257. job_finished = True
  1258. elif not running and job_finished is True:
  1259. return True
  1260. elif running and job_finished is True:
  1261. job_finished = False
  1262. if job_finished is False:
  1263. sys.stdout.write(
  1264. " * {LIGHT_YELLOW}[Quit in {0}]{ENDC} Waiting for {1}".format(
  1265. "{0}".format(expire - now).rsplit(".", 1)[0],
  1266. ", ".join(running),
  1267. **self.colors
  1268. )
  1269. )
  1270. sys.stdout.flush()
  1271. time.sleep(1)
  1272. now = datetime.now()
  1273. else: # pylint: disable=W0120
  1274. sys.stdout.write(
  1275. "\n {LIGHT_RED}*{ENDC} ERROR: Failed to get information "
  1276. "back\n".format(**self.colors)
  1277. )
  1278. sys.stdout.flush()
  1279. return False
  1280. def __client_job_running(self, targets, jid):
  1281. running = self.client.cmd(list(targets), "saltutil.running", tgt_type="list")
  1282. return [k for (k, v) in six.iteritems(running) if v and v[0]["jid"] == jid]
  1283. def sync_minion_modules_(self, modules_kind, targets, timeout=None):
  1284. if not timeout:
  1285. timeout = 120
  1286. # Let's sync all connected minions
  1287. print(
  1288. " {LIGHT_BLUE}*{ENDC} Syncing minion's {1} "
  1289. "(saltutil.sync_{1})".format(
  1290. ", ".join(targets), modules_kind, **self.colors
  1291. )
  1292. )
  1293. syncing = set(targets)
  1294. jid_info = self.client.run_job(
  1295. list(targets),
  1296. "saltutil.sync_{0}".format(modules_kind),
  1297. tgt_type="list",
  1298. timeout=999999999999999,
  1299. )
  1300. if self.wait_for_jid(targets, jid_info["jid"], timeout) is False:
  1301. print(
  1302. " {LIGHT_RED}*{ENDC} WARNING: Minions failed to sync {0}. "
  1303. "Tests requiring these {0} WILL fail".format(
  1304. modules_kind, **self.colors
  1305. )
  1306. )
  1307. raise SystemExit()
  1308. while syncing:
  1309. rdata = self.client.get_full_returns(jid_info["jid"], syncing, 1)
  1310. if rdata:
  1311. for name, output in six.iteritems(rdata):
  1312. if not output["ret"]:
  1313. # Already synced!?
  1314. syncing.remove(name)
  1315. continue
  1316. if isinstance(output["ret"], six.string_types):
  1317. # An errors has occurred
  1318. print(
  1319. " {LIGHT_RED}*{ENDC} {0} Failed to sync {2}: "
  1320. "{1}".format(
  1321. name, output["ret"], modules_kind, **self.colors
  1322. )
  1323. )
  1324. return False
  1325. print(
  1326. " {LIGHT_GREEN}*{ENDC} Synced {0} {2}: "
  1327. "{1}".format(
  1328. name, ", ".join(output["ret"]), modules_kind, **self.colors
  1329. )
  1330. )
  1331. # Synced!
  1332. try:
  1333. syncing.remove(name)
  1334. except KeyError:
  1335. print(
  1336. " {LIGHT_RED}*{ENDC} {0} already synced??? "
  1337. "{1}".format(name, output, **self.colors)
  1338. )
  1339. return True
  1340. def sync_minion_states(self, targets, timeout=None):
  1341. salt.utils.process.appendproctitle("SyncMinionStates")
  1342. self.sync_minion_modules_("states", targets, timeout=timeout)
  1343. def sync_minion_modules(self, targets, timeout=None):
  1344. salt.utils.process.appendproctitle("SyncMinionModules")
  1345. self.sync_minion_modules_("modules", targets, timeout=timeout)
  1346. def sync_minion_grains(self, targets, timeout=None):
  1347. salt.utils.process.appendproctitle("SyncMinionGrains")
  1348. self.sync_minion_modules_("grains", targets, timeout=timeout)
  1349. def wait_for_minions(self, start, timeout, sleep=5):
  1350. """
  1351. Ensure all minions and masters (including sub-masters) are connected.
  1352. """
  1353. while True:
  1354. try:
  1355. ret = self.client.run_job("*", "test.ping")
  1356. except salt.exceptions.SaltClientError:
  1357. ret = None
  1358. if ret and "minions" not in ret:
  1359. continue
  1360. if ret and sorted(ret["minions"]) == sorted(self.minion_targets):
  1361. break
  1362. if time.time() - start >= timeout:
  1363. raise RuntimeError("Ping Minions Failed")
  1364. time.sleep(sleep)