1import copy 2import logging 3import os 4 5import pytest 6import salt.ext.tornado 7import salt.ext.tornado.gen 8import salt.ext.tornado.testing 9import salt.minion 10import salt.syspaths 11import salt.utils.crypt 12import salt.utils.event as event 13import salt.utils.jid 14import salt.utils.platform 15import salt.utils.process 16from salt._compat import ipaddress 17from salt.exceptions import SaltClientError, SaltMasterUnresolvableError, SaltSystemExit 18from tests.support.mock import MagicMock, patch 19 20log = logging.getLogger(__name__) 21 22 23def test_minion_load_grains_false(): 24 """ 25 Minion does not generate grains when load_grains is False 26 """ 27 opts = {"random_startup_delay": 0, "grains": {"foo": "bar"}} 28 with patch("salt.loader.grains") as grainsfunc: 29 minion = salt.minion.Minion(opts, load_grains=False) 30 assert minion.opts["grains"] == opts["grains"] 31 grainsfunc.assert_not_called() 32 33 34def test_minion_load_grains_true(): 35 """ 36 Minion generates grains when load_grains is True 37 """ 38 opts = {"random_startup_delay": 0, "grains": {}} 39 with patch("salt.loader.grains") as grainsfunc: 40 minion = salt.minion.Minion(opts, load_grains=True) 41 assert minion.opts["grains"] != {} 42 grainsfunc.assert_called() 43 44 45def test_minion_load_grains_default(): 46 """ 47 Minion load_grains defaults to True 48 """ 49 opts = {"random_startup_delay": 0, "grains": {}} 50 with patch("salt.loader.grains") as grainsfunc: 51 minion = salt.minion.Minion(opts) 52 assert minion.opts["grains"] != {} 53 grainsfunc.assert_called() 54 55 56@pytest.mark.parametrize( 57 "req_channel", 58 [ 59 ( 60 "salt.transport.client.AsyncReqChannel.factory", 61 lambda load, timeout, tries: salt.ext.tornado.gen.maybe_future(tries), 62 ), 63 ( 64 "salt.transport.client.ReqChannel.factory", 65 lambda load, timeout, tries: tries, 66 ), 67 ], 68) 69def test_send_req_tries(req_channel): 70 channel_enter = MagicMock() 71 channel_enter.send.side_effect = req_channel[1] 72 channel = MagicMock() 73 channel.__enter__.return_value = channel_enter 74 75 with patch(req_channel[0], return_value=channel): 76 opts = { 77 "random_startup_delay": 0, 78 "grains": {}, 79 "return_retry_tries": 30, 80 "minion_sign_messages": False, 81 } 82 with patch("salt.loader.grains"): 83 minion = salt.minion.Minion(opts) 84 85 load = {"load": "value"} 86 timeout = 60 87 88 if "Async" in req_channel[0]: 89 rtn = minion._send_req_async(load, timeout).result() 90 else: 91 rtn = minion._send_req_sync(load, timeout) 92 93 assert rtn == 30 94 95 96@patch("salt.transport.client.ReqChannel.factory") 97def test_mine_send_tries(req_channel_factory): 98 channel_enter = MagicMock() 99 channel_enter.send.side_effect = lambda load, timeout, tries: tries 100 channel = MagicMock() 101 channel.__enter__.return_value = channel_enter 102 req_channel_factory.return_value = channel 103 104 opts = { 105 "random_startup_delay": 0, 106 "grains": {}, 107 "return_retry_tries": 20, 108 "minion_sign_messages": False, 109 } 110 with patch("salt.loader.grains"): 111 minion = salt.minion.Minion(opts) 112 minion.tok = "token" 113 114 data = {} 115 tag = "tag" 116 117 rtn = minion._mine_send(tag, data) 118 assert rtn == 20 119 120 121def test_invalid_master_address(): 122 opts = salt.config.DEFAULT_MINION_OPTS.copy() 123 with patch.dict( 124 opts, 125 { 126 "ipv6": False, 127 "master": float("127.0"), 128 "master_port": "4555", 129 "retry_dns": False, 130 }, 131 ): 132 pytest.raises(SaltSystemExit, salt.minion.resolve_dns, opts) 133 134 135def test_source_int_name_local(): 136 """ 137 test when file_client local and 138 source_interface_name is set 139 """ 140 interfaces = { 141 "bond0.1234": { 142 "hwaddr": "01:01:01:d0:d0:d0", 143 "up": True, 144 "inet": [ 145 { 146 "broadcast": "111.1.111.255", 147 "netmask": "111.1.0.0", 148 "label": "bond0", 149 "address": "111.1.0.1", 150 } 151 ], 152 } 153 } 154 opts = salt.config.DEFAULT_MINION_OPTS.copy() 155 with patch.dict( 156 opts, 157 { 158 "ipv6": False, 159 "master": "127.0.0.1", 160 "master_port": "4555", 161 "file_client": "local", 162 "source_interface_name": "bond0.1234", 163 "source_ret_port": 49017, 164 "source_publish_port": 49018, 165 }, 166 ), patch("salt.utils.network.interfaces", MagicMock(return_value=interfaces)): 167 assert salt.minion.resolve_dns(opts) == { 168 "master_ip": "127.0.0.1", 169 "source_ip": "111.1.0.1", 170 "source_ret_port": 49017, 171 "source_publish_port": 49018, 172 "master_uri": "tcp://127.0.0.1:4555", 173 } 174 175 176@pytest.mark.slow_test 177def test_source_int_name_remote(): 178 """ 179 test when file_client remote and 180 source_interface_name is set and 181 interface is down 182 """ 183 interfaces = { 184 "bond0.1234": { 185 "hwaddr": "01:01:01:d0:d0:d0", 186 "up": False, 187 "inet": [ 188 { 189 "broadcast": "111.1.111.255", 190 "netmask": "111.1.0.0", 191 "label": "bond0", 192 "address": "111.1.0.1", 193 } 194 ], 195 } 196 } 197 opts = salt.config.DEFAULT_MINION_OPTS.copy() 198 with patch.dict( 199 opts, 200 { 201 "ipv6": False, 202 "master": "127.0.0.1", 203 "master_port": "4555", 204 "file_client": "remote", 205 "source_interface_name": "bond0.1234", 206 "source_ret_port": 49017, 207 "source_publish_port": 49018, 208 }, 209 ), patch("salt.utils.network.interfaces", MagicMock(return_value=interfaces)): 210 assert salt.minion.resolve_dns(opts) == { 211 "master_ip": "127.0.0.1", 212 "source_ret_port": 49017, 213 "source_publish_port": 49018, 214 "master_uri": "tcp://127.0.0.1:4555", 215 } 216 217 218@pytest.mark.slow_test 219def test_source_address(): 220 """ 221 test when source_address is set 222 """ 223 interfaces = { 224 "bond0.1234": { 225 "hwaddr": "01:01:01:d0:d0:d0", 226 "up": False, 227 "inet": [ 228 { 229 "broadcast": "111.1.111.255", 230 "netmask": "111.1.0.0", 231 "label": "bond0", 232 "address": "111.1.0.1", 233 } 234 ], 235 } 236 } 237 opts = salt.config.DEFAULT_MINION_OPTS.copy() 238 with patch.dict( 239 opts, 240 { 241 "ipv6": False, 242 "master": "127.0.0.1", 243 "master_port": "4555", 244 "file_client": "local", 245 "source_interface_name": "", 246 "source_address": "111.1.0.1", 247 "source_ret_port": 49017, 248 "source_publish_port": 49018, 249 }, 250 ), patch("salt.utils.network.interfaces", MagicMock(return_value=interfaces)): 251 assert salt.minion.resolve_dns(opts) == { 252 "source_publish_port": 49018, 253 "source_ret_port": 49017, 254 "master_uri": "tcp://127.0.0.1:4555", 255 "source_ip": "111.1.0.1", 256 "master_ip": "127.0.0.1", 257 } 258 259 260# Tests for _handle_decoded_payload in the salt.minion.Minion() class: 3 261@pytest.mark.slow_test 262def test_handle_decoded_payload_jid_match_in_jid_queue(): 263 """ 264 Tests that the _handle_decoded_payload function returns when a jid is given that is already present 265 in the jid_queue. 266 267 Note: This test doesn't contain all of the patch decorators above the function like the other tests 268 for _handle_decoded_payload below. This is essential to this test as the call to the function must 269 return None BEFORE any of the processes are spun up because we should be avoiding firing duplicate 270 jobs. 271 """ 272 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 273 mock_data = {"fun": "foo.bar", "jid": 123} 274 mock_jid_queue = [123] 275 minion = salt.minion.Minion( 276 mock_opts, 277 jid_queue=copy.copy(mock_jid_queue), 278 io_loop=salt.ext.tornado.ioloop.IOLoop(), 279 ) 280 try: 281 ret = minion._handle_decoded_payload(mock_data).result() 282 assert minion.jid_queue == mock_jid_queue 283 assert ret is None 284 finally: 285 minion.destroy() 286 287 288@pytest.mark.slow_test 289def test_handle_decoded_payload_jid_queue_addition(): 290 """ 291 Tests that the _handle_decoded_payload function adds a jid to the minion's jid_queue when the new 292 jid isn't already present in the jid_queue. 293 """ 294 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 295 "salt.utils.process.SignalHandlingProcess.start", 296 MagicMock(return_value=True), 297 ), patch( 298 "salt.utils.process.SignalHandlingProcess.join", 299 MagicMock(return_value=True), 300 ): 301 mock_jid = 11111 302 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 303 mock_data = {"fun": "foo.bar", "jid": mock_jid} 304 mock_jid_queue = [123, 456] 305 minion = salt.minion.Minion( 306 mock_opts, 307 jid_queue=copy.copy(mock_jid_queue), 308 io_loop=salt.ext.tornado.ioloop.IOLoop(), 309 ) 310 try: 311 312 # Assert that the minion's jid_queue attribute matches the mock_jid_queue as a baseline 313 # This can help debug any test failures if the _handle_decoded_payload call fails. 314 assert minion.jid_queue == mock_jid_queue 315 316 # Call the _handle_decoded_payload function and update the mock_jid_queue to include the new 317 # mock_jid. The mock_jid should have been added to the jid_queue since the mock_jid wasn't 318 # previously included. The minion's jid_queue attribute and the mock_jid_queue should be equal. 319 minion._handle_decoded_payload(mock_data).result() 320 mock_jid_queue.append(mock_jid) 321 assert minion.jid_queue == mock_jid_queue 322 finally: 323 minion.destroy() 324 325 326@pytest.mark.slow_test 327def test_handle_decoded_payload_jid_queue_reduced_minion_jid_queue_hwm(): 328 """ 329 Tests that the _handle_decoded_payload function removes a jid from the minion's jid_queue when the 330 minion's jid_queue high water mark (minion_jid_queue_hwm) is hit. 331 """ 332 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 333 "salt.utils.process.SignalHandlingProcess.start", 334 MagicMock(return_value=True), 335 ), patch( 336 "salt.utils.process.SignalHandlingProcess.join", 337 MagicMock(return_value=True), 338 ): 339 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 340 mock_opts["minion_jid_queue_hwm"] = 2 341 mock_data = {"fun": "foo.bar", "jid": 789} 342 mock_jid_queue = [123, 456] 343 minion = salt.minion.Minion( 344 mock_opts, 345 jid_queue=copy.copy(mock_jid_queue), 346 io_loop=salt.ext.tornado.ioloop.IOLoop(), 347 ) 348 try: 349 350 # Assert that the minion's jid_queue attribute matches the mock_jid_queue as a baseline 351 # This can help debug any test failures if the _handle_decoded_payload call fails. 352 assert minion.jid_queue == mock_jid_queue 353 354 # Call the _handle_decoded_payload function and check that the queue is smaller by one item 355 # and contains the new jid 356 minion._handle_decoded_payload(mock_data).result() 357 assert len(minion.jid_queue) == 2 358 assert minion.jid_queue == [456, 789] 359 finally: 360 minion.destroy() 361 362 363@pytest.mark.slow_test 364def test_process_count_max(): 365 """ 366 Tests that the _handle_decoded_payload function does not spawn more than the configured amount of processes, 367 as per process_count_max. 368 """ 369 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 370 "salt.utils.process.SignalHandlingProcess.start", 371 MagicMock(return_value=True), 372 ), patch( 373 "salt.utils.process.SignalHandlingProcess.join", 374 MagicMock(return_value=True), 375 ), patch( 376 "salt.utils.minion.running", MagicMock(return_value=[]) 377 ), patch( 378 "salt.ext.tornado.gen.sleep", 379 MagicMock(return_value=salt.ext.tornado.concurrent.Future()), 380 ): 381 process_count_max = 10 382 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 383 mock_opts["__role"] = "minion" 384 mock_opts["minion_jid_queue_hwm"] = 100 385 mock_opts["process_count_max"] = process_count_max 386 387 io_loop = salt.ext.tornado.ioloop.IOLoop() 388 minion = salt.minion.Minion(mock_opts, jid_queue=[], io_loop=io_loop) 389 try: 390 391 # mock gen.sleep to throw a special Exception when called, so that we detect it 392 class SleepCalledException(Exception): 393 """Thrown when sleep is called""" 394 395 salt.ext.tornado.gen.sleep.return_value.set_exception( 396 SleepCalledException() 397 ) 398 399 # up until process_count_max: gen.sleep does not get called, processes are started normally 400 for i in range(process_count_max): 401 mock_data = {"fun": "foo.bar", "jid": i} 402 io_loop.run_sync( 403 lambda data=mock_data: minion._handle_decoded_payload(data) 404 ) 405 assert ( 406 salt.utils.process.SignalHandlingProcess.start.call_count == i + 1 407 ) 408 assert len(minion.jid_queue) == i + 1 409 salt.utils.minion.running.return_value += [i] 410 411 # above process_count_max: gen.sleep does get called, JIDs are created but no new processes are started 412 mock_data = {"fun": "foo.bar", "jid": process_count_max + 1} 413 414 pytest.raises( 415 SleepCalledException, 416 lambda: io_loop.run_sync( 417 lambda: minion._handle_decoded_payload(mock_data) 418 ), 419 ) 420 assert ( 421 salt.utils.process.SignalHandlingProcess.start.call_count 422 == process_count_max 423 ) 424 assert len(minion.jid_queue) == process_count_max + 1 425 finally: 426 minion.destroy() 427 428 429@pytest.mark.slow_test 430def test_beacons_before_connect(): 431 """ 432 Tests that the 'beacons_before_connect' option causes the beacons to be initialized before connect. 433 """ 434 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 435 "salt.minion.Minion.sync_connect_master", 436 MagicMock(side_effect=RuntimeError("stop execution")), 437 ), patch( 438 "salt.utils.process.SignalHandlingProcess.start", 439 MagicMock(return_value=True), 440 ), patch( 441 "salt.utils.process.SignalHandlingProcess.join", 442 MagicMock(return_value=True), 443 ): 444 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 445 mock_opts["beacons_before_connect"] = True 446 io_loop = salt.ext.tornado.ioloop.IOLoop() 447 io_loop.make_current() 448 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 449 try: 450 451 try: 452 minion.tune_in(start=True) 453 except RuntimeError: 454 pass 455 456 # Make sure beacons are initialized but the sheduler is not 457 assert "beacons" in minion.periodic_callbacks 458 assert "schedule" not in minion.periodic_callbacks 459 finally: 460 minion.destroy() 461 462 463@pytest.mark.slow_test 464def test_scheduler_before_connect(): 465 """ 466 Tests that the 'scheduler_before_connect' option causes the scheduler to be initialized before connect. 467 """ 468 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 469 "salt.minion.Minion.sync_connect_master", 470 MagicMock(side_effect=RuntimeError("stop execution")), 471 ), patch( 472 "salt.utils.process.SignalHandlingProcess.start", 473 MagicMock(return_value=True), 474 ), patch( 475 "salt.utils.process.SignalHandlingProcess.join", 476 MagicMock(return_value=True), 477 ): 478 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 479 mock_opts["scheduler_before_connect"] = True 480 io_loop = salt.ext.tornado.ioloop.IOLoop() 481 io_loop.make_current() 482 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 483 try: 484 try: 485 minion.tune_in(start=True) 486 except RuntimeError: 487 pass 488 489 # Make sure the scheduler is initialized but the beacons are not 490 assert "schedule" in minion.periodic_callbacks 491 assert "beacons" not in minion.periodic_callbacks 492 finally: 493 minion.destroy() 494 495 496def test_minion_module_refresh(): 497 """ 498 Tests that the 'module_refresh' just return in case there is no 'schedule' 499 because destroy method was already called. 500 """ 501 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 502 "salt.utils.process.SignalHandlingProcess.start", 503 MagicMock(return_value=True), 504 ), patch( 505 "salt.utils.process.SignalHandlingProcess.join", 506 MagicMock(return_value=True), 507 ): 508 try: 509 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 510 minion = salt.minion.Minion( 511 mock_opts, 512 io_loop=salt.ext.tornado.ioloop.IOLoop(), 513 ) 514 minion.schedule = salt.utils.schedule.Schedule(mock_opts, {}, returners={}) 515 assert hasattr(minion, "schedule") 516 minion.destroy() 517 assert not hasattr(minion, "schedule") 518 assert not minion.module_refresh() 519 finally: 520 minion.destroy() 521 522 523def test_minion_module_refresh_beacons_refresh(): 524 """ 525 Tests that 'module_refresh' calls beacons_refresh and that the 526 minion object has a beacons attribute with beacons. 527 """ 528 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 529 "salt.utils.process.SignalHandlingProcess.start", 530 MagicMock(return_value=True), 531 ), patch( 532 "salt.utils.process.SignalHandlingProcess.join", 533 MagicMock(return_value=True), 534 ): 535 try: 536 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 537 minion = salt.minion.Minion( 538 mock_opts, 539 io_loop=salt.ext.tornado.ioloop.IOLoop(), 540 ) 541 minion.schedule = salt.utils.schedule.Schedule(mock_opts, {}, returners={}) 542 assert not hasattr(minion, "beacons") 543 minion.module_refresh() 544 assert hasattr(minion, "beacons") 545 assert hasattr(minion.beacons, "beacons") 546 assert "service.beacon" in minion.beacons.beacons 547 minion.destroy() 548 finally: 549 minion.destroy() 550 551 552@pytest.mark.slow_test 553def test_when_ping_interval_is_set_the_callback_should_be_added_to_periodic_callbacks(): 554 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 555 "salt.minion.Minion.sync_connect_master", 556 MagicMock(side_effect=RuntimeError("stop execution")), 557 ), patch( 558 "salt.utils.process.SignalHandlingProcess.start", 559 MagicMock(return_value=True), 560 ), patch( 561 "salt.utils.process.SignalHandlingProcess.join", 562 MagicMock(return_value=True), 563 ): 564 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 565 mock_opts["ping_interval"] = 10 566 io_loop = salt.ext.tornado.ioloop.IOLoop() 567 io_loop.make_current() 568 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 569 try: 570 try: 571 minion.connected = MagicMock(side_effect=(False, True)) 572 minion._fire_master_minion_start = MagicMock() 573 minion.tune_in(start=False) 574 except RuntimeError: 575 pass 576 577 # Make sure the scheduler is initialized but the beacons are not 578 assert "ping" in minion.periodic_callbacks 579 finally: 580 minion.destroy() 581 582 583@pytest.mark.slow_test 584def test_when_passed_start_event_grains(): 585 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 586 # provide mock opts an os grain since we'll look for it later. 587 mock_opts["grains"]["os"] = "linux" 588 mock_opts["start_event_grains"] = ["os"] 589 io_loop = salt.ext.tornado.ioloop.IOLoop() 590 io_loop.make_current() 591 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 592 try: 593 minion.tok = MagicMock() 594 minion._send_req_sync = MagicMock() 595 minion._fire_master( 596 "Minion has started", "minion_start", include_startup_grains=True 597 ) 598 load = minion._send_req_sync.call_args[0][0] 599 600 assert "grains" in load 601 assert "os" in load["grains"] 602 finally: 603 minion.destroy() 604 605 606@pytest.mark.slow_test 607def test_when_not_passed_start_event_grains(): 608 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 609 io_loop = salt.ext.tornado.ioloop.IOLoop() 610 io_loop.make_current() 611 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 612 try: 613 minion.tok = MagicMock() 614 minion._send_req_sync = MagicMock() 615 minion._fire_master("Minion has started", "minion_start") 616 load = minion._send_req_sync.call_args[0][0] 617 618 assert "grains" not in load 619 finally: 620 minion.destroy() 621 622 623@pytest.mark.slow_test 624def test_when_other_events_fired_and_start_event_grains_are_set(): 625 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 626 mock_opts["start_event_grains"] = ["os"] 627 io_loop = salt.ext.tornado.ioloop.IOLoop() 628 io_loop.make_current() 629 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 630 try: 631 minion.tok = MagicMock() 632 minion._send_req_sync = MagicMock() 633 minion._fire_master("Custm_event_fired", "custom_event") 634 load = minion._send_req_sync.call_args[0][0] 635 636 assert "grains" not in load 637 finally: 638 minion.destroy() 639 640 641@pytest.mark.slow_test 642def test_minion_retry_dns_count(): 643 """ 644 Tests that the resolve_dns will retry dns look ups for a maximum of 645 3 times before raising a SaltMasterUnresolvableError exception. 646 """ 647 opts = salt.config.DEFAULT_MINION_OPTS.copy() 648 with patch.dict( 649 opts, 650 { 651 "ipv6": False, 652 "master": "dummy", 653 "master_port": "4555", 654 "retry_dns": 1, 655 "retry_dns_count": 3, 656 }, 657 ): 658 pytest.raises(SaltMasterUnresolvableError, salt.minion.resolve_dns, opts) 659 660 661@pytest.mark.slow_test 662def test_gen_modules_executors(): 663 """ 664 Ensure gen_modules is called with the correct arguments #54429 665 """ 666 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 667 io_loop = salt.ext.tornado.ioloop.IOLoop() 668 io_loop.make_current() 669 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 670 671 class MockPillarCompiler: 672 def compile_pillar(self): 673 return {} 674 675 try: 676 with patch("salt.pillar.get_pillar", return_value=MockPillarCompiler()): 677 with patch("salt.loader.executors") as execmock: 678 minion.gen_modules() 679 assert execmock.called_with(minion.opts, minion.functions) 680 finally: 681 minion.destroy() 682 683 684@patch("salt.utils.process.default_signals") 685@pytest.mark.slow_test 686def test_reinit_crypto_on_fork(def_mock): 687 """ 688 Ensure salt.utils.crypt.reinit_crypto() is executed when forking for new job 689 """ 690 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 691 mock_opts["multiprocessing"] = True 692 693 io_loop = salt.ext.tornado.ioloop.IOLoop() 694 io_loop.make_current() 695 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 696 697 job_data = {"jid": "test-jid", "fun": "test.ping"} 698 699 def mock_start(self): 700 # pylint: disable=comparison-with-callable 701 assert ( 702 len( 703 [ 704 x 705 for x in self._after_fork_methods 706 if x[0] == salt.utils.crypt.reinit_crypto 707 ] 708 ) 709 == 1 710 ) 711 # pylint: enable=comparison-with-callable 712 713 with patch.object(salt.utils.process.SignalHandlingProcess, "start", mock_start): 714 io_loop.run_sync(lambda: minion._handle_decoded_payload(job_data)) 715 716 717def test_minion_manage_schedule(): 718 """ 719 Tests that the manage_schedule will call the add function, adding 720 schedule data into opts. 721 """ 722 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 723 "salt.minion.Minion.sync_connect_master", 724 MagicMock(side_effect=RuntimeError("stop execution")), 725 ), patch( 726 "salt.utils.process.SignalHandlingMultiprocessingProcess.start", 727 MagicMock(return_value=True), 728 ), patch( 729 "salt.utils.process.SignalHandlingMultiprocessingProcess.join", 730 MagicMock(return_value=True), 731 ): 732 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 733 io_loop = salt.ext.tornado.ioloop.IOLoop() 734 io_loop.make_current() 735 736 with patch("salt.utils.schedule.clean_proc_dir", MagicMock(return_value=None)): 737 try: 738 mock_functions = {"test.ping": None} 739 740 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 741 minion.schedule = salt.utils.schedule.Schedule( 742 mock_opts, 743 mock_functions, 744 returners={}, 745 new_instance=True, 746 ) 747 748 minion.opts["foo"] = "bar" 749 schedule_data = { 750 "test_job": { 751 "function": "test.ping", 752 "return_job": False, 753 "jid_include": True, 754 "maxrunning": 2, 755 "seconds": 10, 756 } 757 } 758 759 data = { 760 "name": "test-item", 761 "schedule": schedule_data, 762 "func": "add", 763 "persist": False, 764 } 765 tag = "manage_schedule" 766 767 minion.manage_schedule(tag, data) 768 assert "test_job" in minion.opts["schedule"] 769 finally: 770 del minion.schedule 771 minion.destroy() 772 del minion 773 774 775def test_minion_manage_beacons(): 776 """ 777 Tests that the manage_beacons will call the add function, adding 778 beacon data into opts. 779 """ 780 with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch( 781 "salt.minion.Minion.sync_connect_master", 782 MagicMock(side_effect=RuntimeError("stop execution")), 783 ), patch( 784 "salt.utils.process.SignalHandlingMultiprocessingProcess.start", 785 MagicMock(return_value=True), 786 ), patch( 787 "salt.utils.process.SignalHandlingMultiprocessingProcess.join", 788 MagicMock(return_value=True), 789 ): 790 try: 791 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 792 mock_opts["beacons"] = {} 793 794 io_loop = salt.ext.tornado.ioloop.IOLoop() 795 io_loop.make_current() 796 797 mock_functions = {"test.ping": None} 798 minion = salt.minion.Minion(mock_opts, io_loop=io_loop) 799 minion.beacons = salt.beacons.Beacon(mock_opts, mock_functions) 800 801 bdata = [{"salt-master": "stopped"}, {"apache2": "stopped"}] 802 data = {"name": "ps", "beacon_data": bdata, "func": "add"} 803 804 tag = "manage_beacons" 805 log.debug("==== minion.opts %s ====", minion.opts) 806 807 minion.manage_beacons(tag, data) 808 assert "ps" in minion.opts["beacons"] 809 assert minion.opts["beacons"]["ps"] == bdata 810 finally: 811 minion.destroy() 812 813 814def test_prep_ip_port(): 815 _ip = ipaddress.ip_address 816 817 opts = {"master": "10.10.0.3", "master_uri_format": "ip_only"} 818 ret = salt.minion.prep_ip_port(opts) 819 assert ret == {"master": _ip("10.10.0.3")} 820 821 opts = { 822 "master": "10.10.0.3", 823 "master_port": 1234, 824 "master_uri_format": "default", 825 } 826 ret = salt.minion.prep_ip_port(opts) 827 assert ret == {"master": "10.10.0.3"} 828 829 opts = {"master": "10.10.0.3:1234", "master_uri_format": "default"} 830 ret = salt.minion.prep_ip_port(opts) 831 assert ret == {"master": "10.10.0.3", "master_port": 1234} 832 833 opts = {"master": "host name", "master_uri_format": "default"} 834 pytest.raises(SaltClientError, salt.minion.prep_ip_port, opts) 835 836 opts = {"master": "10.10.0.3:abcd", "master_uri_format": "default"} 837 pytest.raises(SaltClientError, salt.minion.prep_ip_port, opts) 838 839 opts = {"master": "10.10.0.3::1234", "master_uri_format": "default"} 840 pytest.raises(SaltClientError, salt.minion.prep_ip_port, opts) 841 842 843@pytest.mark.skip_if_not_root 844def test_sock_path_len(): 845 """ 846 This tests whether or not a larger hash causes the sock path to exceed 847 the system's max sock path length. See the below link for more 848 information. 849 850 https://github.com/saltstack/salt/issues/12172#issuecomment-43903643 851 """ 852 opts = { 853 "id": "salt-testing", 854 "hash_type": "sha512", 855 "sock_dir": os.path.join(salt.syspaths.SOCK_DIR, "minion"), 856 "extension_modules": "", 857 } 858 opts = salt.config.DEFAULT_MINION_OPTS.copy() 859 with patch.dict(opts, opts): 860 try: 861 event_publisher = event.AsyncEventPublisher(opts) 862 result = True 863 except ValueError: 864 # There are rare cases where we operate a closed socket, especially in containers. 865 # In this case, don't fail the test because we'll catch it down the road. 866 result = True 867 except SaltSystemExit: 868 result = False 869 assert result 870 871 872@pytest.mark.skip_on_windows(reason="Skippin, no Salt master running on Windows.") 873def test_master_type_failover(): 874 """ 875 Tests master_type "failover" to not fall back to 127.0.0.1 address when master does not resolve in DNS 876 """ 877 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 878 mock_opts.update( 879 { 880 "master_type": "failover", 881 "master": ["master1", "master2"], 882 "__role": "", 883 "retry_dns": 0, 884 } 885 ) 886 887 class MockPubChannel: 888 def connect(self): 889 raise SaltClientError("MockedChannel") 890 891 def close(self): 892 return 893 894 def mock_resolve_dns(opts, fallback=False): 895 assert not fallback 896 897 if opts["master"] == "master1": 898 raise SaltClientError("Cannot resolve {}".format(opts["master"])) 899 900 return { 901 "master_ip": "192.168.2.1", 902 "master_uri": "tcp://192.168.2.1:4505", 903 } 904 905 def mock_transport_factory(opts, **kwargs): 906 assert opts["master"] == "master2" 907 return MockPubChannel() 908 909 with patch("salt.minion.resolve_dns", mock_resolve_dns), patch( 910 "salt.transport.client.AsyncPubChannel.factory", mock_transport_factory 911 ), patch("salt.loader.grains", MagicMock(return_value=[])): 912 with pytest.raises(SaltClientError): 913 minion = salt.minion.Minion(mock_opts) 914 yield minion.connect_master() 915 916 917def test_master_type_failover_no_masters(): 918 """ 919 Tests master_type "failover" to not fall back to 127.0.0.1 address when no master can be resolved 920 """ 921 mock_opts = salt.config.DEFAULT_MINION_OPTS.copy() 922 mock_opts.update( 923 { 924 "master_type": "failover", 925 "master": ["master1", "master2"], 926 "__role": "", 927 "retry_dns": 0, 928 } 929 ) 930 931 def mock_resolve_dns(opts, fallback=False): 932 assert not fallback 933 raise SaltClientError("Cannot resolve {}".format(opts["master"])) 934 935 with patch("salt.minion.resolve_dns", mock_resolve_dns), patch( 936 "salt.loader.grains", MagicMock(return_value=[]) 937 ): 938 with pytest.raises(SaltClientError): 939 minion = salt.minion.Minion(mock_opts) 940 yield minion.connect_master() 941 942 943def test_config_cache_path_overrides(): 944 cachedir = os.path.abspath("/path/to/master/cache") 945 opts = {"cachedir": cachedir, "conf_file": None} 946 947 mminion = salt.minion.MasterMinion(opts) 948 assert mminion.opts["cachedir"] == cachedir 949