Mini Shell
"""
Manage chassis via Salt Proxies.
.. versionadded:: 2015.8.2
Below is an example state that sets basic parameters:
.. code-block:: yaml
my-dell-chassis:
dellchassis.chassis:
- chassis_name: my-dell-chassis
- datacenter: dc-1-us
- location: my-location
- mode: 2
- idrac_launch: 1
- slot_names:
- server-1: my-slot-name
- server-2: my-other-slot-name
- blade_power_states:
- server-1: on
- server-2: off
- server-3: powercycle
However, it is possible to place the entire set of chassis configuration
data in pillar. Here's an example pillar structure:
.. code-block:: yaml
proxy:
host: 10.27.20.18
admin_username: root
fallback_admin_username: root
passwords:
- super-secret
- old-secret
proxytype: fx2
chassis:
name: fx2-1
username: root
password: saltstack1
datacenter: london
location: rack-1-shelf-3
management_mode: 2
idrac_launch: 0
slot_names:
- 'server-1': blade1
- 'server-2': blade2
servers:
server-1:
idrac_password: saltstack1
ipmi_over_lan: True
ip: 172.17.17.132
netmask: 255.255.0.0
gateway: 172.17.17.1
server-2:
idrac_password: saltstack1
ipmi_over_lan: True
ip: 172.17.17.2
netmask: 255.255.0.0
gateway: 172.17.17.1
server-3:
idrac_password: saltstack1
ipmi_over_lan: True
ip: 172.17.17.20
netmask: 255.255.0.0
gateway: 172.17.17.1
server-4:
idrac_password: saltstack1
ipmi_over_lan: True
ip: 172.17.17.2
netmask: 255.255.0.0
gateway: 172.17.17.1
switches:
switch-1:
ip: 192.168.1.2
netmask: 255.255.255.0
gateway: 192.168.1.1
snmp: nonpublic
password: saltstack1
switch-2:
ip: 192.168.1.3
netmask: 255.255.255.0
gateway: 192.168.1.1
snmp: nonpublic
password: saltstack1
And to go with it, here's an example state that pulls the data from the
pillar stated above:
.. code-block:: jinja
{% set details = pillar.get('proxy:chassis', {}) %}
standup-step1:
dellchassis.chassis:
- name: {{ details['name'] }}
- location: {{ details['location'] }}
- mode: {{ details['management_mode'] }}
- idrac_launch: {{ details['idrac_launch'] }}
- slot_names:
{% for entry details['slot_names'] %}
- {{ next(iter(entry)) }}: {{ entry[next(iter(entry))] }}
{% endfor %}
blade_powercycle:
dellchassis.chassis:
- blade_power_states:
- server-1: powercycle
- server-2: powercycle
- server-3: powercycle
- server-4: powercycle
# Set idrac_passwords for blades. racadm needs them to be called 'server-x'
{% for k, v in details['servers'].iteritems() %}
{{ k }}:
dellchassis.blade_idrac:
- idrac_password: {{ v['idrac_password'] }}
{% endfor %}
# Set management ip addresses, passwords, and snmp strings for switches
{% for k, v in details['switches'].iteritems() %}
{{ k }}-switch-setup:
dellchassis.switch:
- name: {{ k }}
- ip: {{ v['ip'] }}
- netmask: {{ v['netmask'] }}
- gateway: {{ v['gateway'] }}
- password: {{ v['password'] }}
- snmp: {{ v['snmp'] }}
{% endfor %}
.. note::
This state module relies on the dracr.py execution module, which runs racadm commands on
the chassis, blades, etc. The racadm command runs very slowly and, depending on your state,
the proxy minion return might timeout before the racadm commands have completed. If you
are repeatedly seeing minions timeout after state calls, please use the ``-t`` CLI argument
to increase the timeout variable.
For example:
.. code-block:: bash
salt '*' state.sls my-dell-chasis-state-name -t 60
.. note::
The Dell CMC units perform adequately but many iDRACs are **excruciatingly**
slow. Some functions can take minutes to execute.
"""
import logging
import os
from salt.exceptions import CommandExecutionError
# Import Salt lobs
# Get logging started
log = logging.getLogger(__name__)
def __virtual__():
if "chassis.cmd" in __salt__:
return True
return (False, "chassis module could not be loaded")
def blade_idrac(
name,
idrac_password=None,
idrac_ipmi=None,
idrac_ip=None,
idrac_netmask=None,
idrac_gateway=None,
idrac_dnsname=None,
idrac_dhcp=None,
):
"""
Set parameters for iDRAC in a blade.
:param idrac_password: Password to use to connect to the iDRACs directly
(idrac_ipmi and idrac_dnsname must be set directly on the iDRAC. They
can't be set through the CMC. If this password is present, use it
instead of the CMC password)
:param idrac_ipmi: Enable/Disable IPMI over LAN
:param idrac_ip: Set IP address for iDRAC
:param idrac_netmask: Set netmask for iDRAC
:param idrac_gateway: Set gateway for iDRAC
:param idrac_dhcp: Turn on DHCP for iDRAC (True turns on, False does
nothing becaause setting a static IP will disable DHCP).
:return: A standard Salt changes dictionary
NOTE: If any of the IP address settings is configured, all of ip, netmask,
and gateway must be present
"""
ret = {"name": name, "result": True, "changes": {}, "comment": ""}
if not idrac_password:
(username, password) = __salt__["chassis.chassis_credentials"]()
else:
password = idrac_password
module_network = __salt__["chassis.cmd"]("network_info", module=name)
current_idrac_ip = module_network["Network"]["IP Address"]
if idrac_ipmi is not None:
if idrac_ipmi is True or idrac_ipmi == 1:
idrac_ipmi = "1"
if idrac_ipmi is False or idrac_ipmi == 0:
idrac_ipmi = "0"
current_ipmi = __salt__["dracr.get_general"](
"cfgIpmiLan",
"cfgIpmiLanEnable",
host=current_idrac_ip,
admin_username="root",
admin_password=password,
)
if current_ipmi != idrac_ipmi:
ch = {"Old": current_ipmi, "New": idrac_ipmi}
ret["changes"]["IPMI"] = ch
if idrac_dnsname is not None:
dnsret = __salt__["dracr.get_dns_dracname"](
host=current_idrac_ip, admin_username="root", admin_password=password
)
current_dnsname = dnsret["[Key=iDRAC.Embedded.1#NIC.1]"]["DNSRacName"]
if current_dnsname != idrac_dnsname:
ch = {"Old": current_dnsname, "New": idrac_dnsname}
ret["changes"]["DNSRacName"] = ch
if idrac_dhcp is not None or idrac_ip or idrac_netmask or idrac_gateway:
if idrac_dhcp is True or idrac_dhcp == 1:
idrac_dhcp = 1
else:
idrac_dhcp = 0
if str(module_network["Network"]["DHCP Enabled"]) == "0" and idrac_dhcp == 1:
ch = {"Old": module_network["Network"]["DHCP Enabled"], "New": idrac_dhcp}
ret["changes"]["DRAC DHCP"] = ch
if idrac_dhcp == 0 and all([idrac_ip, idrac_netmask, idrac_netmask]):
current_network = __salt__["chassis.cmd"]("network_info", module=name)
old_ipv4 = {}
new_ipv4 = {}
if current_network["Network"]["IP Address"] != idrac_ip:
old_ipv4["ip"] = current_network["Network"]["IP Address"]
new_ipv4["ip"] = idrac_ip
if current_network["Network"]["Subnet Mask"] != idrac_netmask:
old_ipv4["netmask"] = current_network["Network"]["Subnet Mask"]
new_ipv4["netmask"] = idrac_netmask
if current_network["Network"]["Gateway"] != idrac_gateway:
old_ipv4["gateway"] = current_network["Network"]["Gateway"]
new_ipv4["gateway"] = idrac_gateway
if new_ipv4 != {}:
ret["changes"]["Network"] = {}
ret["changes"]["Network"]["Old"] = old_ipv4
ret["changes"]["Network"]["New"] = new_ipv4
if ret["changes"] == {}:
ret["comment"] = "iDRAC on blade is already in the desired state."
return ret
if __opts__["test"] and ret["changes"] != {}:
ret["result"] = None
ret["comment"] = "iDRAC on blade will change."
return ret
if "IPMI" in ret["changes"]:
ipmi_result = __salt__["dracr.set_general"](
"cfgIpmiLan",
"cfgIpmiLanEnable",
idrac_ipmi,
host=current_idrac_ip,
admin_username="root",
admin_password=password,
)
if not ipmi_result:
ret["result"] = False
ret["changes"]["IPMI"]["success"] = False
if "DNSRacName" in ret["changes"]:
dnsracname_result = __salt__["dracr.set_dns_dracname"](
idrac_dnsname,
host=current_idrac_ip,
admin_username="root",
admin_password=password,
)
if dnsracname_result["retcode"] == 0:
ret["changes"]["DNSRacName"]["success"] = True
else:
ret["result"] = False
ret["changes"]["DNSRacName"]["success"] = False
ret["changes"]["DNSRacName"]["return"] = dnsracname_result
if "DRAC DHCP" in ret["changes"]:
dhcp_result = __salt__["chassis.cmd"]("set_niccfg", dhcp=idrac_dhcp)
if dhcp_result["retcode"]:
ret["changes"]["DRAC DHCP"]["success"] = True
else:
ret["result"] = False
ret["changes"]["DRAC DHCP"]["success"] = False
ret["changes"]["DRAC DHCP"]["return"] = dhcp_result
if "Network" in ret["changes"]:
network_result = __salt__["chassis.cmd"](
"set_niccfg",
ip=idrac_ip,
netmask=idrac_netmask,
gateway=idrac_gateway,
module=name,
)
if network_result["retcode"] == 0:
ret["changes"]["Network"]["success"] = True
else:
ret["result"] = False
ret["changes"]["Network"]["success"] = False
ret["changes"]["Network"]["return"] = network_result
return ret
def chassis(
name,
chassis_name=None,
password=None,
datacenter=None,
location=None,
mode=None,
idrac_launch=None,
slot_names=None,
blade_power_states=None,
):
"""
Manage a Dell Chassis.
chassis_name
The name of the chassis.
datacenter
The datacenter in which the chassis is located
location
The location of the chassis.
password
Password for the chassis. Note: If this password is set for the chassis,
the current implementation of this state will set this password both on
the chassis and the iDrac passwords on any configured blades. If the
password for the blades should be distinct, they should be set separately
with the blade_idrac function.
mode
The management mode of the chassis. Viable options are:
- 0: None
- 1: Monitor
- 2: Manage and Monitor
idrac_launch
The iDRAC launch method of the chassis. Viable options are:
- 0: Disabled (launch iDRAC using IP address)
- 1: Enabled (launch iDRAC using DNS name)
slot_names
The names of the slots, provided as a list identified by
their slot numbers.
blade_power_states
The power states of a blade server, provided as a list and
identified by their server numbers. Viable options are:
- on: Ensure the blade server is powered on.
- off: Ensure the blade server is powered off.
- powercycle: Power cycle the blade server.
Example:
.. code-block:: yaml
my-dell-chassis:
dellchassis.chassis:
- chassis_name: my-dell-chassis
- location: my-location
- datacenter: london
- mode: 2
- idrac_launch: 1
- slot_names:
- 1: my-slot-name
- 2: my-other-slot-name
- blade_power_states:
- server-1: on
- server-2: off
- server-3: powercycle
"""
ret = {
"name": chassis_name,
"chassis_name": chassis_name,
"result": True,
"changes": {},
"comment": "",
}
chassis_cmd = "chassis.cmd"
cfg_tuning = "cfgRacTuning"
mode_cmd = "cfgRacTuneChassisMgmtAtServer"
launch_cmd = "cfgRacTuneIdracDNSLaunchEnable"
inventory = __salt__[chassis_cmd]("inventory")
if idrac_launch:
idrac_launch = str(idrac_launch)
current_name = __salt__[chassis_cmd]("get_chassis_name")
if chassis_name != current_name:
ret["changes"].update({"Name": {"Old": current_name, "New": chassis_name}})
current_dc = __salt__[chassis_cmd]("get_chassis_datacenter")
if datacenter and datacenter != current_dc:
ret["changes"].update({"Datacenter": {"Old": current_dc, "New": datacenter}})
if password:
ret["changes"].update({"Password": {"Old": "******", "New": "******"}})
if location:
current_location = __salt__[chassis_cmd]("get_chassis_location")
if location != current_location:
ret["changes"].update(
{"Location": {"Old": current_location, "New": location}}
)
if mode:
current_mode = __salt__[chassis_cmd]("get_general", cfg_tuning, mode_cmd)
if mode != current_mode:
ret["changes"].update(
{"Management Mode": {"Old": current_mode, "New": mode}}
)
if idrac_launch:
current_launch_method = __salt__[chassis_cmd](
"get_general", cfg_tuning, launch_cmd
)
if idrac_launch != current_launch_method:
ret["changes"].update(
{
"iDrac Launch Method": {
"Old": current_launch_method,
"New": idrac_launch,
}
}
)
if slot_names:
current_slot_names = __salt__[chassis_cmd]("list_slotnames")
for s in slot_names:
key = next(iter(s))
new_name = s[key]
if key.startswith("slot-"):
key = key[5:]
current_slot_name = current_slot_names.get(key).get("slotname")
if current_slot_name != new_name:
old = {key: current_slot_name}
new = {key: new_name}
if ret["changes"].get("Slot Names") is None:
ret["changes"].update({"Slot Names": {"Old": {}, "New": {}}})
ret["changes"]["Slot Names"]["Old"].update(old)
ret["changes"]["Slot Names"]["New"].update(new)
current_power_states = {}
target_power_states = {}
if blade_power_states:
for b in blade_power_states:
key = next(iter(b))
status = __salt__[chassis_cmd]("server_powerstatus", module=key)
current_power_states[key] = status.get("status", -1)
if b[key] == "powerdown":
if current_power_states[key] != -1 and current_power_states[key]:
target_power_states[key] = "powerdown"
if b[key] == "powerup":
if current_power_states[key] != -1 and not current_power_states[key]:
target_power_states[key] = "powerup"
if b[key] == "powercycle":
if current_power_states[key] != -1 and not current_power_states[key]:
target_power_states[key] = "powerup"
if current_power_states[key] != -1 and current_power_states[key]:
target_power_states[key] = "powercycle"
for k, v in target_power_states.items():
old = {k: current_power_states[k]}
new = {k: v}
if ret["changes"].get("Blade Power States") is None:
ret["changes"].update({"Blade Power States": {"Old": {}, "New": {}}})
ret["changes"]["Blade Power States"]["Old"].update(old)
ret["changes"]["Blade Power States"]["New"].update(new)
if ret["changes"] == {}:
ret["comment"] = "Dell chassis is already in the desired state."
return ret
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "Dell chassis configuration will change."
return ret
# Finally, set the necessary configurations on the chassis.
name = __salt__[chassis_cmd]("set_chassis_name", chassis_name)
if location:
location = __salt__[chassis_cmd]("set_chassis_location", location)
pw_result = True
if password:
pw_single = True
if __salt__[chassis_cmd](
"change_password", username="root", uid=1, password=password
):
for blade in inventory["server"]:
pw_single = __salt__[chassis_cmd](
"deploy_password", username="root", password=password, module=blade
)
if not pw_single:
pw_result = False
else:
pw_result = False
if datacenter:
datacenter_result = __salt__[chassis_cmd]("set_chassis_datacenter", datacenter)
if mode:
mode = __salt__[chassis_cmd]("set_general", cfg_tuning, mode_cmd, mode)
if idrac_launch:
idrac_launch = __salt__[chassis_cmd](
"set_general", cfg_tuning, launch_cmd, idrac_launch
)
if ret["changes"].get("Slot Names") is not None:
slot_rets = []
for s in slot_names:
key = next(iter(s))
new_name = s[key]
if key.startswith("slot-"):
key = key[5:]
slot_rets.append(__salt__[chassis_cmd]("set_slotname", key, new_name))
if any(slot_rets) is False:
slot_names = False
else:
slot_names = True
powerchange_all_ok = True
for k, v in target_power_states.items():
powerchange_ok = __salt__[chassis_cmd]("server_power", v, module=k)
if not powerchange_ok:
powerchange_all_ok = False
if (
any([name, location, mode, idrac_launch, slot_names, powerchange_all_ok])
is False
):
ret["result"] = False
ret["comment"] = "There was an error setting the Dell chassis."
ret["comment"] = "Dell chassis was updated."
return ret
def switch(
name, ip=None, netmask=None, gateway=None, dhcp=None, password=None, snmp=None
):
"""
Manage switches in a Dell Chassis.
name
The switch designation (e.g. switch-1, switch-2)
ip
The Static IP Address of the switch
netmask
The netmask for the static IP
gateway
The gateway for the static IP
dhcp
True: Enable DHCP
False: Do not change DHCP setup
(disabling DHCP is automatic when a static IP is set)
password
The access (root) password for the switch
snmp
The SNMP community string for the switch
Example:
.. code-block:: yaml
my-dell-chassis:
dellchassis.switch:
- switch: switch-1
- ip: 192.168.1.1
- netmask: 255.255.255.0
- gateway: 192.168.1.254
- dhcp: True
- password: secret
- snmp: public
"""
ret = {"name": name, "result": True, "changes": {}, "comment": ""}
current_nic = __salt__["chassis.cmd"]("network_info", module=name)
try:
if current_nic.get("retcode", 0) != 0:
ret["result"] = False
ret["comment"] = current_nic["stdout"]
return ret
if ip or netmask or gateway:
if not ip:
ip = current_nic["Network"]["IP Address"]
if not netmask:
ip = current_nic["Network"]["Subnet Mask"]
if not gateway:
ip = current_nic["Network"]["Gateway"]
if current_nic["Network"]["DHCP Enabled"] == "0" and dhcp:
ret["changes"].update(
{
"DHCP": {
"Old": {"DHCP Enabled": current_nic["Network"]["DHCP Enabled"]},
"New": {"DHCP Enabled": dhcp},
}
}
)
if (
(ip or netmask or gateway)
and not dhcp
and (
ip != current_nic["Network"]["IP Address"]
or netmask != current_nic["Network"]["Subnet Mask"]
or gateway != current_nic["Network"]["Gateway"]
)
):
ret["changes"].update(
{
"IP": {
"Old": current_nic["Network"],
"New": {
"IP Address": ip,
"Subnet Mask": netmask,
"Gateway": gateway,
},
}
}
)
if password:
if "New" not in ret["changes"]:
ret["changes"]["New"] = {}
ret["changes"]["New"].update({"Password": "*****"})
if snmp:
if "New" not in ret["changes"]:
ret["changes"]["New"] = {}
ret["changes"]["New"].update({"SNMP": "*****"})
if ret["changes"] == {}:
ret["comment"] = "Switch " + name + " is already in desired state"
return ret
except AttributeError:
ret["changes"] = {}
ret["comment"] = "Something went wrong retrieving the switch details"
return ret
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "Switch " + name + " configuration will change"
return ret
# Finally, set the necessary configurations on the chassis.
dhcp_ret = net_ret = password_ret = snmp_ret = True
if dhcp:
dhcp_ret = __salt__["chassis.cmd"]("set_niccfg", module=name, dhcp=dhcp)
if ip or netmask or gateway:
net_ret = __salt__["chassis.cmd"](
"set_niccfg", ip, netmask, gateway, module=name
)
if password:
password_ret = __salt__["chassis.cmd"](
"deploy_password", "root", password, module=name
)
if snmp:
snmp_ret = __salt__["chassis.cmd"]("deploy_snmp", snmp, module=name)
if any([password_ret, snmp_ret, net_ret, dhcp_ret]) is False:
ret["result"] = False
ret["comment"] = f"There was an error setting the switch {name}."
ret["comment"] = f"Dell chassis switch {name} was updated."
return ret
def _firmware_update(firmwarefile="", host="", directory=""):
"""
Update firmware for a single host
"""
dest = os.path.join(directory, firmwarefile[7:])
__salt__["cp.get_file"](firmwarefile, dest)
username = __pillar__["proxy"]["admin_user"]
password = __pillar__["proxy"]["admin_password"]
__salt__["dracr.update_firmware"](
dest, host=host, admin_username=username, admin_password=password
)
def firmware_update(hosts=None, directory=""):
"""
State to update the firmware on host
using the ``racadm`` command
firmwarefile
filename (string) starting with ``salt://``
host
string representing the hostname
supplied to the ``racadm`` command
directory
Directory name where firmwarefile
will be downloaded
.. code-block:: yaml
dell-chassis-firmware-update:
dellchassis.firmware_update:
hosts:
cmc:
salt://firmware_cmc.exe
server-1:
salt://firmware.exe
directory: /opt/firmwares
"""
ret = {}
ret.changes = {}
success = True
for host, firmwarefile in hosts:
try:
_firmware_update(firmwarefile, host, directory)
ret["changes"].update(
{
"host": {
"comment": f"Firmware update submitted for {host}",
"success": True,
}
}
)
except CommandExecutionError as err:
success = False
ret["changes"].update(
{
"host": {
"comment": f"FAILED to update firmware for {host}",
"success": False,
"reason": str(err),
}
}
)
ret["result"] = success
return ret
Zerion Mini Shell 1.0