Initial commit of ESXi proxy work.

Update grains information to work properly

Added NTP functionality to vsphere module

Add ssh enable/disable capabilities

Allow list of host_names in ntp functions

Password update, ssh_restart, datetime management functionality added

Added enable/disable vsan and disk listing capabilities

Don't stack trace is VSAN System or VSAN Config are unset

Be able to provide a list of passwords via pillar to auth against a host

Fix bug with test.ping returning False, even though other functions work

Add ability to add blank host disks to host's vsan system

Minor bug fix for vsan_add_disks function
This commit is contained in:
rallytime 2015-11-11 14:56:03 -07:00
parent e5bd1c293d
commit 9ba9019419
6 changed files with 2274 additions and 0 deletions

View file

@ -10,6 +10,7 @@ Full list of builtin proxy modules
:toctree:
:template: autosummary.rst.tmpl
esxi
fx2
junos
rest_sample

View file

@ -0,0 +1,6 @@
===============
salt.proxy.esxi
===============
.. automodule:: salt.proxy.esxi
:members:

93
salt/grains/esxi.py Normal file
View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
'''
Generate baseline proxy minion grains for ESXi hosts.
., versionadded:: 2015.8.4
'''
# Import Python Libs
from __future__ import absolute_import
import logging
# Import Salt Libs
from salt.exceptions import SaltSystemExit
import salt.utils
import salt.modules.vsphere
__proxyenabled__ = ['esxi']
__virtualname__ = 'esxi'
log = logging.getLogger(__file__)
GRAINS_CACHE = {}
def __virtual__():
if not salt.utils.is_proxy():
return False
else:
return __virtualname__
def esxi():
return _grains()
def kernel():
return {'kernel': 'proxy'}
def os():
if not GRAINS_CACHE:
GRAINS_CACHE.update(_grains())
try:
return {'os': GRAINS_CACHE.get('fullName')}
except AttributeError:
return {'os': 'Unknown'}
def os_family():
return {'os_family': 'proxy'}
def _find_credentials(host):
'''
Cycle through all the possible credentials and return the first one that
works.
'''
user_names = [__pillar__['proxy'].get('username', 'root')]
passwords = __pillar__['proxy']['passwords']
for user in user_names:
for password in passwords:
try:
# Try to authenticate with the given user/password combination
ret = salt.modules.vsphere.system_info(host=host,
username=user,
password=password)
except SaltSystemExit:
# If we can't authenticate, continue on to try the next password.
continue
# If we have data returned from above, we've successfully authenticated.
if ret:
return user, password
# We've reached the end of the list without successfully authenticating.
raise SaltSystemExit('Cannot complete login due to an incorrect user name or password.')
def _grains():
'''
Get the grains from the proxied device.
'''
host = __pillar__['proxy']['host']
username, password = _find_credentials(host)
protocol = __pillar__['proxy'].get('protocol')
port = __pillar__['proxy'].get('port')
ret = salt.modules.vsphere.system_info(host=host,
username=username,
password=password,
protocol=protocol,
port=port)
GRAINS_CACHE.update(ret)
return GRAINS_CACHE

62
salt/modules/esxi.py Normal file
View file

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
'''
Glues the VMware vSphere Execution Module to the VMware ESXi Proxy Minions to the
:doc:`esxi proxymodule </ref/proxy/all/salt.proxy.esxi>`.
.. versionadded:: 2015.8.4
Depends: :doc:`vSphere Remote Execution Module (salt.modules.vsphere)
</ref/modules/all/salt.modules.vsphere>`
For documentation on commands that you can direct to an ESXi host via proxy,
look in the documentation for :doc:`salt.modules.vsphere
</ref/modules/all/salt.modules.vsphere>`.
This execution module calls through to a function in the ESXi proxy module
called ``ch_config``, which looks up the function passed in the ``command``
parameter in :doc:`salt.modules.vsphere </ref/modules/all/salt.modules.vsphere>`
and calls it.
To execute commands with an ESXi Proxy Minion using the vSphere Execution Module,
use the ``esxi.cmd <vsphere-function-name`` syntax:
.. code-block:: bash
salt 'esxi-proxy' esxi.cmd system_info
'''
from __future__ import absolute_import
# Import python libs
import logging
import salt.utils
log = logging.getLogger(__name__)
__proxyenabled__ = ['esxi']
__virtualname__ = 'esxi'
def __virtual__():
'''
Only work on proxy
'''
if salt.utils.is_proxy():
return __virtualname__
return False
def cmd(command, *args, **kwargs):
proxy_prefix = __opts__['proxy']['proxytype']
proxy_cmd = proxy_prefix + '.ch_config'
host = __pillar__['proxy']['host']
username, password = __proxy__[proxy_prefix + '.find_credentials'](host)
kwargs['host'] = host
kwargs['username'] = username
kwargs['password'] = password
kwargs['protocol'] = __pillar__['proxy'].get('protocol')
kwargs['port'] = __pillar__['proxy'].get('port')
return __proxy__[proxy_cmd](command, *args, **kwargs)

