__init__.py 53 KB

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