1
0

__init__.py 55 KB

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