test_docker.py 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143
  1. # -*- coding: utf-8 -*-
  2. '''
  3. tests.unit.utils.test_docker
  4. ============================
  5. Test the funcs in salt.utils.docker and salt.utils.docker.translate
  6. '''
  7. # Import Python Libs
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import copy
  10. import functools
  11. import logging
  12. import os
  13. log = logging.getLogger(__name__)
  14. # Import Salt Testing Libs
  15. from tests.support.unit import TestCase
  16. # Import salt libs
  17. import salt.config
  18. import salt.loader
  19. import salt.utils.platform
  20. import salt.utils.docker.translate.container
  21. import salt.utils.docker.translate.network
  22. from salt.utils.docker.translate import helpers as translate_helpers
  23. from salt.exceptions import CommandExecutionError
  24. # Import 3rd-party libs
  25. from salt.ext import six
  26. class Assert(object):
  27. def __init__(self, translator):
  28. self.translator = translator
  29. def __call__(self, func):
  30. self.func = func
  31. return functools.wraps(func)(
  32. lambda testcase, *args, **kwargs: self.wrap(testcase, *args, **kwargs) # pylint: disable=W0108
  33. )
  34. def wrap(self, *args, **kwargs):
  35. raise NotImplementedError
  36. def test_stringlist(self, testcase, name):
  37. alias = self.translator.ALIASES_REVMAP.get(name)
  38. # Using file paths here because "volumes" must be passed through this
  39. # set of assertions and it requires absolute paths.
  40. if salt.utils.platform.is_windows():
  41. data = [r'c:\foo', r'c:\bar', r'c:\baz']
  42. else:
  43. data = ['/foo', '/bar', '/baz']
  44. for item in (name, alias):
  45. if item is None:
  46. continue
  47. testcase.assertEqual(
  48. salt.utils.docker.translate_input(
  49. self.translator,
  50. **{item: ','.join(data)}
  51. ),
  52. testcase.apply_defaults({name: data})
  53. )
  54. testcase.assertEqual(
  55. salt.utils.docker.translate_input(
  56. self.translator,
  57. **{item: data}
  58. ),
  59. testcase.apply_defaults({name: data})
  60. )
  61. if name != 'volumes':
  62. # Test coercing to string
  63. testcase.assertEqual(
  64. salt.utils.docker.translate_input(
  65. self.translator,
  66. **{item: ['one', 2]}
  67. ),
  68. testcase.apply_defaults({name: ['one', '2']})
  69. )
  70. if alias is not None:
  71. # Test collision
  72. # sorted() used here because we want to confirm that we discard the
  73. # alias' value and go with the unsorted version.
  74. test_kwargs = {name: data, alias: sorted(data)}
  75. testcase.assertEqual(
  76. salt.utils.docker.translate_input(
  77. self.translator,
  78. ignore_collisions=True,
  79. **test_kwargs
  80. ),
  81. testcase.apply_defaults({name: test_kwargs[name]})
  82. )
  83. with testcase.assertRaisesRegex(
  84. CommandExecutionError,
  85. 'is an alias for.+cannot both be used'):
  86. salt.utils.docker.translate_input(
  87. self.translator,
  88. ignore_collisions=False,
  89. **test_kwargs
  90. )
  91. def test_key_value(self, testcase, name, delimiter):
  92. '''
  93. Common logic for key/value pair testing. IP address validation is
  94. turned off here, and must be done separately in the wrapped function.
  95. '''
  96. alias = self.translator.ALIASES_REVMAP.get(name)
  97. expected = {'foo': 'bar', 'baz': 'qux'}
  98. vals = 'foo{0}bar,baz{0}qux'.format(delimiter)
  99. for item in (name, alias):
  100. if item is None:
  101. continue
  102. for val in (vals, vals.split(',')):
  103. testcase.assertEqual(
  104. salt.utils.docker.translate_input(
  105. self.translator,
  106. validate_ip_addrs=False,
  107. **{item: val}
  108. ),
  109. testcase.apply_defaults({name: expected})
  110. )
  111. # Dictionary input
  112. testcase.assertEqual(
  113. salt.utils.docker.translate_input(
  114. self.translator,
  115. validate_ip_addrs=False,
  116. **{item: expected}
  117. ),
  118. testcase.apply_defaults({name: expected})
  119. )
  120. # "Dictlist" input from states
  121. testcase.assertEqual(
  122. salt.utils.docker.translate_input(
  123. self.translator,
  124. validate_ip_addrs=False,
  125. **{item: [{'foo': 'bar'}, {'baz': 'qux'}]}
  126. ),
  127. testcase.apply_defaults({name: expected})
  128. )
  129. if alias is not None:
  130. # Test collision
  131. test_kwargs = {name: vals, alias: 'hello{0}world'.format(delimiter)}
  132. testcase.assertEqual(
  133. salt.utils.docker.translate_input(
  134. self.translator,
  135. validate_ip_addrs=False,
  136. ignore_collisions=True,
  137. **test_kwargs
  138. ),
  139. testcase.apply_defaults({name: expected})
  140. )
  141. with testcase.assertRaisesRegex(
  142. CommandExecutionError,
  143. 'is an alias for.+cannot both be used'):
  144. salt.utils.docker.translate_input(
  145. self.translator,
  146. validate_ip_addrs=False,
  147. ignore_collisions=False,
  148. **test_kwargs
  149. )
  150. class assert_bool(Assert):
  151. '''
  152. Test a boolean value
  153. '''
  154. def wrap(self, testcase, *args, **kwargs):
  155. # Strip off the "test_" from the function name
  156. name = self.func.__name__[5:]
  157. alias = self.translator.ALIASES_REVMAP.get(name)
  158. for item in (name, alias):
  159. if item is None:
  160. continue
  161. testcase.assertEqual(
  162. salt.utils.docker.translate_input(
  163. self.translator,
  164. **{item: True}
  165. ),
  166. testcase.apply_defaults({name: True})
  167. )
  168. # These two are contrived examples, but they will test bool-ifying
  169. # a non-bool value to ensure proper input format.
  170. testcase.assertEqual(
  171. salt.utils.docker.translate_input(
  172. self.translator,
  173. **{item: 'foo'}
  174. ),
  175. testcase.apply_defaults({name: True})
  176. )
  177. testcase.assertEqual(
  178. salt.utils.docker.translate_input(
  179. self.translator,
  180. **{item: 0}
  181. ),
  182. testcase.apply_defaults({name: False})
  183. )
  184. if alias is not None:
  185. # Test collision
  186. test_kwargs = {name: True, alias: False}
  187. testcase.assertEqual(
  188. salt.utils.docker.translate_input(
  189. self.translator,
  190. ignore_collisions=True,
  191. **test_kwargs
  192. ),
  193. testcase.apply_defaults({name: test_kwargs[name]})
  194. )
  195. with testcase.assertRaisesRegex(
  196. CommandExecutionError,
  197. 'is an alias for.+cannot both be used'):
  198. salt.utils.docker.translate_input(
  199. self.translator,
  200. ignore_collisions=False,
  201. **test_kwargs
  202. )
  203. return self.func(testcase, *args, **kwargs)
  204. class assert_int(Assert):
  205. '''
  206. Test an integer value
  207. '''
  208. def wrap(self, testcase, *args, **kwargs):
  209. # Strip off the "test_" from the function name
  210. name = self.func.__name__[5:]
  211. alias = self.translator.ALIASES_REVMAP.get(name)
  212. for item in (name, alias):
  213. if item is None:
  214. continue
  215. for val in (100, '100'):
  216. testcase.assertEqual(
  217. salt.utils.docker.translate_input(
  218. self.translator,
  219. **{item: val}
  220. ),
  221. testcase.apply_defaults({name: 100})
  222. )
  223. # Error case: non-numeric value passed
  224. with testcase.assertRaisesRegex(
  225. CommandExecutionError,
  226. "'foo' is not an integer"):
  227. salt.utils.docker.translate_input(
  228. self.translator,
  229. **{item: 'foo'}
  230. )
  231. if alias is not None:
  232. # Test collision
  233. test_kwargs = {name: 100, alias: 200}
  234. testcase.assertEqual(
  235. salt.utils.docker.translate_input(
  236. self.translator,
  237. ignore_collisions=True,
  238. **test_kwargs
  239. ),
  240. testcase.apply_defaults({name: test_kwargs[name]})
  241. )
  242. with testcase.assertRaisesRegex(
  243. CommandExecutionError,
  244. 'is an alias for.+cannot both be used'):
  245. salt.utils.docker.translate_input(
  246. self.translator,
  247. ignore_collisions=False,
  248. **test_kwargs
  249. )
  250. return self.func(testcase, *args, **kwargs)
  251. class assert_string(Assert):
  252. '''
  253. Test that item is a string or is converted to one
  254. '''
  255. def wrap(self, testcase, *args, **kwargs):
  256. # Strip off the "test_" from the function name
  257. name = self.func.__name__[5:]
  258. alias = self.translator.ALIASES_REVMAP.get(name)
  259. # Using file paths here because "working_dir" must be passed through
  260. # this set of assertions and it requires absolute paths.
  261. if salt.utils.platform.is_windows():
  262. data = r'c:\foo'
  263. else:
  264. data = '/foo'
  265. for item in (name, alias):
  266. if item is None:
  267. continue
  268. testcase.assertEqual(
  269. salt.utils.docker.translate_input(
  270. self.translator,
  271. **{item: data}
  272. ),
  273. testcase.apply_defaults({name: data})
  274. )
  275. if name != 'working_dir':
  276. # Test coercing to string
  277. testcase.assertEqual(
  278. salt.utils.docker.translate_input(
  279. self.translator,
  280. **{item: 123}
  281. ),
  282. testcase.apply_defaults({name: '123'})
  283. )
  284. if alias is not None:
  285. # Test collision
  286. test_kwargs = {name: data, alias: data}
  287. testcase.assertEqual(
  288. salt.utils.docker.translate_input(
  289. self.translator,
  290. ignore_collisions=True,
  291. **test_kwargs
  292. ),
  293. testcase.apply_defaults({name: test_kwargs[name]})
  294. )
  295. with testcase.assertRaisesRegex(
  296. CommandExecutionError,
  297. 'is an alias for.+cannot both be used'):
  298. salt.utils.docker.translate_input(
  299. self.translator,
  300. ignore_collisions=False,
  301. **test_kwargs
  302. )
  303. return self.func(testcase, *args, **kwargs)
  304. class assert_int_or_string(Assert):
  305. '''
  306. Test an integer or string value
  307. '''
  308. def wrap(self, testcase, *args, **kwargs):
  309. # Strip off the "test_" from the function name
  310. name = self.func.__name__[5:]
  311. alias = self.translator.ALIASES_REVMAP.get(name)
  312. for item in (name, alias):
  313. if item is None:
  314. continue
  315. testcase.assertEqual(
  316. salt.utils.docker.translate_input(
  317. self.translator,
  318. **{item: 100}
  319. ),
  320. testcase.apply_defaults({name: 100})
  321. )
  322. testcase.assertEqual(
  323. salt.utils.docker.translate_input(
  324. self.translator,
  325. **{item: '100M'}
  326. ),
  327. testcase.apply_defaults({name: '100M'})
  328. )
  329. if alias is not None:
  330. # Test collision
  331. test_kwargs = {name: 100, alias: '100M'}
  332. testcase.assertEqual(
  333. salt.utils.docker.translate_input(
  334. self.translator,
  335. ignore_collisions=True,
  336. **test_kwargs
  337. ),
  338. testcase.apply_defaults({name: test_kwargs[name]})
  339. )
  340. with testcase.assertRaisesRegex(
  341. CommandExecutionError,
  342. 'is an alias for.+cannot both be used'):
  343. salt.utils.docker.translate_input(
  344. self.translator,
  345. ignore_collisions=False,
  346. **test_kwargs
  347. )
  348. return self.func(testcase, *args, **kwargs)
  349. class assert_stringlist(Assert):
  350. '''
  351. Test a comma-separated or Python list of strings
  352. '''
  353. def wrap(self, testcase, *args, **kwargs):
  354. # Strip off the "test_" from the function name
  355. name = self.func.__name__[5:]
  356. self.test_stringlist(testcase, name)
  357. return self.func(testcase, *args, **kwargs)
  358. class assert_dict(Assert):
  359. '''
  360. Dictionaries should be untouched, dictlists should be repacked and end up
  361. as a single dictionary.
  362. '''
  363. def wrap(self, testcase, *args, **kwargs):
  364. # Strip off the "test_" from the function name
  365. name = self.func.__name__[5:]
  366. alias = self.translator.ALIASES_REVMAP.get(name)
  367. expected = {'foo': 'bar', 'baz': 'qux'}
  368. for item in (name, alias):
  369. if item is None:
  370. continue
  371. testcase.assertEqual(
  372. salt.utils.docker.translate_input(
  373. self.translator,
  374. **{item: expected}
  375. ),
  376. testcase.apply_defaults({name: expected})
  377. )
  378. # "Dictlist" input from states
  379. testcase.assertEqual(
  380. salt.utils.docker.translate_input(
  381. self.translator,
  382. **{item: [{x: y} for x, y in six.iteritems(expected)]}
  383. ),
  384. testcase.apply_defaults({name: expected})
  385. )
  386. # Error case: non-dictionary input
  387. with testcase.assertRaisesRegex(
  388. CommandExecutionError,
  389. "'foo' is not a dictionary"):
  390. salt.utils.docker.translate_input(
  391. self.translator,
  392. **{item: 'foo'}
  393. )
  394. if alias is not None:
  395. # Test collision
  396. test_kwargs = {name: 'foo', alias: 'bar'}
  397. testcase.assertEqual(
  398. salt.utils.docker.translate_input(
  399. self.translator,
  400. ignore_collisions=True,
  401. **test_kwargs
  402. ),
  403. testcase.apply_defaults({name: test_kwargs[name]})
  404. )
  405. with testcase.assertRaisesRegex(
  406. CommandExecutionError,
  407. 'is an alias for.+cannot both be used'):
  408. salt.utils.docker.translate_input(
  409. self.translator,
  410. ignore_collisions=False,
  411. **test_kwargs
  412. )
  413. return self.func(testcase, *args, **kwargs)
  414. class assert_cmd(Assert):
  415. '''
  416. Test for a string, or a comma-separated or Python list of strings. This is
  417. different from a stringlist in that we do not do any splitting. This
  418. decorator is used both by the "command" and "entrypoint" arguments.
  419. '''
  420. def wrap(self, testcase, *args, **kwargs):
  421. # Strip off the "test_" from the function name
  422. name = self.func.__name__[5:]
  423. alias = self.translator.ALIASES_REVMAP.get(name)
  424. for item in (name, alias):
  425. if item is None:
  426. continue
  427. testcase.assertEqual(
  428. salt.utils.docker.translate_input(
  429. self.translator,
  430. **{item: 'foo bar'}
  431. ),
  432. testcase.apply_defaults({name: 'foo bar'})
  433. )
  434. testcase.assertEqual(
  435. salt.utils.docker.translate_input(
  436. self.translator,
  437. **{item: ['foo', 'bar']}
  438. ),
  439. testcase.apply_defaults({name: ['foo', 'bar']})
  440. )
  441. # Test coercing to string
  442. testcase.assertEqual(
  443. salt.utils.docker.translate_input(
  444. self.translator,
  445. **{item: 123}
  446. ),
  447. testcase.apply_defaults({name: '123'})
  448. )
  449. testcase.assertEqual(
  450. salt.utils.docker.translate_input(
  451. self.translator,
  452. **{item: ['one', 2]}
  453. ),
  454. testcase.apply_defaults({name: ['one', '2']})
  455. )
  456. if alias is not None:
  457. # Test collision
  458. test_kwargs = {name: 'foo', alias: 'bar'}
  459. testcase.assertEqual(
  460. salt.utils.docker.translate_input(
  461. self.translator,
  462. ignore_collisions=True,
  463. **test_kwargs
  464. ),
  465. testcase.apply_defaults({name: test_kwargs[name]})
  466. )
  467. with testcase.assertRaisesRegex(
  468. CommandExecutionError,
  469. 'is an alias for.+cannot both be used'):
  470. salt.utils.docker.translate_input(
  471. self.translator,
  472. ignore_collisions=False,
  473. **test_kwargs
  474. )
  475. return self.func(testcase, *args, **kwargs)
  476. class assert_key_colon_value(Assert):
  477. '''
  478. Test a key/value pair with parameters passed as key:value pairs
  479. '''
  480. def wrap(self, testcase, *args, **kwargs):
  481. # Strip off the "test_" from the function name
  482. name = self.func.__name__[5:]
  483. self.test_key_value(testcase, name, ':')
  484. return self.func(testcase, *args, **kwargs)
  485. class assert_key_equals_value(Assert):
  486. '''
  487. Test a key/value pair with parameters passed as key=value pairs
  488. '''
  489. def wrap(self, testcase, *args, **kwargs):
  490. # Strip off the "test_" from the function name
  491. name = self.func.__name__[5:]
  492. self.test_key_value(testcase, name, '=')
  493. if name == 'labels':
  494. self.test_stringlist(testcase, name)
  495. return self.func(testcase, *args, **kwargs)
  496. class assert_labels(Assert):
  497. def wrap(self, testcase, *args, **kwargs):
  498. # Strip off the "test_" from the function name
  499. name = self.func.__name__[5:]
  500. alias = self.translator.ALIASES_REVMAP.get(name)
  501. labels = ['foo', 'bar=baz', {'hello': 'world'}]
  502. expected = {'foo': '', 'bar': 'baz', 'hello': 'world'}
  503. for item in (name, alias):
  504. if item is None:
  505. continue
  506. testcase.assertEqual(
  507. salt.utils.docker.translate_input(
  508. self.translator,
  509. **{item: labels}
  510. ),
  511. testcase.apply_defaults({name: expected})
  512. )
  513. # Error case: Passed a mutli-element dict in dictlist
  514. bad_labels = copy.deepcopy(labels)
  515. bad_labels[-1]['bad'] = 'input'
  516. with testcase.assertRaisesRegex(
  517. CommandExecutionError, r'Invalid label\(s\)'):
  518. salt.utils.docker.translate_input(
  519. self.translator,
  520. **{item: bad_labels}
  521. )
  522. return self.func(testcase, *args, **kwargs)
  523. class assert_device_rates(Assert):
  524. '''
  525. Tests for device_{read,write}_{bps,iops}. The bps values have a "Rate"
  526. value expressed in bytes/kb/mb/gb, while the iops values have a "Rate"
  527. expressed as a simple integer.
  528. '''
  529. def wrap(self, testcase, *args, **kwargs):
  530. # Strip off the "test_" from the function name
  531. name = self.func.__name__[5:]
  532. alias = self.translator.ALIASES_REVMAP.get(name)
  533. for item in (name, alias):
  534. if item is None:
  535. continue
  536. # Error case: Not an absolute path
  537. path = os.path.join('foo', 'bar', 'baz')
  538. with testcase.assertRaisesRegex(
  539. CommandExecutionError,
  540. "Path '{0}' is not absolute".format(path.replace('\\', '\\\\'))):
  541. salt.utils.docker.translate_input(
  542. self.translator,
  543. **{item: '{0}:1048576'.format(path)}
  544. )
  545. if name.endswith('_bps'):
  546. # Both integer bytes and a string providing a shorthand for kb,
  547. # mb, or gb can be used, so we need to test for both.
  548. expected = (
  549. {}, []
  550. )
  551. vals = '/dev/sda:1048576,/dev/sdb:1048576'
  552. for val in (vals, vals.split(',')):
  553. testcase.assertEqual(
  554. salt.utils.docker.translate_input(
  555. self.translator,
  556. **{item: val}
  557. ),
  558. testcase.apply_defaults(
  559. {name: [{'Path': '/dev/sda', 'Rate': 1048576},
  560. {'Path': '/dev/sdb', 'Rate': 1048576}]}
  561. )
  562. )
  563. vals = '/dev/sda:1mb,/dev/sdb:5mb'
  564. for val in (vals, vals.split(',')):
  565. testcase.assertEqual(
  566. salt.utils.docker.translate_input(
  567. self.translator,
  568. **{item: val}
  569. ),
  570. testcase.apply_defaults(
  571. {name: [{'Path': '/dev/sda', 'Rate': '1mb'},
  572. {'Path': '/dev/sdb', 'Rate': '5mb'}]}
  573. )
  574. )
  575. if alias is not None:
  576. # Test collision
  577. test_kwargs = {
  578. name: '/dev/sda:1048576,/dev/sdb:1048576',
  579. alias: '/dev/sda:1mb,/dev/sdb:5mb'
  580. }
  581. testcase.assertEqual(
  582. salt.utils.docker.translate_input(
  583. self.translator,
  584. ignore_collisions=True,
  585. **test_kwargs
  586. ),
  587. testcase.apply_defaults(
  588. {name: [{'Path': '/dev/sda', 'Rate': 1048576},
  589. {'Path': '/dev/sdb', 'Rate': 1048576}]}
  590. )
  591. )
  592. with testcase.assertRaisesRegex(
  593. CommandExecutionError,
  594. 'is an alias for.+cannot both be used'):
  595. salt.utils.docker.translate_input(
  596. self.translator,
  597. ignore_collisions=False,
  598. **test_kwargs
  599. )
  600. else:
  601. # The "Rate" value must be an integer
  602. vals = '/dev/sda:1000,/dev/sdb:500'
  603. for val in (vals, vals.split(',')):
  604. testcase.assertEqual(
  605. salt.utils.docker.translate_input(
  606. self.translator,
  607. **{item: val}
  608. ),
  609. testcase.apply_defaults(
  610. {name: [{'Path': '/dev/sda', 'Rate': 1000},
  611. {'Path': '/dev/sdb', 'Rate': 500}]}
  612. )
  613. )
  614. # Test non-integer input
  615. expected = (
  616. {},
  617. {item: 'Rate \'5mb\' for path \'/dev/sdb\' is non-numeric'},
  618. []
  619. )
  620. vals = '/dev/sda:1000,/dev/sdb:5mb'
  621. for val in (vals, vals.split(',')):
  622. with testcase.assertRaisesRegex(
  623. CommandExecutionError,
  624. "Rate '5mb' for path '/dev/sdb' is non-numeric"):
  625. salt.utils.docker.translate_input(
  626. self.translator,
  627. **{item: val}
  628. )
  629. if alias is not None:
  630. # Test collision
  631. test_kwargs = {
  632. name: '/dev/sda:1000,/dev/sdb:500',
  633. alias: '/dev/sda:888,/dev/sdb:999'
  634. }
  635. testcase.assertEqual(
  636. salt.utils.docker.translate_input(
  637. self.translator,
  638. ignore_collisions=True,
  639. **test_kwargs
  640. ),
  641. testcase.apply_defaults(
  642. {name: [{'Path': '/dev/sda', 'Rate': 1000},
  643. {'Path': '/dev/sdb', 'Rate': 500}]}
  644. )
  645. )
  646. with testcase.assertRaisesRegex(
  647. CommandExecutionError,
  648. 'is an alias for.+cannot both be used'):
  649. salt.utils.docker.translate_input(
  650. self.translator,
  651. ignore_collisions=False,
  652. **test_kwargs
  653. )
  654. return self.func(testcase, *args, **kwargs)
  655. class assert_subnet(Assert):
  656. '''
  657. Test an IPv4 or IPv6 subnet
  658. '''
  659. def wrap(self, testcase, *args, **kwargs):
  660. # Strip off the "test_" from the function name
  661. name = self.func.__name__[5:]
  662. alias = self.translator.ALIASES_REVMAP.get(name)
  663. for item in (name, alias):
  664. if item is None:
  665. continue
  666. for val in ('127.0.0.1/32', '::1/128'):
  667. log.debug('Verifying \'%s\' is a valid subnet', val)
  668. testcase.assertEqual(
  669. salt.utils.docker.translate_input(
  670. self.translator,
  671. validate_ip_addrs=True,
  672. **{item: val}
  673. ),
  674. testcase.apply_defaults({name: val})
  675. )
  676. # Error case: invalid subnet caught by validation
  677. for val in ('127.0.0.1', '999.999.999.999/24', '10.0.0.0/33',
  678. '::1', 'feaz::1/128', '::1/129'):
  679. log.debug('Verifying \'%s\' is not a valid subnet', val)
  680. with testcase.assertRaisesRegex(
  681. CommandExecutionError,
  682. "'{0}' is not a valid subnet".format(val)):
  683. salt.utils.docker.translate_input(
  684. self.translator,
  685. validate_ip_addrs=True,
  686. **{item: val}
  687. )
  688. # This is not valid input but it will test whether or not subnet
  689. # validation happened
  690. val = 'foo'
  691. testcase.assertEqual(
  692. salt.utils.docker.translate_input(
  693. self.translator,
  694. validate_ip_addrs=False,
  695. **{item: val}
  696. ),
  697. testcase.apply_defaults({name: val})
  698. )
  699. if alias is not None:
  700. # Test collision
  701. test_kwargs = {name: '10.0.0.0/24', alias: '192.168.50.128/25'}
  702. testcase.assertEqual(
  703. salt.utils.docker.translate_input(
  704. self.translator,
  705. ignore_collisions=True,
  706. **test_kwargs
  707. ),
  708. testcase.apply_defaults({name: test_kwargs[name]})
  709. )
  710. with testcase.assertRaisesRegex(
  711. CommandExecutionError,
  712. 'is an alias for.+cannot both be used'):
  713. salt.utils.docker.translate_input(
  714. self.translator,
  715. ignore_collisions=False,
  716. **test_kwargs
  717. )
  718. return self.func(testcase, *args, **kwargs)
  719. class TranslateBase(TestCase):
  720. maxDiff = None
  721. translator = None # Must be overridden in the subclass
  722. def apply_defaults(self, ret, skip_translate=None):
  723. if skip_translate is not True:
  724. defaults = getattr(self.translator, 'DEFAULTS', {})
  725. for key, val in six.iteritems(defaults):
  726. if key not in ret:
  727. ret[key] = val
  728. return ret
  729. @staticmethod
  730. def normalize_ports(ret):
  731. '''
  732. When we translate exposed ports, we can end up with a mixture of ints
  733. (representing TCP ports) and tuples (representing UDP ports). Python 2
  734. will sort an iterable containing these mixed types, but Python 3 will
  735. not. This helper is used to munge the ports in the return data so that
  736. the resulting list is sorted in a way that can reliably be compared to
  737. the expected results in the test.
  738. This helper should only be needed for port_bindings and ports.
  739. '''
  740. if 'ports' in ret[0]:
  741. tcp_ports = []
  742. udp_ports = []
  743. for item in ret[0]['ports']:
  744. if isinstance(item, six.integer_types):
  745. tcp_ports.append(item)
  746. else:
  747. udp_ports.append(item)
  748. ret[0]['ports'] = sorted(tcp_ports) + sorted(udp_ports)
  749. return ret
  750. def tearDown(self):
  751. '''
  752. Test skip_translate kwarg
  753. '''
  754. name = self.id().split('.')[-1][5:]
  755. # The below is not valid input for the Docker API, but these
  756. # assertions confirm that we successfully skipped translation.
  757. for val in (True, name, [name]):
  758. self.assertEqual(
  759. salt.utils.docker.translate_input(
  760. self.translator,
  761. skip_translate=val,
  762. **{name: 'foo'}
  763. ),
  764. self.apply_defaults({name: 'foo'}, skip_translate=val)
  765. )
  766. class TranslateContainerInputTestCase(TranslateBase):
  767. '''
  768. Tests for salt.utils.docker.translate_input(), invoked using
  769. salt.utils.docker.translate.container as the translator module.
  770. '''
  771. translator = salt.utils.docker.translate.container
  772. @staticmethod
  773. def normalize_ports(ret):
  774. '''
  775. When we translate exposed ports, we can end up with a mixture of ints
  776. (representing TCP ports) and tuples (representing UDP ports). Python 2
  777. will sort an iterable containing these mixed types, but Python 3 will
  778. not. This helper is used to munge the ports in the return data so that
  779. the resulting list is sorted in a way that can reliably be compared to
  780. the expected results in the test.
  781. This helper should only be needed for port_bindings and ports.
  782. '''
  783. if 'ports' in ret:
  784. tcp_ports = []
  785. udp_ports = []
  786. for item in ret['ports']:
  787. if isinstance(item, six.integer_types):
  788. tcp_ports.append(item)
  789. else:
  790. udp_ports.append(item)
  791. ret['ports'] = sorted(tcp_ports) + sorted(udp_ports)
  792. return ret
  793. @assert_bool(salt.utils.docker.translate.container)
  794. def test_auto_remove(self):
  795. '''
  796. Should be a bool or converted to one
  797. '''
  798. pass
  799. def test_binds(self):
  800. '''
  801. Test the "binds" kwarg. Any volumes not defined in the "volumes" kwarg
  802. should be added to the results.
  803. '''
  804. self.assertEqual(
  805. salt.utils.docker.translate_input(
  806. self.translator,
  807. binds='/srv/www:/var/www:ro',
  808. volumes='/testing'),
  809. {'binds': ['/srv/www:/var/www:ro'],
  810. 'volumes': ['/testing', '/var/www']}
  811. )
  812. self.assertEqual(
  813. salt.utils.docker.translate_input(
  814. self.translator,
  815. binds=['/srv/www:/var/www:ro'],
  816. volumes='/testing'),
  817. {'binds': ['/srv/www:/var/www:ro'],
  818. 'volumes': ['/testing', '/var/www']}
  819. )
  820. self.assertEqual(
  821. salt.utils.docker.translate_input(
  822. self.translator,
  823. binds={'/srv/www': {'bind': '/var/www', 'mode': 'ro'}},
  824. volumes='/testing'),
  825. {'binds': {'/srv/www': {'bind': '/var/www', 'mode': 'ro'}},
  826. 'volumes': ['/testing', '/var/www']}
  827. )
  828. @assert_int(salt.utils.docker.translate.container)
  829. def test_blkio_weight(self):
  830. '''
  831. Should be an int or converted to one
  832. '''
  833. pass
  834. def test_blkio_weight_device(self):
  835. '''
  836. Should translate a list of PATH:WEIGHT pairs to a list of dictionaries
  837. with the following format: {'Path': PATH, 'Weight': WEIGHT}
  838. '''
  839. for val in ('/dev/sda:100,/dev/sdb:200',
  840. ['/dev/sda:100', '/dev/sdb:200']):
  841. self.assertEqual(
  842. salt.utils.docker.translate_input(
  843. self.translator,
  844. blkio_weight_device='/dev/sda:100,/dev/sdb:200'
  845. ),
  846. {'blkio_weight_device': [{'Path': '/dev/sda', 'Weight': 100},
  847. {'Path': '/dev/sdb', 'Weight': 200}]}
  848. )
  849. # Error cases
  850. with self.assertRaisesRegex(
  851. CommandExecutionError,
  852. r"'foo' contains 1 value\(s\) \(expected 2\)"):
  853. salt.utils.docker.translate_input(
  854. self.translator,
  855. blkio_weight_device='foo'
  856. )
  857. with self.assertRaisesRegex(
  858. CommandExecutionError,
  859. r"'foo:bar:baz' contains 3 value\(s\) \(expected 2\)"):
  860. salt.utils.docker.translate_input(
  861. self.translator,
  862. blkio_weight_device='foo:bar:baz'
  863. )
  864. with self.assertRaisesRegex(
  865. CommandExecutionError,
  866. r"Weight 'foo' for path '/dev/sdb' is not an integer"):
  867. salt.utils.docker.translate_input(
  868. self.translator,
  869. blkio_weight_device=['/dev/sda:100', '/dev/sdb:foo']
  870. )
  871. @assert_stringlist(salt.utils.docker.translate.container)
  872. def test_cap_add(self):
  873. '''
  874. Should be a list of strings or converted to one
  875. '''
  876. pass
  877. @assert_stringlist(salt.utils.docker.translate.container)
  878. def test_cap_drop(self):
  879. '''
  880. Should be a list of strings or converted to one
  881. '''
  882. pass
  883. @assert_cmd(salt.utils.docker.translate.container)
  884. def test_command(self):
  885. '''
  886. Can either be a string or a comma-separated or Python list of strings.
  887. '''
  888. pass
  889. @assert_string(salt.utils.docker.translate.container)
  890. def test_cpuset_cpus(self):
  891. '''
  892. Should be a string or converted to one
  893. '''
  894. pass
  895. @assert_string(salt.utils.docker.translate.container)
  896. def test_cpuset_mems(self):
  897. '''
  898. Should be a string or converted to one
  899. '''
  900. pass
  901. @assert_int(salt.utils.docker.translate.container)
  902. def test_cpu_group(self):
  903. '''
  904. Should be an int or converted to one
  905. '''
  906. pass
  907. @assert_int(salt.utils.docker.translate.container)
  908. def test_cpu_period(self):
  909. '''
  910. Should be an int or converted to one
  911. '''
  912. pass
  913. @assert_int(salt.utils.docker.translate.container)
  914. def test_cpu_shares(self):
  915. '''
  916. Should be an int or converted to one
  917. '''
  918. pass
  919. @assert_bool(salt.utils.docker.translate.container)
  920. def test_detach(self):
  921. '''
  922. Should be a bool or converted to one
  923. '''
  924. pass
  925. @assert_device_rates(salt.utils.docker.translate.container)
  926. def test_device_read_bps(self):
  927. '''
  928. CLI input is a list of PATH:RATE pairs, but the API expects a list of
  929. dictionaries in the format [{'Path': path, 'Rate': rate}]
  930. '''
  931. pass
  932. @assert_device_rates(salt.utils.docker.translate.container)
  933. def test_device_read_iops(self):
  934. '''
  935. CLI input is a list of PATH:RATE pairs, but the API expects a list of
  936. dictionaries in the format [{'Path': path, 'Rate': rate}]
  937. '''
  938. pass
  939. @assert_device_rates(salt.utils.docker.translate.container)
  940. def test_device_write_bps(self):
  941. '''
  942. CLI input is a list of PATH:RATE pairs, but the API expects a list of
  943. dictionaries in the format [{'Path': path, 'Rate': rate}]
  944. '''
  945. pass
  946. @assert_device_rates(salt.utils.docker.translate.container)
  947. def test_device_write_iops(self):
  948. '''
  949. CLI input is a list of PATH:RATE pairs, but the API expects a list of
  950. dictionaries in the format [{'Path': path, 'Rate': rate}]
  951. '''
  952. pass
  953. @assert_stringlist(salt.utils.docker.translate.container)
  954. def test_devices(self):
  955. '''
  956. Should be a list of strings or converted to one
  957. '''
  958. pass
  959. @assert_stringlist(salt.utils.docker.translate.container)
  960. def test_dns_opt(self):
  961. '''
  962. Should be a list of strings or converted to one
  963. '''
  964. pass
  965. @assert_stringlist(salt.utils.docker.translate.container)
  966. def test_dns_search(self):
  967. '''
  968. Should be a list of strings or converted to one
  969. '''
  970. pass
  971. def test_dns(self):
  972. '''
  973. While this is a stringlist, it also supports IP address validation, so
  974. it can't use the test_stringlist decorator because we need to test both
  975. with and without validation, and it isn't necessary to make all other
  976. stringlist tests also do that same kind of testing.
  977. '''
  978. for val in ('8.8.8.8,8.8.4.4', ['8.8.8.8', '8.8.4.4']):
  979. self.assertEqual(
  980. salt.utils.docker.translate_input(
  981. self.translator,
  982. dns=val,
  983. validate_ip_addrs=True,
  984. ),
  985. {'dns': ['8.8.8.8', '8.8.4.4']}
  986. )
  987. # Error case: invalid IP address caught by validation
  988. for val in ('8.8.8.888,8.8.4.4', ['8.8.8.888', '8.8.4.4']):
  989. with self.assertRaisesRegex(
  990. CommandExecutionError,
  991. r"'8.8.8.888' is not a valid IP address"):
  992. salt.utils.docker.translate_input(
  993. self.translator,
  994. dns=val,
  995. validate_ip_addrs=True,
  996. )
  997. # This is not valid input but it will test whether or not IP address
  998. # validation happened.
  999. for val in ('foo,bar', ['foo', 'bar']):
  1000. self.assertEqual(
  1001. salt.utils.docker.translate_input(
  1002. self.translator,
  1003. dns=val,
  1004. validate_ip_addrs=False,
  1005. ),
  1006. {'dns': ['foo', 'bar']}
  1007. )
  1008. @assert_string(salt.utils.docker.translate.container)
  1009. def test_domainname(self):
  1010. '''
  1011. Should be a list of strings or converted to one
  1012. '''
  1013. pass
  1014. @assert_cmd(salt.utils.docker.translate.container)
  1015. def test_entrypoint(self):
  1016. '''
  1017. Can either be a string or a comma-separated or Python list of strings.
  1018. '''
  1019. pass
  1020. @assert_key_equals_value(salt.utils.docker.translate.container)
  1021. def test_environment(self):
  1022. '''
  1023. Can be passed in several formats but must end up as a dictionary
  1024. mapping keys to values
  1025. '''
  1026. pass
  1027. def test_extra_hosts(self):
  1028. '''
  1029. Can be passed as a list of key:value pairs but can't be simply tested
  1030. using @assert_key_colon_value since we need to test both with and without
  1031. IP address validation.
  1032. '''
  1033. for val in ('web1:10.9.8.7,web2:10.9.8.8',
  1034. ['web1:10.9.8.7', 'web2:10.9.8.8']):
  1035. self.assertEqual(
  1036. salt.utils.docker.translate_input(
  1037. self.translator,
  1038. extra_hosts=val,
  1039. validate_ip_addrs=True,
  1040. ),
  1041. {'extra_hosts': {'web1': '10.9.8.7', 'web2': '10.9.8.8'}}
  1042. )
  1043. # Error case: invalid IP address caught by validation
  1044. for val in ('web1:10.9.8.299,web2:10.9.8.8',
  1045. ['web1:10.9.8.299', 'web2:10.9.8.8']):
  1046. with self.assertRaisesRegex(
  1047. CommandExecutionError,
  1048. r"'10.9.8.299' is not a valid IP address"):
  1049. salt.utils.docker.translate_input(
  1050. self.translator,
  1051. extra_hosts=val,
  1052. validate_ip_addrs=True,
  1053. )
  1054. # This is not valid input but it will test whether or not IP address
  1055. # validation happened.
  1056. for val in ('foo:bar,baz:qux', ['foo:bar', 'baz:qux']):
  1057. self.assertEqual(
  1058. salt.utils.docker.translate_input(
  1059. self.translator,
  1060. extra_hosts=val,
  1061. validate_ip_addrs=False,
  1062. ),
  1063. {'extra_hosts': {'foo': 'bar', 'baz': 'qux'}}
  1064. )
  1065. @assert_stringlist(salt.utils.docker.translate.container)
  1066. def test_group_add(self):
  1067. '''
  1068. Should be a list of strings or converted to one
  1069. '''
  1070. pass
  1071. @assert_string(salt.utils.docker.translate.container)
  1072. def test_hostname(self):
  1073. '''
  1074. Should be a string or converted to one
  1075. '''
  1076. pass
  1077. @assert_string(salt.utils.docker.translate.container)
  1078. def test_ipc_mode(self):
  1079. '''
  1080. Should be a string or converted to one
  1081. '''
  1082. pass
  1083. @assert_string(salt.utils.docker.translate.container)
  1084. def test_isolation(self):
  1085. '''
  1086. Should be a string or converted to one
  1087. '''
  1088. pass
  1089. @assert_labels(salt.utils.docker.translate.container)
  1090. def test_labels(self):
  1091. '''
  1092. Can be passed as a list of key=value pairs or a dictionary, and must
  1093. ultimately end up as a dictionary.
  1094. '''
  1095. pass
  1096. @assert_key_colon_value(salt.utils.docker.translate.container)
  1097. def test_links(self):
  1098. '''
  1099. Can be passed as a list of key:value pairs or a dictionary, and must
  1100. ultimately end up as a dictionary.
  1101. '''
  1102. pass
  1103. def test_log_config(self):
  1104. '''
  1105. This is a mixture of log_driver and log_opt, which get combined into a
  1106. dictionary.
  1107. log_driver is a simple string, but log_opt can be passed in several
  1108. ways, so we need to test them all.
  1109. '''
  1110. expected = (
  1111. {'log_config': {'Type': 'foo',
  1112. 'Config': {'foo': 'bar', 'baz': 'qux'}}},
  1113. {}, []
  1114. )
  1115. for val in ('foo=bar,baz=qux',
  1116. ['foo=bar', 'baz=qux'],
  1117. [{'foo': 'bar'}, {'baz': 'qux'}],
  1118. {'foo': 'bar', 'baz': 'qux'}):
  1119. self.assertEqual(
  1120. salt.utils.docker.translate_input(
  1121. self.translator,
  1122. log_driver='foo',
  1123. log_opt='foo=bar,baz=qux'
  1124. ),
  1125. {'log_config': {'Type': 'foo',
  1126. 'Config': {'foo': 'bar', 'baz': 'qux'}}}
  1127. )
  1128. # Ensure passing either `log_driver` or `log_opt` alone works
  1129. self.assertEqual(
  1130. salt.utils.docker.translate_input(
  1131. self.translator,
  1132. log_driver='foo'
  1133. ),
  1134. {'log_config': {'Type': 'foo', 'Config': {}}}
  1135. )
  1136. self.assertEqual(
  1137. salt.utils.docker.translate_input(
  1138. self.translator,
  1139. log_opt={'foo': 'bar', 'baz': 'qux'}
  1140. ),
  1141. {'log_config': {'Type': 'none',
  1142. 'Config': {'foo': 'bar', 'baz': 'qux'}}}
  1143. )
  1144. @assert_key_equals_value(salt.utils.docker.translate.container)
  1145. def test_lxc_conf(self):
  1146. '''
  1147. Can be passed as a list of key=value pairs or a dictionary, and must
  1148. ultimately end up as a dictionary.
  1149. '''
  1150. pass
  1151. @assert_string(salt.utils.docker.translate.container)
  1152. def test_mac_address(self):
  1153. '''
  1154. Should be a string or converted to one
  1155. '''
  1156. pass
  1157. @assert_int_or_string(salt.utils.docker.translate.container)
  1158. def test_mem_limit(self):
  1159. '''
  1160. Should be a string or converted to one
  1161. '''
  1162. pass
  1163. @assert_int(salt.utils.docker.translate.container)
  1164. def test_mem_swappiness(self):
  1165. '''
  1166. Should be an int or converted to one
  1167. '''
  1168. pass
  1169. @assert_int_or_string(salt.utils.docker.translate.container)
  1170. def test_memswap_limit(self):
  1171. '''
  1172. Should be a string or converted to one
  1173. '''
  1174. pass
  1175. @assert_string(salt.utils.docker.translate.container)
  1176. def test_name(self):
  1177. '''
  1178. Should be a string or converted to one
  1179. '''
  1180. pass
  1181. @assert_bool(salt.utils.docker.translate.container)
  1182. def test_network_disabled(self):
  1183. '''
  1184. Should be a bool or converted to one
  1185. '''
  1186. pass
  1187. @assert_string(salt.utils.docker.translate.container)
  1188. def test_network_mode(self):
  1189. '''
  1190. Should be a string or converted to one
  1191. '''
  1192. pass
  1193. @assert_bool(salt.utils.docker.translate.container)
  1194. def test_oom_kill_disable(self):
  1195. '''
  1196. Should be a bool or converted to one
  1197. '''
  1198. pass
  1199. @assert_int(salt.utils.docker.translate.container)
  1200. def test_oom_score_adj(self):
  1201. '''
  1202. Should be an int or converted to one
  1203. '''
  1204. pass
  1205. @assert_string(salt.utils.docker.translate.container)
  1206. def test_pid_mode(self):
  1207. '''
  1208. Should be a string or converted to one
  1209. '''
  1210. pass
  1211. @assert_int(salt.utils.docker.translate.container)
  1212. def test_pids_limit(self):
  1213. '''
  1214. Should be an int or converted to one
  1215. '''
  1216. pass
  1217. def test_port_bindings(self):
  1218. '''
  1219. This has several potential formats and can include port ranges. It
  1220. needs its own test.
  1221. '''
  1222. # ip:hostPort:containerPort - Bind a specific IP and port on the host
  1223. # to a specific port within the container.
  1224. bindings = (
  1225. '10.1.2.3:8080:80,10.1.2.3:8888:80,10.4.5.6:3333:3333,'
  1226. '10.7.8.9:14505-14506:4505-4506,10.1.2.3:8080:81/udp,'
  1227. '10.1.2.3:8888:81/udp,10.4.5.6:3334:3334/udp,'
  1228. '10.7.8.9:15505-15506:5505-5506/udp'
  1229. )
  1230. for val in (bindings, bindings.split(',')):
  1231. self.assertEqual(
  1232. self.normalize_ports(
  1233. salt.utils.docker.translate_input(
  1234. self.translator,
  1235. port_bindings=val,
  1236. )
  1237. ),
  1238. {'port_bindings': {80: [('10.1.2.3', 8080),
  1239. ('10.1.2.3', 8888)],
  1240. 3333: ('10.4.5.6', 3333),
  1241. 4505: ('10.7.8.9', 14505),
  1242. 4506: ('10.7.8.9', 14506),
  1243. '81/udp': [('10.1.2.3', 8080),
  1244. ('10.1.2.3', 8888)],
  1245. '3334/udp': ('10.4.5.6', 3334),
  1246. '5505/udp': ('10.7.8.9', 15505),
  1247. '5506/udp': ('10.7.8.9', 15506)},
  1248. 'ports': [80, 3333, 4505, 4506,
  1249. (81, 'udp'), (3334, 'udp'),
  1250. (5505, 'udp'), (5506, 'udp')]}
  1251. )
  1252. # ip::containerPort - Bind a specific IP and an ephemeral port to a
  1253. # specific port within the container.
  1254. bindings = (
  1255. '10.1.2.3::80,10.1.2.3::80,10.4.5.6::3333,10.7.8.9::4505-4506,'
  1256. '10.1.2.3::81/udp,10.1.2.3::81/udp,10.4.5.6::3334/udp,'
  1257. '10.7.8.9::5505-5506/udp'
  1258. )
  1259. for val in (bindings, bindings.split(',')):
  1260. self.assertEqual(
  1261. self.normalize_ports(
  1262. salt.utils.docker.translate_input(
  1263. self.translator,
  1264. port_bindings=val,
  1265. )
  1266. ),
  1267. {'port_bindings': {80: [('10.1.2.3',), ('10.1.2.3',)],
  1268. 3333: ('10.4.5.6',),
  1269. 4505: ('10.7.8.9',),
  1270. 4506: ('10.7.8.9',),
  1271. '81/udp': [('10.1.2.3',), ('10.1.2.3',)],
  1272. '3334/udp': ('10.4.5.6',),
  1273. '5505/udp': ('10.7.8.9',),
  1274. '5506/udp': ('10.7.8.9',)},
  1275. 'ports': [80, 3333, 4505, 4506,
  1276. (81, 'udp'), (3334, 'udp'),
  1277. (5505, 'udp'), (5506, 'udp')]}
  1278. )
  1279. # hostPort:containerPort - Bind a specific port on all of the host's
  1280. # interfaces to a specific port within the container.
  1281. bindings = (
  1282. '8080:80,8888:80,3333:3333,14505-14506:4505-4506,8080:81/udp,'
  1283. '8888:81/udp,3334:3334/udp,15505-15506:5505-5506/udp'
  1284. )
  1285. for val in (bindings, bindings.split(',')):
  1286. self.assertEqual(
  1287. self.normalize_ports(
  1288. salt.utils.docker.translate_input(
  1289. self.translator,
  1290. port_bindings=val,
  1291. )
  1292. ),
  1293. {'port_bindings': {80: [8080, 8888],
  1294. 3333: 3333,
  1295. 4505: 14505,
  1296. 4506: 14506,
  1297. '81/udp': [8080, 8888],
  1298. '3334/udp': 3334,
  1299. '5505/udp': 15505,
  1300. '5506/udp': 15506},
  1301. 'ports': [80, 3333, 4505, 4506,
  1302. (81, 'udp'), (3334, 'udp'),
  1303. (5505, 'udp'), (5506, 'udp')]}
  1304. )
  1305. # containerPort - Bind an ephemeral port on all of the host's
  1306. # interfaces to a specific port within the container.
  1307. bindings = '80,3333,4505-4506,81/udp,3334/udp,5505-5506/udp'
  1308. for val in (bindings, bindings.split(',')):
  1309. self.assertEqual(
  1310. self.normalize_ports(
  1311. salt.utils.docker.translate_input(
  1312. self.translator,
  1313. port_bindings=val,
  1314. )
  1315. ),
  1316. {'port_bindings': {80: None,
  1317. 3333: None,
  1318. 4505: None,
  1319. 4506: None,
  1320. '81/udp': None,
  1321. '3334/udp': None,
  1322. '5505/udp': None,
  1323. '5506/udp': None},
  1324. 'ports': [80, 3333, 4505, 4506,
  1325. (81, 'udp'), (3334, 'udp'),
  1326. (5505, 'udp'), (5506, 'udp')]}
  1327. )
  1328. # Test a mixture of different types of input
  1329. bindings = (
  1330. '10.1.2.3:8080:80,10.4.5.6::3333,14505-14506:4505-4506,'
  1331. '9999-10001,10.1.2.3:8080:81/udp,10.4.5.6::3334/udp,'
  1332. '15505-15506:5505-5506/udp,19999-20001/udp'
  1333. )
  1334. for val in (bindings, bindings.split(',')):
  1335. self.assertEqual(
  1336. self.normalize_ports(
  1337. salt.utils.docker.translate_input(
  1338. self.translator,
  1339. port_bindings=val,
  1340. )
  1341. ),
  1342. {'port_bindings': {80: ('10.1.2.3', 8080),
  1343. 3333: ('10.4.5.6',),
  1344. 4505: 14505,
  1345. 4506: 14506,
  1346. 9999: None,
  1347. 10000: None,
  1348. 10001: None,
  1349. '81/udp': ('10.1.2.3', 8080),
  1350. '3334/udp': ('10.4.5.6',),
  1351. '5505/udp': 15505,
  1352. '5506/udp': 15506,
  1353. '19999/udp': None,
  1354. '20000/udp': None,
  1355. '20001/udp': None},
  1356. 'ports': [80, 3333, 4505, 4506, 9999, 10000, 10001,
  1357. (81, 'udp'), (3334, 'udp'), (5505, 'udp'),
  1358. (5506, 'udp'), (19999, 'udp'),
  1359. (20000, 'udp'), (20001, 'udp')]}
  1360. )
  1361. # Error case: too many items (max 3)
  1362. with self.assertRaisesRegex(
  1363. CommandExecutionError,
  1364. r"'10.1.2.3:8080:80:123' is an invalid port binding "
  1365. r"definition \(at most 3 components are allowed, found 4\)"):
  1366. salt.utils.docker.translate_input(
  1367. self.translator,
  1368. port_bindings='10.1.2.3:8080:80:123'
  1369. )
  1370. # Error case: port range start is greater than end
  1371. for val in ('10.1.2.3:5555-5554:1111-1112',
  1372. '10.1.2.3:1111-1112:5555-5554',
  1373. '10.1.2.3::5555-5554',
  1374. '5555-5554:1111-1112',
  1375. '1111-1112:5555-5554',
  1376. '5555-5554'):
  1377. with self.assertRaisesRegex(
  1378. CommandExecutionError,
  1379. r"Start of port range \(5555\) cannot be greater than end "
  1380. r"of port range \(5554\)"):
  1381. salt.utils.docker.translate_input(
  1382. self.translator,
  1383. port_bindings=val,
  1384. )
  1385. # Error case: non-numeric port range
  1386. for val in ('10.1.2.3:foo:1111-1112',
  1387. '10.1.2.3:1111-1112:foo',
  1388. '10.1.2.3::foo',
  1389. 'foo:1111-1112',
  1390. '1111-1112:foo',
  1391. 'foo'):
  1392. with self.assertRaisesRegex(
  1393. CommandExecutionError,
  1394. "'foo' is non-numeric or an invalid port range"):
  1395. salt.utils.docker.translate_input(
  1396. self.translator,
  1397. port_bindings=val,
  1398. )
  1399. # Error case: misatched port range
  1400. for val in ('10.1.2.3:1111-1113:1111-1112', '1111-1113:1111-1112'):
  1401. with self.assertRaisesRegex(
  1402. CommandExecutionError,
  1403. r'Host port range \(1111-1113\) does not have the same '
  1404. r'number of ports as the container port range \(1111-1112\)'):
  1405. salt.utils.docker.translate_input(
  1406. self.translator,
  1407. port_bindings=val
  1408. )
  1409. for val in ('10.1.2.3:1111-1112:1111-1113', '1111-1112:1111-1113'):
  1410. with self.assertRaisesRegex(
  1411. CommandExecutionError,
  1412. r'Host port range \(1111-1112\) does not have the same '
  1413. r'number of ports as the container port range \(1111-1113\)'):
  1414. salt.utils.docker.translate_input(
  1415. self.translator,
  1416. port_bindings=val,
  1417. )
  1418. # Error case: empty host port or container port
  1419. with self.assertRaisesRegex(
  1420. CommandExecutionError,
  1421. "Empty host port in port binding definition ':1111'"):
  1422. salt.utils.docker.translate_input(
  1423. self.translator,
  1424. port_bindings=':1111'
  1425. )
  1426. with self.assertRaisesRegex(
  1427. CommandExecutionError,
  1428. "Empty container port in port binding definition '1111:'"):
  1429. salt.utils.docker.translate_input(
  1430. self.translator,
  1431. port_bindings='1111:'
  1432. )
  1433. with self.assertRaisesRegex(
  1434. CommandExecutionError,
  1435. 'Empty port binding definition found'):
  1436. salt.utils.docker.translate_input(
  1437. self.translator,
  1438. port_bindings=''
  1439. )
  1440. def test_ports(self):
  1441. '''
  1442. Ports can be passed as a comma-separated or Python list of port
  1443. numbers, with '/tcp' being optional for TCP ports. They must ultimately
  1444. be a list of port definitions, in which an integer denotes a TCP port,
  1445. and a tuple in the format (port_num, 'udp') denotes a UDP port. Also,
  1446. the port numbers must end up as integers. None of the decorators will
  1447. suffice so this one must be tested specially.
  1448. '''
  1449. for val in ('1111,2222/tcp,3333/udp,4505-4506',
  1450. [1111, '2222/tcp', '3333/udp', '4505-4506'],
  1451. ['1111', '2222/tcp', '3333/udp', '4505-4506']):
  1452. self.assertEqual(
  1453. self.normalize_ports(
  1454. salt.utils.docker.translate_input(
  1455. self.translator,
  1456. ports=val,
  1457. )
  1458. ),
  1459. {'ports': [1111, 2222, 4505, 4506, (3333, 'udp')]}
  1460. )
  1461. # Error case: non-integer and non/string value
  1462. for val in (1.0, [1.0]):
  1463. with self.assertRaisesRegex(
  1464. CommandExecutionError,
  1465. "'1.0' is not a valid port definition"):
  1466. salt.utils.docker.translate_input(
  1467. self.translator,
  1468. ports=val,
  1469. )
  1470. # Error case: port range start is greater than end
  1471. with self.assertRaisesRegex(
  1472. CommandExecutionError,
  1473. r"Start of port range \(5555\) cannot be greater than end of "
  1474. r"port range \(5554\)"):
  1475. salt.utils.docker.translate_input(
  1476. self.translator,
  1477. ports='5555-5554',
  1478. )
  1479. @assert_bool(salt.utils.docker.translate.container)
  1480. def test_privileged(self):
  1481. '''
  1482. Should be a bool or converted to one
  1483. '''
  1484. pass
  1485. @assert_bool(salt.utils.docker.translate.container)
  1486. def test_publish_all_ports(self):
  1487. '''
  1488. Should be a bool or converted to one
  1489. '''
  1490. pass
  1491. @assert_bool(salt.utils.docker.translate.container)
  1492. def test_read_only(self):
  1493. '''
  1494. Should be a bool or converted to one
  1495. '''
  1496. pass
  1497. def test_restart_policy(self):
  1498. '''
  1499. Input is in the format "name[:retry_count]", but the API wants it
  1500. in the format {'Name': name, 'MaximumRetryCount': retry_count}
  1501. '''
  1502. name = 'restart_policy'
  1503. alias = 'restart'
  1504. for item in (name, alias):
  1505. # Test with retry count
  1506. self.assertEqual(
  1507. salt.utils.docker.translate_input(
  1508. self.translator,
  1509. **{item: 'on-failure:5'}
  1510. ),
  1511. {name: {'Name': 'on-failure', 'MaximumRetryCount': 5}}
  1512. )
  1513. # Test without retry count
  1514. self.assertEqual(
  1515. salt.utils.docker.translate_input(
  1516. self.translator,
  1517. **{item: 'on-failure'}
  1518. ),
  1519. {name: {'Name': 'on-failure', 'MaximumRetryCount': 0}}
  1520. )
  1521. # Error case: more than one policy passed
  1522. with self.assertRaisesRegex(
  1523. CommandExecutionError,
  1524. 'Only one policy is permitted'):
  1525. salt.utils.docker.translate_input(
  1526. self.translator,
  1527. **{item: 'on-failure,always'}
  1528. )
  1529. # Test collision
  1530. test_kwargs = {name: 'on-failure:5', alias: 'always'}
  1531. self.assertEqual(
  1532. salt.utils.docker.translate_input(
  1533. self.translator,
  1534. ignore_collisions=True,
  1535. **test_kwargs
  1536. ),
  1537. {name: {'Name': 'on-failure', 'MaximumRetryCount': 5}}
  1538. )
  1539. with self.assertRaisesRegex(
  1540. CommandExecutionError,
  1541. "'restart' is an alias for 'restart_policy'"):
  1542. salt.utils.docker.translate_input(
  1543. self.translator,
  1544. ignore_collisions=False,
  1545. **test_kwargs
  1546. )
  1547. @assert_stringlist(salt.utils.docker.translate.container)
  1548. def test_security_opt(self):
  1549. '''
  1550. Should be a list of strings or converted to one
  1551. '''
  1552. pass
  1553. @assert_int_or_string(salt.utils.docker.translate.container)
  1554. def test_shm_size(self):
  1555. '''
  1556. Should be a string or converted to one
  1557. '''
  1558. pass
  1559. @assert_bool(salt.utils.docker.translate.container)
  1560. def test_stdin_open(self):
  1561. '''
  1562. Should be a bool or converted to one
  1563. '''
  1564. pass
  1565. @assert_string(salt.utils.docker.translate.container)
  1566. def test_stop_signal(self):
  1567. '''
  1568. Should be a string or converted to one
  1569. '''
  1570. pass
  1571. @assert_int(salt.utils.docker.translate.container)
  1572. def test_stop_timeout(self):
  1573. '''
  1574. Should be an int or converted to one
  1575. '''
  1576. pass
  1577. @assert_key_equals_value(salt.utils.docker.translate.container)
  1578. def test_storage_opt(self):
  1579. '''
  1580. Can be passed in several formats but must end up as a dictionary
  1581. mapping keys to values
  1582. '''
  1583. pass
  1584. @assert_key_equals_value(salt.utils.docker.translate.container)
  1585. def test_sysctls(self):
  1586. '''
  1587. Can be passed in several formats but must end up as a dictionary
  1588. mapping keys to values
  1589. '''
  1590. pass
  1591. @assert_dict(salt.utils.docker.translate.container)
  1592. def test_tmpfs(self):
  1593. '''
  1594. Can be passed in several formats but must end up as a dictionary
  1595. mapping keys to values
  1596. '''
  1597. pass
  1598. @assert_bool(salt.utils.docker.translate.container)
  1599. def test_tty(self):
  1600. '''
  1601. Should be a bool or converted to one
  1602. '''
  1603. pass
  1604. def test_ulimits(self):
  1605. '''
  1606. Input is in the format "name=soft_limit[:hard_limit]", but the API
  1607. wants it in the format
  1608. {'Name': name, 'Soft': soft_limit, 'Hard': hard_limit}
  1609. '''
  1610. # Test with and without hard limit
  1611. ulimits = 'nofile=1024:2048,nproc=50'
  1612. for val in (ulimits, ulimits.split(',')):
  1613. self.assertEqual(
  1614. salt.utils.docker.translate_input(
  1615. self.translator,
  1616. ulimits=val,
  1617. ),
  1618. {'ulimits': [{'Name': 'nofile', 'Soft': 1024, 'Hard': 2048},
  1619. {'Name': 'nproc', 'Soft': 50, 'Hard': 50}]}
  1620. )
  1621. # Error case: Invalid format
  1622. with self.assertRaisesRegex(
  1623. CommandExecutionError,
  1624. r"Ulimit definition 'nofile:1024:2048' is not in the format "
  1625. r"type=soft_limit\[:hard_limit\]"):
  1626. salt.utils.docker.translate_input(
  1627. self.translator,
  1628. ulimits='nofile:1024:2048'
  1629. )
  1630. # Error case: Invalid format
  1631. with self.assertRaisesRegex(
  1632. CommandExecutionError,
  1633. r"Limit 'nofile=foo:2048' contains non-numeric value\(s\)"):
  1634. salt.utils.docker.translate_input(
  1635. self.translator,
  1636. ulimits='nofile=foo:2048'
  1637. )
  1638. def test_user(self):
  1639. '''
  1640. Must be either username (string) or uid (int). An int passed as a
  1641. string (e.g. '0') should be converted to an int.
  1642. '''
  1643. # Username passed as string
  1644. self.assertEqual(
  1645. salt.utils.docker.translate_input(
  1646. self.translator,
  1647. user='foo'
  1648. ),
  1649. {'user': 'foo'}
  1650. )
  1651. for val in (0, '0'):
  1652. self.assertEqual(
  1653. salt.utils.docker.translate_input(
  1654. self.translator,
  1655. user=val
  1656. ),
  1657. {'user': 0}
  1658. )
  1659. # Error case: non string/int passed
  1660. with self.assertRaisesRegex(
  1661. CommandExecutionError,
  1662. 'Value must be a username or uid'):
  1663. salt.utils.docker.translate_input(
  1664. self.translator,
  1665. user=['foo']
  1666. )
  1667. # Error case: negative int passed
  1668. with self.assertRaisesRegex(
  1669. CommandExecutionError,
  1670. "'-1' is an invalid uid"):
  1671. salt.utils.docker.translate_input(
  1672. self.translator,
  1673. user=-1
  1674. )
  1675. @assert_string(salt.utils.docker.translate.container)
  1676. def test_userns_mode(self):
  1677. '''
  1678. Should be a bool or converted to one
  1679. '''
  1680. pass
  1681. @assert_string(salt.utils.docker.translate.container)
  1682. def test_volume_driver(self):
  1683. '''
  1684. Should be a bool or converted to one
  1685. '''
  1686. pass
  1687. @assert_stringlist(salt.utils.docker.translate.container)
  1688. def test_volumes(self):
  1689. '''
  1690. Should be a list of absolute paths
  1691. '''
  1692. # Error case: Not an absolute path
  1693. path = os.path.join('foo', 'bar', 'baz')
  1694. with self.assertRaisesRegex(
  1695. CommandExecutionError,
  1696. "'{0}' is not an absolute path".format(path.replace('\\', '\\\\'))):
  1697. salt.utils.docker.translate_input(
  1698. self.translator,
  1699. volumes=path
  1700. )
  1701. @assert_stringlist(salt.utils.docker.translate.container)
  1702. def test_volumes_from(self):
  1703. '''
  1704. Should be a list of strings or converted to one
  1705. '''
  1706. pass
  1707. @assert_string(salt.utils.docker.translate.container)
  1708. def test_working_dir(self):
  1709. '''
  1710. Should be a single absolute path
  1711. '''
  1712. # Error case: Not an absolute path
  1713. path = os.path.join('foo', 'bar', 'baz')
  1714. with self.assertRaisesRegex(
  1715. CommandExecutionError,
  1716. "'{0}' is not an absolute path".format(path.replace('\\', '\\\\'))):
  1717. salt.utils.docker.translate_input(
  1718. self.translator,
  1719. working_dir=path
  1720. )
  1721. class TranslateNetworkInputTestCase(TranslateBase):
  1722. '''
  1723. Tests for salt.utils.docker.translate_input(), invoked using
  1724. salt.utils.docker.translate.network as the translator module.
  1725. '''
  1726. translator = salt.utils.docker.translate.network
  1727. ip_addrs = {
  1728. True: ('10.1.2.3', '::1'),
  1729. False: ('FOO', '0.9.800.1000', 'feaz::1', 'aj01::feac'),
  1730. }
  1731. @assert_string(salt.utils.docker.translate.network)
  1732. def test_driver(self):
  1733. '''
  1734. Should be a string or converted to one
  1735. '''
  1736. pass
  1737. @assert_key_equals_value(salt.utils.docker.translate.network)
  1738. def test_options(self):
  1739. '''
  1740. Can be passed in several formats but must end up as a dictionary
  1741. mapping keys to values
  1742. '''
  1743. pass
  1744. @assert_dict(salt.utils.docker.translate.network)
  1745. def test_ipam(self):
  1746. '''
  1747. Must be a dict
  1748. '''
  1749. pass
  1750. @assert_bool(salt.utils.docker.translate.network)
  1751. def test_check_duplicate(self):
  1752. '''
  1753. Should be a bool or converted to one
  1754. '''
  1755. pass
  1756. @assert_bool(salt.utils.docker.translate.network)
  1757. def test_internal(self):
  1758. '''
  1759. Should be a bool or converted to one
  1760. '''
  1761. pass
  1762. @assert_labels(salt.utils.docker.translate.network)
  1763. def test_labels(self):
  1764. '''
  1765. Can be passed as a list of key=value pairs or a dictionary, and must
  1766. ultimately end up as a dictionary.
  1767. '''
  1768. pass
  1769. @assert_bool(salt.utils.docker.translate.network)
  1770. def test_enable_ipv6(self):
  1771. '''
  1772. Should be a bool or converted to one
  1773. '''
  1774. pass
  1775. @assert_bool(salt.utils.docker.translate.network)
  1776. def test_attachable(self):
  1777. '''
  1778. Should be a bool or converted to one
  1779. '''
  1780. pass
  1781. @assert_bool(salt.utils.docker.translate.network)
  1782. def test_ingress(self):
  1783. '''
  1784. Should be a bool or converted to one
  1785. '''
  1786. pass
  1787. @assert_string(salt.utils.docker.translate.network)
  1788. def test_ipam_driver(self):
  1789. '''
  1790. Should be a bool or converted to one
  1791. '''
  1792. pass
  1793. @assert_key_equals_value(salt.utils.docker.translate.network)
  1794. def test_ipam_opts(self):
  1795. '''
  1796. Can be passed in several formats but must end up as a dictionary
  1797. mapping keys to values
  1798. '''
  1799. pass
  1800. def ipam_pools(self):
  1801. '''
  1802. Must be a list of dictionaries (not a dictlist)
  1803. '''
  1804. good_pool = {
  1805. 'subnet': '10.0.0.0/24',
  1806. 'iprange': '10.0.0.128/25',
  1807. 'gateway': '10.0.0.254',
  1808. 'aux_addresses': {'foo.bar.tld': '10.0.0.20',
  1809. 'hello.world.tld': '10.0.0.21'},
  1810. }
  1811. bad_pools = [
  1812. {'subnet': '10.0.0.0/33',
  1813. 'iprange': '10.0.0.128/25',
  1814. 'gateway': '10.0.0.254',
  1815. 'aux_addresses': {'foo.bar.tld': '10.0.0.20',
  1816. 'hello.world.tld': '10.0.0.21'}},
  1817. {'subnet': '10.0.0.0/24',
  1818. 'iprange': 'foo/25',
  1819. 'gateway': '10.0.0.254',
  1820. 'aux_addresses': {'foo.bar.tld': '10.0.0.20',
  1821. 'hello.world.tld': '10.0.0.21'}},
  1822. {'subnet': '10.0.0.0/24',
  1823. 'iprange': '10.0.0.128/25',
  1824. 'gateway': '10.0.0.256',
  1825. 'aux_addresses': {'foo.bar.tld': '10.0.0.20',
  1826. 'hello.world.tld': '10.0.0.21'}},
  1827. {'subnet': '10.0.0.0/24',
  1828. 'iprange': '10.0.0.128/25',
  1829. 'gateway': '10.0.0.254',
  1830. 'aux_addresses': {'foo.bar.tld': '10.0.0.20',
  1831. 'hello.world.tld': '999.0.0.21'}},
  1832. ]
  1833. self.assertEqual(
  1834. salt.utils.docker.translate_input(
  1835. self.translator,
  1836. ipam_pools=[good_pool],
  1837. ),
  1838. {'ipam_pools': [good_pool]}
  1839. )
  1840. for bad_pool in bad_pools:
  1841. with self.assertRaisesRegex(CommandExecutionError, 'not a valid'):
  1842. salt.utils.docker.translate_input(
  1843. self.translator,
  1844. ipam_pools=[good_pool, bad_pool]
  1845. )
  1846. @assert_subnet(salt.utils.docker.translate.network)
  1847. def test_subnet(self):
  1848. '''
  1849. Must be an IPv4 or IPv6 subnet
  1850. '''
  1851. pass
  1852. @assert_subnet(salt.utils.docker.translate.network)
  1853. def test_iprange(self):
  1854. '''
  1855. Must be an IPv4 or IPv6 subnet
  1856. '''
  1857. pass
  1858. def test_gateway(self):
  1859. '''
  1860. Must be an IPv4 or IPv6 address
  1861. '''
  1862. for val in self.ip_addrs[True]:
  1863. self.assertEqual(
  1864. salt.utils.docker.translate_input(
  1865. self.translator,
  1866. validate_ip_addrs=True,
  1867. gateway=val,
  1868. ),
  1869. self.apply_defaults({'gateway': val})
  1870. )
  1871. for val in self.ip_addrs[False]:
  1872. with self.assertRaisesRegex(
  1873. CommandExecutionError,
  1874. "'{0}' is not a valid IP address".format(val)):
  1875. salt.utils.docker.translate_input(
  1876. self.translator,
  1877. validate_ip_addrs=True,
  1878. gateway=val,
  1879. )
  1880. self.assertEqual(
  1881. salt.utils.docker.translate_input(
  1882. self.translator,
  1883. validate_ip_addrs=False,
  1884. gateway=val,
  1885. ),
  1886. self.apply_defaults(
  1887. {'gateway': val if isinstance(val, six.string_types)
  1888. else six.text_type(val)}
  1889. )
  1890. )
  1891. @assert_key_equals_value(salt.utils.docker.translate.network)
  1892. def test_aux_addresses(self):
  1893. '''
  1894. Must be a mapping of hostnames to IP addresses
  1895. '''
  1896. name = 'aux_addresses'
  1897. alias = 'aux_address'
  1898. for item in (name, alias):
  1899. for val in self.ip_addrs[True]:
  1900. addresses = {'foo.bar.tld': val}
  1901. self.assertEqual(
  1902. salt.utils.docker.translate_input(
  1903. self.translator,
  1904. validate_ip_addrs=True,
  1905. **{item: addresses}
  1906. ),
  1907. self.apply_defaults({name: addresses})
  1908. )
  1909. for val in self.ip_addrs[False]:
  1910. addresses = {'foo.bar.tld': val}
  1911. with self.assertRaisesRegex(
  1912. CommandExecutionError,
  1913. "'{0}' is not a valid IP address".format(val)):
  1914. salt.utils.docker.translate_input(
  1915. self.translator,
  1916. validate_ip_addrs=True,
  1917. **{item: addresses}
  1918. )
  1919. self.assertEqual(
  1920. salt.utils.docker.translate_input(
  1921. self.translator,
  1922. validate_ip_addrs=False,
  1923. aux_addresses=addresses,
  1924. ),
  1925. self.apply_defaults({name: addresses})
  1926. )
  1927. class DockerTranslateHelperTestCase(TestCase):
  1928. '''
  1929. Tests for a couple helper functions in salt.utils.docker.translate
  1930. '''
  1931. def test_get_port_def(self):
  1932. '''
  1933. Test translation of port definition (1234, '1234/tcp', '1234/udp',
  1934. etc.) into the format which docker-py uses (integer for TCP ports,
  1935. 'port_num/udp' for UDP ports).
  1936. '''
  1937. # Test TCP port (passed as int, no protocol passed)
  1938. self.assertEqual(translate_helpers.get_port_def(2222), 2222)
  1939. # Test TCP port (passed as str, no protocol passed)
  1940. self.assertEqual(translate_helpers.get_port_def('2222'), 2222)
  1941. # Test TCP port (passed as str, with protocol passed)
  1942. self.assertEqual(translate_helpers.get_port_def('2222', 'tcp'), 2222)
  1943. # Test TCP port (proto passed in port_num, with passed proto ignored).
  1944. # This is a contrived example as we would never invoke the function in
  1945. # this way, but it tests that we are taking the port number from the
  1946. # port_num argument and ignoring the passed protocol.
  1947. self.assertEqual(translate_helpers.get_port_def('2222/tcp', 'udp'), 2222)
  1948. # Test UDP port (passed as int)
  1949. self.assertEqual(translate_helpers.get_port_def(2222, 'udp'), (2222, 'udp'))
  1950. # Test UDP port (passed as string)
  1951. self.assertEqual(translate_helpers.get_port_def('2222', 'udp'), (2222, 'udp'))
  1952. # Test UDP port (proto passed in port_num
  1953. self.assertEqual(translate_helpers.get_port_def('2222/udp'), (2222, 'udp'))
  1954. def test_get_port_range(self):
  1955. '''
  1956. Test extracting the start and end of a port range from a port range
  1957. expression (e.g. 4505-4506)
  1958. '''
  1959. # Passing a single int should return the start and end as the same value
  1960. self.assertEqual(translate_helpers.get_port_range(2222), (2222, 2222))
  1961. # Same as above but with port number passed as a string
  1962. self.assertEqual(translate_helpers.get_port_range('2222'), (2222, 2222))
  1963. # Passing a port range
  1964. self.assertEqual(translate_helpers.get_port_range('2222-2223'), (2222, 2223))
  1965. # Error case: port range start is greater than end
  1966. with self.assertRaisesRegex(
  1967. ValueError,
  1968. r'Start of port range \(2222\) cannot be greater than end of '
  1969. r'port range \(2221\)'):
  1970. translate_helpers.get_port_range('2222-2221')
  1971. # Error case: non-numeric input
  1972. with self.assertRaisesRegex(
  1973. ValueError,
  1974. '\'2222-bar\' is non-numeric or an invalid port range'):
  1975. translate_helpers.get_port_range('2222-bar')