1"""
2Manage chassis via Salt Proxies.
3
4.. versionadded:: 2015.8.2
5
6Below is an example state that sets basic parameters:
7
8.. code-block:: yaml
9
10    my-dell-chassis:
11      dellchassis.chassis:
12        - chassis_name: my-dell-chassis
13        - datacenter: dc-1-us
14        - location: my-location
15        - mode: 2
16        - idrac_launch: 1
17        - slot_names:
18          - server-1: my-slot-name
19          - server-2: my-other-slot-name
20        - blade_power_states:
21          - server-1: on
22          - server-2: off
23          - server-3: powercycle
24
25However, it is possible to place the entire set of chassis configuration
26data in pillar. Here's an example pillar structure:
27
28.. code-block:: yaml
29
30    proxy:
31      host: 10.27.20.18
32      admin_username: root
33      fallback_admin_username: root
34      passwords:
35        - super-secret
36        - old-secret
37      proxytype: fx2
38
39      chassis:
40        name: fx2-1
41        username: root
42        password: saltstack1
43        datacenter: london
44        location: rack-1-shelf-3
45        management_mode: 2
46        idrac_launch: 0
47        slot_names:
48          - 'server-1': blade1
49          - 'server-2': blade2
50
51        servers:
52          server-1:
53            idrac_password: saltstack1
54            ipmi_over_lan: True
55            ip: 172.17.17.132
56            netmask: 255.255.0.0
57            gateway: 172.17.17.1
58          server-2:
59            idrac_password: saltstack1
60            ipmi_over_lan: True
61            ip: 172.17.17.2
62            netmask: 255.255.0.0
63            gateway: 172.17.17.1
64          server-3:
65            idrac_password: saltstack1
66            ipmi_over_lan: True
67            ip: 172.17.17.20
68            netmask: 255.255.0.0
69            gateway: 172.17.17.1
70          server-4:
71            idrac_password: saltstack1
72            ipmi_over_lan: True
73            ip: 172.17.17.2
74            netmask: 255.255.0.0
75            gateway: 172.17.17.1
76
77        switches:
78          switch-1:
79            ip: 192.168.1.2
80            netmask: 255.255.255.0
81            gateway: 192.168.1.1
82            snmp: nonpublic
83            password: saltstack1
84          switch-2:
85            ip: 192.168.1.3
86            netmask: 255.255.255.0
87            gateway: 192.168.1.1
88            snmp: nonpublic
89            password: saltstack1
90
91And to go with it, here's an example state that pulls the data from the
92pillar stated above:
93
94.. code-block:: jinja
95
96    {% set details = pillar.get('proxy:chassis', {}) %}
97    standup-step1:
98      dellchassis.chassis:
99        - name: {{ details['name'] }}
100        - location: {{ details['location'] }}
101        - mode: {{ details['management_mode'] }}
102        - idrac_launch: {{ details['idrac_launch'] }}
103        - slot_names:
104          {% for entry details['slot_names'] %}
105            - {{ next(iter(entry)) }}: {{ entry[next(iter(entry))]  }}
106          {% endfor %}
107
108    blade_powercycle:
109      dellchassis.chassis:
110        - blade_power_states:
111          - server-1: powercycle
112          - server-2: powercycle
113          - server-3: powercycle
114          - server-4: powercycle
115
116    # Set idrac_passwords for blades.  racadm needs them to be called 'server-x'
117    {% for k, v in details['servers'].iteritems() %}
118    {{ k }}:
119      dellchassis.blade_idrac:
120        - idrac_password: {{ v['idrac_password'] }}
121    {% endfor %}
122
123    # Set management ip addresses, passwords, and snmp strings for switches
124    {% for k, v in details['switches'].iteritems() %}
125    {{ k }}-switch-setup:
126      dellchassis.switch:
127        - name: {{ k }}
128        - ip: {{ v['ip'] }}
129        - netmask: {{ v['netmask'] }}
130        - gateway: {{ v['gateway'] }}
131        - password: {{ v['password'] }}
132        - snmp: {{ v['snmp'] }}
133    {% endfor %}
134
135.. note::
136
137    This state module relies on the dracr.py execution module, which runs racadm commands on
138    the chassis, blades, etc. The racadm command runs very slowly and, depending on your state,
139    the proxy minion return might timeout before the racadm commands have completed. If you
140    are repeatedly seeing minions timeout after state calls, please use the ``-t`` CLI argument
141    to increase the timeout variable.
142
143    For example:
144
145    .. code-block:: bash
146
147        salt '*' state.sls my-dell-chasis-state-name -t 60
148
149.. note::
150
151    The Dell CMC units perform adequately but many iDRACs are **excruciatingly**
152    slow.  Some functions can take minutes to execute.
153
154"""
155
156
157import logging
158import os
159
160from salt.exceptions import CommandExecutionError
161
162# Import Salt lobs
163
164# Get logging started
165log = logging.getLogger(__name__)
166
167
168def __virtual__():
169    if "chassis.cmd" in __salt__:
170        return True
171    return (False, "chassis module could not be loaded")
172
173
174def blade_idrac(
175    name,
176    idrac_password=None,
177    idrac_ipmi=None,
178    idrac_ip=None,
179    idrac_netmask=None,
180    idrac_gateway=None,
181    idrac_dnsname=None,
182    idrac_dhcp=None,
183):
184    """
185    Set parameters for iDRAC in a blade.
186
187    :param idrac_password: Password to use to connect to the iDRACs directly
188        (idrac_ipmi and idrac_dnsname must be set directly on the iDRAC.  They
189        can't be set through the CMC.  If this password is present, use it
190        instead of the CMC password)
191    :param idrac_ipmi: Enable/Disable IPMI over LAN
192    :param idrac_ip: Set IP address for iDRAC
193    :param idrac_netmask: Set netmask for iDRAC
194    :param idrac_gateway: Set gateway for iDRAC
195    :param idrac_dhcp: Turn on DHCP for iDRAC (True turns on, False does
196        nothing becaause setting a static IP will disable DHCP).
197
198    :return: A standard Salt changes dictionary
199
200    NOTE: If any of the IP address settings is configured, all of ip, netmask,
201    and gateway must be present
202    """
203
204    ret = {"name": name, "result": True, "changes": {}, "comment": ""}
205
206    if not idrac_password:
207        (username, password) = __salt__["chassis.chassis_credentials"]()
208    else:
209        password = idrac_password
210
211    module_network = __salt__["chassis.cmd"]("network_info", module=name)
212    current_idrac_ip = module_network["Network"]["IP Address"]
213
214    if idrac_ipmi is not None:
215        if idrac_ipmi is True or idrac_ipmi == 1:
216            idrac_ipmi = "1"
217        if idrac_ipmi is False or idrac_ipmi == 0:
218            idrac_ipmi = "0"
219        current_ipmi = __salt__["dracr.get_general"](
220            "cfgIpmiLan",
221            "cfgIpmiLanEnable",
222            host=current_idrac_ip,
223            admin_username="root",
224            admin_password=password,
225        )
226
227        if current_ipmi != idrac_ipmi:
228            ch = {"Old": current_ipmi, "New": idrac_ipmi}
229            ret["changes"]["IPMI"] = ch
230
231    if idrac_dnsname is not None:
232        dnsret = __salt__["dracr.get_dns_dracname"](
233            host=current_idrac_ip, admin_username="root", admin_password=password
234        )
235        current_dnsname = dnsret["[Key=iDRAC.Embedded.1#NIC.1]"]["DNSRacName"]
236        if current_dnsname != idrac_dnsname:
237            ch = {"Old": current_dnsname, "New": idrac_dnsname}
238            ret["changes"]["DNSRacName"] = ch
239
240    if idrac_dhcp is not None or idrac_ip or idrac_netmask or idrac_gateway:
241        if idrac_dhcp is True or idrac_dhcp == 1:
242            idrac_dhcp = 1
243        else:
244            idrac_dhcp = 0
245        if str(module_network["Network"]["DHCP Enabled"]) == "0" and idrac_dhcp == 1:
246            ch = {"Old": module_network["Network"]["DHCP Enabled"], "New": idrac_dhcp}
247            ret["changes"]["DRAC DHCP"] = ch
248
249        if idrac_dhcp == 0 and all([idrac_ip, idrac_netmask, idrac_netmask]):
250            current_network = __salt__["chassis.cmd"]("network_info", module=name)
251            old_ipv4 = {}
252            new_ipv4 = {}
253            if current_network["Network"]["IP Address"] != idrac_ip:
254                old_ipv4["ip"] = current_network["Network"]["IP Address"]
255                new_ipv4["ip"] = idrac_ip
256            if current_network["Network"]["Subnet Mask"] != idrac_netmask:
257                old_ipv4["netmask"] = current_network["Network"]["Subnet Mask"]
258                new_ipv4["netmask"] = idrac_netmask
259            if current_network["Network"]["Gateway"] != idrac_gateway:
260                old_ipv4["gateway"] = current_network["Network"]["Gateway"]
261                new_ipv4["gateway"] = idrac_gateway
262
263            if new_ipv4 != {}:
264                ret["changes"]["Network"] = {}
265                ret["changes"]["Network"]["Old"] = old_ipv4
266                ret["changes"]["Network"]["New"] = new_ipv4
267
268    if ret["changes"] == {}:
269        ret["comment"] = "iDRAC on blade is already in the desired state."
270        return ret
271
272    if __opts__["test"] and ret["changes"] != {}:
273        ret["result"] = None
274        ret["comment"] = "iDRAC on blade will change."
275        return ret
276
277    if "IPMI" in ret["changes"]:
278        ipmi_result = __salt__["dracr.set_general"](
279            "cfgIpmiLan",
280            "cfgIpmiLanEnable",
281            idrac_ipmi,
282            host=current_idrac_ip,
283            admin_username="root",
284            admin_password=password,
285        )
286        if not ipmi_result:
287            ret["result"] = False
288            ret["changes"]["IPMI"]["success"] = False
289
290    if "DNSRacName" in ret["changes"]:
291        dnsracname_result = __salt__["dracr.set_dns_dracname"](
292            idrac_dnsname,
293            host=current_idrac_ip,
294            admin_username="root",
295            admin_password=password,
296        )
297        if dnsracname_result["retcode"] == 0:
298            ret["changes"]["DNSRacName"]["success"] = True
299        else:
300            ret["result"] = False
301            ret["changes"]["DNSRacName"]["success"] = False
302            ret["changes"]["DNSRacName"]["return"] = dnsracname_result
303
304    if "DRAC DHCP" in ret["changes"]:
305        dhcp_result = __salt__["chassis.cmd"]("set_niccfg", dhcp=idrac_dhcp)
306        if dhcp_result["retcode"]:
307            ret["changes"]["DRAC DHCP"]["success"] = True
308        else:
309            ret["result"] = False
310            ret["changes"]["DRAC DHCP"]["success"] = False
311            ret["changes"]["DRAC DHCP"]["return"] = dhcp_result
312
313    if "Network" in ret["changes"]:
314        network_result = __salt__["chassis.cmd"](
315            "set_niccfg",
316            ip=idrac_ip,
317            netmask=idrac_netmask,
318            gateway=idrac_gateway,
319            module=name,
320        )
321        if network_result["retcode"] == 0:
322            ret["changes"]["Network"]["success"] = True
323        else:
324            ret["result"] = False
325            ret["changes"]["Network"]["success"] = False
326            ret["changes"]["Network"]["return"] = network_result
327
328    return ret
329
330
331def chassis(
332    name,
333    chassis_name=None,
334    password=None,
335    datacenter=None,
336    location=None,
337    mode=None,
338    idrac_launch=None,
339    slot_names=None,
340    blade_power_states=None,
341):
342    """
343    Manage a Dell Chassis.
344
345    chassis_name
346        The name of the chassis.
347
348    datacenter
349        The datacenter in which the chassis is located
350
351    location
352        The location of the chassis.
353
354    password
355        Password for the chassis. Note: If this password is set for the chassis,
356        the current implementation of this state will set this password both on
357        the chassis and the iDrac passwords on any configured blades. If the
358        password for the blades should be distinct, they should be set separately
359        with the blade_idrac function.
360
361    mode
362        The management mode of the chassis. Viable options are:
363
364        - 0: None
365        - 1: Monitor
366        - 2: Manage and Monitor
367
368    idrac_launch
369        The iDRAC launch method of the chassis. Viable options are:
370
371        - 0: Disabled (launch iDRAC using IP address)
372        - 1: Enabled (launch iDRAC using DNS name)
373
374    slot_names
375        The names of the slots, provided as a list identified by
376        their slot numbers.
377
378    blade_power_states
379        The power states of a blade server, provided as a list and
380        identified by their server numbers. Viable options are:
381
382         - on: Ensure the blade server is powered on.
383         - off: Ensure the blade server is powered off.
384         - powercycle: Power cycle the blade server.
385
386    Example:
387
388    .. code-block:: yaml
389
390        my-dell-chassis:
391          dellchassis.chassis:
392            - chassis_name: my-dell-chassis
393            - location: my-location
394            - datacenter: london
395            - mode: 2
396            - idrac_launch: 1
397            - slot_names:
398              - 1: my-slot-name
399              - 2: my-other-slot-name
400            - blade_power_states:
401              - server-1: on
402              - server-2: off
403              - server-3: powercycle
404    """
405    ret = {
406        "name": chassis_name,
407        "chassis_name": chassis_name,
408        "result": True,
409        "changes": {},
410        "comment": "",
411    }
412
413    chassis_cmd = "chassis.cmd"
414    cfg_tuning = "cfgRacTuning"
415    mode_cmd = "cfgRacTuneChassisMgmtAtServer"
416    launch_cmd = "cfgRacTuneIdracDNSLaunchEnable"
417
418    inventory = __salt__[chassis_cmd]("inventory")
419
420    if idrac_launch:
421        idrac_launch = str(idrac_launch)
422
423    current_name = __salt__[chassis_cmd]("get_chassis_name")
424    if chassis_name != current_name:
425        ret["changes"].update({"Name": {"Old": current_name, "New": chassis_name}})
426
427    current_dc = __salt__[chassis_cmd]("get_chassis_datacenter")
428    if datacenter and datacenter != current_dc:
429        ret["changes"].update({"Datacenter": {"Old": current_dc, "New": datacenter}})
430
431    if password:
432        ret["changes"].update({"Password": {"Old": "******", "New": "******"}})
433    if location:
434        current_location = __salt__[chassis_cmd]("get_chassis_location")
435        if location != current_location:
436            ret["changes"].update(
437                {"Location": {"Old": current_location, "New": location}}
438            )
439    if mode:
440        current_mode = __salt__[chassis_cmd]("get_general", cfg_tuning, mode_cmd)
441        if mode != current_mode:
442            ret["changes"].update(
443                {"Management Mode": {"Old": current_mode, "New": mode}}
444            )
445
446    if idrac_launch:
447        current_launch_method = __salt__[chassis_cmd](
448            "get_general", cfg_tuning, launch_cmd
449        )
450        if idrac_launch != current_launch_method:
451            ret["changes"].update(
452                {
453                    "iDrac Launch Method": {
454                        "Old": current_launch_method,
455                        "New": idrac_launch,
456                    }
457                }
458            )
459
460    if slot_names:
461        current_slot_names = __salt__[chassis_cmd]("list_slotnames")
462        for s in slot_names:
463            key = next(iter(s))
464            new_name = s[key]
465            if key.startswith("slot-"):
466                key = key[5:]
467
468            current_slot_name = current_slot_names.get(key).get("slotname")
469            if current_slot_name != new_name:
470                old = {key: current_slot_name}
471                new = {key: new_name}
472                if ret["changes"].get("Slot Names") is None:
473                    ret["changes"].update({"Slot Names": {"Old": {}, "New": {}}})
474                ret["changes"]["Slot Names"]["Old"].update(old)
475                ret["changes"]["Slot Names"]["New"].update(new)
476
477    current_power_states = {}
478    target_power_states = {}
479    if blade_power_states:
480        for b in blade_power_states:
481            key = next(iter(b))
482            status = __salt__[chassis_cmd]("server_powerstatus", module=key)
483            current_power_states[key] = status.get("status", -1)
484            if b[key] == "powerdown":
485                if current_power_states[key] != -1 and current_power_states[key]:
486                    target_power_states[key] = "powerdown"
487            if b[key] == "powerup":
488                if current_power_states[key] != -1 and not current_power_states[key]:
489                    target_power_states[key] = "powerup"
490            if b[key] == "powercycle":
491                if current_power_states[key] != -1 and not current_power_states[key]:
492                    target_power_states[key] = "powerup"
493                if current_power_states[key] != -1 and current_power_states[key]:
494                    target_power_states[key] = "powercycle"
495        for k, v in target_power_states.items():
496            old = {k: current_power_states[k]}
497            new = {k: v}
498            if ret["changes"].get("Blade Power States") is None:
499                ret["changes"].update({"Blade Power States": {"Old": {}, "New": {}}})
500            ret["changes"]["Blade Power States"]["Old"].update(old)
501            ret["changes"]["Blade Power States"]["New"].update(new)
502
503    if ret["changes"] == {}:
504        ret["comment"] = "Dell chassis is already in the desired state."
505        return ret
506
507    if __opts__["test"]:
508        ret["result"] = None
509        ret["comment"] = "Dell chassis configuration will change."
510        return ret
511
512    # Finally, set the necessary configurations on the chassis.
513    name = __salt__[chassis_cmd]("set_chassis_name", chassis_name)
514    if location:
515        location = __salt__[chassis_cmd]("set_chassis_location", location)
516    pw_result = True
517    if password:
518        pw_single = True
519        if __salt__[chassis_cmd](
520            "change_password", username="root", uid=1, password=password
521        ):
522            for blade in inventory["server"]:
523                pw_single = __salt__[chassis_cmd](
524                    "deploy_password", username="root", password=password, module=blade
525                )
526                if not pw_single:
527                    pw_result = False
528        else:
529            pw_result = False
530
531    if datacenter:
532        datacenter_result = __salt__[chassis_cmd]("set_chassis_datacenter", datacenter)
533    if mode:
534        mode = __salt__[chassis_cmd]("set_general", cfg_tuning, mode_cmd, mode)
535    if idrac_launch:
536        idrac_launch = __salt__[chassis_cmd](
537            "set_general", cfg_tuning, launch_cmd, idrac_launch
538        )
539    if ret["changes"].get("Slot Names") is not None:
540        slot_rets = []
541        for s in slot_names:
542            key = next(iter(s))
543            new_name = s[key]
544            if key.startswith("slot-"):
545                key = key[5:]
546            slot_rets.append(__salt__[chassis_cmd]("set_slotname", key, new_name))
547
548        if any(slot_rets) is False:
549            slot_names = False
550        else:
551            slot_names = True
552
553    powerchange_all_ok = True
554    for k, v in target_power_states.items():
555        powerchange_ok = __salt__[chassis_cmd]("server_power", v, module=k)
556        if not powerchange_ok:
557            powerchange_all_ok = False
558
559    if (
560        any([name, location, mode, idrac_launch, slot_names, powerchange_all_ok])
561        is False
562    ):
563        ret["result"] = False
564        ret["comment"] = "There was an error setting the Dell chassis."
565
566    ret["comment"] = "Dell chassis was updated."
567    return ret
568
569
570def switch(
571    name, ip=None, netmask=None, gateway=None, dhcp=None, password=None, snmp=None
572):
573    """
574    Manage switches in a Dell Chassis.
575
576    name
577        The switch designation (e.g. switch-1, switch-2)
578
579    ip
580        The Static IP Address of the switch
581
582    netmask
583        The netmask for the static IP
584
585    gateway
586        The gateway for the static IP
587
588    dhcp
589        True: Enable DHCP
590        False: Do not change DHCP setup
591        (disabling DHCP is automatic when a static IP is set)
592
593    password
594        The access (root) password for the switch
595
596    snmp
597        The SNMP community string for the switch
598
599    Example:
600
601    .. code-block:: yaml
602
603        my-dell-chassis:
604          dellchassis.switch:
605            - switch: switch-1
606            - ip: 192.168.1.1
607            - netmask: 255.255.255.0
608            - gateway: 192.168.1.254
609            - dhcp: True
610            - password: secret
611            - snmp: public
612
613    """
614    ret = {"name": name, "result": True, "changes": {}, "comment": ""}
615
616    current_nic = __salt__["chassis.cmd"]("network_info", module=name)
617    try:
618        if current_nic.get("retcode", 0) != 0:
619            ret["result"] = False
620            ret["comment"] = current_nic["stdout"]
621            return ret
622
623        if ip or netmask or gateway:
624            if not ip:
625                ip = current_nic["Network"]["IP Address"]
626            if not netmask:
627                ip = current_nic["Network"]["Subnet Mask"]
628            if not gateway:
629                ip = current_nic["Network"]["Gateway"]
630
631        if current_nic["Network"]["DHCP Enabled"] == "0" and dhcp:
632            ret["changes"].update(
633                {
634                    "DHCP": {
635                        "Old": {"DHCP Enabled": current_nic["Network"]["DHCP Enabled"]},
636                        "New": {"DHCP Enabled": dhcp},
637                    }
638                }
639            )
640
641        if (
642            (ip or netmask or gateway)
643            and not dhcp
644            and (
645                ip != current_nic["Network"]["IP Address"]
646                or netmask != current_nic["Network"]["Subnet Mask"]
647                or gateway != current_nic["Network"]["Gateway"]
648            )
649        ):
650            ret["changes"].update(
651                {
652                    "IP": {
653                        "Old": current_nic["Network"],
654                        "New": {
655                            "IP Address": ip,
656                            "Subnet Mask": netmask,
657                            "Gateway": gateway,
658                        },
659                    }
660                }
661            )
662
663        if password:
664            if "New" not in ret["changes"]:
665                ret["changes"]["New"] = {}
666            ret["changes"]["New"].update({"Password": "*****"})
667
668        if snmp:
669            if "New" not in ret["changes"]:
670                ret["changes"]["New"] = {}
671            ret["changes"]["New"].update({"SNMP": "*****"})
672
673        if ret["changes"] == {}:
674            ret["comment"] = "Switch " + name + " is already in desired state"
675            return ret
676    except AttributeError:
677        ret["changes"] = {}
678        ret["comment"] = "Something went wrong retrieving the switch details"
679        return ret
680
681    if __opts__["test"]:
682        ret["result"] = None
683        ret["comment"] = "Switch " + name + " configuration will change"
684        return ret
685
686    # Finally, set the necessary configurations on the chassis.
687    dhcp_ret = net_ret = password_ret = snmp_ret = True
688    if dhcp:
689        dhcp_ret = __salt__["chassis.cmd"]("set_niccfg", module=name, dhcp=dhcp)
690    if ip or netmask or gateway:
691        net_ret = __salt__["chassis.cmd"](
692            "set_niccfg", ip, netmask, gateway, module=name
693        )
694    if password:
695        password_ret = __salt__["chassis.cmd"](
696            "deploy_password", "root", password, module=name
697        )
698
699    if snmp:
700        snmp_ret = __salt__["chassis.cmd"]("deploy_snmp", snmp, module=name)
701
702    if any([password_ret, snmp_ret, net_ret, dhcp_ret]) is False:
703        ret["result"] = False
704        ret["comment"] = "There was an error setting the switch {}.".format(name)
705
706    ret["comment"] = "Dell chassis switch {} was updated.".format(name)
707    return ret
708
709
710def _firmware_update(firmwarefile="", host="", directory=""):
711    """
712    Update firmware for a single host
713    """
714    dest = os.path.join(directory, firmwarefile[7:])
715
716    __salt__["cp.get_file"](firmwarefile, dest)
717
718    username = __pillar__["proxy"]["admin_user"]
719    password = __pillar__["proxy"]["admin_password"]
720    __salt__["dracr.update_firmware"](
721        dest, host=host, admin_username=username, admin_password=password
722    )
723
724
725def firmware_update(hosts=None, directory=""):
726    """
727        State to update the firmware on host
728        using the ``racadm`` command
729
730        firmwarefile
731            filename (string) starting with ``salt://``
732        host
733            string representing the hostname
734            supplied to the ``racadm`` command
735        directory
736            Directory name where firmwarefile
737            will be downloaded
738
739    .. code-block:: yaml
740
741        dell-chassis-firmware-update:
742          dellchassis.firmware_update:
743            hosts:
744              cmc:
745                salt://firmware_cmc.exe
746              server-1:
747                salt://firmware.exe
748            directory: /opt/firmwares
749    """
750    ret = {}
751    ret.changes = {}
752    success = True
753    for host, firmwarefile in hosts:
754        try:
755            _firmware_update(firmwarefile, host, directory)
756            ret["changes"].update(
757                {
758                    "host": {
759                        "comment": "Firmware update submitted for {}".format(host),
760                        "success": True,
761                    }
762                }
763            )
764        except CommandExecutionError as err:
765            success = False
766            ret["changes"].update(
767                {
768                    "host": {
769                        "comment": "FAILED to update firmware for {}".format(host),
770                        "success": False,
771                        "reason": str(err),
772                    }
773                }
774            )
775    ret["result"] = success
776    return ret
777