__init__.py 52 KB

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