1750
salt/modules/vsphere.py Normal file

File diff suppressed because it is too large Load diff

362
salt/proxy/esxi.py Normal file
View file

@ -0,0 +1,362 @@
# -*- coding: utf-8 -*-
'''
Proxy Minion interface module for managing VMWare ESXi hosts.
.. versionadded:: 2015.8.4
:depends: pyVmomi
**Special Note: SaltStack thanks** `Adobe Corporation <http://adobe.com/>`_
**for their support in creating this Proxy Minion integration.**
This proxy minion enables VMware ESXi (hereafter referred to as simply 'ESXi')
hosts to be treated individually like a Salt Minion.
Since the ESXi host may not necessarily run on an OS capable of hosting a
Python stack, the ESXi host can't run a Salt Minion directly. Salt's
"Proxy Minion" functionality enables you to designate another machine to host
a minion process that "proxies" communication from the Salt Master. The master
does not know nor care that the target is not a "real" Salt Minion.
More in-depth conceptual reading on Proxy Minions can be found in the
:doc:`Proxy Minion </topics/proxyminion/index>` section of Salt's
documentation.
Configuration
=============
To use this integration proxy module, please configure the following:
Pillar
------
Proxy minions get their configuration from Salt's Pillar. Every proxy must
have a stanza in Pillar and a reference in the Pillar top-file that matches
the ID. At a minimum for communication with the ESXi host, the pillar should
look like this:
.. code-block:: yaml
proxy:
proxytype: esxi
host: <ip or dns name of esxi host>
username: <ESXi username>
passwords:
- first_password
- second_password
- third_password
proxytype
^^^^^^^^^
The ``proxytype`` key and value pair is critical, as 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). To use this ESXi Proxy Module, set this to
``esxi``.
host
^^^^
The location, or ip/dns, of the ESXi host. Required.
username
^^^^^^^^
The username used to login to the ESXi host, such as ``root``. Required.
passwords
^^^^^^^^^
A list of passwords to be used to try and login to the ESXi host. At least
one password in this list is required.
The proxy integration will try the passwords listed in order. It is
configured this way so you can have a regular password and the password you
may be updating for an ESXi host either via the
:doc:`vsphere.update_host_password </ref/modules/all/salt.modules.vsphere>`
function or via an :doc:`ESXi state </ref/modules/all/salt.states.esxi>`
function. This way, after the is changed, you should not need to restart
the proxy minion--it should just pick up the the new password provided in
the list. You can then change pillar at will to move that password to the
front and retire the unused ones.
This also allows you to use any number of potential fallback passwords.
protocol
^^^^^^^^
If the ESXi host is not using the default protocol, set this value to an
alternate protocol. Default is ``https``.
port
^^^^
If the ESXi host is not using the default port, set this value to an
alternate port. Default is ``443``.
Salt Proxy
----------
After your pillar is in place, you can test the proxy. The proxy can run on
any machine that has network connectivity to your Salt Master and to the
ESXi host in question. SaltStack recommends that the machine running the
salt-proxy process also run a regular minion, though it is not strictly
necessary.
On the machine that will run the proxy, make sure there is an ``/etc/salt/proxy``
file with at least the following in it:
.. code-block:: yaml
master: <ip or hostname of salt-master>
You can then start the salt-proxy process with:
.. code-block:: bash
salt-proxy --proxyid <id you want to give the host>
You may want to add ``-l debug`` to run the above in the foreground in
debug mode just to make sure everything is OK.
Next, accept the key for the proxy on your salt-master, just like you
would for a regular minion:
.. code-block:: bash
salt-key -a <id you gave the esxi host>
You can confirm that the pillar data is in place for the proxy:
.. code-block:: bash
salt <id> pillar.items
And now you should be able to ping the ESXi host to make sure it is
responding:
.. code-block:: bash
salt <id> test.ping
At this point you can execute one-off commands against the host. For
example, you can get the ESXi host's system information:
.. code-block:: bash
salt <id> esxi.cmd system_info
Note that you don't need to provide credentials or an ip/hostname. Salt
knows to use the credentials you stored in Pillar.
It's important to understand how this particular proxy works.
:doc:`Salt.modules.vsphere </ref/modules/all/salt.modules.vsphere>` is a
standard Salt execution module. If you pull up the docs for it you'll see
that almost every function in the module takes credentials and a target
host. When credentials and a host aren't passed, Salt runs commands
through ``pyVmomi``against the local machine. If you wanted, you could run
functions from this module on any host where an appropriate version of
``pyVmomi`` is installed, and that host would reach out over the network
and communicate with the ESXi host.
``esxi.cmd`` acts as a "shim" between the execution module and the proxy. Its
first parameter is always the function from salt.modules.vsphere. If the
function takes more positional or keyword arguments you can append them to the
call. It's this shim that speaks to the ESXi host through the proxy, arranging
for the credentials and hostname to be pulled from the Pillar section for this
Proxy Minion.
Because of the presence of the shim, to lookup documentation for what
functions you can use to interface with the ESXi host, you'll want to
look in :doc:`salt.modules.vsphere </ref/modules/all/salt.modules.vsphere>`
instead of :doc:`salt.modules.esxi </ref/modules/all/salt.modules.esxi>`.
States
------
Associated states are thoroughly documented in
:doc:`salt.states.vsphere </ref/states/all/salt.states.vsphere>`. Look there
to find an example structure for Pillar as well as an example ``.sls`` file f
or standing up an ESXi host from scratch.
'''
# Import Python Libs
from __future__ import absolute_import
import logging
# Import Salt Libs
from salt.exceptions import SaltSystemExit
# This must be present or the Salt loader won't load this module.
__proxyenabled__ = ['esxi']
# Variables are scoped to this module so we can have persistent data
# across calls to fns in here.
GRAINS_CACHE = {}
DETAILS = {}
# Set up logging
log = logging.getLogger(__file__)
# Define the module's virtual name
__virtualname__ = 'esxi'
def __virtual__():
'''
Only load if the ESXi execution module is available.
'''
if 'vsphere.system_info' in __salt__:
return __virtualname__
return False, 'The ESXi Proxy Minion module did not load.'
def init(opts):
'''
This function gets called when the proxy starts up. For
ESXi devices, the host, login credentials, and, if configured,
the protocol and port are cached.
'''
if 'host' not in opts['proxy']:
log.critical('No \'host\' key found in pillar for this proxy.')
return False
if 'username' not in opts['proxy']:
log.critical('No \'username\' key found in pillar for this proxy.')
return False
if 'passwords' not in opts['proxy']:
log.critical('No \'passwords\' key found in pillar for this proxy.')
host = opts['proxy']['host']
# Get the correct login details
try:
username, password = find_credentials(host)
except SaltSystemExit as err:
log.critical('Error: {0}'.format(err))
return False
# Set configuration details
DETAILS['host'] = host
DETAILS['username'] = username
DETAILS['password'] = password
DETAILS['protocol'] = opts['proxy'].get('protocol')
DETAILS['port'] = opts['proxy'].get('port')
def grains():
'''
Get the grains from the proxy device.
'''
if not GRAINS_CACHE:
return _grains(DETAILS['host'],
DETAILS['protocol'],
DETAILS['port'])
return GRAINS_CACHE
def grains_refresh():
'''
Refresh the grains from the proxy device.
'''
GRAINS_CACHE = {}
return grains()
def ping():
'''
Check to see if the host is responding. Returns False if the host didn't
respond, True otherwise.
CLI Example::
.. code-block:: bash
salt esxi-host test.ping
'''
find_credentials(DETAILS['host'])
try:
__salt__['vsphere.system_info'](host=DETAILS['host'],
username=DETAILS['username'],
password=DETAILS['password'])
except SaltSystemExit as err:
log.warning(err)
return False
return True
def shutdown():
'''
Shutdown the connection to the proxy device. For this proxy,
shutdown is a no-op.
'''
log.debug('ESXi proxy shutdown() called...')
def ch_config(cmd, *args, **kwargs):
'''
This function is called by the
:doc:`salt.modules.esxi.cmd </ref/modules/all/salt.modules.esxi>` shim.
It then calls whatever is passed in ``cmd`` inside the
:doc:`salt.modules.vsphere </ref/modules/all/salt.modules.vsphere>` module.
Passes the return through from the vsphere module.
cmd
The command to call inside salt.modules.vsphere
args
Arguments that need to be passed to that command.
kwargs
Keyword arguments that need to be passed to that command.
'''
# Strip the __pub_ keys...is there a better way to do this?
for k in kwargs.keys():
if k.startswith('__pub_'):
kwargs.pop(k)
if 'vsphere.' + cmd not in __salt__:
return {'retcode': -1, 'message': 'vsphere.' + cmd + ' is not available.'}
else:
return __salt__['vsphere.' + cmd](*args, **kwargs)
def find_credentials(host):
'''
Cycle through all the possible credentials and return the first one that
works.
'''
user_names = [__pillar__['proxy'].get('username', 'root')]
passwords = __pillar__['proxy']['passwords']
for user in user_names:
for password in passwords:
try:
# Try to authenticate with the given user/password combination
ret = __salt__['vsphere.system_info'](host=host,
username=user,
password=password)
except SaltSystemExit:
# If we can't authenticate, continue on to try the next password.
continue
# If we have data returned from above, we've successfully authenticated.
if ret:
DETAILS['username'] = user
DETAILS['password'] = password
return user, password
# We've reached the end of the list without successfully authenticating.
raise SaltSystemExit('Cannot complete login due to an incorrect user name or password.')
def _grains(host, protocol=None, port=None):
'''
Helper function to the grains from the proxied device.
'''
username, password = find_credentials(DETAILS['host'])
ret = __salt__['vsphere.system_info'](host=host,
username=username,
password=password,
protocol=protocol,
port=port)
GRAINS_CACHE.update(ret)
return GRAINS_CACHE