test_localfs.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. """
  2. unit tests for the localfs cache
  3. """
  4. # Import Python libs
  5. import errno
  6. import shutil
  7. import tempfile
  8. import salt.cache.localfs as localfs
  9. # Import Salt libs
  10. import salt.payload
  11. import salt.utils.files
  12. from salt.exceptions import SaltCacheError
  13. from tests.support.mixins import LoaderModuleMockMixin
  14. from tests.support.mock import MagicMock, patch
  15. # Import Salt Testing libs
  16. from tests.support.runtests import RUNTIME_VARS
  17. from tests.support.unit import TestCase
  18. class LocalFSTest(TestCase, LoaderModuleMockMixin):
  19. """
  20. Validate the functions in the localfs cache
  21. """
  22. def setup_loader_modules(self):
  23. return {localfs: {}}
  24. def _create_tmp_cache_file(self, tmp_dir, serializer):
  25. """
  26. Helper function that creates a temporary cache file using localfs.store. This
  27. is to used to create DRY unit tests for the localfs cache.
  28. """
  29. self.addCleanup(shutil.rmtree, tmp_dir)
  30. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  31. with patch.dict(localfs.__context__, {"serial": serializer}):
  32. localfs.store(
  33. bank="bank", key="key", data="payload data", cachedir=tmp_dir
  34. )
  35. # 'store' function tests: 5
  36. def test_handled_exception_cache_dir(self):
  37. """
  38. Tests that a SaltCacheError is raised when the base directory doesn't exist and
  39. cannot be created.
  40. """
  41. with patch("os.makedirs", MagicMock(side_effect=OSError(errno.EEXIST, ""))):
  42. with patch("tempfile.mkstemp", MagicMock(side_effect=Exception)):
  43. self.assertRaises(
  44. Exception, localfs.store, bank="", key="", data="", cachedir=""
  45. )
  46. def test_unhandled_exception_cache_dir(self):
  47. """
  48. Tests that a SaltCacheError is raised when the base directory doesn't exist and
  49. cannot be created.
  50. """
  51. with patch("os.makedirs", MagicMock(side_effect=OSError(1, ""))):
  52. self.assertRaises(
  53. SaltCacheError, localfs.store, bank="", key="", data="", cachedir=""
  54. )
  55. def test_store_close_mkstemp_file_handle(self):
  56. """
  57. Tests that the file descriptor that is opened by os.open during the mkstemp call
  58. in localfs.store is closed before calling salt.utils.files.fopen on the filename.
  59. This test mocks the call to mkstemp, but forces an OSError to be raised when the
  60. close() function is called on a file descriptor that doesn't exist.
  61. """
  62. with patch("os.makedirs", MagicMock(side_effect=OSError(errno.EEXIST, ""))):
  63. with patch("tempfile.mkstemp", MagicMock(return_value=(12345, "foo"))):
  64. self.assertRaises(
  65. OSError, localfs.store, bank="", key="", data="", cachedir=""
  66. )
  67. def test_store_error_writing_cache(self):
  68. """
  69. Tests that a SaltCacheError is raised when there is a problem writing to the
  70. cache file.
  71. """
  72. with patch("os.makedirs", MagicMock(side_effect=OSError(errno.EEXIST, ""))):
  73. with patch("tempfile.mkstemp", MagicMock(return_value=("one", "two"))):
  74. with patch("os.close", MagicMock(return_value=None)):
  75. with patch(
  76. "salt.utils.files.fopen", MagicMock(side_effect=IOError)
  77. ):
  78. self.assertRaises(
  79. SaltCacheError,
  80. localfs.store,
  81. bank="",
  82. key="",
  83. data="",
  84. cachedir="",
  85. )
  86. def test_store_success(self):
  87. """
  88. Tests that the store function writes the data to the serializer for storage.
  89. """
  90. # Create a temporary cache dir
  91. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  92. # Use the helper function to create the cache file using localfs.store()
  93. self._create_tmp_cache_file(tmp_dir, salt.payload.Serial(self))
  94. # Read in the contents of the key.p file and assert "payload data" was written
  95. with salt.utils.files.fopen(tmp_dir + "/bank/key.p", "rb") as fh_:
  96. for line in fh_:
  97. self.assertIn(b"payload data", line)
  98. # 'fetch' function tests: 3
  99. def test_fetch_return_when_cache_file_does_not_exist(self):
  100. """
  101. Tests that the fetch function returns an empty dic when the cache key file
  102. doesn't exist.
  103. """
  104. with patch("os.path.isfile", MagicMock(return_value=False)):
  105. self.assertEqual(localfs.fetch(bank="", key="", cachedir=""), {})
  106. def test_fetch_error_reading_cache(self):
  107. """
  108. Tests that a SaltCacheError is raised when there is a problem reading the cache
  109. file.
  110. """
  111. with patch("os.path.isfile", MagicMock(return_value=True)):
  112. with patch("salt.utils.files.fopen", MagicMock(side_effect=IOError)):
  113. self.assertRaises(
  114. SaltCacheError, localfs.fetch, bank="", key="", cachedir=""
  115. )
  116. def test_fetch_success(self):
  117. """
  118. Tests that the fetch function is able to read the cache file and return its data.
  119. """
  120. # Create a temporary cache dir
  121. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  122. # Create a new serializer object to use in function patches
  123. serializer = salt.payload.Serial(self)
  124. # Use the helper function to create the cache file using localfs.store()
  125. self._create_tmp_cache_file(tmp_dir, serializer)
  126. # Now fetch the data from the new cache key file
  127. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  128. with patch.dict(localfs.__context__, {"serial": serializer}):
  129. self.assertIn(
  130. "payload data",
  131. localfs.fetch(bank="bank", key="key", cachedir=tmp_dir),
  132. )
  133. # 'updated' function tests: 3
  134. def test_updated_return_when_cache_file_does_not_exist(self):
  135. """
  136. Tests that the updated function returns None when the cache key file doesn't
  137. exist.
  138. """
  139. with patch("os.path.isfile", MagicMock(return_value=False)):
  140. self.assertIsNone(localfs.updated(bank="", key="", cachedir=""))
  141. def test_updated_error_when_reading_mtime(self):
  142. """
  143. Tests that a SaltCacheError is raised when there is a problem reading the mtime
  144. of the cache file.
  145. """
  146. with patch("os.path.isfile", MagicMock(return_value=True)):
  147. with patch("os.path.getmtime", MagicMock(side_effect=IOError)):
  148. self.assertRaises(
  149. SaltCacheError, localfs.updated, bank="", key="", cachedir=""
  150. )
  151. def test_updated_success(self):
  152. """
  153. Test that the updated function returns the modification time of the cache file
  154. """
  155. # Create a temporary cache dir
  156. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  157. # Use the helper function to create the cache file using localfs.store()
  158. self._create_tmp_cache_file(tmp_dir, salt.payload.Serial(self))
  159. with patch("os.path.join", MagicMock(return_value=tmp_dir + "/bank/key.p")):
  160. self.assertIsInstance(
  161. localfs.updated(bank="bank", key="key", cachedir=tmp_dir), int
  162. )
  163. # 'flush' function tests: 4
  164. def test_flush_key_is_none_and_no_target_dir(self):
  165. """
  166. Tests that the flush function returns False when no key is passed in and the
  167. target directory doesn't exist.
  168. """
  169. with patch("os.path.isdir", MagicMock(return_value=False)):
  170. self.assertFalse(localfs.flush(bank="", key=None, cachedir=""))
  171. def test_flush_key_provided_and_no_key_file_false(self):
  172. """
  173. Tests that the flush function returns False when a key file is provided but
  174. the target key file doesn't exist in the cache bank.
  175. """
  176. with patch("os.path.isfile", MagicMock(return_value=False)):
  177. self.assertFalse(localfs.flush(bank="", key="key", cachedir=""))
  178. def test_flush_success(self):
  179. """
  180. Tests that the flush function returns True when a key file is provided and
  181. the target key exists in the cache bank.
  182. """
  183. with patch("os.path.isfile", MagicMock(return_value=True)):
  184. # Create a temporary cache dir
  185. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  186. # Use the helper function to create the cache file using localfs.store()
  187. self._create_tmp_cache_file(tmp_dir, salt.payload.Serial(self))
  188. # Now test the return of the flush function
  189. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  190. self.assertTrue(localfs.flush(bank="bank", key="key", cachedir=tmp_dir))
  191. def test_flush_error_raised(self):
  192. """
  193. Tests that a SaltCacheError is raised when there is a problem removing the
  194. key file from the cache bank
  195. """
  196. with patch("os.path.isfile", MagicMock(return_value=True)):
  197. with patch("os.remove", MagicMock(side_effect=OSError)):
  198. self.assertRaises(
  199. SaltCacheError,
  200. localfs.flush,
  201. bank="",
  202. key="key",
  203. cachedir="/var/cache/salt",
  204. )
  205. # 'list' function tests: 3
  206. def test_list_no_base_dir(self):
  207. """
  208. Tests that the ls function returns an empty list if the bank directory
  209. doesn't exist.
  210. """
  211. with patch("os.path.isdir", MagicMock(return_value=False)):
  212. self.assertEqual(localfs.list_(bank="", cachedir=""), [])
  213. def test_list_error_raised_no_bank_directory_access(self):
  214. """
  215. Tests that a SaltCacheError is raised when there is a problem accessing the
  216. cache bank directory.
  217. """
  218. with patch("os.path.isdir", MagicMock(return_value=True)):
  219. with patch("os.listdir", MagicMock(side_effect=OSError)):
  220. self.assertRaises(SaltCacheError, localfs.list_, bank="", cachedir="")
  221. def test_list_success(self):
  222. """
  223. Tests the return of the ls function containing bank entries.
  224. """
  225. # Create a temporary cache dir
  226. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  227. # Use the helper function to create the cache file using localfs.store()
  228. self._create_tmp_cache_file(tmp_dir, salt.payload.Serial(self))
  229. # Now test the return of the ls function
  230. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  231. self.assertEqual(localfs.list_(bank="bank", cachedir=tmp_dir), ["key"])
  232. # 'contains' function tests: 1
  233. def test_contains(self):
  234. """
  235. Test the return of the contains function when key=None and when a key
  236. is provided.
  237. """
  238. # Create a temporary cache dir
  239. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  240. # Use the helper function to create the cache file using localfs.store()
  241. self._create_tmp_cache_file(tmp_dir, salt.payload.Serial(self))
  242. # Now test the return of the contains function when key=None
  243. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  244. self.assertTrue(localfs.contains(bank="bank", key=None, cachedir=tmp_dir))
  245. # Now test the return of the contains function when key='key'
  246. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  247. self.assertTrue(localfs.contains(bank="bank", key="key", cachedir=tmp_dir))
  248. def test_mix_of_utf8_and_non_utf8_can_be_round_tripped(self):
  249. tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  250. data = {
  251. # Any unicode, which ideally is invalid ascii.
  252. "unicode": "áéí",
  253. # Any bytes so long as they're not valid utf-8
  254. "bytes": b"\xfe\x99\x00\xff",
  255. }
  256. bank = "bank"
  257. key = "key"
  258. self.addCleanup(shutil.rmtree, tmp_dir)
  259. with patch.dict(localfs.__opts__, {"cachedir": tmp_dir}):
  260. with patch.dict(
  261. localfs.__context__, {"serial": salt.payload.Serial("msgpack")}
  262. ):
  263. localfs.store(bank, key, data, tmp_dir)
  264. actual = localfs.fetch(bank, key, tmp_dir)
  265. self.assertEqual(data, actual)