1
0

test_mysql.py 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import logging
  5. # Import Salt Testing libs
  6. from tests.support.case import ModuleCase
  7. from tests.support.unit import skipIf
  8. from tests.support.helpers import destructiveTest
  9. from tests.support.mixins import SaltReturnAssertsMixin
  10. # Import salt libs
  11. import salt.utils.path
  12. from salt.modules import mysql as mysqlmod
  13. # Import 3rd-party libs
  14. from salt.ext import six
  15. from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
  16. log = logging.getLogger(__name__)
  17. NO_MYSQL = False
  18. try:
  19. import MySQLdb # pylint: disable=import-error,unused-import
  20. except Exception:
  21. NO_MYSQL = True
  22. if not salt.utils.path.which('mysqladmin'):
  23. NO_MYSQL = True
  24. @skipIf(
  25. NO_MYSQL,
  26. 'Please install MySQL bindings and a MySQL Server before running'
  27. 'MySQL integration tests.'
  28. )
  29. class MysqlModuleDbTest(ModuleCase, SaltReturnAssertsMixin):
  30. '''
  31. Module testing database creation on a real MySQL Server.
  32. '''
  33. user = 'root'
  34. password = 'poney'
  35. @destructiveTest
  36. def setUp(self):
  37. '''
  38. Test presence of MySQL server, enforce a root password
  39. '''
  40. super(MysqlModuleDbTest, self).setUp()
  41. NO_MYSQL_SERVER = True
  42. # now ensure we know the mysql root password
  43. # one of theses two at least should work
  44. ret1 = self.run_state(
  45. 'cmd.run',
  46. name='mysqladmin --host="localhost" -u '
  47. + self.user
  48. + ' flush-privileges password "'
  49. + self.password
  50. + '"'
  51. )
  52. ret2 = self.run_state(
  53. 'cmd.run',
  54. name='mysqladmin --host="localhost" -u '
  55. + self.user
  56. + ' --password="'
  57. + self.password
  58. + '" flush-privileges password "'
  59. + self.password
  60. + '"'
  61. )
  62. key, value = ret2.popitem()
  63. if value['result']:
  64. NO_MYSQL_SERVER = False
  65. else:
  66. self.skipTest('No MySQL Server running, or no root access on it.')
  67. def _db_creation_loop(self,
  68. db_name,
  69. returning_name,
  70. test_conn=False,
  71. **kwargs):
  72. '''
  73. Used in db testCase, create, check exists, check in list and removes.
  74. '''
  75. ret = self.run_function(
  76. 'mysql.db_create',
  77. name=db_name,
  78. **kwargs
  79. )
  80. self.assertEqual(
  81. True,
  82. ret,
  83. 'Problem while creating db for db name: \'{0}\''.format(db_name)
  84. )
  85. # test db exists
  86. ret = self.run_function(
  87. 'mysql.db_exists',
  88. name=db_name,
  89. **kwargs
  90. )
  91. self.assertEqual(
  92. True,
  93. ret,
  94. 'Problem while testing db exists for db name: \'{0}\''.format(db_name)
  95. )
  96. # List db names to ensure db is created with the right utf8 string
  97. ret = self.run_function(
  98. 'mysql.db_list',
  99. **kwargs
  100. )
  101. if not isinstance(ret, list):
  102. raise AssertionError(
  103. ('Unexpected query result while retrieving databases list'
  104. ' \'{0}\' for \'{1}\' test').format(
  105. ret,
  106. db_name
  107. )
  108. )
  109. self.assertIn(
  110. returning_name,
  111. ret,
  112. ('Problem while testing presence of db name in db lists'
  113. ' for db name: \'{0}\' in list \'{1}\'').format(
  114. db_name,
  115. ret
  116. ))
  117. if test_conn:
  118. # test connections on database with root user
  119. ret = self.run_function(
  120. 'mysql.query',
  121. database=db_name,
  122. query='SELECT 1',
  123. **kwargs
  124. )
  125. if not isinstance(ret, dict) or 'results' not in ret:
  126. raise AssertionError(
  127. ('Unexpected result while testing connection'
  128. ' on database : {0}').format(
  129. repr(db_name)
  130. )
  131. )
  132. self.assertEqual([['1']], ret['results'])
  133. # Now remove database
  134. ret = self.run_function(
  135. 'mysql.db_remove',
  136. name=db_name,
  137. **kwargs
  138. )
  139. self.assertEqual(
  140. True,
  141. ret,
  142. 'Problem while removing db for db name: \'{0}\''.format(db_name)
  143. )
  144. @destructiveTest
  145. def test_database_creation_level1(self):
  146. '''
  147. Create database, test presence, then drop db. All theses with complex names.
  148. '''
  149. # name with space
  150. db_name = 'foo 1'
  151. self._db_creation_loop(db_name=db_name,
  152. returning_name=db_name,
  153. test_conn=True,
  154. connection_user=self.user,
  155. connection_pass=self.password
  156. )
  157. # ```````
  158. # create
  159. # also with character_set and collate only
  160. ret = self.run_function(
  161. 'mysql.db_create',
  162. name='foo`2',
  163. character_set='utf8',
  164. collate='utf8_general_ci',
  165. connection_user=self.user,
  166. connection_pass=self.password
  167. )
  168. self.assertEqual(True, ret)
  169. # test db exists
  170. ret = self.run_function(
  171. 'mysql.db_exists',
  172. name='foo`2',
  173. connection_user=self.user,
  174. connection_pass=self.password
  175. )
  176. self.assertEqual(True, ret)
  177. # redoing the same should fail
  178. # even with other character sets or collations
  179. ret = self.run_function(
  180. 'mysql.db_create',
  181. name='foo`2',
  182. character_set='utf8',
  183. collate='utf8_general_ci',
  184. connection_user=self.user,
  185. connection_pass=self.password
  186. )
  187. self.assertEqual(False, ret)
  188. # redoing the same should fail
  189. ret = self.run_function(
  190. 'mysql.db_create',
  191. name='foo`2',
  192. character_set='utf8',
  193. collate='utf8_general_ci',
  194. connection_user=self.user,
  195. connection_pass=self.password
  196. )
  197. self.assertEqual(False, ret)
  198. # Now remove database
  199. ret = self.run_function(
  200. 'mysql.db_remove',
  201. name='foo`2',
  202. connection_user=self.user,
  203. connection_pass=self.password
  204. )
  205. self.assertEqual(True, ret)
  206. # '''''''
  207. # create
  208. # also with character_set only
  209. db_name = "foo'3"
  210. self._db_creation_loop(db_name=db_name,
  211. returning_name=db_name,
  212. test_conn=True,
  213. character_set='utf8',
  214. connection_user=self.user,
  215. connection_pass=self.password
  216. )
  217. # """"""""
  218. # also with collate only
  219. db_name = 'foo"4'
  220. self._db_creation_loop(db_name=db_name,
  221. returning_name=db_name,
  222. test_conn=True,
  223. collate='utf8_general_ci',
  224. connection_user=self.user,
  225. connection_pass=self.password
  226. )
  227. # fuzzy
  228. db_name = '<foo` --"5>'
  229. self._db_creation_loop(db_name=db_name,
  230. returning_name=db_name,
  231. test_conn=True,
  232. connection_user=self.user,
  233. connection_pass=self.password
  234. )
  235. @destructiveTest
  236. def test_mysql_dbname_character_percent(self):
  237. '''
  238. Play with the '%' character problems
  239. This character should be escaped in the form '%%' on queries, but only
  240. when theses queries have arguments. It is also a special character
  241. in LIKE SQL queries. Finally it is used to indicate query arguments.
  242. '''
  243. db_name1 = "foo%1_"
  244. db_name2 = "foo%12"
  245. ret = self.run_function(
  246. 'mysql.db_create',
  247. name=db_name1,
  248. character_set='utf8',
  249. collate='utf8_general_ci',
  250. connection_user=self.user,
  251. connection_pass=self.password
  252. )
  253. self.assertEqual(True, ret)
  254. ret = self.run_function(
  255. 'mysql.db_create',
  256. name=db_name2,
  257. connection_user=self.user,
  258. connection_pass=self.password
  259. )
  260. self.assertEqual(True, ret)
  261. ret = self.run_function(
  262. 'mysql.db_remove',
  263. name=db_name1,
  264. connection_user=self.user,
  265. connection_pass=self.password
  266. )
  267. self.assertEqual(True, ret)
  268. ret = self.run_function(
  269. 'mysql.db_exists',
  270. name=db_name1,
  271. connection_user=self.user,
  272. connection_pass=self.password
  273. )
  274. self.assertEqual(False, ret)
  275. ret = self.run_function(
  276. 'mysql.db_exists',
  277. name=db_name2,
  278. connection_user=self.user,
  279. connection_pass=self.password
  280. )
  281. self.assertEqual(True, ret)
  282. ret = self.run_function(
  283. 'mysql.db_remove',
  284. name=db_name2,
  285. connection_user=self.user,
  286. connection_pass=self.password
  287. )
  288. self.assertEqual(True, ret)
  289. @destructiveTest
  290. def test_database_creation_utf8(self):
  291. '''
  292. Test support of utf8 in database names
  293. '''
  294. # Simple accents : using utf8 string
  295. db_name_unicode = u'notam\xe9rican'
  296. # same as 'notamérican' because of file encoding
  297. # but ensure it on this test
  298. db_name_utf8 = 'notam\xc3\xa9rican'
  299. # FIXME: MySQLdb problems on conn strings containing
  300. # utf-8 on user name of db name prevent conn test
  301. self._db_creation_loop(db_name=db_name_utf8,
  302. returning_name=db_name_utf8,
  303. test_conn=False,
  304. connection_user=self.user,
  305. connection_pass=self.password,
  306. connection_charset='utf8',
  307. saltenv={"LC_ALL": "en_US.utf8"}
  308. )
  309. # test unicode entry will also return utf8 name
  310. self._db_creation_loop(db_name=db_name_unicode,
  311. returning_name=db_name_utf8,
  312. test_conn=False,
  313. connection_user=self.user,
  314. connection_pass=self.password,
  315. connection_charset='utf8',
  316. saltenv={"LC_ALL": "en_US.utf8"}
  317. )
  318. # Using more complex unicode characters:
  319. db_name_unicode = u'\u6a19\u6e96\u8a9e'
  320. # same as '標準語' because of file encoding
  321. # but ensure it on this test
  322. db_name_utf8 = '\xe6\xa8\x99\xe6\xba\x96\xe8\xaa\x9e'
  323. self._db_creation_loop(db_name=db_name_utf8,
  324. returning_name=db_name_utf8,
  325. test_conn=False,
  326. connection_user=self.user,
  327. connection_pass=self.password,
  328. connection_charset='utf8',
  329. saltenv={"LC_ALL": "en_US.utf8"}
  330. )
  331. # test unicode entry will also return utf8 name
  332. self._db_creation_loop(db_name=db_name_unicode,
  333. returning_name=db_name_utf8,
  334. test_conn=False,
  335. connection_user=self.user,
  336. connection_pass=self.password,
  337. connection_charset='utf8',
  338. saltenv={"LC_ALL": "en_US.utf8"}
  339. )
  340. @destructiveTest
  341. def test_database_maintenance(self):
  342. '''
  343. Test maintenance operations on a created database
  344. '''
  345. dbname = u"foo%'-- `\"'"
  346. # create database
  347. # but first silently try to remove it
  348. # in case of previous tests failures
  349. ret = self.run_function(
  350. 'mysql.db_remove',
  351. name=dbname,
  352. connection_user=self.user,
  353. connection_pass=self.password
  354. )
  355. ret = self.run_function(
  356. 'mysql.db_create',
  357. name=dbname,
  358. character_set='utf8',
  359. collate='utf8_general_ci',
  360. connection_user=self.user,
  361. connection_pass=self.password
  362. )
  363. self.assertEqual(True, ret)
  364. # test db exists
  365. ret = self.run_function(
  366. 'mysql.db_exists',
  367. name=dbname,
  368. connection_user=self.user,
  369. connection_pass=self.password
  370. )
  371. self.assertEqual(True, ret)
  372. # Create 3 tables
  373. tablenames = {'A%table "`1': 'MYISAM',
  374. 'B%table \'`2': 'InnoDB',
  375. 'Ctable --`3': 'MEMORY'
  376. }
  377. for tablename, engine in sorted(six.iteritems(tablenames)):
  378. # prepare queries
  379. create_query = ('CREATE TABLE {tblname} ('
  380. ' id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,'
  381. ' data VARCHAR(100)) ENGINE={engine};'.format(
  382. tblname=mysqlmod.quote_identifier(tablename),
  383. engine=engine,
  384. ))
  385. insert_query = ('INSERT INTO {tblname} (data)'
  386. ' VALUES '.format(
  387. tblname=mysqlmod.quote_identifier(tablename)
  388. ))
  389. delete_query = ('DELETE from {tblname}'
  390. ' order by rand() limit 50;'.format(
  391. tblname=mysqlmod.quote_identifier(tablename)
  392. ))
  393. for x in range(100):
  394. insert_query += "('foo"+six.text_type(x)+"'),"
  395. insert_query += "('bar');"
  396. # populate database
  397. log.info('Adding table \'%s\'', tablename)
  398. ret = self.run_function(
  399. 'mysql.query',
  400. database=dbname,
  401. query=create_query,
  402. connection_user=self.user,
  403. connection_pass=self.password
  404. )
  405. if not isinstance(ret, dict) or 'rows affected' not in ret:
  406. raise AssertionError(
  407. ('Unexpected query result while populating test table'
  408. ' \'{0}\' : \'{1}\'').format(
  409. tablename,
  410. ret,
  411. )
  412. )
  413. self.assertEqual(ret['rows affected'], 0)
  414. log.info('Populating table \'%s\'', tablename)
  415. ret = self.run_function(
  416. 'mysql.query',
  417. database=dbname,
  418. query=insert_query,
  419. connection_user=self.user,
  420. connection_pass=self.password
  421. )
  422. if not isinstance(ret, dict) or 'rows affected' not in ret:
  423. raise AssertionError(
  424. ('Unexpected query result while populating test table'
  425. ' \'{0}\' : \'{1}\'').format(
  426. tablename,
  427. ret,
  428. )
  429. )
  430. self.assertEqual(ret['rows affected'], 101)
  431. log.info('Removing some rows on table\'%s\'', tablename)
  432. ret = self.run_function(
  433. 'mysql.query',
  434. database=dbname,
  435. query=delete_query,
  436. connection_user=self.user,
  437. connection_pass=self.password
  438. )
  439. if not isinstance(ret, dict) or 'rows affected' not in ret:
  440. raise AssertionError(
  441. ('Unexpected query result while removing rows on test table'
  442. ' \'{0}\' : \'{1}\'').format(
  443. tablename,
  444. ret,
  445. )
  446. )
  447. self.assertEqual(ret['rows affected'], 50)
  448. # test check/repair/opimize on 1 table
  449. tablename = 'A%table "`1'
  450. ret = self.run_function(
  451. 'mysql.db_check',
  452. name=dbname,
  453. table=tablename,
  454. connection_user=self.user,
  455. connection_pass=self.password
  456. )
  457. # Note that returned result does not quote_identifier of table and db
  458. self.assertEqual(ret, [{'Table': dbname+'.'+tablename,
  459. 'Msg_text': 'OK',
  460. 'Msg_type': 'status',
  461. 'Op': 'check'}]
  462. )
  463. ret = self.run_function(
  464. 'mysql.db_repair',
  465. name=dbname,
  466. table=tablename,
  467. connection_user=self.user,
  468. connection_pass=self.password
  469. )
  470. # Note that returned result does not quote_identifier of table and db
  471. self.assertEqual(ret, [{'Table': dbname+'.'+tablename,
  472. 'Msg_text': 'OK',
  473. 'Msg_type': 'status',
  474. 'Op': 'repair'}]
  475. )
  476. ret = self.run_function(
  477. 'mysql.db_optimize',
  478. name=dbname,
  479. table=tablename,
  480. connection_user=self.user,
  481. connection_pass=self.password
  482. )
  483. # Note that returned result does not quote_identifier of table and db
  484. self.assertEqual(ret, [{'Table': dbname+'.'+tablename,
  485. 'Msg_text': 'OK',
  486. 'Msg_type': 'status',
  487. 'Op': 'optimize'}]
  488. )
  489. # test check/repair/opimize on all tables
  490. ret = self.run_function(
  491. 'mysql.db_check',
  492. name=dbname,
  493. connection_user=self.user,
  494. connection_pass=self.password
  495. )
  496. expected = []
  497. for tablename, engine in sorted(six.iteritems(tablenames)):
  498. if engine is 'MEMORY':
  499. expected.append([{
  500. 'Table': dbname+'.'+tablename,
  501. 'Msg_text': ("The storage engine for the table doesn't"
  502. " support check"),
  503. 'Msg_type': 'note',
  504. 'Op': 'check'
  505. }])
  506. else:
  507. expected.append([{
  508. 'Table': dbname+'.'+tablename,
  509. 'Msg_text': 'OK',
  510. 'Msg_type': 'status',
  511. 'Op': 'check'
  512. }])
  513. self.assertEqual(ret, expected)
  514. ret = self.run_function(
  515. 'mysql.db_repair',
  516. name=dbname,
  517. connection_user=self.user,
  518. connection_pass=self.password
  519. )
  520. expected = []
  521. for tablename, engine in sorted(six.iteritems(tablenames)):
  522. if engine is 'MYISAM':
  523. expected.append([{
  524. 'Table': dbname+'.'+tablename,
  525. 'Msg_text': 'OK',
  526. 'Msg_type': 'status',
  527. 'Op': 'repair'
  528. }])
  529. else:
  530. expected.append([{
  531. 'Table': dbname+'.'+tablename,
  532. 'Msg_text': ("The storage engine for the table doesn't"
  533. " support repair"),
  534. 'Msg_type': 'note',
  535. 'Op': 'repair'
  536. }])
  537. self.assertEqual(ret, expected)
  538. ret = self.run_function(
  539. 'mysql.db_optimize',
  540. name=dbname,
  541. connection_user=self.user,
  542. connection_pass=self.password
  543. )
  544. expected = []
  545. for tablename, engine in sorted(six.iteritems(tablenames)):
  546. if engine is 'MYISAM':
  547. expected.append([{
  548. 'Table': dbname+'.'+tablename,
  549. 'Msg_text': 'OK',
  550. 'Msg_type': 'status',
  551. 'Op': 'optimize'
  552. }])
  553. elif engine is 'InnoDB':
  554. expected.append([{
  555. 'Table': dbname+'.'+tablename,
  556. 'Msg_text': ("Table does not support optimize, "
  557. "doing recreate + analyze instead"),
  558. 'Msg_type': 'note',
  559. 'Op': 'optimize'
  560. },
  561. {
  562. 'Table': dbname+'.'+tablename,
  563. 'Msg_text': 'OK',
  564. 'Msg_type': 'status',
  565. 'Op': 'optimize'
  566. }])
  567. elif engine is 'MEMORY':
  568. expected.append([{
  569. 'Table': dbname+'.'+tablename,
  570. 'Msg_text': ("The storage engine for the table doesn't"
  571. " support optimize"),
  572. 'Msg_type': 'note',
  573. 'Op': 'optimize'
  574. }])
  575. self.assertEqual(ret, expected)
  576. # Teardown, remove database
  577. ret = self.run_function(
  578. 'mysql.db_remove',
  579. name=dbname,
  580. connection_user=self.user,
  581. connection_pass=self.password
  582. )
  583. self.assertEqual(True, ret)
  584. @skipIf(
  585. NO_MYSQL,
  586. 'Please install MySQL bindings and a MySQL Server before running'
  587. 'MySQL integration tests.'
  588. )
  589. class MysqlModuleUserTest(ModuleCase, SaltReturnAssertsMixin):
  590. '''
  591. User Creation and connection tests
  592. '''
  593. user = 'root'
  594. password = 'poney'
  595. @destructiveTest
  596. def setUp(self):
  597. '''
  598. Test presence of MySQL server, enforce a root password
  599. '''
  600. super(MysqlModuleUserTest, self).setUp()
  601. NO_MYSQL_SERVER = True
  602. # now ensure we know the mysql root password
  603. # one of theses two at least should work
  604. ret1 = self.run_state(
  605. 'cmd.run',
  606. name='mysqladmin --host="localhost" -u '
  607. + self.user
  608. + ' flush-privileges password "'
  609. + self.password
  610. + '"'
  611. )
  612. ret2 = self.run_state(
  613. 'cmd.run',
  614. name='mysqladmin --host="localhost" -u '
  615. + self.user
  616. + ' --password="'
  617. + self.password
  618. + '" flush-privileges password "'
  619. + self.password
  620. + '"'
  621. )
  622. key, value = ret2.popitem()
  623. if value['result']:
  624. NO_MYSQL_SERVER = False
  625. else:
  626. self.skipTest('No MySQL Server running, or no root access on it.')
  627. def _userCreationLoop(self,
  628. uname,
  629. host,
  630. password=None,
  631. new_password=None,
  632. new_password_hash=None,
  633. **kwargs):
  634. '''
  635. Perform some tests around creation of the given user
  636. '''
  637. # First silently remove it, in case of
  638. ret = self.run_function(
  639. 'mysql.user_remove',
  640. user=uname,
  641. host=host,
  642. **kwargs
  643. )
  644. # creation
  645. ret = self.run_function(
  646. 'mysql.user_create',
  647. user=uname,
  648. host=host,
  649. password=password,
  650. **kwargs
  651. )
  652. self.assertEqual(True, ret, ('Calling user_create on'
  653. ' user \'{0}\' did not return True: {1}').format(
  654. uname,
  655. repr(ret)
  656. ))
  657. # double creation failure
  658. ret = self.run_function(
  659. 'mysql.user_create',
  660. user=uname,
  661. host=host,
  662. password=password,
  663. **kwargs
  664. )
  665. self.assertEqual(False, ret, ('Calling user_create a second time on'
  666. ' user \'{0}\' did not return False: {1}').format(
  667. uname,
  668. repr(ret)
  669. ))
  670. # Alter password
  671. if new_password is not None or new_password_hash is not None:
  672. ret = self.run_function(
  673. 'mysql.user_chpass',
  674. user=uname,
  675. host=host,
  676. password=new_password,
  677. password_hash=new_password_hash,
  678. connection_user=self.user,
  679. connection_pass=self.password,
  680. connection_charset='utf8',
  681. saltenv={"LC_ALL": "en_US.utf8"}
  682. )
  683. self.assertEqual(True, ret, ('Calling user_chpass on'
  684. ' user \'{0}\' did not return True: {1}').format(
  685. uname,
  686. repr(ret)
  687. ))
  688. def _chck_userinfo(self, user, host, check_user, check_hash):
  689. '''
  690. Internal routine to check user_info returned results
  691. '''
  692. ret = self.run_function(
  693. 'mysql.user_info',
  694. user=user,
  695. host=host,
  696. connection_user=self.user,
  697. connection_pass=self.password,
  698. connection_charset='utf8',
  699. saltenv={"LC_ALL": "en_US.utf8"}
  700. )
  701. if not isinstance(ret, dict):
  702. raise AssertionError(
  703. 'Unexpected result while retrieving user_info for '
  704. '\'{0}\''.format(user)
  705. )
  706. self.assertEqual(ret['Host'], host)
  707. self.assertEqual(ret['Password'], check_hash)
  708. self.assertEqual(ret['User'], check_user)
  709. def _chk_remove_user(self, user, host, **kwargs):
  710. '''
  711. Internal routine to check user_remove
  712. '''
  713. ret = self.run_function(
  714. 'mysql.user_remove',
  715. user=user,
  716. host=host,
  717. **kwargs
  718. )
  719. self.assertEqual(True, ret, ('Assertion failed while removing user'
  720. ' \'{0}\' on host \'{1}\': {2}').format(
  721. user,
  722. host,
  723. repr(ret)
  724. ))
  725. @destructiveTest
  726. def test_user_management(self):
  727. '''
  728. Test various users creation settings
  729. '''
  730. # Create users with rights on this database
  731. # and rights on other databases
  732. user1 = "user '1"
  733. user1_pwd = 'pwd`\'"1b'
  734. user1_pwd_hash = '*4DF33B3B12E43384677050A818327877FAB2F4BA'
  735. # this is : user "2'標
  736. user2 = 'user "2\'\xe6\xa8\x99'
  737. user2_pwd = 'user "2\'\xe6\xa8\x99b'
  738. user2_pwd_hash = '*3A38A7B94B024B983687BB9B44FB60B7AA38FE61'
  739. user3 = 'user "3;,?:@=&/'
  740. user3_pwd = 'user "3;,?:@=&/'
  741. user3_pwd_hash = '*AA3B1D4105A45D381C23A5C221C47EA349E1FD7D'
  742. # this is : user ":=;4標 in unicode instead of utf-8
  743. # if unicode char is counted as 1 char we hit the max user
  744. # size (16)
  745. user4 = u'user":;,?:@=&/4\u6a19'
  746. user4_utf8 = 'user":;,?:@=&/4\xe6\xa8\x99'
  747. user4_pwd = 'user "4;,?:@=&/'
  748. user4_pwd_hash = '*FC8EF8DBF27628E4E113359F8E7478D5CF3DD57C'
  749. user5 = u'user ``"5'
  750. user5_utf8 = 'user ``"5'
  751. # this is 標標標\
  752. user5_pwd = '\xe6\xa8\x99\xe6\xa8\x99\\'
  753. # this is password('標標\\')
  754. user5_pwd_hash = '*3752E65CDD8751AF8D889C62CFFC6C998B12C376'
  755. user6 = u'user %--"6'
  756. user6_utf8 = 'user %--"6'
  757. # this is : --'"% SIX標b
  758. user6_pwd_u = u' --\'"% SIX\u6a19b'
  759. user6_pwd_utf8 = ' --\'"% SIX\xe6\xa8\x99b'
  760. # this is password(' --\'"% SIX標b')
  761. user6_pwd_hash = '*90AE800593E2D407CD9E28CCAFBE42D17EEA5369'
  762. self._userCreationLoop(
  763. uname=user1,
  764. host='localhost',
  765. password='pwd`\'"1',
  766. new_password='pwd`\'"1b',
  767. connection_user=self.user,
  768. connection_pass=self.password
  769. )
  770. # Now check for results
  771. ret = self.run_function(
  772. 'mysql.user_exists',
  773. user=user1,
  774. host='localhost',
  775. password=user1_pwd,
  776. password_hash=None,
  777. connection_user=self.user,
  778. connection_pass=self.password,
  779. connection_charset='utf8',
  780. saltenv={"LC_ALL": "en_US.utf8"}
  781. )
  782. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  783. ' existence failed').format(user1, 'localhost')
  784. )
  785. self._userCreationLoop(
  786. uname=user2,
  787. host='localhost',
  788. password=None,
  789. # this is his name hash : user "2'標
  790. password_hash='*EEF6F854748ACF841226BB1C2422BEC70AE7F1FF',
  791. # and this is the same with a 'b' added
  792. new_password_hash=user2_pwd_hash,
  793. connection_user=self.user,
  794. connection_pass=self.password,
  795. connection_charset='utf8',
  796. saltenv={"LC_ALL": "en_US.utf8"}
  797. )
  798. # user2 can connect from other places with other password
  799. self._userCreationLoop(
  800. uname=user2,
  801. host='10.0.0.1',
  802. allow_passwordless=True,
  803. connection_user=self.user,
  804. connection_pass=self.password,
  805. connection_charset='utf8',
  806. saltenv={"LC_ALL": "en_US.utf8"}
  807. )
  808. self._userCreationLoop(
  809. uname=user2,
  810. host='10.0.0.2',
  811. allow_passwordless=True,
  812. unix_socket=True,
  813. connection_user=self.user,
  814. connection_pass=self.password,
  815. connection_charset='utf8',
  816. saltenv={"LC_ALL": "en_US.utf8"}
  817. )
  818. # Now check for results
  819. ret = self.run_function(
  820. 'mysql.user_exists',
  821. user=user2,
  822. host='localhost',
  823. password=None,
  824. password_hash=user2_pwd_hash,
  825. connection_user=self.user,
  826. connection_pass=self.password,
  827. connection_charset='utf8',
  828. saltenv={"LC_ALL": "en_US.utf8"}
  829. )
  830. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  831. ' failed').format(user2, 'localhost')
  832. )
  833. ret = self.run_function(
  834. 'mysql.user_exists',
  835. user=user2,
  836. host='10.0.0.1',
  837. allow_passwordless=True,
  838. connection_user=self.user,
  839. connection_pass=self.password,
  840. connection_charset='utf8',
  841. saltenv={"LC_ALL": "en_US.utf8"}
  842. )
  843. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  844. ' without password failed').format(user2, '10.0.0.1')
  845. )
  846. ret = self.run_function(
  847. 'mysql.user_exists',
  848. user=user2,
  849. host='10.0.0.2',
  850. allow_passwordless=True,
  851. unix_socket=True,
  852. connection_user=self.user,
  853. connection_pass=self.password,
  854. connection_charset='utf8',
  855. saltenv={"LC_ALL": "en_US.utf8"}
  856. )
  857. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  858. ' without password failed').format(user2, '10.0.0.2')
  859. )
  860. # Empty password is not passwordless (or is it a bug?)
  861. self._userCreationLoop(
  862. uname=user3,
  863. host='localhost',
  864. password='',
  865. connection_user=self.user,
  866. connection_pass=self.password
  867. )
  868. # user 3 on another host with a password
  869. self._userCreationLoop(
  870. uname=user3,
  871. host='%',
  872. password='foo',
  873. new_password=user3_pwd,
  874. connection_user=self.user,
  875. connection_pass=self.password
  876. )
  877. # Now check for results
  878. ret = self.run_function(
  879. 'mysql.user_exists',
  880. user=user3,
  881. host='localhost',
  882. password='',
  883. connection_user=self.user,
  884. connection_pass=self.password
  885. )
  886. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  887. ' without empty password failed').format(user3, 'localhost')
  888. )
  889. ret = self.run_function(
  890. 'mysql.user_exists',
  891. user=user3,
  892. host='%',
  893. password=user3_pwd,
  894. connection_user=self.user,
  895. connection_pass=self.password
  896. )
  897. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  898. ' with password failed').format(user3, '%')
  899. )
  900. # check unicode name, and password > password_hash
  901. self._userCreationLoop(
  902. uname=user4,
  903. host='%',
  904. password=user4_pwd,
  905. # this is password('foo')
  906. password_hash='*F3A2A51A9B0F2BE2468926B4132313728C250DBF',
  907. connection_user=self.user,
  908. connection_pass=self.password,
  909. connection_charset='utf8',
  910. saltenv={"LC_ALL": "en_US.utf8"}
  911. )
  912. # Now check for results
  913. ret = self.run_function(
  914. 'mysql.user_exists',
  915. user=user4_utf8,
  916. host='%',
  917. password=user4_pwd,
  918. connection_user=self.user,
  919. connection_pass=self.password,
  920. connection_charset='utf8',
  921. saltenv={"LC_ALL": "en_US.utf8"}
  922. )
  923. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  924. ' with password take from password and not password_hash'
  925. ' failed').format(user4_utf8, '%')
  926. )
  927. self._userCreationLoop(
  928. uname=user5,
  929. host='localhost',
  930. password='\xe6\xa8\x99\xe6\xa8\x99',
  931. new_password=user5_pwd,
  932. unix_socket=True,
  933. connection_user=self.user,
  934. connection_pass=self.password,
  935. connection_charset='utf8',
  936. saltenv={"LC_ALL": "en_US.utf8"}
  937. )
  938. ret = self.run_function(
  939. 'mysql.user_exists',
  940. user=user5_utf8,
  941. host='localhost',
  942. password=user5_pwd,
  943. connection_user=self.user,
  944. connection_pass=self.password,
  945. connection_charset='utf8',
  946. saltenv={"LC_ALL": "en_US.utf8"}
  947. )
  948. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  949. ' with utf8 password failed').format(user5_utf8, 'localhost')
  950. )
  951. # for this one we give password in unicode and check it in utf-8
  952. self._userCreationLoop(
  953. uname=user6,
  954. host='10.0.0.1',
  955. password=' foobar',
  956. new_password=user6_pwd_u,
  957. connection_user=self.user,
  958. connection_pass=self.password,
  959. connection_charset='utf8',
  960. saltenv={"LC_ALL": "en_US.utf8"}
  961. )
  962. # Now check for results
  963. ret = self.run_function(
  964. 'mysql.user_exists',
  965. user=user6_utf8,
  966. host='10.0.0.1',
  967. password=user6_pwd_utf8,
  968. connection_user=self.user,
  969. connection_pass=self.password,
  970. connection_charset='utf8',
  971. saltenv={"LC_ALL": "en_US.utf8"}
  972. )
  973. self.assertEqual(True, ret, ('Testing final user \'{0}\' on host \'{1}\''
  974. ' with unicode password failed').format(user6_utf8, '10.0.0.1')
  975. )
  976. # Final result should be:
  977. # mysql> select Host, User, Password from user where user like 'user%';
  978. # +--------------------+-----------+-------------------------------+
  979. # | User | Host | Password |
  980. # +--------------------+-----------+-------------------------------+
  981. # | user "2'標 | 10.0.0.1 | |
  982. # | user "2'標 | 10.0.0.2 | |
  983. # | user "2'標 | localhost | *3A38A7B94B0(...)60B7AA38FE61 |
  984. # | user "3;,?:@=&/ | % | *AA3B1D4105(...)47EA349E1FD7D |
  985. # | user "3;,?:@=&/ | localhost | |
  986. # | user %--"6 | 10.0.0.1 | *90AE800593(...)E42D17EEA5369 |
  987. # | user '1 | localhost | *4DF33B3B1(...)327877FAB2F4BA |
  988. # | user ``"5 | localhost | *3752E65CD(...)FC6C998B12C376 |
  989. # | user":;,?:@=&/4標 | % | *FC8EF8DBF(...)7478D5CF3DD57C |
  990. # +--------------------+-----------+-------------------------------+
  991. self._chck_userinfo(user=user2,
  992. host='10.0.0.1',
  993. check_user=user2,
  994. check_hash=''
  995. )
  996. self._chck_userinfo(user=user2,
  997. host='10.0.0.2',
  998. check_user=user2,
  999. check_hash=''
  1000. )
  1001. self._chck_userinfo(user=user2,
  1002. host='localhost',
  1003. check_user=user2,
  1004. check_hash=user2_pwd_hash
  1005. )
  1006. self._chck_userinfo(user=user3,
  1007. host='%',
  1008. check_user=user3,
  1009. check_hash=user3_pwd_hash
  1010. )
  1011. self._chck_userinfo(user=user3,
  1012. host='localhost',
  1013. check_user=user3,
  1014. check_hash=''
  1015. )
  1016. self._chck_userinfo(user=user4,
  1017. host='%',
  1018. check_user=user4_utf8,
  1019. check_hash=user4_pwd_hash
  1020. )
  1021. self._chck_userinfo(user=user6,
  1022. host='10.0.0.1',
  1023. check_user=user6_utf8,
  1024. check_hash=user6_pwd_hash
  1025. )
  1026. self._chck_userinfo(user=user1,
  1027. host='localhost',
  1028. check_user=user1,
  1029. check_hash=user1_pwd_hash
  1030. )
  1031. self._chck_userinfo(user=user5,
  1032. host='localhost',
  1033. check_user=user5_utf8,
  1034. check_hash=user5_pwd_hash
  1035. )
  1036. # check user_list function
  1037. ret = self.run_function(
  1038. 'mysql.user_list',
  1039. connection_user=self.user,
  1040. connection_pass=self.password,
  1041. connection_charset='utf8',
  1042. saltenv={"LC_ALL": "en_US.utf8"}
  1043. )
  1044. self.assertIn({'Host': 'localhost', 'User': user1}, ret)
  1045. self.assertIn({'Host': 'localhost', 'User': user2}, ret)
  1046. self.assertIn({'Host': '10.0.0.1', 'User': user2}, ret)
  1047. self.assertIn({'Host': '10.0.0.2', 'User': user2}, ret)
  1048. self.assertIn({'Host': '%', 'User': user3}, ret)
  1049. self.assertIn({'Host': 'localhost', 'User': user3}, ret)
  1050. self.assertIn({'Host': '%', 'User': user4_utf8}, ret)
  1051. self.assertIn({'Host': 'localhost', 'User': user5_utf8}, ret)
  1052. self.assertIn({'Host': '10.0.0.1', 'User': user6_utf8}, ret)
  1053. # And finally, test connections on MySQL with theses users
  1054. ret = self.run_function(
  1055. 'mysql.query',
  1056. database='information_schema',
  1057. query='SELECT 1',
  1058. connection_user=user1,
  1059. connection_pass='pwd`\'"1b',
  1060. connection_host='localhost'
  1061. )
  1062. if not isinstance(ret, dict) or 'results' not in ret:
  1063. raise AssertionError(
  1064. ('Unexpected result while testing connection'
  1065. ' with user \'{0}\': {1}').format(
  1066. user1,
  1067. repr(ret)
  1068. )
  1069. )
  1070. self.assertEqual([['1']], ret['results'])
  1071. # FIXME: still failing, but works by hand...
  1072. # mysql --user="user \"2'標" --password="user \"2'標b" information_schema
  1073. # Seems to be a python-mysql library problem with user names containing
  1074. # utf8 characters
  1075. # @see https://github.com/farcepest/MySQLdb1/issues/40
  1076. #import urllib
  1077. #ret = self.run_function(
  1078. # 'mysql.query',
  1079. # database='information_schema',
  1080. # query='SELECT 1',
  1081. # connection_user=urllib.quote_plus(user2),
  1082. # connection_pass=urllib.quote_plus(user2_pwd),
  1083. # connection_host='localhost',
  1084. # connection_charset='utf8',
  1085. # saltenv={"LC_ALL": "en_US.utf8"}
  1086. #)
  1087. #if not isinstance(ret, dict) or 'results' not in ret:
  1088. # raise AssertionError(
  1089. # ('Unexpected result while testing connection'
  1090. # ' with user \'{0}\': {1}').format(
  1091. # user2,
  1092. # repr(ret)
  1093. # )
  1094. # )
  1095. #self.assertEqual([['1']], ret['results'])
  1096. ret = self.run_function(
  1097. 'mysql.query',
  1098. database='information_schema',
  1099. query='SELECT 1',
  1100. connection_user=user3,
  1101. connection_pass='',
  1102. connection_host='localhost',
  1103. )
  1104. if not isinstance(ret, dict) or 'results' not in ret:
  1105. raise AssertionError(
  1106. ('Unexpected result while testing connection'
  1107. ' with user \'{0}\': {1}').format(
  1108. user3,
  1109. repr(ret)
  1110. )
  1111. )
  1112. self.assertEqual([['1']], ret['results'])
  1113. # FIXME: Failing
  1114. #ret = self.run_function(
  1115. # 'mysql.query',
  1116. # database='information_schema',
  1117. # query='SELECT 1',
  1118. # connection_user=user4_utf8,
  1119. # connection_pass=user4_pwd,
  1120. # connection_host='localhost',
  1121. # connection_charset='utf8',
  1122. # saltenv={"LC_ALL": "en_US.utf8"}
  1123. #)
  1124. #if not isinstance(ret, dict) or 'results' not in ret:
  1125. # raise AssertionError(
  1126. # ('Unexpected result while testing connection'
  1127. # ' with user \'{0}\': {1}').format(
  1128. # user4_utf8,
  1129. # repr(ret)
  1130. # )
  1131. # )
  1132. #self.assertEqual([['1']], ret['results'])
  1133. ret = self.run_function(
  1134. 'mysql.query',
  1135. database='information_schema',
  1136. query='SELECT 1',
  1137. connection_user=user5_utf8,
  1138. connection_pass=user5_pwd,
  1139. connection_host='localhost',
  1140. connection_charset='utf8',
  1141. saltenv={"LC_ALL": "en_US.utf8"}
  1142. )
  1143. if not isinstance(ret, dict) or 'results' not in ret:
  1144. raise AssertionError(
  1145. ('Unexpected result while testing connection'
  1146. ' with user \'{0}\': {1}').format(
  1147. user5_utf8,
  1148. repr(ret)
  1149. )
  1150. )
  1151. self.assertEqual([['1']], ret['results'])
  1152. # Teardown by deleting with user_remove
  1153. self._chk_remove_user(user=user2,
  1154. host='10.0.0.1',
  1155. connection_user=self.user,
  1156. connection_pass=self.password,
  1157. connection_charset='utf8',
  1158. saltenv={"LC_ALL": "en_US.utf8"}
  1159. )
  1160. self._chk_remove_user(user=user2,
  1161. host='10.0.0.2',
  1162. connection_user=self.user,
  1163. connection_pass=self.password,
  1164. connection_charset='utf8',
  1165. saltenv={"LC_ALL": "en_US.utf8"}
  1166. )
  1167. self._chk_remove_user(user=user2,
  1168. host='localhost',
  1169. connection_user=self.user,
  1170. connection_pass=self.password,
  1171. connection_charset='utf8',
  1172. saltenv={"LC_ALL": "en_US.utf8"}
  1173. )
  1174. self._chk_remove_user(user=user3,
  1175. host='%',
  1176. connection_user=self.user,
  1177. connection_pass=self.password,
  1178. )
  1179. self._chk_remove_user(user=user3,
  1180. host='localhost',
  1181. connection_user=self.user,
  1182. connection_pass=self.password,
  1183. )
  1184. self._chk_remove_user(user=user4,
  1185. host='%',
  1186. connection_user=self.user,
  1187. connection_pass=self.password,
  1188. connection_charset='utf8',
  1189. saltenv={"LC_ALL": "en_US.utf8"}
  1190. )
  1191. self._chk_remove_user(user=user6,
  1192. host='10.0.0.1',
  1193. connection_user=self.user,
  1194. connection_pass=self.password,
  1195. )
  1196. self._chk_remove_user(user=user1,
  1197. host='localhost',
  1198. connection_user=self.user,
  1199. connection_pass=self.password,
  1200. )
  1201. self._chk_remove_user(user=user5,
  1202. host='localhost',
  1203. connection_user=self.user,
  1204. connection_pass=self.password,
  1205. )
  1206. # Final verification of the cleanup
  1207. ret = self.run_function(
  1208. 'mysql.user_list',
  1209. connection_user=self.user,
  1210. connection_pass=self.password,
  1211. connection_charset='utf8',
  1212. saltenv={"LC_ALL": "en_US.utf8"}
  1213. )
  1214. self.assertNotIn({'Host': 'localhost', 'User': user1}, ret)
  1215. self.assertNotIn({'Host': 'localhost', 'User': user2}, ret)
  1216. self.assertNotIn({'Host': '10.0.0.1', 'User': user2}, ret)
  1217. self.assertNotIn({'Host': '10.0.0.2', 'User': user2}, ret)
  1218. self.assertNotIn({'Host': '%', 'User': user3}, ret)
  1219. self.assertNotIn({'Host': 'localhost', 'User': user3}, ret)
  1220. self.assertNotIn({'Host': '%', 'User': user4_utf8}, ret)
  1221. self.assertNotIn({'Host': 'localhost', 'User': user5_utf8}, ret)
  1222. self.assertNotIn({'Host': '10.0.0.1', 'User': user6_utf8}, ret)
  1223. @skipIf(
  1224. NO_MYSQL,
  1225. 'Please install MySQL bindings and a MySQL Server before running'
  1226. 'MySQL integration tests.'
  1227. )
  1228. class MysqlModuleUserGrantTest(ModuleCase, SaltReturnAssertsMixin):
  1229. '''
  1230. User Creation and connection tests
  1231. '''
  1232. user = 'root'
  1233. password = 'poney'
  1234. # yep, theses are valid MySQL db names
  1235. # very special chars are _ % and .
  1236. testdb1 = 'tes.t\'"saltdb'
  1237. testdb2 = 't_st `(:=salt%b)'
  1238. testdb3 = 'test `(:=salteeb)'
  1239. test_file_query_db = 'test_query'
  1240. table1 = 'foo'
  1241. table2 = "foo `\'%_bar"
  1242. users = {
  1243. 'user1': {
  1244. 'name': 'foo',
  1245. 'pwd': 'bar',
  1246. },
  1247. 'user2': {
  1248. 'name': 'user ";--,?:&/\\',
  1249. 'pwd': '";--(),?:@=&/\\',
  1250. },
  1251. # this is : passwd 標標
  1252. 'user3': {
  1253. 'name': 'user( @ )=foobar',
  1254. 'pwd': '\xe6\xa8\x99\xe6\xa8\x99',
  1255. },
  1256. # this is : user/password containing 標標
  1257. 'user4': {
  1258. 'name': 'user \xe6\xa8\x99',
  1259. 'pwd': '\xe6\xa8\x99\xe6\xa8\x99',
  1260. },
  1261. }
  1262. @destructiveTest
  1263. def setUp(self):
  1264. '''
  1265. Test presence of MySQL server, enforce a root password, create users
  1266. '''
  1267. super(MysqlModuleUserGrantTest, self).setUp()
  1268. NO_MYSQL_SERVER = True
  1269. # now ensure we know the mysql root password
  1270. # one of theses two at least should work
  1271. ret1 = self.run_state(
  1272. 'cmd.run',
  1273. name='mysqladmin --host="localhost" -u '
  1274. + self.user
  1275. + ' flush-privileges password "'
  1276. + self.password
  1277. + '"'
  1278. )
  1279. ret2 = self.run_state(
  1280. 'cmd.run',
  1281. name='mysqladmin --host="localhost" -u '
  1282. + self.user
  1283. + ' --password="'
  1284. + self.password
  1285. + '" flush-privileges password "'
  1286. + self.password
  1287. + '"'
  1288. )
  1289. key, value = ret2.popitem()
  1290. if value['result']:
  1291. NO_MYSQL_SERVER = False
  1292. else:
  1293. self.skipTest('No MySQL Server running, or no root access on it.')
  1294. # Create some users and a test db
  1295. for user, userdef in six.iteritems(self.users):
  1296. self._userCreation(uname=userdef['name'], password=userdef['pwd'])
  1297. self.run_function(
  1298. 'mysql.db_create',
  1299. name=self.testdb1,
  1300. connection_user=self.user,
  1301. connection_pass=self.password,
  1302. )
  1303. self.run_function(
  1304. 'mysql.db_create',
  1305. name=self.testdb2,
  1306. connection_user=self.user,
  1307. connection_pass=self.password,
  1308. )
  1309. create_query = ('CREATE TABLE {tblname} ('
  1310. ' id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,'
  1311. ' data VARCHAR(100)) ENGINE={engine};'.format(
  1312. tblname=mysqlmod.quote_identifier(self.table1),
  1313. engine='MYISAM',
  1314. ))
  1315. log.info('Adding table \'%s\'', self.table1)
  1316. self.run_function(
  1317. 'mysql.query',
  1318. database=self.testdb2,
  1319. query=create_query,
  1320. connection_user=self.user,
  1321. connection_pass=self.password
  1322. )
  1323. create_query = ('CREATE TABLE {tblname} ('
  1324. ' id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,'
  1325. ' data VARCHAR(100)) ENGINE={engine};'.format(
  1326. tblname=mysqlmod.quote_identifier(self.table2),
  1327. engine='MYISAM',
  1328. ))
  1329. log.info('Adding table \'%s\'', self.table2)
  1330. self.run_function(
  1331. 'mysql.query',
  1332. database=self.testdb2,
  1333. query=create_query,
  1334. connection_user=self.user,
  1335. connection_pass=self.password
  1336. )
  1337. @destructiveTest
  1338. def tearDown(self):
  1339. '''
  1340. Removes created users and db
  1341. '''
  1342. for user, userdef in six.iteritems(self.users):
  1343. self._userRemoval(uname=userdef['name'], password=userdef['pwd'])
  1344. self.run_function(
  1345. 'mysql.db_remove',
  1346. name=self.testdb1,
  1347. connection_user=self.user,
  1348. connection_pass=self.password,
  1349. )
  1350. self.run_function(
  1351. 'mysql.db_remove',
  1352. name=self.testdb2,
  1353. connection_user=self.user,
  1354. connection_pass=self.password,
  1355. )
  1356. self.run_function(
  1357. 'mysql.db_remove',
  1358. name=self.test_file_query_db,
  1359. connection_user=self.user,
  1360. connection_pass=self.password,
  1361. )
  1362. def _userCreation(self,
  1363. uname,
  1364. password=None):
  1365. '''
  1366. Create a test user
  1367. '''
  1368. self.run_function(
  1369. 'mysql.user_create',
  1370. user=uname,
  1371. host='localhost',
  1372. password=password,
  1373. connection_user=self.user,
  1374. connection_pass=self.password,
  1375. connection_charset='utf8',
  1376. saltenv={"LC_ALL": "en_US.utf8"}
  1377. )
  1378. def _userRemoval(self,
  1379. uname,
  1380. password=None):
  1381. '''
  1382. Removes a test user
  1383. '''
  1384. self.run_function(
  1385. 'mysql.user_remove',
  1386. user=uname,
  1387. host='localhost',
  1388. connection_user=self.user,
  1389. connection_pass=self.password,
  1390. connection_charset='utf8',
  1391. saltenv={"LC_ALL": "en_US.utf8"}
  1392. )
  1393. def _addGrantRoutine(self,
  1394. grant,
  1395. user,
  1396. db,
  1397. grant_option=False,
  1398. escape=True,
  1399. **kwargs):
  1400. '''
  1401. Perform some tests around creation of the given grants
  1402. '''
  1403. ret = self.run_function(
  1404. 'mysql.grant_add',
  1405. grant=grant,
  1406. database=db,
  1407. user=user,
  1408. grant_option=grant_option,
  1409. escape=escape,
  1410. **kwargs
  1411. )
  1412. self.assertEqual(True, ret, ('Calling grant_add on'
  1413. ' user \'{0}\' and grants \'{1}\' did not return True: {2}').format(
  1414. user,
  1415. grant,
  1416. repr(ret)
  1417. ))
  1418. ret = self.run_function(
  1419. 'mysql.grant_exists',
  1420. grant=grant,
  1421. database=db,
  1422. user=user,
  1423. grant_option=grant_option,
  1424. escape=escape,
  1425. **kwargs
  1426. )
  1427. self.assertEqual(True, ret, ('Calling grant_exists on'
  1428. ' user \'{0}\' and grants \'{1}\' did not return True: {2}').format(
  1429. user,
  1430. grant,
  1431. repr(ret)
  1432. ))
  1433. @destructiveTest
  1434. def testGrants(self):
  1435. '''
  1436. Test user grant methods
  1437. '''
  1438. self._addGrantRoutine(
  1439. grant='SELECT, INSERT,UPDATE, CREATE',
  1440. user=self.users['user1']['name'],
  1441. db=self.testdb1 + '.*',
  1442. grant_option=True,
  1443. escape=True,
  1444. connection_user=self.user,
  1445. connection_pass=self.password
  1446. )
  1447. self._addGrantRoutine(
  1448. grant='INSERT, SELECT',
  1449. user=self.users['user1']['name'],
  1450. db=self.testdb2 + '.' + self.table1,
  1451. grant_option=True,
  1452. escape=True,
  1453. connection_user=self.user,
  1454. connection_pass=self.password
  1455. )
  1456. self._addGrantRoutine(
  1457. grant=' SELECT, UPDATE,DELETE, CREATE TEMPORARY TABLES',
  1458. user=self.users['user2']['name'],
  1459. db=self.testdb1 + '.*',
  1460. grant_option=True,
  1461. escape=True,
  1462. connection_user=self.user,
  1463. connection_pass=self.password
  1464. )
  1465. self._addGrantRoutine(
  1466. grant='select, ALTER,CREATE TEMPORARY TABLES, EXECUTE ',
  1467. user=self.users['user3']['name'],
  1468. db=self.testdb1 + '.*',
  1469. grant_option=True,
  1470. escape=True,
  1471. connection_user=self.user,
  1472. connection_pass=self.password
  1473. )
  1474. self._addGrantRoutine(
  1475. grant='SELECT, INSERT',
  1476. user=self.users['user4']['name'],
  1477. db=self.testdb2 + '.' + self.table2,
  1478. grant_option=False,
  1479. escape=True,
  1480. connection_user=self.user,
  1481. connection_pass=self.password,
  1482. connection_charset='utf8'
  1483. )
  1484. self._addGrantRoutine(
  1485. grant='CREATE',
  1486. user=self.users['user4']['name'],
  1487. db=self.testdb2 + '.*',
  1488. grant_option=False,
  1489. escape=True,
  1490. connection_user=self.user,
  1491. connection_pass=self.password,
  1492. connection_charset='utf8'
  1493. )
  1494. self._addGrantRoutine(
  1495. grant='SELECT, INSERT',
  1496. user=self.users['user4']['name'],
  1497. db=self.testdb2 + '.' + self.table1,
  1498. grant_option=False,
  1499. escape=True,
  1500. connection_user=self.user,
  1501. connection_pass=self.password,
  1502. connection_charset='utf8'
  1503. )
  1504. # '' is valid for anonymous users
  1505. self._addGrantRoutine(
  1506. grant='DELETE',
  1507. user='',
  1508. db=self.testdb3 + '.*',
  1509. grant_option=False,
  1510. escape=True,
  1511. connection_user=self.user,
  1512. connection_pass=self.password
  1513. )
  1514. # Check result for users
  1515. ret = self.run_function(
  1516. 'mysql.user_grants',
  1517. user=self.users['user1']['name'],
  1518. host='localhost',
  1519. connection_user=self.user,
  1520. connection_pass=self.password
  1521. )
  1522. self.assertEqual(ret, [
  1523. "GRANT USAGE ON *.* TO 'foo'@'localhost'",
  1524. ('GRANT SELECT, INSERT, UPDATE, CREATE ON '
  1525. '`tes.t\'"saltdb`.* TO \'foo\'@\'localhost\' WITH GRANT OPTION'),
  1526. ("GRANT SELECT, INSERT ON `t_st ``(:=salt%b)`.`foo`"
  1527. " TO 'foo'@'localhost' WITH GRANT OPTION")
  1528. ])
  1529. ret = self.run_function(
  1530. 'mysql.user_grants',
  1531. user=self.users['user2']['name'],
  1532. host='localhost',
  1533. connection_user=self.user,
  1534. connection_pass=self.password
  1535. )
  1536. self.assertEqual(ret, [
  1537. 'GRANT USAGE ON *.* TO \'user ";--,?:&/\\\'@\'localhost\'',
  1538. ('GRANT SELECT, UPDATE, DELETE, CREATE TEMPORARY TABLES ON `tes.t\''
  1539. '"saltdb`.* TO \'user ";--,?:&/\\\'@\'localhost\''
  1540. ' WITH GRANT OPTION')
  1541. ])
  1542. ret = self.run_function(
  1543. 'mysql.user_grants',
  1544. user=self.users['user3']['name'],
  1545. host='localhost',
  1546. connection_user=self.user,
  1547. connection_pass=self.password
  1548. )
  1549. self.assertEqual(ret, [
  1550. "GRANT USAGE ON *.* TO 'user( @ )=foobar'@'localhost'",
  1551. ('GRANT SELECT, ALTER, CREATE TEMPORARY TABLES, EXECUTE ON '
  1552. '`tes.t\'"saltdb`.* TO \'user( @ )=foobar\'@\'localhost\' '
  1553. 'WITH GRANT OPTION')
  1554. ])
  1555. ret = self.run_function(
  1556. 'mysql.user_grants',
  1557. user=self.users['user4']['name'],
  1558. host='localhost',
  1559. connection_user=self.user,
  1560. connection_pass=self.password,
  1561. connection_charset='utf8'
  1562. )
  1563. self.assertEqual(ret, [
  1564. "GRANT USAGE ON *.* TO 'user \xe6\xa8\x99'@'localhost'",
  1565. (r"GRANT CREATE ON `t\_st ``(:=salt\%b)`.* TO "
  1566. "'user \xe6\xa8\x99'@'localhost'"),
  1567. ("GRANT SELECT, INSERT ON `t_st ``(:=salt%b)`.`foo ``'%_bar` TO "
  1568. "'user \xe6\xa8\x99'@'localhost'"),
  1569. ("GRANT SELECT, INSERT ON `t_st ``(:=salt%b)`.`foo` TO "
  1570. "'user \xe6\xa8\x99'@'localhost'"),
  1571. ])
  1572. ret = self.run_function(
  1573. 'mysql.user_grants',
  1574. user='',
  1575. host='localhost',
  1576. connection_user=self.user,
  1577. connection_pass=self.password
  1578. )
  1579. self.assertEqual(ret, [
  1580. "GRANT USAGE ON *.* TO ''@'localhost'",
  1581. "GRANT DELETE ON `test ``(:=salteeb)`.* TO ''@'localhost'"
  1582. ])
  1583. @skipIf(
  1584. NO_MYSQL,
  1585. 'Please install MySQL bindings and a MySQL Server before running'
  1586. 'MySQL integration tests.'
  1587. )
  1588. class MysqlModuleFileQueryTest(ModuleCase, SaltReturnAssertsMixin):
  1589. '''
  1590. Test file query module
  1591. '''
  1592. user = 'root'
  1593. password = 'poney'
  1594. testdb = 'test_file_query'
  1595. @destructiveTest
  1596. def setUp(self):
  1597. '''
  1598. Test presence of MySQL server, enforce a root password, create users
  1599. '''
  1600. super(MysqlModuleFileQueryTest, self).setUp()
  1601. NO_MYSQL_SERVER = True
  1602. # now ensure we know the mysql root password
  1603. # one of theses two at least should work
  1604. ret1 = self.run_state(
  1605. 'cmd.run',
  1606. name='mysqladmin --host="localhost" -u '
  1607. + self.user
  1608. + ' flush-privileges password "'
  1609. + self.password
  1610. + '"'
  1611. )
  1612. ret2 = self.run_state(
  1613. 'cmd.run',
  1614. name='mysqladmin --host="localhost" -u '
  1615. + self.user
  1616. + ' --password="'
  1617. + self.password
  1618. + '" flush-privileges password "'
  1619. + self.password
  1620. + '"'
  1621. )
  1622. key, value = ret2.popitem()
  1623. if value['result']:
  1624. NO_MYSQL_SERVER = False
  1625. else:
  1626. self.skipTest('No MySQL Server running, or no root access on it.')
  1627. # Create some users and a test db
  1628. self.run_function(
  1629. 'mysql.db_create',
  1630. name=self.testdb,
  1631. connection_user=self.user,
  1632. connection_pass=self.password,
  1633. connection_db='mysql',
  1634. )
  1635. @destructiveTest
  1636. def tearDown(self):
  1637. '''
  1638. Removes created users and db
  1639. '''
  1640. self.run_function(
  1641. 'mysql.db_remove',
  1642. name=self.testdb,
  1643. connection_user=self.user,
  1644. connection_pass=self.password,
  1645. connection_db='mysql',
  1646. )
  1647. @destructiveTest
  1648. def test_update_file_query(self):
  1649. '''
  1650. Test query without any output
  1651. '''
  1652. ret = self.run_function(
  1653. 'mysql.file_query',
  1654. database=self.testdb,
  1655. file_name='salt://mysql/update_query.sql',
  1656. character_set='utf8',
  1657. collate='utf8_general_ci',
  1658. connection_user=self.user,
  1659. connection_pass=self.password
  1660. )
  1661. self.assertTrue('query time' in ret)
  1662. ret.pop('query time')
  1663. self.assertEqual(ret, {'rows affected': 2})
  1664. @destructiveTest
  1665. def test_select_file_query(self):
  1666. '''
  1667. Test query with table output
  1668. '''
  1669. ret = self.run_function(
  1670. 'mysql.file_query',
  1671. database=self.testdb,
  1672. file_name='salt://mysql/select_query.sql',
  1673. character_set='utf8',
  1674. collate='utf8_general_ci',
  1675. connection_user=self.user,
  1676. connection_pass=self.password
  1677. )
  1678. expected = {
  1679. 'rows affected': 5,
  1680. 'rows returned': 4,
  1681. 'results': [
  1682. [
  1683. ['2'],
  1684. ['3'],
  1685. ['4'],
  1686. ['5']
  1687. ]
  1688. ],
  1689. 'columns': [
  1690. ['a']
  1691. ],
  1692. }
  1693. self.assertTrue('query time' in ret)
  1694. ret.pop('query time')
  1695. self.assertEqual(ret, expected)