__init__.py 56 KB

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