TestRecoveryManager.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. #!/usr/bin/env python
  2. '''
  3. Licensed to the Apache Software Foundation (ASF) under one
  4. or more contributor license agreements. See the NOTICE file
  5. distributed with this work for additional information
  6. regarding copyright ownership. The ASF licenses this file
  7. to you under the Apache License, Version 2.0 (the
  8. "License"); you may not use this file except in compliance
  9. with the License. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. '''
  17. from unittest import TestCase
  18. import copy
  19. from ambari_agent.RecoveryManager import RecoveryManager
  20. from mock.mock import patch, MagicMock, call
  21. class TestRecoveryManager(TestCase):
  22. command = {
  23. "commandType": "STATUS_COMMAND",
  24. "payloadLevel": "EXECUTION_COMMAND",
  25. "componentName": "NODEMANAGER",
  26. "desiredState": "STARTED",
  27. "hasStaleConfigs": False,
  28. "executionCommandDetails": {
  29. "commandType": "EXECUTION_COMMAND",
  30. "roleCommand": "INSTALL",
  31. "role": "NODEMANAGER",
  32. "hostLevelParams": {
  33. "custom_command":""},
  34. "configurations": {
  35. "capacity-scheduler": {
  36. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  37. "capacity-calculator": {
  38. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  39. "commandParams": {
  40. "service_package_folder": "common-services/YARN/2.1.0.2.0/package"
  41. }
  42. }
  43. }
  44. }
  45. exec_command1 = {
  46. "commandType": "EXECUTION_COMMAND",
  47. "roleCommand": "INSTALL",
  48. "role": "NODEMANAGER",
  49. "configurations": {
  50. "capacity-scheduler": {
  51. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  52. "capacity-calculator": {
  53. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  54. "commandParams": {
  55. "service_package_folder": "common-services/YARN/2.1.0.2.0/package"
  56. }
  57. },
  58. "hostLevelParams": {}
  59. }
  60. exec_command2 = {
  61. "commandType": "EXECUTION_COMMAND",
  62. "roleCommand": "START",
  63. "role": "NODEMANAGER",
  64. "configurations": {
  65. "capacity-scheduler": {
  66. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  67. "capacity-calculator": {
  68. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  69. "commandParams": {
  70. "service_package_folder": "common-services/YARN/2.1.0.2.0/package"
  71. }
  72. },
  73. "hostLevelParams": {}
  74. }
  75. exec_command3 = {
  76. "commandType": "EXECUTION_COMMAND",
  77. "roleCommand": "SERVICE_CHECK",
  78. "role": "NODEMANAGER",
  79. "configurations": {
  80. "capacity-scheduler": {
  81. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  82. "capacity-calculator": {
  83. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  84. "commandParams": {
  85. "service_package_folder": "common-services/YARN/2.1.0.2.0/package"
  86. }
  87. },
  88. "hostLevelParams": {}
  89. }
  90. exec_command4 = {
  91. "commandType": "EXECUTION_COMMAND",
  92. "roleCommand": "CUSTOM_COMMAND",
  93. "role": "NODEMANAGER",
  94. "configurations": {
  95. "capacity-scheduler": {
  96. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  97. "capacity-calculator": {
  98. "yarn.scheduler.capacity.default.minimum-user-limit-percent": "100"},
  99. "commandParams": {
  100. "service_package_folder": "common-services/YARN/2.1.0.2.0/package"
  101. }
  102. },
  103. "hostLevelParams": {
  104. "custom_command": "RESTART"
  105. }
  106. }
  107. def setUp(self):
  108. pass
  109. def tearDown(self):
  110. pass
  111. @patch.object(RecoveryManager, "update_desired_status")
  112. def test_process_commands(self, mock_uds):
  113. rm = RecoveryManager(True)
  114. rm.process_status_commands(None)
  115. self.assertFalse(mock_uds.called)
  116. rm.process_status_commands([])
  117. self.assertFalse(mock_uds.called)
  118. rm.process_status_commands([self.command])
  119. mock_uds.assert_has_calls([call("NODEMANAGER", "STARTED")])
  120. mock_uds.reset_mock()
  121. rm.process_status_commands([self.command, self.exec_command1, self.command])
  122. mock_uds.assert_has_calls([call("NODEMANAGER", "STARTED")], [call("NODEMANAGER", "STARTED")])
  123. mock_uds.reset_mock()
  124. rm.process_execution_commands([self.exec_command1, self.exec_command2, self.exec_command3])
  125. mock_uds.assert_has_calls([call("NODEMANAGER", "INSTALLED")], [call("NODEMANAGER", "STARTED")])
  126. mock_uds.reset_mock()
  127. rm.process_execution_commands([self.exec_command1, self.command])
  128. mock_uds.assert_has_calls([call("NODEMANAGER", "INSTALLED")])
  129. rm.process_execution_commands([self.exec_command4])
  130. mock_uds.assert_has_calls([call("NODEMANAGER", "STARTED")])
  131. pass
  132. def test_defaults(self):
  133. rm = RecoveryManager()
  134. self.assertFalse(rm.enabled())
  135. self.assertEqual(None, rm.get_install_command("NODEMANAGER"))
  136. self.assertEqual(None, rm.get_start_command("NODEMANAGER"))
  137. rm.update_current_status("NODEMANAGER", "INSTALLED")
  138. rm.update_desired_status("NODEMANAGER", "STARTED")
  139. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  140. pass
  141. @patch.object(RecoveryManager, "_now_")
  142. def test_sliding_window(self, time_mock):
  143. time_mock.side_effect = \
  144. [1000, 1001, 1002, 1003, 1004, 1071, 1150, 1151, 1152, 1153, 1400, 1401,
  145. 1500, 1571, 1572, 1653, 1900, 1971, 2300, 2301]
  146. rm = RecoveryManager(True, False)
  147. self.assertTrue(rm.enabled())
  148. rm.update_config(0, 60, 5, 12, True, False)
  149. self.assertFalse(rm.enabled())
  150. rm.update_config(6, 60, 5, 12, True, False)
  151. self.assertTrue(rm.enabled())
  152. rm.update_config(6, 0, 5, 12, True, False)
  153. self.assertFalse(rm.enabled())
  154. rm.update_config(6, 60, 0, 12, True, False)
  155. self.assertFalse(rm.enabled())
  156. rm.update_config(6, 60, 1, 12, True, False)
  157. self.assertTrue(rm.enabled())
  158. rm.update_config(6, 60, 61, 12, True, False)
  159. self.assertFalse(rm.enabled())
  160. rm.update_config(6, 60, 5, 0, True, False)
  161. self.assertFalse(rm.enabled())
  162. rm.update_config(6, 60, 5, 4, True, False)
  163. self.assertFalse(rm.enabled())
  164. # maximum 2 in 2 minutes and at least 1 minute wait
  165. rm.update_config(2, 5, 1, 4, True, False)
  166. self.assertTrue(rm.enabled())
  167. # T = 1000-2
  168. self.assertTrue(rm.may_execute("NODEMANAGER"))
  169. self.assertTrue(rm.may_execute("NODEMANAGER"))
  170. self.assertTrue(rm.may_execute("NODEMANAGER"))
  171. # T = 1003-4
  172. self.assertTrue(rm.execute("NODEMANAGER"))
  173. self.assertFalse(rm.execute("NODEMANAGER")) # too soon
  174. # T = 1071
  175. self.assertTrue(rm.execute("NODEMANAGER")) # 60+ seconds passed
  176. # T = 1150-3
  177. self.assertFalse(rm.execute("NODEMANAGER")) # limit 2 exceeded
  178. self.assertFalse(rm.may_execute("NODEMANAGER"))
  179. self.assertTrue(rm.execute("DATANODE"))
  180. self.assertTrue(rm.may_execute("NAMENODE"))
  181. # T = 1400-1
  182. self.assertTrue(rm.execute("NODEMANAGER")) # windows reset
  183. self.assertFalse(rm.may_execute("NODEMANAGER")) # too soon
  184. # maximum 2 in 2 minutes and no min wait
  185. rm.update_config(2, 5, 1, 5, True, True)
  186. # T = 1500-3
  187. self.assertTrue(rm.execute("NODEMANAGER2"))
  188. self.assertTrue(rm.may_execute("NODEMANAGER2"))
  189. self.assertTrue(rm.execute("NODEMANAGER2"))
  190. self.assertFalse(rm.execute("NODEMANAGER2")) # max limit
  191. # T = 1900-2
  192. self.assertTrue(rm.execute("NODEMANAGER2"))
  193. self.assertTrue(rm.execute("NODEMANAGER2"))
  194. # T = 2300-2
  195. # lifetime max reached
  196. self.assertTrue(rm.execute("NODEMANAGER2"))
  197. self.assertFalse(rm.execute("NODEMANAGER2"))
  198. pass
  199. def test_recovery_required(self):
  200. rm = RecoveryManager(True, False)
  201. rm.update_current_status("NODEMANAGER", "INSTALLED")
  202. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  203. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  204. rm.update_desired_status("NODEMANAGER", "STARTED")
  205. self.assertTrue(rm.requires_recovery("NODEMANAGER"))
  206. rm.update_current_status("NODEMANAGER", "STARTED")
  207. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  208. self.assertTrue(rm.requires_recovery("NODEMANAGER"))
  209. rm.update_desired_status("NODEMANAGER", "STARTED")
  210. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  211. rm.update_current_status("NODEMANAGER", "INSTALLED")
  212. rm.update_desired_status("NODEMANAGER", "XYS")
  213. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  214. rm.update_desired_status("NODEMANAGER", "")
  215. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  216. rm.update_current_status("NODEMANAGER", "INIT")
  217. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  218. self.assertTrue(rm.requires_recovery("NODEMANAGER"))
  219. rm.update_desired_status("NODEMANAGER", "STARTED")
  220. self.assertTrue(rm.requires_recovery("NODEMANAGER"))
  221. rm = RecoveryManager(True, True)
  222. rm.update_current_status("NODEMANAGER", "INIT")
  223. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  224. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  225. rm.update_current_status("NODEMANAGER", "INIT")
  226. rm.update_desired_status("NODEMANAGER", "START")
  227. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  228. rm.update_current_status("NODEMANAGER", "INSTALLED")
  229. rm.update_desired_status("NODEMANAGER", "START")
  230. self.assertFalse(rm.requires_recovery("NODEMANAGER"))
  231. pass
  232. @patch('time.time', MagicMock(side_effects=[1]))
  233. def test_store_from_status_and_use(self):
  234. rm = RecoveryManager(True)
  235. command1 = copy.deepcopy(self.command)
  236. rm.store_or_update_command(command1)
  237. self.assertTrue(rm.command_exists("NODEMANAGER", "EXECUTION_COMMAND"))
  238. install_command = rm.get_install_command("NODEMANAGER")
  239. start_command = rm.get_start_command("NODEMANAGER")
  240. self.assertEqual("INSTALL", install_command["roleCommand"])
  241. self.assertEqual("START", start_command["roleCommand"])
  242. self.assertEqual("AUTO_EXECUTION_COMMAND", install_command["commandType"])
  243. self.assertEqual("AUTO_EXECUTION_COMMAND", start_command["commandType"])
  244. self.assertEqual("NODEMANAGER", install_command["role"])
  245. self.assertEqual("NODEMANAGER", start_command["role"])
  246. self.assertEquals(install_command["configurations"], start_command["configurations"])
  247. self.assertEqual(2, install_command["taskId"])
  248. self.assertEqual(3, start_command["taskId"])
  249. self.assertEqual(None, rm.get_install_command("component2"))
  250. self.assertEqual(None, rm.get_start_command("component2"))
  251. self.assertTrue(rm.remove_command("NODEMANAGER"))
  252. self.assertFalse(rm.remove_command("NODEMANAGER"))
  253. self.assertEqual(None, rm.get_install_command("NODEMANAGER"))
  254. self.assertEqual(None, rm.get_start_command("NODEMANAGER"))
  255. self.assertEqual(None, rm.get_install_command("component2"))
  256. self.assertEqual(None, rm.get_start_command("component2"))
  257. rm.store_or_update_command(command1)
  258. self.assertTrue(rm.command_exists("NODEMANAGER", "EXECUTION_COMMAND"))
  259. rm.set_paused(True)
  260. self.assertEqual(None, rm.get_install_command("NODEMANAGER"))
  261. self.assertEqual(None, rm.get_start_command("NODEMANAGER"))
  262. pass
  263. @patch.object(RecoveryManager, "_now_")
  264. def test_get_recovery_commands(self, time_mock):
  265. time_mock.side_effect = \
  266. [1000, 1001, 1002, 1003,
  267. 1100, 1101, 1102,
  268. 1200, 1201, 1203,
  269. 4000, 4001, 4002, 4003,
  270. 4100, 4101, 4102, 4103,
  271. 4200, 4201, 4202,
  272. 4300, 4301, 4302]
  273. rm = RecoveryManager(True)
  274. rm.update_config(15, 5, 1, 16, True, False)
  275. command1 = copy.deepcopy(self.command)
  276. rm.store_or_update_command(command1)
  277. rm.update_current_status("NODEMANAGER", "INSTALLED")
  278. rm.update_desired_status("NODEMANAGER", "STARTED")
  279. self.assertEqual("INSTALLED", rm.get_current_status("NODEMANAGER"))
  280. self.assertEqual("STARTED", rm.get_desired_status("NODEMANAGER"))
  281. commands = rm.get_recovery_commands()
  282. self.assertEqual(1, len(commands))
  283. self.assertEqual("START", commands[0]["roleCommand"])
  284. rm.update_current_status("NODEMANAGER", "INIT")
  285. rm.update_desired_status("NODEMANAGER", "STARTED")
  286. # Starts at 1100
  287. commands = rm.get_recovery_commands()
  288. self.assertEqual(1, len(commands))
  289. self.assertEqual("INSTALL", commands[0]["roleCommand"])
  290. rm.update_current_status("NODEMANAGER", "INIT")
  291. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  292. # Starts at 1200
  293. commands = rm.get_recovery_commands()
  294. self.assertEqual(1, len(commands))
  295. self.assertEqual("INSTALL", commands[0]["roleCommand"])
  296. rm.update_config(2, 5, 1, 5, True, True)
  297. rm.update_current_status("NODEMANAGER", "INIT")
  298. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  299. commands = rm.get_recovery_commands()
  300. self.assertEqual(0, len(commands))
  301. rm.update_config(12, 5, 1, 15, True, False)
  302. rm.update_current_status("NODEMANAGER", "INIT")
  303. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  304. rm.store_or_update_command(command1)
  305. commands = rm.get_recovery_commands()
  306. self.assertEqual(1, len(commands))
  307. self.assertEqual("INSTALL", commands[0]["roleCommand"])
  308. rm.update_config_staleness("NODEMANAGER", False)
  309. rm.update_current_status("NODEMANAGER", "INSTALLED")
  310. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  311. commands = rm.get_recovery_commands()
  312. self.assertEqual(0, len(commands))
  313. command_install = copy.deepcopy(self.command)
  314. command_install["desiredState"] = "INSTALLED"
  315. rm.store_or_update_command(command_install)
  316. rm.update_config_staleness("NODEMANAGER", True)
  317. commands = rm.get_recovery_commands()
  318. self.assertEqual(1, len(commands))
  319. self.assertEqual("INSTALL", commands[0]["roleCommand"])
  320. rm.update_current_status("NODEMANAGER", "STARTED")
  321. rm.update_desired_status("NODEMANAGER", "STARTED")
  322. commands = rm.get_recovery_commands()
  323. self.assertEqual(1, len(commands))
  324. self.assertEqual("CUSTOM_COMMAND", commands[0]["roleCommand"])
  325. self.assertEqual("RESTART", commands[0]["hostLevelParams"]["custom_command"])
  326. rm.update_current_status("NODEMANAGER", "STARTED")
  327. rm.update_desired_status("NODEMANAGER", "INSTALLED")
  328. commands = rm.get_recovery_commands()
  329. self.assertEqual(1, len(commands))
  330. self.assertEqual("STOP", commands[0]["roleCommand"])
  331. pass
  332. @patch.object(RecoveryManager, "update_config")
  333. def test_update_rm_config(self, mock_uc):
  334. rm = RecoveryManager()
  335. rm.update_configuration_from_registration(None)
  336. mock_uc.assert_has_calls([call(6, 60, 5, 12, False, True)])
  337. mock_uc.reset_mock()
  338. rm.update_configuration_from_registration({})
  339. mock_uc.assert_has_calls([call(6, 60, 5, 12, False, True)])
  340. mock_uc.reset_mock()
  341. rm.update_configuration_from_registration(
  342. {"recoveryConfig": {
  343. "type" : "DEFAULT"}}
  344. )
  345. mock_uc.assert_has_calls([call(6, 60, 5, 12, False, True)])
  346. mock_uc.reset_mock()
  347. rm.update_configuration_from_registration(
  348. {"recoveryConfig": {
  349. "type" : "FULL"}}
  350. )
  351. mock_uc.assert_has_calls([call(6, 60, 5, 12, True, False)])
  352. mock_uc.reset_mock()
  353. rm.update_configuration_from_registration(
  354. {"recoveryConfig": {
  355. "type" : "AUTO_START",
  356. "max_count" : "med"}}
  357. )
  358. mock_uc.assert_has_calls([call(6, 60, 5, 12, True, True)])
  359. mock_uc.reset_mock()
  360. rm.update_configuration_from_registration(
  361. {"recoveryConfig": {
  362. "type" : "AUTO_START",
  363. "maxCount" : "5",
  364. "windowInMinutes" : 20,
  365. "retryGap" : 2,
  366. "maxLifetimeCount" : 5}}
  367. )
  368. mock_uc.assert_has_calls([call(5, 20, 2, 5, True, True)])
  369. pass
  370. @patch.object(RecoveryManager, "_now_")
  371. def test_recovery_report(self, time_mock):
  372. time_mock.side_effect = \
  373. [1000, 1071, 1072, 1470, 1471, 1472, 1543, 1644, 1715]
  374. rm = RecoveryManager()
  375. rec_st = rm.get_recovery_status()
  376. self.assertEquals(rec_st, {"summary": "DISABLED"})
  377. rm.update_config(2, 5, 1, 4, True, True)
  378. rec_st = rm.get_recovery_status()
  379. self.assertEquals(rec_st, {"summary": "RECOVERABLE", "componentReports": []})
  380. rm.execute("PUMA")
  381. rec_st = rm.get_recovery_status()
  382. self.assertEquals(rec_st, {"summary": "RECOVERABLE",
  383. "componentReports": [{"name": "PUMA", "numAttempts": 1, "limitReached": False}]})
  384. rm.execute("PUMA")
  385. rm.execute("LION")
  386. rec_st = rm.get_recovery_status()
  387. self.assertEquals(rec_st, {"summary": "RECOVERABLE",
  388. "componentReports": [
  389. {"name": "LION", "numAttempts": 1, "limitReached": False},
  390. {"name": "PUMA", "numAttempts": 2, "limitReached": False}
  391. ]})
  392. rm.execute("PUMA")
  393. rm.execute("LION")
  394. rm.execute("PUMA")
  395. rm.execute("PUMA")
  396. rm.execute("LION")
  397. rec_st = rm.get_recovery_status()
  398. self.assertEquals(rec_st, {"summary": "PARTIALLY_RECOVERABLE",
  399. "componentReports": [
  400. {"name": "LION", "numAttempts": 3, "limitReached": False},
  401. {"name": "PUMA", "numAttempts": 4, "limitReached": True}
  402. ]})
  403. rm.execute("LION")
  404. rec_st = rm.get_recovery_status()
  405. self.assertEquals(rec_st, {"summary": "UNRECOVERABLE",
  406. "componentReports": [
  407. {"name": "LION", "numAttempts": 4, "limitReached": True},
  408. {"name": "PUMA", "numAttempts": 4, "limitReached": True}
  409. ]})
  410. pass
  411. @patch.object(RecoveryManager, "_now_")
  412. def test_command_expiry(self, time_mock):
  413. time_mock.side_effect = \
  414. [1000, 1001, 1002, 1003, 1104, 1105, 1106, 1807, 1808, 1809, 1810, 1811, 1812]
  415. rm = RecoveryManager(True)
  416. rm.update_config(5, 5, 1, 11, True, False)
  417. command1 = copy.deepcopy(self.command)
  418. rm.store_or_update_command(command1)
  419. rm.update_current_status("NODEMANAGER", "INSTALLED")
  420. rm.update_desired_status("NODEMANAGER", "STARTED")
  421. commands = rm.get_recovery_commands()
  422. self.assertEqual(1, len(commands))
  423. self.assertEqual("START", commands[0]["roleCommand"])
  424. commands = rm.get_recovery_commands()
  425. self.assertEqual(1, len(commands))
  426. self.assertEqual("START", commands[0]["roleCommand"])
  427. #1807 command is stale
  428. commands = rm.get_recovery_commands()
  429. self.assertEqual(0, len(commands))
  430. rm.store_or_update_command(command1)
  431. commands = rm.get_recovery_commands()
  432. self.assertEqual(1, len(commands))
  433. self.assertEqual("START", commands[0]["roleCommand"])
  434. pass
  435. def test_command_count(self):
  436. rm = RecoveryManager(True)
  437. self.assertFalse(rm.has_active_command())
  438. rm.start_execution_command()
  439. self.assertTrue(rm.has_active_command())
  440. rm.start_execution_command()
  441. self.assertTrue(rm.has_active_command())
  442. rm.stop_execution_command()
  443. self.assertTrue(rm.has_active_command())
  444. rm.stop_execution_command()
  445. self.assertFalse(rm.has_active_command())