1
0

__init__.py 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302
  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. os.makedirs(RUNTIME_VARS.TMP_CONF_DIR)
  634. os.makedirs(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR)
  635. os.makedirs(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR)
  636. os.makedirs(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR)
  637. if not os.path.exists(RUNTIME_VARS.TMP):
  638. os.makedirs(RUNTIME_VARS.TMP)
  639. print(' * Transplanting configuration files to \'{0}\''.format(RUNTIME_VARS.TMP_CONF_DIR))
  640. tests_known_hosts_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'salt_ssh_known_hosts')
  641. with salt.utils.files.fopen(tests_known_hosts_file, 'w') as known_hosts:
  642. known_hosts.write('')
  643. # This master connects to syndic_master via a syndic
  644. master_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'master'))
  645. master_opts['known_hosts_file'] = tests_known_hosts_file
  646. master_opts['cachedir'] = os.path.join(TMP, 'rootdir', 'cache')
  647. master_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
  648. master_opts['config_dir'] = RUNTIME_VARS.TMP_CONF_DIR
  649. master_opts['root_dir'] = os.path.join(TMP, 'rootdir')
  650. master_opts['pki_dir'] = os.path.join(TMP, 'rootdir', 'pki', 'master')
  651. master_opts['syndic_master'] = 'localhost'
  652. file_tree = {
  653. 'root_dir': os.path.join(FILES, 'pillar', 'base', 'file_tree'),
  654. 'follow_dir_links': False,
  655. 'keep_newline': True,
  656. }
  657. master_opts['ext_pillar'].append({'file_tree': file_tree})
  658. # Config settings to test `event_return`
  659. if 'returner_dirs' not in master_opts:
  660. master_opts['returner_dirs'] = []
  661. master_opts['returner_dirs'].append(os.path.join(RUNTIME_VARS.FILES, 'returners'))
  662. master_opts['event_return'] = 'runtests_noop'
  663. # Under windows we can't seem to properly create a virtualenv off of another
  664. # virtualenv, we can on linux but we will still point to the virtualenv binary
  665. # outside the virtualenv running the test suite, if that's the case.
  666. try:
  667. real_prefix = sys.real_prefix
  668. # The above attribute exists, this is a virtualenv
  669. if salt.utils.is_windows():
  670. virtualenv_binary = os.path.join(real_prefix, 'Scripts', 'virtualenv.exe')
  671. else:
  672. # We need to remove the virtualenv from PATH or we'll get the virtualenv binary
  673. # from within the virtualenv, we don't want that
  674. path = os.environ.get('PATH')
  675. if path is not None:
  676. path_items = path.split(os.pathsep)
  677. for item in path_items[:]:
  678. if item.startswith(sys.base_prefix):
  679. path_items.remove(item)
  680. os.environ['PATH'] = os.pathsep.join(path_items)
  681. virtualenv_binary = salt.utils.which('virtualenv')
  682. if path is not None:
  683. # Restore previous environ PATH
  684. os.environ['PATH'] = path
  685. if not virtualenv_binary.startswith(real_prefix):
  686. virtualenv_binary = None
  687. if virtualenv_binary and not os.path.exists(virtualenv_binary):
  688. # It doesn't exist?!
  689. virtualenv_binary = None
  690. except AttributeError:
  691. # We're not running inside a virtualenv
  692. virtualenv_binary = None
  693. # This minion connects to master
  694. minion_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'minion'))
  695. minion_opts['cachedir'] = os.path.join(TMP, 'rootdir', 'cache')
  696. minion_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
  697. minion_opts['config_dir'] = RUNTIME_VARS.TMP_CONF_DIR
  698. minion_opts['root_dir'] = os.path.join(TMP, 'rootdir')
  699. minion_opts['pki_dir'] = os.path.join(TMP, 'rootdir', 'pki')
  700. minion_opts['hosts.file'] = os.path.join(TMP, 'rootdir', 'hosts')
  701. minion_opts['aliases.file'] = os.path.join(TMP, 'rootdir', 'aliases')
  702. if virtualenv_binary:
  703. minion_opts['venv_bin'] = virtualenv_binary
  704. # This sub_minion also connects to master
  705. sub_minion_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'sub_minion'))
  706. sub_minion_opts['cachedir'] = os.path.join(TMP, 'rootdir-sub-minion', 'cache')
  707. sub_minion_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
  708. sub_minion_opts['config_dir'] = RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR
  709. sub_minion_opts['root_dir'] = os.path.join(TMP, 'rootdir-sub-minion')
  710. sub_minion_opts['pki_dir'] = os.path.join(TMP, 'rootdir-sub-minion', 'pki', 'minion')
  711. sub_minion_opts['hosts.file'] = os.path.join(TMP, 'rootdir', 'hosts')
  712. sub_minion_opts['aliases.file'] = os.path.join(TMP, 'rootdir', 'aliases')
  713. if virtualenv_binary:
  714. sub_minion_opts['venv_bin'] = virtualenv_binary
  715. # This is the master of masters
  716. syndic_master_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'syndic_master'))
  717. syndic_master_opts['cachedir'] = os.path.join(TMP, 'rootdir-syndic-master', 'cache')
  718. syndic_master_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
  719. syndic_master_opts['config_dir'] = RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR
  720. syndic_master_opts['root_dir'] = os.path.join(TMP, 'rootdir-syndic-master')
  721. syndic_master_opts['pki_dir'] = os.path.join(TMP, 'rootdir-syndic-master', 'pki', 'master')
  722. # This proxy connects to master
  723. proxy_opts = salt.config._read_conf_file(os.path.join(CONF_DIR, 'proxy'))
  724. proxy_opts['cachedir'] = os.path.join(TMP, 'rootdir-proxy', 'cache')
  725. if not os.path.exists(proxy_opts['cachedir']):
  726. os.makedirs(proxy_opts['cachedir'])
  727. # proxy_opts['user'] = running_tests_user
  728. proxy_opts['config_dir'] = RUNTIME_VARS.TMP_CONF_DIR
  729. proxy_opts['root_dir'] = os.path.join(TMP, 'rootdir-proxy')
  730. proxy_opts['pki_dir'] = os.path.join(TMP, 'rootdir-proxy', 'pki')
  731. if not os.path.exists(proxy_opts['pki_dir']):
  732. os.makedirs(proxy_opts['pki_dir'])
  733. proxy_opts['hosts.file'] = os.path.join(TMP, 'rootdir-proxy', 'hosts')
  734. proxy_opts['aliases.file'] = os.path.join(TMP, 'rootdir-proxy', 'aliases')
  735. if transport == 'tcp':
  736. master_opts['transport'] = 'tcp'
  737. minion_opts['transport'] = 'tcp'
  738. sub_minion_opts['transport'] = 'tcp'
  739. syndic_master_opts['transport'] = 'tcp'
  740. proxy_opts['transport'] = 'tcp'
  741. # This is the syndic for master
  742. # Let's start with a copy of the syndic master configuration
  743. syndic_opts = copy.deepcopy(master_opts)
  744. # Let's update with the syndic configuration
  745. syndic_opts.update(salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'syndic')))
  746. syndic_opts['cachedir'] = os.path.join(TMP, 'rootdir', 'cache')
  747. syndic_opts['config_dir'] = RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR
  748. # Set up config options that require internal data
  749. master_opts['pillar_roots'] = syndic_master_opts['pillar_roots'] = {
  750. 'base': [
  751. RUNTIME_VARS.TMP_PILLAR_TREE,
  752. os.path.join(FILES, 'pillar', 'base'),
  753. ]
  754. }
  755. minion_opts['pillar_roots'] = {
  756. 'base': [
  757. RUNTIME_VARS.TMP_PILLAR_TREE,
  758. os.path.join(FILES, 'pillar', 'base'),
  759. ]
  760. }
  761. master_opts['file_roots'] = syndic_master_opts['file_roots'] = {
  762. 'base': [
  763. # Let's support runtime created files that can be used like:
  764. # salt://my-temp-file.txt
  765. RUNTIME_VARS.TMP_STATE_TREE,
  766. os.path.join(FILES, 'file', 'base'),
  767. ],
  768. # Alternate root to test __env__ choices
  769. 'prod': [
  770. os.path.join(FILES, 'file', 'prod'),
  771. RUNTIME_VARS.TMP_PRODENV_STATE_TREE
  772. ]
  773. }
  774. minion_opts['file_roots'] = {
  775. 'base': [
  776. # Let's support runtime created files that can be used like:
  777. # salt://my-temp-file.txt
  778. RUNTIME_VARS.TMP_STATE_TREE,
  779. os.path.join(FILES, 'file', 'base'),
  780. ],
  781. # Alternate root to test __env__ choices
  782. 'prod': [
  783. os.path.join(FILES, 'file', 'prod'),
  784. RUNTIME_VARS.TMP_PRODENV_STATE_TREE
  785. ]
  786. }
  787. master_opts.setdefault('reactor', []).append(
  788. {
  789. 'salt/minion/*/start': [
  790. os.path.join(FILES, 'reactor-sync-minion.sls')
  791. ],
  792. }
  793. )
  794. for opts_dict in (master_opts, syndic_master_opts):
  795. if 'ext_pillar' not in opts_dict:
  796. opts_dict['ext_pillar'] = []
  797. if salt.utils.platform.is_windows():
  798. opts_dict['ext_pillar'].append(
  799. {'cmd_yaml': 'type {0}'.format(os.path.join(FILES, 'ext.yaml'))})
  800. else:
  801. opts_dict['ext_pillar'].append(
  802. {'cmd_yaml': 'cat {0}'.format(os.path.join(FILES, 'ext.yaml'))})
  803. # all read, only owner write
  804. autosign_file_permissions = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  805. for opts_dict in (master_opts, syndic_master_opts):
  806. # We need to copy the extension modules into the new master root_dir or
  807. # it will be prefixed by it
  808. new_extension_modules_path = os.path.join(opts_dict['root_dir'], 'extension_modules')
  809. if not os.path.exists(new_extension_modules_path):
  810. shutil.copytree(
  811. os.path.join(
  812. INTEGRATION_TEST_DIR, 'files', 'extension_modules'
  813. ),
  814. new_extension_modules_path
  815. )
  816. opts_dict['extension_modules'] = os.path.join(opts_dict['root_dir'], 'extension_modules')
  817. # Copy the autosign_file to the new master root_dir
  818. new_autosign_file_path = os.path.join(opts_dict['root_dir'], 'autosign_file')
  819. shutil.copyfile(
  820. os.path.join(INTEGRATION_TEST_DIR, 'files', 'autosign_file'),
  821. new_autosign_file_path
  822. )
  823. os.chmod(new_autosign_file_path, autosign_file_permissions)
  824. # Point the config values to the correct temporary paths
  825. for name in ('hosts', 'aliases'):
  826. optname = '{0}.file'.format(name)
  827. optname_path = os.path.join(TMP, name)
  828. master_opts[optname] = optname_path
  829. minion_opts[optname] = optname_path
  830. sub_minion_opts[optname] = optname_path
  831. syndic_opts[optname] = optname_path
  832. syndic_master_opts[optname] = optname_path
  833. proxy_opts[optname] = optname_path
  834. master_opts['runtests_conn_check_port'] = get_unused_localhost_port()
  835. minion_opts['runtests_conn_check_port'] = get_unused_localhost_port()
  836. sub_minion_opts['runtests_conn_check_port'] = get_unused_localhost_port()
  837. syndic_opts['runtests_conn_check_port'] = get_unused_localhost_port()
  838. syndic_master_opts['runtests_conn_check_port'] = get_unused_localhost_port()
  839. proxy_opts['runtests_conn_check_port'] = get_unused_localhost_port()
  840. for conf in (master_opts, minion_opts, sub_minion_opts, syndic_opts, syndic_master_opts, proxy_opts):
  841. if 'engines' not in conf:
  842. conf['engines'] = []
  843. conf['engines'].append({'salt_runtests': {}})
  844. if 'engines_dirs' not in conf:
  845. conf['engines_dirs'] = []
  846. conf['engines_dirs'].insert(0, ENGINES_DIR)
  847. if 'log_handlers_dirs' not in conf:
  848. conf['log_handlers_dirs'] = []
  849. conf['log_handlers_dirs'].insert(0, LOG_HANDLERS_DIR)
  850. conf['runtests_log_port'] = SALT_LOG_PORT
  851. conf['runtests_log_level'] = os.environ.get('TESTS_MIN_LOG_LEVEL_NAME') or 'debug'
  852. # ----- Transcribe Configuration ---------------------------------------------------------------------------->
  853. for entry in os.listdir(RUNTIME_VARS.CONF_DIR):
  854. if entry in ('master', 'minion', 'sub_minion', 'syndic', 'syndic_master', 'proxy'):
  855. # These have runtime computed values and will be handled
  856. # differently
  857. continue
  858. entry_path = os.path.join(RUNTIME_VARS.CONF_DIR, entry)
  859. if os.path.isfile(entry_path):
  860. shutil.copy(
  861. entry_path,
  862. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry)
  863. )
  864. elif os.path.isdir(entry_path):
  865. shutil.copytree(
  866. entry_path,
  867. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry)
  868. )
  869. for entry in ('master', 'minion', 'sub_minion', 'syndic', 'syndic_master', 'proxy'):
  870. computed_config = copy.deepcopy(locals()['{0}_opts'.format(entry)])
  871. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry), 'w') as fp_:
  872. salt.utils.yaml.safe_dump(computed_config, fp_, default_flow_style=False)
  873. sub_minion_computed_config = copy.deepcopy(sub_minion_opts)
  874. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, 'minion'), 'w') as wfh:
  875. salt.utils.yaml.safe_dump(sub_minion_computed_config, wfh, default_flow_style=False)
  876. shutil.copyfile(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'master'), os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, 'master'))
  877. syndic_master_computed_config = copy.deepcopy(syndic_master_opts)
  878. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR, 'master'), 'w') as wfh:
  879. salt.utils.yaml.safe_dump(syndic_master_computed_config, wfh, default_flow_style=False)
  880. syndic_computed_config = copy.deepcopy(syndic_opts)
  881. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, 'minion'), 'w') as wfh:
  882. salt.utils.yaml.safe_dump(syndic_computed_config, wfh, default_flow_style=False)
  883. shutil.copyfile(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'master'), os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, 'master'))
  884. # <---- Transcribe Configuration -----------------------------------------------------------------------------
  885. # ----- Verify Environment ---------------------------------------------------------------------------------->
  886. master_opts = salt.config.master_config(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'master'))
  887. minion_opts = salt.config.minion_config(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'minion'))
  888. syndic_opts = salt.config.syndic_config(
  889. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, 'master'),
  890. os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, 'minion'),
  891. )
  892. sub_minion_opts = salt.config.minion_config(os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, 'minion'))
  893. syndic_master_opts = salt.config.master_config(os.path.join(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR, 'master'))
  894. proxy_opts = salt.config.proxy_config(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'proxy'))
  895. RUNTIME_VARS.RUNTIME_CONFIGS['master'] = freeze(master_opts)
  896. RUNTIME_VARS.RUNTIME_CONFIGS['minion'] = freeze(minion_opts)
  897. RUNTIME_VARS.RUNTIME_CONFIGS['syndic'] = freeze(syndic_opts)
  898. RUNTIME_VARS.RUNTIME_CONFIGS['sub_minion'] = freeze(sub_minion_opts)
  899. RUNTIME_VARS.RUNTIME_CONFIGS['syndic_master'] = freeze(syndic_master_opts)
  900. RUNTIME_VARS.RUNTIME_CONFIGS['proxy'] = freeze(proxy_opts)
  901. verify_env([os.path.join(master_opts['pki_dir'], 'minions'),
  902. os.path.join(master_opts['pki_dir'], 'minions_pre'),
  903. os.path.join(master_opts['pki_dir'], 'minions_rejected'),
  904. os.path.join(master_opts['pki_dir'], 'minions_denied'),
  905. os.path.join(master_opts['cachedir'], 'jobs'),
  906. os.path.join(master_opts['root_dir'], 'cache', 'tokens'),
  907. os.path.join(syndic_master_opts['pki_dir'], 'minions'),
  908. os.path.join(syndic_master_opts['pki_dir'], 'minions_pre'),
  909. os.path.join(syndic_master_opts['pki_dir'], 'minions_rejected'),
  910. os.path.join(syndic_master_opts['cachedir'], 'jobs'),
  911. os.path.join(syndic_master_opts['root_dir'], 'cache', 'tokens'),
  912. os.path.join(master_opts['pki_dir'], 'accepted'),
  913. os.path.join(master_opts['pki_dir'], 'rejected'),
  914. os.path.join(master_opts['pki_dir'], 'pending'),
  915. os.path.join(syndic_master_opts['pki_dir'], 'accepted'),
  916. os.path.join(syndic_master_opts['pki_dir'], 'rejected'),
  917. os.path.join(syndic_master_opts['pki_dir'], 'pending'),
  918. os.path.join(minion_opts['pki_dir'], 'accepted'),
  919. os.path.join(minion_opts['pki_dir'], 'rejected'),
  920. os.path.join(minion_opts['pki_dir'], 'pending'),
  921. os.path.join(sub_minion_opts['pki_dir'], 'accepted'),
  922. os.path.join(sub_minion_opts['pki_dir'], 'rejected'),
  923. os.path.join(sub_minion_opts['pki_dir'], 'pending'),
  924. os.path.dirname(master_opts['log_file']),
  925. minion_opts['extension_modules'],
  926. sub_minion_opts['extension_modules'],
  927. sub_minion_opts['pki_dir'],
  928. master_opts['sock_dir'],
  929. syndic_master_opts['sock_dir'],
  930. sub_minion_opts['sock_dir'],
  931. minion_opts['sock_dir'],
  932. RUNTIME_VARS.TMP_STATE_TREE,
  933. RUNTIME_VARS.TMP_PILLAR_TREE,
  934. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  935. TMP,
  936. ],
  937. RUNTIME_VARS.RUNNING_TESTS_USER,
  938. root_dir=master_opts['root_dir'],
  939. )
  940. cls.master_opts = master_opts
  941. cls.minion_opts = minion_opts
  942. # cls.proxy_opts = proxy_opts
  943. cls.sub_minion_opts = sub_minion_opts
  944. cls.syndic_opts = syndic_opts
  945. cls.syndic_master_opts = syndic_master_opts
  946. cls.proxy_opts = proxy_opts
  947. # <---- Verify Environment -----------------------------------------------------------------------------------
  948. def __exit__(self, type, value, traceback):
  949. '''
  950. Kill the minion and master processes
  951. '''
  952. try:
  953. if hasattr(self.sub_minion_process, 'terminate'):
  954. self.sub_minion_process.terminate()
  955. else:
  956. log.error('self.sub_minion_process can\'t be terminate.')
  957. except AttributeError:
  958. pass
  959. try:
  960. if hasattr(self.minion_process, 'terminate'):
  961. self.minion_process.terminate()
  962. else:
  963. log.error('self.minion_process can\'t be terminate.')
  964. except AttributeError:
  965. pass
  966. if hasattr(self, 'proxy_process'):
  967. self.proxy_process.terminate()
  968. try:
  969. if hasattr(self.master_process, 'terminate'):
  970. self.master_process.terminate()
  971. else:
  972. log.error('self.master_process can\'t be terminate.')
  973. except AttributeError:
  974. pass
  975. try:
  976. self.syndic_process.terminate()
  977. except AttributeError:
  978. pass
  979. try:
  980. self.smaster_process.terminate()
  981. except AttributeError:
  982. pass
  983. self._exit_mockbin()
  984. self._exit_ssh()
  985. # Shutdown the multiprocessing logging queue listener
  986. salt_log_setup.shutdown_multiprocessing_logging()
  987. salt_log_setup.shutdown_multiprocessing_logging_listener(daemonizing=True)
  988. # Shutdown the log server
  989. self.log_server.shutdown()
  990. self.log_server.server_close()
  991. self.log_server_process.join()
  992. def pre_setup_minions(self):
  993. '''
  994. Subclass this method for additional minion setups.
  995. '''
  996. def setup_minions(self):
  997. '''
  998. Minions setup routines
  999. '''
  1000. def post_setup_minions(self):
  1001. '''
  1002. Subclass this method to execute code after the minions have been setup
  1003. '''
  1004. def _enter_mockbin(self):
  1005. path = os.environ.get('PATH', '')
  1006. path_items = path.split(os.pathsep)
  1007. if MOCKBIN not in path_items:
  1008. path_items.insert(0, MOCKBIN)
  1009. os.environ['PATH'] = os.pathsep.join(path_items)
  1010. def _exit_ssh(self):
  1011. if hasattr(self, 'sshd_process'):
  1012. try:
  1013. self.sshd_process.kill()
  1014. except OSError as exc:
  1015. if exc.errno != 3:
  1016. raise
  1017. with salt.utils.files.fopen(self.sshd_pidfile) as fhr:
  1018. try:
  1019. os.kill(int(fhr.read()), signal.SIGKILL)
  1020. except OSError as exc:
  1021. if exc.errno != 3:
  1022. raise
  1023. def _exit_mockbin(self):
  1024. path = os.environ.get('PATH', '')
  1025. path_items = path.split(os.pathsep)
  1026. try:
  1027. path_items.remove(MOCKBIN)
  1028. except ValueError:
  1029. pass
  1030. os.environ['PATH'] = os.pathsep.join(path_items)
  1031. @classmethod
  1032. def clean(cls):
  1033. '''
  1034. Clean out the tmp files
  1035. '''
  1036. def remove_readonly(func, path, excinfo):
  1037. if os.path.exists(path):
  1038. # Give full permissions to owner
  1039. os.chmod(path, stat.S_IRWXU)
  1040. func(path)
  1041. for dirname in (TMP, RUNTIME_VARS.TMP_STATE_TREE,
  1042. RUNTIME_VARS.TMP_PILLAR_TREE, RUNTIME_VARS.TMP_PRODENV_STATE_TREE):
  1043. if os.path.isdir(dirname):
  1044. try:
  1045. shutil.rmtree(six.text_type(dirname), onerror=remove_readonly)
  1046. except Exception:
  1047. log.exception('Failed to remove directory: %s', dirname)
  1048. def wait_for_jid(self, targets, jid, timeout=120):
  1049. time.sleep(1) # Allow some time for minions to accept jobs
  1050. now = datetime.now()
  1051. expire = now + timedelta(seconds=timeout)
  1052. job_finished = False
  1053. while now <= expire:
  1054. running = self.__client_job_running(targets, jid)
  1055. sys.stdout.write(
  1056. '\r{0}\r'.format(
  1057. ' ' * getattr(self.parser.options, 'output_columns', PNUM)
  1058. )
  1059. )
  1060. if not running and job_finished is False:
  1061. # Let's not have false positives and wait one more seconds
  1062. job_finished = True
  1063. elif not running and job_finished is True:
  1064. return True
  1065. elif running and job_finished is True:
  1066. job_finished = False
  1067. if job_finished is False:
  1068. sys.stdout.write(
  1069. ' * {LIGHT_YELLOW}[Quit in {0}]{ENDC} Waiting for {1}'.format(
  1070. '{0}'.format(expire - now).rsplit('.', 1)[0],
  1071. ', '.join(running),
  1072. **self.colors
  1073. )
  1074. )
  1075. sys.stdout.flush()
  1076. time.sleep(1)
  1077. now = datetime.now()
  1078. else: # pylint: disable=W0120
  1079. sys.stdout.write(
  1080. '\n {LIGHT_RED}*{ENDC} ERROR: Failed to get information '
  1081. 'back\n'.format(**self.colors)
  1082. )
  1083. sys.stdout.flush()
  1084. return False
  1085. def __client_job_running(self, targets, jid):
  1086. running = self.client.cmd(
  1087. list(targets), 'saltutil.running', tgt_type='list'
  1088. )
  1089. return [
  1090. k for (k, v) in six.iteritems(running) if v and v[0]['jid'] == jid
  1091. ]
  1092. def sync_minion_modules_(self, modules_kind, targets, timeout=None):
  1093. if not timeout:
  1094. timeout = 120
  1095. # Let's sync all connected minions
  1096. print(
  1097. ' {LIGHT_BLUE}*{ENDC} Syncing minion\'s {1} '
  1098. '(saltutil.sync_{1})'.format(
  1099. ', '.join(targets),
  1100. modules_kind,
  1101. **self.colors
  1102. )
  1103. )
  1104. syncing = set(targets)
  1105. jid_info = self.client.run_job(
  1106. list(targets), 'saltutil.sync_{0}'.format(modules_kind),
  1107. tgt_type='list',
  1108. timeout=999999999999999,
  1109. )
  1110. if self.wait_for_jid(targets, jid_info['jid'], timeout) is False:
  1111. print(
  1112. ' {LIGHT_RED}*{ENDC} WARNING: Minions failed to sync {0}. '
  1113. 'Tests requiring these {0} WILL fail'.format(
  1114. modules_kind, **self.colors)
  1115. )
  1116. raise SystemExit()
  1117. while syncing:
  1118. rdata = self.client.get_full_returns(jid_info['jid'], syncing, 1)
  1119. if rdata:
  1120. for name, output in six.iteritems(rdata):
  1121. if not output['ret']:
  1122. # Already synced!?
  1123. syncing.remove(name)
  1124. continue
  1125. if isinstance(output['ret'], six.string_types):
  1126. # An errors has occurred
  1127. print(
  1128. ' {LIGHT_RED}*{ENDC} {0} Failed to sync {2}: '
  1129. '{1}'.format(
  1130. name, output['ret'],
  1131. modules_kind,
  1132. **self.colors)
  1133. )
  1134. return False
  1135. print(
  1136. ' {LIGHT_GREEN}*{ENDC} Synced {0} {2}: '
  1137. '{1}'.format(
  1138. name,
  1139. ', '.join(output['ret']),
  1140. modules_kind, **self.colors
  1141. )
  1142. )
  1143. # Synced!
  1144. try:
  1145. syncing.remove(name)
  1146. except KeyError:
  1147. print(
  1148. ' {LIGHT_RED}*{ENDC} {0} already synced??? '
  1149. '{1}'.format(name, output, **self.colors)
  1150. )
  1151. return True
  1152. def sync_minion_states(self, targets, timeout=None):
  1153. salt.utils.process.appendproctitle('SyncMinionStates')
  1154. self.sync_minion_modules_('states', targets, timeout=timeout)
  1155. def sync_minion_modules(self, targets, timeout=None):
  1156. salt.utils.process.appendproctitle('SyncMinionModules')
  1157. self.sync_minion_modules_('modules', targets, timeout=timeout)
  1158. def sync_minion_grains(self, targets, timeout=None):
  1159. salt.utils.process.appendproctitle('SyncMinionGrains')
  1160. self.sync_minion_modules_('grains', targets, timeout=timeout)
  1161. def wait_for_minions(self, start, timeout, sleep=5):
  1162. '''
  1163. Ensure all minions and masters (including sub-masters) are connected.
  1164. '''
  1165. while True:
  1166. try:
  1167. ret = self.client.run_job('*', 'test.ping')
  1168. except salt.exceptions.SaltClientError:
  1169. ret = None
  1170. if ret and 'minions' not in ret:
  1171. continue
  1172. if ret and sorted(ret['minions']) == sorted(self.minion_targets):
  1173. break
  1174. if time.time() - start >= timeout:
  1175. raise RuntimeError("Ping Minions Failed")
  1176. time.sleep(sleep)