__init__.py 55 KB

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