Merge pull request #28967 from cro/fx2_switch

Fix some issues with password changes
This commit is contained in:
Mike Place 2015-11-19 11:57:39 -07:00
commit bcec8d8608
6 changed files with 194 additions and 100 deletions

View file

@ -1202,7 +1202,7 @@ DEFAULT_PROXY_MINION_OPTS = {
# salt.vt will have trouble with our forking model.
# Proxies with non-persistent (mostly REST API) connections
# can change this back to True
'multiprocessing': False
'multiprocessing': True
}
# ----- Salt Cloud Configuration Defaults ----------------------------------->

View file

@ -30,28 +30,47 @@ def __virtual__():
return __virtualname__
def _find_credentials():
'''
Cycle through all the possible credentials and return the first one that
works
'''
usernames = []
usernames.append(__pillar__['proxy'].get('admin_username', 'root'))
if 'fallback_admin_username' in __pillar__.get('proxy'):
usernames.append(__pillar__['proxy'].get('fallback_admin_username'))
for u in usernames:
for p in __pillar__['proxy']['passwords']:
r = salt.modules.dracr.get_chassis_name(host=__pillar__['proxy']['host'],
admin_username=u,
admin_password=p)
# Retcode will be present if the chassis_name call failed
try:
if r.get('retcode', None) is None:
return (u, p)
except AttributeError:
# Then the above was a string, and we can return the username
# and password
return (u, p)
logger.debug('grains fx2._find_credentials found no valid credentials, using Dell default')
return ('root', 'calvin')
def _grains():
'''
Get the grains from the proxied device
'''
(username, password) = _find_credentials()
r = salt.modules.dracr.system_info(host=__pillar__['proxy']['host'],
admin_username=__pillar__['proxy']['admin_username'],
admin_password=__pillar__['proxy']['admin_password'])
admin_username=username,
admin_password=password)
if r.get('retcode', 0) == 0:
GRAINS_CACHE = r
username = __pillar__['proxy']['admin_username']
password = __pillar__['proxy']['admin_password']
else:
r = salt.modules.dracr.system_info(host=__pillar__['proxy']['host'],
admin_username=__pillar__['proxy']['fallback_admin_username'],
admin_password=__pillar__['proxy']['fallback_admin_password'])
if r.get('retcode', 0) == 0:
GRAINS_CACHE = r
username = __pillar__['proxy']['fallback_admin_username']
password = __pillar__['proxy']['fallback_admin_password']
else:
GRAINS_CACHE = {}
GRAINS_CACHE = {}
GRAINS_CACHE.update(salt.modules.dracr.inventory(host=__pillar__['proxy']['host'],
admin_username=username,

View file

@ -37,8 +37,9 @@ def __virtual__():
def cmd(cmd, *args, **kwargs):
proxyprefix = __opts__['proxy']['proxytype']
kwargs['admin_username'] = __proxy__[proxyprefix+'.admin_username']()
kwargs['admin_password'] = __proxy__[proxyprefix+'.admin_password']()
(username, password) = __proxy__[proxyprefix+'.find_credentials']()
kwargs['admin_username'] = username
kwargs['admin_password'] = password
kwargs['host'] = __proxy__[proxyprefix+'.host']()
proxycmd = __opts__['proxy']['proxytype'] + '.chconfig'
return __proxy__[proxycmd](cmd, *args, **kwargs)

View file

@ -243,6 +243,12 @@ def network_info(host=None,
inv = inventory(host=host, admin_username=admin_username,
admin_password=admin_password)
if inv is None:
cmd = {}
cmd['retcode'] = -1
cmd['stdout'] = 'Problem getting switch inventory'
return cmd
if module not in inv.get('switch'):
cmd = {}
cmd['retcode'] = -1
@ -1076,9 +1082,9 @@ def get_chassis_name(host=None, admin_username=None, admin_password=None):
admin_username=root admin_password=secret
'''
return system_info(host=host, admin_username=admin_username,
admin_password=
admin_password)['Chassis Information']['Chassis Name']
return bare_rac_cmd('getchassisname', host=host,
admin_username=admin_username,
admin_password=admin_password)
def inventory(host=None, admin_username=None, admin_password=None):
@ -1301,3 +1307,16 @@ def get_general(cfg_sec, cfg_var, host=None,
return ret['stdout']
else:
return ret
def bare_rac_cmd(cmd, host=None,
admin_username=None, admin_password=None):
ret = __execute_ret('{0}'.format(cmd),
host=host,
admin_username=admin_username,
admin_password=admin_password)
if ret['retcode'] == 0:
return ret['stdout']
else:
return ret

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-,
'''
Dell FX2 chassis
================
@ -50,13 +50,46 @@ look like this:
proxy:
host: <ip or dns name of chassis controller>
admin_username: <iDRAC username for the CMC, usually 'root'>
admin_password: <iDRAC password. Dell default is 'calvin'>
fallback_admin_username: <username to try if the first fails>
passwords:
- first_password
- second_password
- third-password
proxytype: fx2
The ``proxytype`` line above is critical, it tells Salt which interface to load
from the ``proxy`` directory in Salt's install hierarchy, or from ``/srv/salt/_proxy``
on the salt-master (if you have created your own proxy module, for example).
The proxy integration will try the passwords listed in order. It is
configured this way so you can have a regular password, a potential
fallback password, and the third password can be the one you intend
to change the chassis to use. This way, after it is changed, you
should not need to restart the proxy minion--it should just pick up the
third password in the list. You can then change pillar at will to
move that password to the front and retire the unused ones.
Beware, many Dell CMC and iDRAC units are configured to lockout
IP addresses or users after too many failed password attempts. This can
generate user panic in the form of "I no longer know what the password is!!!".
To mitigate panic try the web interface from a different IP, or setup a
emergency administrator user in the CMC before doing a wholesale
password rotation.
The automatic lockout can be disabled via Salt with the following:
.. code-block:: bash
salt <cmc> chassis.cmd set_general cfgRacTuning cfgRacTuneIpBlkEnable 0
and then verified with
.. code-block:: bash
salt <cmc> chassis.cmd get_general cfgRacTuning cfgRacTuneIpBlkEnable
salt-proxy
----------
@ -186,42 +219,27 @@ def init(opts):
DETAILS['host'] = opts['proxy']['host']
first_user = opts['proxy']['admin_username']
first_password = opts['proxy']['admin_password']
if 'fallback_admin_username' in opts['proxy'] \
and 'fallback_admin_password' in opts['proxy']:
fallback_available = True
fallback_user = opts['proxy']['fallback_admin_username']
fallback_password = opts['proxy']['fallback_admin_password']
else:
fallback_available = False
check_grains = _grains(DETAILS['host'], first_user, first_password)
if check_grains:
DETAILS['admin_username'] = opts['proxy']['admin_username']
DETAILS['admin_password'] = opts['proxy']['admin_password']
return True
elif fallback_available and _grains(DETAILS['host'],
fallback_user,
fallback_password):
log.info('Fallback credentials used'
' to access chassis {0}'.format(opts['proxy']['host']))
DETAILS['admin_username'] = opts['proxy']['fallback_admin_username']
DETAILS['admin_password'] = opts['proxy']['fallback_admin_password']
return True
else:
log.critical('Neither the primary nor the fallback credentials'
' were able to access the chassis '
' at {0}'.format(opts['proxy']['host']))
return False
(username, password) = find_credentials()
def admin_username():
return DETAILS['admin_username']
'''
Return the admin_username in the DETAILS dictionary, or root if there
is none present
'''
return DETAILS.get('admin_username', 'root')
def admin_password():
return DETAILS['admin_password']
'''
Return the admin_password in the DETAILS dictionary, or 'calvin'
(the Dell default) if there is none present
'''
if 'admin_password' not in DETAILS:
log.info('proxy.fx2: No admin_password in DETAILS, returning Dell default')
return 'calvin'
return DETAILS.get('admin_password', 'calvin')
def host():
@ -262,6 +280,42 @@ def grains_refresh():
return grains()
def find_credentials():
'''
Cycle through all the possible credentials and return the first one that
works
'''
usernames = []
usernames.append(__pillar__['proxy'].get('admin_username', 'root'))
if 'fallback_admin_username' in __pillar__.get('proxy'):
usernames.append(__pillar__['proxy'].get('fallback_admin_username'))
for u in usernames:
for p in __pillar__['proxy']['passwords']:
r = __salt__['dracr.get_chassis_name'](host=__pillar__['proxy']['host'],
admin_username=u,
admin_password=p)
# Retcode will be present if the chassis_name call failed
try:
if r.get('retcode', None) is None:
DETAILS['admin_username'] = u
DETAILS['admin_password'] = p
__opts__['proxy']['admin_username'] = u
__opts__['proxy']['admin_password'] = p
return (u, p)
except AttributeError:
# Then the above was a string, and we can return the username
# and password
DETAILS['admin_username'] = u
DETAILS['admin_password'] = p
__opts__['proxy']['admin_username'] = u
__opts__['proxy']['admin_password'] = p
return (u, p)
log.debug('proxy fx2.find_credentials found no valid credentials, using Dell default')
return ('root', 'calvin')
def chconfig(cmd, *args, **kwargs):
'''
This function is called by the :doc:`salt.modules.chassis.cmd </ref/modules/all/salt.modules.chassis>`
@ -281,15 +335,20 @@ def chconfig(cmd, *args, **kwargs):
kwargs.pop(k)
# Catch password reset
if 'dracr.'+cmd not in __salt__:
ret = {'retcode': -1, 'message': 'dracr.' + cmd + ' is not available'}
else:
ret = __salt__['dracr.'+cmd](*args, **kwargs)
if cmd == 'change_password':
if 'username' in kwargs:
__opts__['proxy']['admin_username'] = kwargs['username']
DETAILS['admin_username'] = kwargs['username']
if 'password' in kwargs:
__opts__['proxy']['admin_password'] = kwargs['password']
DETAILS['admin_password'] = kwargs['password']
if 'dracr.'+cmd not in __salt__:
return {'retcode': -1, 'message': 'dracr.' + cmd + ' is not available'}
else:
return __salt__['dracr.'+cmd](*args, **kwargs)
return ret
def ping():

View file

@ -31,9 +31,10 @@ data in pillar. Here's an example pillar structure:
proxy:
host: 10.27.20.18
admin_username: root
admin_password: super-secret
fallback_admin_username: root
fallback_admin_password: old-secret
passwords:
- super-secret
- old-secret
proxytype: fx2
chassis:
@ -105,17 +106,6 @@ pillar stated above:
- {{ k }}: {{ v }}
{% endfor %}
{% for k, v in details['switches'].iteritems() %}
standup-switches-{{ k }}:
dellchassis.switch:
- name: {{ k }}
- ip: {{ v['ip'] }}
- netmask: {{ v['netmask'] }}
- gateway: {{ v['gateway'] }}
- password: {{ v['password'] }}
- snmp: {{ v['snmp'] }}
{% endfor %}
blade_powercycle:
dellchassis.chassis:
- blade_power_states:
@ -277,6 +267,7 @@ def chassis(name, chassis_name=None, password=None, datacenter=None,
- server-3: powercycle
'''
ret = {'name': chassis_name,
'chassis_name': chassis_name,
'result': True,
'changes': {},
'comment': ''}
@ -487,43 +478,48 @@ def switch(name, ip=None, netmask=None, gateway=None, dhcp=None,
'comment': ''}
current_nic = __salt__['chassis.cmd']('network_info', module=name)
if current_nic.get('retcode', 0) != 0:
ret['result'] = False
ret['comment'] = current_nic['stdout']
return ret
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 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 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 ((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 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 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'
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']:
@ -541,7 +537,7 @@ def switch(name, ip=None, netmask=None, gateway=None, dhcp=None,
password_ret = __salt__['chassis.cmd']('deploy_password', 'root', password, module=name)
if snmp:
snmp_ret = __salt__['chassis.cmd']('deploy_snmp', password, module=name)
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