1"""
2    :codeauthor: Rahul Handay <rahulha@saltstack.com>
3"""
4
5
6import types
7from datetime import datetime
8
9import salt.modules.win_system as win_system
10import salt.utils.platform
11import salt.utils.stringutils
12from tests.support.mixins import LoaderModuleMockMixin
13from tests.support.mock import MagicMock, Mock, patch
14from tests.support.unit import TestCase, skipIf
15
16try:
17    import wmi
18
19    HAS_WMI = True
20except ImportError:
21    HAS_WMI = False
22
23
24class MockWMI_ComputerSystem:
25    """
26    Mock WMI Win32_ComputerSystem Class
27    """
28
29    BootupState = "Normal boot"
30    Caption = "SALT SERVER"
31    ChassisBootupState = 3
32    ChassisSKUNumber = "3.14159"
33    DNSHostname = "SALT SERVER"
34    Domain = "WORKGROUP"
35    DomainRole = 2
36    Manufacturer = "Dell Inc."
37    Model = "Dell 2980"
38    NetworkServerModeEnabled = True
39    PartOfDomain = False
40    PCSystemType = 4
41    PowerState = 0
42    Status = "OK"
43    SystemType = "x64-based PC"
44    TotalPhysicalMemory = 17078214656
45    ThermalState = 3
46    Workgroup = "WORKGROUP"
47
48    def __init__(self):
49        pass
50
51    @staticmethod
52    def Rename(Name):
53        return Name == Name
54
55    @staticmethod
56    def JoinDomainOrWorkgroup(Name):
57        return [0]
58
59    @staticmethod
60    def UnjoinDomainOrWorkgroup(Password, UserName, FUnjoinOptions):
61        return [0]
62
63
64class MockWMI_OperatingSystem:
65    """
66    Mock WMI Win32_OperatingSystem Class
67    """
68
69    Description = "Because salt goes EVERYWHERE"
70    InstallDate = "20110211131800"
71    LastBootUpTime = "19620612120000"
72    Manufacturer = "Python"
73    Caption = "Salty"
74    NumberOfUsers = 7530000000
75    Organization = "SaltStack"
76    OSArchitecture = "Windows"
77    Primary = True
78    ProductType = 3
79    RegisteredUser = "thatch@saltstack.com"
80    SystemDirectory = "C:\\Windows\\System32"
81    SystemDrive = "C:\\"
82    Version = "10.0.17763"
83    WindowsDirectory = "C:\\Windows"
84
85    def __init__(self):
86        pass
87
88
89class MockWMI_ComputerSystemProduct:
90    def __init__(self):
91        self.SKUNumber = None
92
93
94class MockWMI_Processor:
95    """
96    Mock WMI Win32_Processor Class
97    """
98
99    Manufacturer = "Intel"
100    MaxClockSpeed = 2301
101    NumberOfLogicalProcessors = 8
102    NumberOfCores = 4
103    NumberOfEnabledCore = 4
104
105    def __init__(self):
106        pass
107
108
109class MockWMI_BIOS:
110    """
111    Mock WMI Win32_BIOS Class
112    """
113
114    SerialNumber = "SALTY2011"
115    Manufacturer = "Dell Inc."
116    Version = "DELL - 10283849"
117    Caption = "A12"
118    BIOSVersion = [Version, Caption, "ASUS - 3948D"]
119    Description = Caption
120
121    def __init__(self):
122        pass
123
124
125@skipIf(not HAS_WMI, "WMI only available on Windows")
126@skipIf(not salt.utils.platform.is_windows(), "System is not Windows")
127class WinSystemTestCase(TestCase, LoaderModuleMockMixin):
128    """
129    Test cases for salt.modules.win_system
130    """
131
132    def setup_loader_modules(self):
133        modules_globals = {}
134        # wmi and pythoncom modules are platform specific...
135        mock_pythoncom = types.ModuleType(salt.utils.stringutils.to_str("pythoncom"))
136        sys_modules_patcher = patch.dict("sys.modules", {"pythoncom": mock_pythoncom})
137        sys_modules_patcher.start()
138        self.addCleanup(sys_modules_patcher.stop)
139        self.WMI = Mock()
140        self.addCleanup(delattr, self, "WMI")
141        modules_globals["wmi"] = wmi
142
143        if win_system.HAS_WIN32NET_MODS is False:
144            win32api = types.ModuleType("win32api")
145            now = datetime.now()
146            win32api.GetLocalTime = MagicMock(
147                return_value=[
148                    now.year,
149                    now.month,
150                    now.weekday(),
151                    now.day,
152                    now.hour,
153                    now.minute,
154                    now.second,
155                    now.microsecond,
156                ]
157            )
158            modules_globals["win32api"] = win32api
159            win32net = types.ModuleType("win32net")
160            win32net.NetServerGetInfo = MagicMock()
161            win32net.NetServerSetInfo = MagicMock()
162            modules_globals["win32net"] = win32net
163
164        return {win_system: modules_globals}
165
166    def test_halt(self):
167        """
168        Test to halt a running system
169        """
170        mock = MagicMock(return_value="salt")
171        with patch.object(win_system, "shutdown", mock):
172            self.assertEqual(win_system.halt(), "salt")
173
174    def test_init(self):
175        """
176        Test to change the system runlevel on sysV compatible systems
177        """
178        self.assertEqual(win_system.init(3), "Not implemented on Windows at this time.")
179
180    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
181    def test_poweroff(self):
182        """
183        Test to poweroff a running system
184        """
185        mock = MagicMock(return_value="salt")
186        with patch.object(win_system, "shutdown", mock):
187            self.assertEqual(win_system.poweroff(), "salt")
188
189    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
190    def test_reboot(self):
191        """
192        Test to reboot the system
193        """
194        with patch("salt.modules.win_system.shutdown", MagicMock(return_value=True)):
195            self.assertEqual(win_system.reboot(), True)
196
197    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
198    def test_reboot_with_timeout_in_minutes(self):
199        """
200        Test to reboot the system with a timeout
201        """
202        with patch(
203            "salt.modules.win_system.shutdown", MagicMock(return_value=True)
204        ) as shutdown:
205            self.assertEqual(win_system.reboot(5, in_seconds=False), True)
206            shutdown.assert_called_with(
207                timeout=5, in_seconds=False, reboot=True, only_on_pending_reboot=False
208            )
209
210    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
211    def test_reboot_with_timeout_in_seconds(self):
212        """
213        Test to reboot the system with a timeout
214        """
215        with patch(
216            "salt.modules.win_system.shutdown", MagicMock(return_value=True)
217        ) as shutdown:
218            self.assertEqual(win_system.reboot(5, in_seconds=True), True)
219            shutdown.assert_called_with(
220                timeout=5, in_seconds=True, reboot=True, only_on_pending_reboot=False
221            )
222
223    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
224    def test_reboot_with_wait(self):
225        """
226        Test to reboot the system with a timeout and
227        wait for it to finish
228        """
229        with patch(
230            "salt.modules.win_system.shutdown", MagicMock(return_value=True)
231        ), patch("salt.modules.win_system.time.sleep", MagicMock()) as time:
232            self.assertEqual(win_system.reboot(wait_for_reboot=True), True)
233            time.assert_called_with(330)
234
235    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
236    def test_shutdown(self):
237        """
238        Test to shutdown a running system
239        """
240        with patch(
241            "salt.modules.win_system.win32api.InitiateSystemShutdown", MagicMock()
242        ):
243            self.assertEqual(win_system.shutdown(), True)
244
245    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
246    def test_shutdown_hard(self):
247        """
248        Test to shutdown a running system with no timeout or warning
249        """
250        with patch(
251            "salt.modules.win_system.shutdown", MagicMock(return_value=True)
252        ) as shutdown:
253            self.assertEqual(win_system.shutdown_hard(), True)
254            shutdown.assert_called_with(timeout=0)
255
256    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
257    def test_set_computer_name(self):
258        """
259        Test to set the Windows computer name
260        """
261        with patch(
262            "salt.modules.win_system.windll.kernel32.SetComputerNameExW",
263            MagicMock(return_value=True),
264        ), patch.object(
265            win_system, "get_computer_name", MagicMock(return_value="salt")
266        ), patch.object(
267            win_system,
268            "get_pending_computer_name",
269            MagicMock(return_value="salt_new"),
270        ):
271            self.assertDictEqual(
272                win_system.set_computer_name("salt_new"),
273                {"Computer Name": {"Current": "salt", "Pending": "salt_new"}},
274            )
275        # Test set_computer_name failure
276        with patch(
277            "salt.modules.win_system.windll.kernel32.SetComputerNameExW",
278            MagicMock(return_value=False),
279        ):
280            self.assertFalse(win_system.set_computer_name("salt"))
281
282    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
283    def test_set_computer_desc(self):
284        """
285        Test to set the Windows computer description
286        """
287        mock = MagicMock()
288        mock_get_info = MagicMock(return_value={"comment": ""})
289        mock_get_desc = MagicMock(return_value="Salt's comp")
290        with patch(
291            "salt.modules.win_system.win32net.NetServerGetInfo", mock_get_info
292        ), patch(
293            "salt.modules.win_system.win32net.NetServerSetInfo", mock
294        ), patch.object(
295            win_system, "get_computer_desc", mock_get_desc
296        ):
297            self.assertDictEqual(
298                win_system.set_computer_desc("Salt's comp"),
299                {"Computer Description": "Salt's comp"},
300            )
301
302    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
303    def test_get_computer_desc(self):
304        """
305        Test to get the Windows computer description
306        """
307        with patch(
308            "salt.modules.win_system.get_system_info",
309            MagicMock(
310                side_effect=[{"description": "salt description"}, {"description": None}]
311            ),
312        ):
313            self.assertEqual(win_system.get_computer_desc(), "salt description")
314            self.assertFalse(win_system.get_computer_desc())
315
316    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
317    def test_join_domain(self):
318        """
319        Test to join a computer to an Active Directory domain
320        """
321        with patch(
322            "salt.modules.win_system._join_domain", MagicMock(return_value=0)
323        ), patch(
324            "salt.modules.win_system.get_domain_workgroup",
325            MagicMock(return_value={"Workgroup": "Workgroup"}),
326        ):
327            self.assertDictEqual(
328                win_system.join_domain("saltstack", "salt", "salt@123"),
329                {"Domain": "saltstack", "Restart": False},
330            )
331
332    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
333    def test_join_domain_already_joined(self):
334        """
335        Test to join a computer to an Active Directory domain when it is
336        already joined
337        """
338        with patch(
339            "salt.modules.win_system._join_domain", MagicMock(return_value=0)
340        ), patch(
341            "salt.modules.win_system.get_domain_workgroup",
342            MagicMock(return_value={"Domain": "saltstack"}),
343        ):
344            self.assertEqual(
345                win_system.join_domain("saltstack", "salt", "salt@123"),
346                "Already joined to saltstack",
347            )
348
349    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
350    def test_unjoin_domain(self):
351        """
352        Test unjoining a computer from an Active Directory domain
353        """
354        with patch("salt.utils.winapi.Com", MagicMock()), patch.object(
355            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
356        ), patch.object(wmi, "WMI", Mock(return_value=self.WMI)), patch(
357            "salt.modules.win_system.get_domain_workgroup",
358            MagicMock(return_value={"Domain": "contoso.com"}),
359        ):
360            self.assertDictEqual(
361                win_system.unjoin_domain(),
362                {"Workgroup": "WORKGROUP", "Restart": False},
363            )
364
365    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
366    def test_unjoin_domain_already_unjoined(self):
367        """
368        Test unjoining a computer from an Active Directory domain
369        """
370        with patch("salt.utils.winapi.Com", MagicMock()), patch.object(
371            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
372        ), patch.object(wmi, "WMI", Mock(return_value=self.WMI)), patch(
373            "salt.modules.win_system.get_domain_workgroup",
374            MagicMock(return_value={"Workgroup": "WORKGROUP"}),
375        ):
376            self.assertEqual(win_system.unjoin_domain(), "Already joined to WORKGROUP")
377
378    def test_get_system_time(self):
379        """
380        Test to get system time
381        """
382        tm = datetime.strftime(datetime.now(), "%I:%M:%S %p")
383        win_tm = win_system.get_system_time()
384        try:
385            self.assertEqual(win_tm, tm)
386        except AssertionError:
387            # handle race condition
388            import re
389
390            self.assertTrue(re.search(r"^\d{2}:\d{2} \w{2}$", win_tm))
391
392    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
393    def test_set_system_time(self):
394        """
395        Test to set system time
396        """
397        with patch(
398            "salt.modules.win_system.set_system_date_time",
399            MagicMock(side_effect=[False, True]),
400        ):
401            self.assertFalse(win_system.set_system_time("11:31:15 AM"))
402            self.assertTrue(win_system.set_system_time("11:31:15 AM"))
403
404    def test_get_system_date(self):
405        """
406        Test to get system date
407        """
408        date = datetime.strftime(datetime.now(), "%m/%d/%Y")
409        self.assertEqual(win_system.get_system_date(), date)
410
411    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
412    def test_set_system_date(self):
413        """
414        Test to set system date
415        """
416        with patch(
417            "salt.modules.win_system.set_system_date_time",
418            MagicMock(side_effect=[False, True]),
419        ):
420            self.assertFalse(win_system.set_system_date("03-28-13"))
421            self.assertTrue(win_system.set_system_date("03-28-13"))
422
423    def test_start_time_service(self):
424        """
425        Test to start the Windows time service
426        """
427        mock = MagicMock(return_value=True)
428        with patch.dict(win_system.__salt__, {"service.start": mock}):
429            self.assertTrue(win_system.start_time_service())
430
431    def test_stop_time_service(self):
432        """
433        Test to stop the windows time service
434        """
435        mock = MagicMock(return_value=True)
436        with patch.dict(win_system.__salt__, {"service.stop": mock}):
437            self.assertTrue(win_system.stop_time_service())
438
439    def test_set_hostname(self):
440        """
441        Test setting a new hostname
442        """
443        with patch("salt.utils.winapi.Com", MagicMock()), patch.object(
444            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
445        ), patch.object(wmi, "WMI", Mock(return_value=self.WMI)):
446            self.assertTrue(win_system.set_hostname("NEW"))
447
448    def test_get_domain_workgroup(self):
449        """
450        Test get_domain_workgroup
451        """
452        with patch.object(wmi, "WMI", Mock(return_value=self.WMI)), patch(
453            "salt.utils.winapi.Com", MagicMock()
454        ), patch.object(
455            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
456        ):
457            self.assertDictEqual(
458                win_system.get_domain_workgroup(), {"Workgroup": "WORKGROUP"}
459            )
460
461    def test_set_domain_workgroup(self):
462        """
463        Test set_domain_workgroup
464        """
465        with patch.object(wmi, "WMI", Mock(return_value=self.WMI)), patch(
466            "salt.utils.winapi.Com", MagicMock()
467        ), patch.object(
468            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
469        ):
470            self.assertTrue(win_system.set_domain_workgroup("test"))
471
472    def test_get_hostname(self):
473        """
474        Test getting a new hostname
475        """
476        cmd_run_mock = MagicMock(return_value="MINION")
477        with patch.dict(win_system.__salt__, {"cmd.run": cmd_run_mock}):
478            ret = win_system.get_hostname()
479            self.assertEqual(ret, "MINION")
480        cmd_run_mock.assert_called_once_with(cmd="hostname")
481
482    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
483    def test_get_system_info(self):
484        fields = [
485            "bios_caption",
486            "bios_description",
487            "bios_details",
488            "bios_manufacturer",
489            "bios_version",
490            "bootup_state",
491            "caption",
492            "chassis_bootup_state",
493            "chassis_sku_number",
494            "description",
495            "dns_hostname",
496            "domain",
497            "domain_role",
498            "hardware_manufacturer",
499            "hardware_model",
500            "hardware_serial",
501            "install_date",
502            "last_boot",
503            "name",
504            "network_server_mode_enabled",
505            "organization",
506            "os_architecture",
507            "os_manufacturer",
508            "os_name",
509            "os_type",
510            "os_version",
511            "part_of_domain",
512            "pc_system_type",
513            "power_state",
514            "primary",
515            "processor_cores",
516            "processor_cores_enabled",
517            "processor_manufacturer",
518            "processor_max_clock_speed",
519            "processors",
520            "processors_logical",
521            "registered_user",
522            "status",
523            "system_directory",
524            "system_drive",
525            "system_type",
526            "thermal_state",
527            "total_physical_memory",
528            "total_physical_memory_raw",
529            "users",
530            "windows_directory",
531            "workgroup",
532        ]
533        with patch("salt.utils.win_system.get_computer_name", MagicMock()), patch(
534            "salt.utils.winapi.Com", MagicMock()
535        ), patch.object(
536            self.WMI, "Win32_OperatingSystem", return_value=[MockWMI_OperatingSystem()]
537        ), patch.object(
538            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
539        ), patch.object(
540            self.WMI,
541            "Win32_ComputerSystemProduct",
542            return_value=[MockWMI_ComputerSystemProduct()],
543        ), patch.object(
544            self.WMI,
545            "Win32_Processor",
546            return_value=[MockWMI_Processor(), MockWMI_Processor()],
547        ), patch.object(
548            self.WMI, "Win32_BIOS", return_value=[MockWMI_BIOS()]
549        ), patch.object(
550            wmi, "WMI", Mock(return_value=self.WMI)
551        ):
552            ret = win_system.get_system_info()
553        # Make sure all the fields are in the return
554        for field in fields:
555            self.assertIn(field, ret)
556        # os_type
557        os_types = ["Work Station", "Domain Controller", "Server"]
558        self.assertIn(ret["os_type"], os_types)
559        domain_roles = [
560            "Standalone Workstation",
561            "Member Workstation",
562            "Standalone Server",
563            "Member Server",
564            "Backup Domain Controller",
565            "Primary Domain Controller",
566        ]
567        self.assertIn(ret["domain_role"], domain_roles)
568        system_types = [
569            "Unspecified",
570            "Desktop",
571            "Mobile",
572            "Workstation",
573            "Enterprise Server",
574            "SOHO Server",
575            "Appliance PC",
576            "Performance Server",
577            "Slate",
578            "Maximum",
579        ]
580        self.assertIn(ret["pc_system_type"], system_types)
581        warning_states = [
582            "Other",
583            "Unknown",
584            "Safe",
585            "Warning",
586            "Critical",
587            "Non-recoverable",
588        ]
589        self.assertIn(ret["chassis_bootup_state"], warning_states)
590        self.assertIn(ret["thermal_state"], warning_states)
591
592    @skipIf(not win_system.HAS_WIN32NET_MODS, "Missing win32 libraries")
593    def test_get_system_info_no_number_of_enabled_core(self):
594        """
595        Tests get_system_info when there is no `NumberOfEnabledCore` property in
596        the WMI Class Win32_Processor. Older versions of Windows are missing
597        this property
598        """
599        # Create a mock processor class that does not have the
600        # NumberOfCoresEnabled property
601        class MockWMIProcessor:
602            """
603            Mock WMI Win32_Processor Class
604            """
605
606            def __init__(self):
607                self.Manufacturer = "Intel"
608                self.MaxClockSpeed = 2301
609                self.NumberOfLogicalProcessors = 8
610                self.NumberOfCores = 4
611
612        with patch("salt.utils.win_system.get_computer_name", MagicMock()), patch(
613            "salt.utils.winapi.Com", MagicMock()
614        ), patch.object(
615            self.WMI, "Win32_OperatingSystem", return_value=[MockWMI_OperatingSystem()]
616        ), patch.object(
617            self.WMI, "Win32_ComputerSystem", return_value=[MockWMI_ComputerSystem()]
618        ), patch.object(
619            self.WMI,
620            "Win32_ComputerSystemProduct",
621            return_value=[MockWMI_ComputerSystemProduct()],
622        ), patch.object(
623            self.WMI,
624            "Win32_Processor",
625            return_value=[MockWMIProcessor(), MockWMIProcessor()],
626        ), patch.object(
627            self.WMI, "Win32_BIOS", return_value=[MockWMI_BIOS()]
628        ), patch.object(
629            wmi, "WMI", Mock(return_value=self.WMI)
630        ):
631            ret = win_system.get_system_info()
632            self.assertIn("processors", ret)
633            self.assertIn("processors_logical", ret)
634            self.assertIn("processor_cores", ret)
635            self.assertIn("processor_manufacturer", ret)
636            self.assertIn("processor_max_clock_speed", ret)
637            self.assertNotIn("processor_cores_enabled", ret)
638