1
0

test_mysql.py 61 KB

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