mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Proxy minion support.
This commit is contained in:
parent
552ff8308b
commit
637901e23e
6 changed files with 143 additions and 103 deletions
|
@ -2,10 +2,11 @@
|
|||
Salt Proxy Minion Documentation
|
||||
===============================
|
||||
|
||||
Proxy minions are a Salt feature that enables controlling devices that, for
|
||||
whatever reason, cannot run a standard salt-minion. Examples include network
|
||||
gear that has an API but runs a proprietary OS, devices with limited CPU or
|
||||
memory, or devices that could run a minion, but for security reasons, will not.
|
||||
Proxy minions are a developing Salt feature that enables controlling devices
|
||||
that, for whatever reason, cannot run a standard salt-minion. Examples include
|
||||
network gear that has an API but runs a proprietary OS, devices with limited
|
||||
CPU or memory, or devices that could run a minion, but for security reasons,
|
||||
will not.
|
||||
|
||||
*Proxy minions are not an "out of the box" feature*. Because
|
||||
there are an infinite number of controllable devices,
|
||||
|
@ -41,21 +42,15 @@ any way with the minion that started it.
|
|||
Configuration parameters on the master
|
||||
######################################
|
||||
|
||||
In ``/etc/salt/master``, add the following key:
|
||||
Proxy minions require no configuration parameters in /etc/salt/master.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
enumerate_proxy_minions: True
|
||||
|
||||
This will invoke the salt-master routines to look for other configuration parameters
|
||||
to drive the proxies.
|
||||
|
||||
Salt's Pillar system is ideally suited for configuring proxy-minions. Proxies can
|
||||
either be designated via a pillar file in pillar_roots, or through an external pillar.
|
||||
External pillars afford the opportunity for interfacing with a configuration management
|
||||
system, database, or other knowledgeable system that that may already contain all the details
|
||||
of proxy targets. To use static files in pillar_roots, pattern your files after the following
|
||||
examples, which are based on the diagram above:
|
||||
Salt's Pillar system is ideally suited for configuring proxy-minions. Proxies
|
||||
can either be designated via a pillar file in pillar_roots, or through an
|
||||
external pillar. External pillars afford the opportunity for interfacing with
|
||||
a configuration management system, database, or other knowledgeable system that
|
||||
that may already contain all the details of proxy targets. To use static files
|
||||
in pillar_roots, pattern your files after the following examples, which are
|
||||
based on the diagram above:
|
||||
|
||||
``/srv/salt/pillar/top.sls``
|
||||
|
||||
|
@ -145,22 +140,26 @@ to enable control.
|
|||
Proxytypes
|
||||
##########
|
||||
|
||||
A proxytype is a Python file that encapsulates all the code necessary to interface with
|
||||
a device. Proxytypes are located inside the salt.proxy module.
|
||||
At a minimum a proxytype must implement the following functions:
|
||||
A proxytype is a Python class called 'Proxyconn' that encapsulates all the code
|
||||
necessary to interface with a device. Proxytypes are located inside the
|
||||
salt.proxy module. At a minimum a proxytype object must implement the
|
||||
following methods:
|
||||
|
||||
``proxytype()``: Returns a string with the name of the proxy type.
|
||||
``proxytype(self)``: Returns a string with the name of the proxy type.
|
||||
|
||||
``proxyconn(*args, **kwargs)``: Provides the primary way to connect and communicate
|
||||
``proxyconn(self, **kwargs)``: Provides the primary way to connect and communicate
|
||||
with the device. Some proxyconns instantiate a particular object that opens a
|
||||
network connection to a device and leaves the connection open for communication.
|
||||
Others simply abstract a serial connection or even implement endpoints to communicate
|
||||
via REST over HTTP.
|
||||
|
||||
``id(opts)``: Returns a unique, unchanging id for the controlled device. This is
|
||||
``id(self, opts)``: Returns a unique, unchanging id for the controlled device. This is
|
||||
the "name" of the device, and is used by the salt-master for targeting and key
|
||||
authentication.
|
||||
|
||||
Optionally, the class may define a ``shutdown(self, opts)`` method if the
|
||||
controlled device should be informed when the minion goes away cleanly.
|
||||
|
||||
It is highly recommended that the ``test.ping`` execution module also be defined
|
||||
for a proxytype. The code for ``ping`` should contact the controlled device and make
|
||||
sure it is really available.
|
||||
|
@ -170,6 +169,7 @@ the Junos operating system. Note the additional library requirements--most of t
|
|||
"hard part" of talking to these devices is handled by the jnpr.junos, jnpr.junos.utils
|
||||
and jnpr.junos.cfg modules.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Import python libs
|
||||
|
@ -181,32 +181,49 @@ and jnpr.junos.cfg modules.
|
|||
import jnpr.junos.cfg
|
||||
HAS_JUNOS = True
|
||||
|
||||
def proxyconn(user=None, host=None, passwd=None):
|
||||
jdev = jnpr.junos.Device(user=user, host=host, password=passwd)
|
||||
jdev.open()
|
||||
jdev.bind(cu=jnpr.junos.utils.Config)
|
||||
return jdev
|
||||
class Proxyconn(object):
|
||||
|
||||
def proxytype():
|
||||
return 'junos'
|
||||
|
||||
def id(opts):
|
||||
return opts['proxyconn'].facts['hostname']
|
||||
def __init__(self, details):
|
||||
self.conn = jnpr.junos.Device(user=details['username'], host=details['host'], password=details['passwd'])
|
||||
self.conn.open()
|
||||
self.conn.bind(cu=jnpr.junos.cfg.Resource)
|
||||
|
||||
|
||||
def proxytype(self):
|
||||
return 'junos'
|
||||
|
||||
|
||||
def id(self, opts):
|
||||
return self.conn.facts['hostname']
|
||||
|
||||
|
||||
def ping(self):
|
||||
return self.conn.connected
|
||||
|
||||
|
||||
def shutdown(self, opts):
|
||||
|
||||
print('Proxy module {} shutting down!!'.format(opts['id']))
|
||||
try:
|
||||
self.conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
The __proxyenabled__ directive
|
||||
##############################
|
||||
|
||||
Salt states and execution modules, by and large, cannot "automatically" work with
|
||||
proxied devices. Execution modules like ``pkg`` or ``sqlite3`` have no meaning on
|
||||
a network switch or a housecat. For a state/execution module to be available to
|
||||
a proxy-minion, the ``__proxyenabled__`` variable must be defined in the module as an
|
||||
array containing the names of all the proxytypes that this module can support. The
|
||||
array can contain the special value ``*`` to indicate that the module supports all
|
||||
proxies.
|
||||
Salt states and execution modules, by and large, cannot "automatically" work
|
||||
with proxied devices. Execution modules like ``pkg`` or ``sqlite3`` have no
|
||||
meaning on a network switch or a housecat. For a state/execution module to be
|
||||
available to a proxy-minion, the ``__proxyenabled__`` variable must be defined
|
||||
in the module as an array containing the names of all the proxytypes that this
|
||||
module can support. The array can contain the special value ``*`` to indicate
|
||||
that the module supports all proxies.
|
||||
|
||||
If no ``__proxyenabled__`` variable is defined, then by default, the state/execution
|
||||
module is unavailable to any proxy.
|
||||
If no ``__proxyenabled__`` variable is defined, then by default, the
|
||||
state/execution module is unavailable to any proxy.
|
||||
|
||||
Here is an excerpt from a module that was modified to support proxy-minions:
|
||||
|
||||
|
@ -214,10 +231,9 @@ Here is an excerpt from a module that was modified to support proxy-minions:
|
|||
|
||||
def ping():
|
||||
|
||||
if 'proxytype' in __opts__:
|
||||
fun = 'salt.proxy.{0}.ping'.format(__opts__['proxytype'])
|
||||
if fun in __salt__:
|
||||
return __salt__[fun]()
|
||||
if 'proxyobject' in __opts__:
|
||||
if 'ping' in __opts__['proxyobject'].__attr__():
|
||||
return __opts['proxyobject'].ping()
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
|
@ -227,21 +243,11 @@ And then in salt.proxy.junos we find
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def ping():
|
||||
def ping(self):
|
||||
|
||||
return self.connected
|
||||
|
||||
try:
|
||||
got = junos.rpc.get_config(
|
||||
E.system(
|
||||
E('host-name'),
|
||||
E('domain-name')
|
||||
),
|
||||
JXML.INHERIT
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
The Junos API layer lacks the ability to do a traditional 'ping', so the
|
||||
example simply requests a very small bit of data via RPC. If the RPC call
|
||||
throws an exception or times out, the device is unavailable and ping returns
|
||||
False. Otherwise, we can return True.
|
||||
example simply checks the connection object field that indicates
|
||||
if the ssh connection was successfully made to the device.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# coding: utf-8 -*-
|
||||
'''
|
||||
Make me some salt!
|
||||
'''
|
||||
|
@ -239,7 +239,7 @@ class ProxyMinion(parsers.MinionOptionParser):
|
|||
'''
|
||||
Create a proxy minion server
|
||||
'''
|
||||
def prepare(self):
|
||||
def prepare(self, proxydetails):
|
||||
'''
|
||||
Run the preparation sequence required to start a salt minion.
|
||||
|
||||
|
@ -280,7 +280,10 @@ class ProxyMinion(parsers.MinionOptionParser):
|
|||
permissive=self.config['permissive_pki_access'],
|
||||
pki_dir=self.config['pki_dir'],
|
||||
)
|
||||
logfile = self.config['log_file']
|
||||
if 'proxy_log' in proxydetails:
|
||||
logfile = proxydetails['proxy_log']
|
||||
else:
|
||||
logfile = None
|
||||
if logfile is not None and not logfile.startswith(('tcp://',
|
||||
'udp://',
|
||||
'file://')):
|
||||
|
@ -289,6 +292,7 @@ class ProxyMinion(parsers.MinionOptionParser):
|
|||
except OSError as err:
|
||||
sys.exit(err.errno)
|
||||
|
||||
self.config['proxy'] = proxydetails
|
||||
self.setup_logfile_logger()
|
||||
logger.info(
|
||||
'Setting up a Salt Proxy Minion "{0}"'.format(
|
||||
|
@ -309,7 +313,7 @@ class ProxyMinion(parsers.MinionOptionParser):
|
|||
else:
|
||||
self.minion = salt.minion.ProxyMinion(self.config)
|
||||
|
||||
def start(self):
|
||||
def start(self, proxydetails):
|
||||
'''
|
||||
Start the actual minion.
|
||||
|
||||
|
@ -319,23 +323,26 @@ class ProxyMinion(parsers.MinionOptionParser):
|
|||
|
||||
NOTE: Run any required code before calling `super()`.
|
||||
'''
|
||||
self.prepare()
|
||||
try:
|
||||
if check_user(self.config['user']):
|
||||
self.minion.tune_in()
|
||||
except (KeyboardInterrupt, SaltSystemExit) as exc:
|
||||
logger.warn('Stopping the Salt Minion')
|
||||
if isinstance(exc, KeyboardInterrupt):
|
||||
logger.warn('Exiting on Ctrl-c')
|
||||
else:
|
||||
logger.error(str(exc))
|
||||
finally:
|
||||
self.shutdown()
|
||||
self.prepare(proxydetails)
|
||||
self.minion.tune_in()
|
||||
# try:
|
||||
# if check_user(self.config['user']):
|
||||
# except (KeyboardInterrupt, SaltSystemExit) as exc:
|
||||
# logger.warn('Stopping the Salt Minion')
|
||||
# if isinstance(exc, KeyboardInterrupt):
|
||||
# logger.warn('Exiting on Ctrl-c')
|
||||
# else:
|
||||
# logger.error(str(exc))
|
||||
# finally:
|
||||
# self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
'''
|
||||
If sub-classed, run any shutdown operations on this method.
|
||||
'''
|
||||
if 'proxy' in self.minion.opts:
|
||||
self.minion.opts['proxyobject'].shutdown(self.minion.opts)
|
||||
|
||||
|
||||
|
||||
class Syndic(parsers.SyndicOptionParser):
|
||||
|
|
|
@ -181,6 +181,7 @@ VALID_OPTS = {
|
|||
'sign_pub_messages': bool,
|
||||
'keysize': int,
|
||||
'salt_transport': str,
|
||||
'enumerate_proxy_minions': bool,
|
||||
}
|
||||
|
||||
# default configurations
|
||||
|
@ -383,6 +384,7 @@ DEFAULT_MASTER_OPTS = {
|
|||
'sign_pub_messages': False,
|
||||
'keysize': 4096,
|
||||
'salt_transport': 'zeromq',
|
||||
'enumerate_proxy_minions': False
|
||||
}
|
||||
|
||||
# ----- Salt Cloud Configuration Defaults ----------------------------------->
|
||||
|
|
|
@ -19,6 +19,7 @@ import traceback
|
|||
import sys
|
||||
import signal
|
||||
from random import randint
|
||||
import salt
|
||||
import importlib
|
||||
|
||||
# Import third party libs
|
||||
|
@ -537,8 +538,22 @@ class Minion(object):
|
|||
self.opts,
|
||||
self.functions,
|
||||
self.returners)
|
||||
|
||||
self.grains_cache = self.opts['grains']
|
||||
|
||||
if 'proxy' in self.opts['pillar']:
|
||||
log.debug('I am {} and I need to start some proxies for {}'.format(self.opts['id'], self.opts['pillar']['proxy']))
|
||||
for p in self.opts['pillar']['proxy']:
|
||||
log.debug('Starting {} proxy.'.format(p))
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
continue
|
||||
else:
|
||||
proxyminion = salt.ProxyMinion()
|
||||
proxyminion.start(self.opts['pillar']['proxy'][p])
|
||||
else:
|
||||
log.debug("I am {} and I am not supposed to start any proxies.".format(self.opts['id']))
|
||||
|
||||
def __prep_mod_opts(self):
|
||||
'''
|
||||
Returns a copy of the opts with key bits stripped out
|
||||
|
@ -1852,11 +1867,10 @@ class ProxyMinion(object):
|
|||
# Late setup the of the opts grains, so we can log from the grains
|
||||
# module
|
||||
# print opts['proxymodule']
|
||||
fq_proxyname = 'proxy.'+opts['proxymodule']
|
||||
proxymodule = salt.loader.proxy(opts, fq_proxyname)
|
||||
opts['proxytype'] = proxymodule[opts['proxymodule']+'.proxytype']()
|
||||
opts['proxyconn'] = proxymodule[opts['proxymodule']+'.proxyconn'](user='cro', host='junos', passwd='croldham123')
|
||||
opts['id'] = proxymodule[opts['proxymodule']+'.id'](opts)
|
||||
fq_proxyname = 'proxy.'+opts['proxy']['proxytype']
|
||||
self.proxymodule = salt.loader.proxy(opts, fq_proxyname)
|
||||
opts['proxyobject'] = self.proxymodule[opts['proxy']['proxytype']+'.Proxyconn'](opts['proxy'])
|
||||
opts['id'] = opts['proxyobject'].id(opts)
|
||||
opts.update(resolve_dns(opts))
|
||||
self.opts = opts
|
||||
self.authenticate(timeout, safe)
|
||||
|
@ -1878,7 +1892,6 @@ class ProxyMinion(object):
|
|||
self.grains_cache = self.opts['grains']
|
||||
|
||||
|
||||
|
||||
def __prep_mod_opts(self):
|
||||
'''
|
||||
Returns a copy of the opts with key bits stripped out
|
||||
|
@ -2509,14 +2522,14 @@ class ProxyMinion(object):
|
|||
self.opts['id'],
|
||||
time.asctime()
|
||||
),
|
||||
'minion_start'
|
||||
'proxy_minion_start'
|
||||
)
|
||||
self._fire_master(
|
||||
'Minion {0} started at {1}'.format(
|
||||
self.opts['id'],
|
||||
time.asctime()
|
||||
),
|
||||
tagify([self.opts['id'], 'start'], 'minion'),
|
||||
tagify([self.opts['id'], 'start'], 'proxy_minion'),
|
||||
)
|
||||
|
||||
# Make sure to gracefully handle SIGUSR1
|
||||
|
@ -2640,11 +2653,11 @@ class ProxyMinion(object):
|
|||
self.opts['id'],
|
||||
time.asctime()
|
||||
),
|
||||
'minion_start'
|
||||
'proxy_minion_start'
|
||||
)
|
||||
# dup name spaced event
|
||||
self._fire_master(
|
||||
'Minion {0} started at {1}'.format(
|
||||
'Proxy Minion {0} started at {1}'.format(
|
||||
self.opts['id'],
|
||||
time.asctime()
|
||||
),
|
||||
|
@ -2665,7 +2678,7 @@ class ProxyMinion(object):
|
|||
yield True
|
||||
except Exception:
|
||||
log.critical(
|
||||
'An exception occurred while polling the minion',
|
||||
'An exception occurred while polling the proxy minion',
|
||||
exc_info=True
|
||||
)
|
||||
yield True
|
||||
|
|
|
@ -41,12 +41,8 @@ def ping():
|
|||
salt '*' test.ping
|
||||
'''
|
||||
|
||||
if 'proxytype' in __opts__:
|
||||
fun = 'salt.proxy.{0}.ping'.format(__opts__['proxytype'])
|
||||
if fun in __salt__:
|
||||
return __salt__[fun]()
|
||||
else:
|
||||
return False
|
||||
if 'proxyobject' in __opts__:
|
||||
return __opts__['proxyobject'].ping()
|
||||
else:
|
||||
return True
|
||||
|
||||
|
|
|
@ -12,15 +12,31 @@ import jnpr.junos.utils
|
|||
import jnpr.junos.cfg
|
||||
HAS_JUNOS = True
|
||||
|
||||
class Proxyconn(object):
|
||||
|
||||
def proxyconn(user=None, host=None, passwd=None):
|
||||
jdev = jnpr.junos.Device(user=user, host=host, password=passwd)
|
||||
jdev.open()
|
||||
jdev.bind(cu=jnpr.junos.utils.Config)
|
||||
return jdev
|
||||
|
||||
def proxytype():
|
||||
return 'junos'
|
||||
def __init__(self, details):
|
||||
self.conn = jnpr.junos.Device(user=details['username'], host=details['host'], password=details['passwd'])
|
||||
self.conn.open()
|
||||
self.conn.bind(cu=jnpr.junos.cfg.Resource)
|
||||
|
||||
def id(opts):
|
||||
return opts['proxyconn'].facts['hostname']
|
||||
|
||||
def proxytype(self):
|
||||
return 'junos'
|
||||
|
||||
|
||||
def id(self, opts):
|
||||
return self.conn.facts['hostname']
|
||||
|
||||
|
||||
def ping(self):
|
||||
return self.conn.connected
|
||||
|
||||
|
||||
def shutdown(self, opts):
|
||||
|
||||
print('Proxy module {} shutting down!!'.format(opts['id']))
|
||||
try:
|
||||
self.conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
|
Loading…
Add table
Reference in a new issue