1""" 2 :codeauthor: Justin Anderson <janderson@saltstack.com> 3""" 4import pathlib 5import shutil 6 7import attr 8import pytest 9from tests.support.helpers import PRE_PYTEST_SKIP_OR_NOT 10 11pytestmark = [ 12 pytest.mark.slow_test, 13 pytest.mark.windows_whitelisted, 14] 15 16 17@attr.s(frozen=True, slots=True) 18class SaltCallCliWrapper: 19 salt_call_cli = attr.ib() 20 21 def run(self, *args, **kwargs): 22 beacon_event_timeout = 120 23 cli_run_timeout = beacon_event_timeout + 30 24 return self.salt_call_cli.run( 25 *args, timeout=beacon_event_timeout, _timeout=cli_run_timeout, **kwargs 26 ) 27 28 29@pytest.fixture(scope="module") 30def salt_call_cli(salt_call_cli): 31 return SaltCallCliWrapper(salt_call_cli) 32 33 34@pytest.fixture(scope="module") 35def cleanup_beacons_config_module(salt_minion, salt_call_cli): 36 minion_conf_d_dir = ( 37 pathlib.Path(salt_minion.config_dir) 38 / pathlib.Path(salt_minion.config["default_include"]).parent 39 ) 40 if not minion_conf_d_dir.is_dir(): 41 minion_conf_d_dir.mkdir() 42 beacons_config_file_path = minion_conf_d_dir / "beacons.conf" 43 ret = salt_call_cli.run("beacons.reset") 44 assert ret.exitcode == 0 45 assert ret.json 46 assert ret.json["result"] is True 47 if beacons_config_file_path.exists(): 48 beacons_config_file_path.unlink() 49 try: 50 yield beacons_config_file_path 51 finally: 52 if beacons_config_file_path.exists(): 53 beacons_config_file_path.unlink() 54 55 56@pytest.fixture(autouse=True) 57def cleanup_beacons_config(cleanup_beacons_config_module, salt_call_cli): 58 try: 59 yield cleanup_beacons_config_module 60 finally: 61 ret = salt_call_cli.run("beacons.reset") 62 assert ret.exitcode == 0 63 assert ret.json 64 assert ret.json["result"] is True 65 66 67@pytest.fixture(scope="module") 68def inotify_file_path(tmp_path_factory): 69 inotify_directory = tmp_path_factory.mktemp("important-files") 70 try: 71 yield inotify_directory / "really-important" 72 finally: 73 shutil.rmtree(str(inotify_directory), ignore_errors=True) 74 75 76@pytest.fixture(scope="module") 77def pillar_tree( 78 base_env_pillar_tree_root_dir, salt_minion, salt_call_cli, inotify_file_path 79): 80 top_file = """ 81 base: 82 '{}': 83 - beacons 84 """.format( 85 salt_minion.id 86 ) 87 beacon_pillar_file = """ 88 beacons: 89 inotify: 90 - files: 91 {}: 92 mask: 93 - open 94 - create 95 - close_write 96 """.format( 97 inotify_file_path 98 ) 99 top_tempfile = pytest.helpers.temp_file( 100 "top.sls", top_file, base_env_pillar_tree_root_dir 101 ) 102 beacon_tempfile = pytest.helpers.temp_file( 103 "beacons.sls", beacon_pillar_file, base_env_pillar_tree_root_dir 104 ) 105 try: 106 with top_tempfile, beacon_tempfile: 107 ret = salt_call_cli.run("saltutil.refresh_pillar", wait=True) 108 assert ret.exitcode == 0 109 assert ret.json is True 110 yield 111 finally: 112 # Refresh pillar again to cleaup the temp pillar 113 ret = salt_call_cli.run("saltutil.refresh_pillar", wait=True) 114 assert ret.exitcode == 0 115 assert ret.json is True 116 117 118@attr.s(frozen=True, slots=True) 119class Beacon: 120 name = attr.ib() 121 data = attr.ib() 122 123 124def beacon_instance_ids(value): 125 return str(value) 126 127 128@pytest.fixture( 129 params=[ 130 Beacon("ps", [{"processes": {"apache2": "stopped"}}]), 131 Beacon( 132 "watch_apache", 133 [{"processes": {"apache2": "stopped"}}, {"beacon_module": "ps"}], 134 ), 135 ], 136 ids=beacon_instance_ids, 137) 138def beacon_instance(request): 139 return request.param 140 141 142def test_add_and_delete(salt_call_cli, beacon_instance): 143 """ 144 Test adding and deleting a beacon 145 """ 146 # Add the beacon 147 ret = salt_call_cli.run( 148 "beacons.add", beacon_instance.name, beacon_data=beacon_instance.data 149 ) 150 assert ret.exitcode == 0 151 assert ret.json 152 assert ret.json["result"] is True 153 154 # Save beacons 155 ret = salt_call_cli.run("beacons.save") 156 assert ret.exitcode == 0 157 assert ret.json 158 assert ret.json["result"] is True 159 160 # Delete beacon 161 ret = salt_call_cli.run("beacons.delete", beacon_instance.name) 162 assert ret.exitcode == 0 163 assert ret.json 164 assert ret.json["result"] is True 165 166 167@pytest.fixture 168def beacon(beacon_instance, salt_call_cli): 169 ret = salt_call_cli.run( 170 "beacons.add", beacon_instance.name, beacon_data=beacon_instance.data 171 ) 172 assert ret.exitcode == 0 173 assert ret.json 174 assert ret.json["result"] is True 175 176 # Save beacons 177 ret = salt_call_cli.run("beacons.save") 178 assert ret.exitcode == 0 179 assert ret.json 180 assert ret.json["result"] is True 181 182 # assert beacon exists 183 ret = salt_call_cli.run("beacons.list", return_yaml=False) 184 assert ret.exitcode == 0 185 assert ret.json 186 assert beacon_instance.name in ret.json 187 188 yield beacon_instance 189 190 191def test_disable(salt_call_cli, beacon): 192 """ 193 Test disabling beacons 194 """ 195 ret = salt_call_cli.run("beacons.disable") 196 assert ret.exitcode == 0 197 assert ret.json 198 assert ret.json["result"] is True 199 200 # assert beacons are disabled 201 ret = salt_call_cli.run("beacons.list", return_yaml=False) 202 assert ret.exitcode == 0 203 assert ret.json 204 assert ret.json["enabled"] is False 205 206 # disable added beacon 207 ret = salt_call_cli.run("beacons.disable_beacon", beacon.name) 208 assert ret.exitcode == 0 209 assert ret.json 210 assert ret.json["result"] is True 211 212 # assert beacon is disabled 213 ret = salt_call_cli.run("beacons.list", return_yaml=False) 214 assert ret.exitcode == 0 215 assert ret.json 216 assert beacon.name in ret.json 217 for beacon_data in ret.json[beacon.name]: 218 if "enabled" in beacon_data: 219 assert beacon_data["enabled"] is False 220 break 221 else: 222 pytest.fail("Did not find the beacon data with the 'enabled' key") 223 224 225@pytest.fixture 226def disabled_beacon(beacon, salt_call_cli): 227 ret = salt_call_cli.run("beacons.disable") 228 assert ret.exitcode == 0 229 assert ret.json 230 assert ret.json["result"] is True 231 return beacon 232 233 234def test_enable(salt_call_cli, disabled_beacon): 235 """ 236 Test enabling beacons 237 """ 238 # enable beacons on minion 239 ret = salt_call_cli.run("beacons.enable") 240 assert ret.exitcode == 0 241 assert ret.json 242 assert ret.json["result"] is True 243 244 # assert beacons are enabled 245 ret = salt_call_cli.run("beacons.list", return_yaml=False) 246 assert ret.exitcode == 0 247 assert ret.json 248 assert ret.json["enabled"] is True 249 250 251@pytest.mark.skipif( 252 PRE_PYTEST_SKIP_OR_NOT, 253 reason=( 254 "Skip until https://github.com/saltstack/salt/issues/31516 problems are" 255 " resolved." 256 ), 257) 258def test_enabled_beacons(salt_call_cli, beacon): 259 """ 260 Test enabled specific beacon 261 """ 262 # enable added beacon 263 ret = salt_call_cli.run("beacons.enable_beacon", beacon.name) 264 assert ret.exitcode == 0 265 assert ret.json 266 assert ret.json["result"] is True 267 268 # assert beacon ps is enabled 269 ret = salt_call_cli.run("beacons.list", return_yaml=False) 270 assert ret.exitcode == 0 271 assert ret.json 272 assert ret.json["enabled"] is True 273 assert beacon.name in ret.json 274 for beacon_data in ret.json[beacon.name]: 275 if "enabled" in beacon_data: 276 assert beacon_data["enabled"] is False 277 break 278 else: 279 pytest.fail("Did not find the beacon data with the 'enabled' key") 280 281 282@pytest.mark.usefixtures("pillar_tree") 283def test_list(salt_call_cli, beacon, inotify_file_path): 284 """ 285 Test listing the beacons 286 """ 287 # list beacons 288 ret = salt_call_cli.run("beacons.list", return_yaml=False) 289 assert ret.exitcode == 0 290 assert ret.json 291 assert ret.json == { 292 beacon.name: beacon.data, 293 "inotify": [ 294 { 295 "files": { 296 str(inotify_file_path): {"mask": ["open", "create", "close_write"]} 297 } 298 } 299 ], 300 } 301 302 303@pytest.mark.usefixtures("pillar_tree") 304def test_list_only_include_opts(salt_call_cli, beacon): 305 """ 306 Test listing the beacons which only exist in opts 307 308 When beacon.save is used to save the running beacons to 309 a file, it uses beacons.list to get that list and should 310 only return those from opts and not pillar. 311 312 In this test, we're making sure we get only get back the 313 beacons that are in opts and not those in pillar. 314 """ 315 # list beacons 316 ret = salt_call_cli.run( 317 "beacons.list", return_yaml=False, include_opts=True, include_pillar=False 318 ) 319 assert ret.exitcode == 0 320 assert ret.json 321 assert ret.json == {beacon.name: beacon.data} 322 323 324@pytest.mark.usefixtures("pillar_tree", "beacon") 325def test_list_only_include_pillar(salt_call_cli, inotify_file_path): 326 """ 327 Test listing the beacons which only exist in pillar 328 """ 329 # list beacons 330 ret = salt_call_cli.run( 331 "beacons.list", return_yaml=False, include_opts=False, include_pillar=True 332 ) 333 assert ret.exitcode == 0 334 assert ret.json 335 assert ret.json == { 336 "inotify": [ 337 { 338 "files": { 339 str(inotify_file_path): {"mask": ["open", "create", "close_write"]} 340 } 341 } 342 ] 343 } 344 345 346def test_list_available(salt_call_cli): 347 """ 348 Test listing the beacons 349 """ 350 # list beacons 351 ret = salt_call_cli.run("beacons.list_available", return_yaml=False) 352 assert ret.exitcode == 0 353 assert ret.json 354 assert "ps" in ret.json 355