Merge pull request #39896 from cloudflare/not-always-alive

Add always_alive option for napalm proxy
This commit is contained in:
Mike Place 2017-03-13 12:28:21 -06:00 committed by GitHub
commit 1e200b92e2
3 changed files with 124 additions and 13 deletions

View File

@ -95,6 +95,25 @@ def _filter_dict(input_dict, search_key, search_value):
return output_dict
def _explicit_close(napalm_device):
'''
Will explicitely close the config session with the network device,
when running in a now-always-alive proxy minion or regular minion.
This helper must be used in configuration-related functions,
as the session is preserved and not closed before making any changes.
'''
if salt.utils.napalm.not_always_alive(__opts__):
# force closing the configuration session
# when running in a non-always-alive proxy
# or regular minion
try:
napalm_device['DRIVER'].close()
except Exception as err:
log.error('Unable to close the temp connection with the device:')
log.error(err)
log.error('Please report.')
def _config_logic(napalm_device,
loaded_result,
test=False,
@ -131,7 +150,6 @@ def _config_logic(napalm_device,
loaded_result.pop('out', '') # not needed
_loaded_res = loaded_result.get('result', False)
if not _loaded_res or test:
# if unable to load the config (errors / warnings)
# or in testing mode,
@ -147,11 +165,13 @@ def _config_logic(napalm_device,
loaded_result['result'] = False
# make sure it notifies
# that something went wrong
_explicit_close(napalm_device)
return loaded_result
loaded_result['comment'] += 'Configuration discarded.'
# loaded_result['result'] = False not necessary
# as the result can be true when test=True
_explicit_close(napalm_device)
return loaded_result
if not test and commit_config:
@ -181,10 +201,11 @@ def _config_logic(napalm_device,
else 'Unable to discard config.'
loaded_result['result'] = False
# notify if anything goes wrong
_explicit_close(napalm_device)
return loaded_result
loaded_result['already_configured'] = True
loaded_result['comment'] = 'Already configured.'
_explicit_close(napalm_device)
return loaded_result
@ -841,6 +862,15 @@ def load_config(filename=None,
fun = 'load_merge_candidate'
if replace:
fun = 'load_replace_candidate'
if salt.utils.napalm.not_always_alive(__opts__):
# if a not-always-alive proxy
# or regular minion
# do not close the connection after loading the config
# this will be handled in _config_logic
# after running the other features:
# compare_config, discard / commit
# which have to be over the same session
napalm_device['CLOSE'] = False # pylint: disable=undefined-variable
_loaded = salt.utils.napalm.call(
napalm_device, # pylint: disable=undefined-variable
fun,
@ -1207,6 +1237,15 @@ def load_template(template_name,
fun = 'load_merge_candidate'
if replace: # replace requested
fun = 'load_replace_candidate'
if salt.utils.napalm.not_always_alive(__opts__):
# if a not-always-alive proxy
# or regular minion
# do not close the connection after loading the config
# this will be handled in _config_logic
# after running the other features:
# compare_config, discard / commit
# which have to be over the same session
napalm_device['CLOSE'] = False # pylint: disable=undefined-variable
_loaded = salt.utils.napalm.call(
napalm_device, # pylint: disable=undefined-variable
fun,
@ -1228,12 +1267,21 @@ def load_template(template_name,
'opts': __opts__ # inject opts content
}
)
if salt.utils.napalm.not_always_alive(__opts__):
# if a not-always-alive proxy
# or regular minion
# do not close the connection after loading the config
# this will be handled in _config_logic
# after running the other features:
# compare_config, discard / commit
# which have to be over the same session
# so we'll set the CLOSE global explicitely as False
napalm_device['CLOSE'] = False # pylint: disable=undefined-variable
_loaded = salt.utils.napalm.call(
napalm_device, # pylint: disable=undefined-variable
'load_template',
**load_templates_params
)
return _config_logic(napalm_device, # pylint: disable=undefined-variable
_loaded,
test=test,

View File

@ -129,6 +129,9 @@ def alive(opts):
.. versionadded:: Nitrogen
'''
if salt.utils.napalm.not_always_alive(opts):
return True # don't force reconnection for not-always alive proxies
# or regular minion
is_alive_ret = call('is_alive', **{})
if not is_alive_ret.get('result', False):
log.debug('[{proxyid}] Unable to execute `is_alive`: {comment}'.format(

View File

@ -19,6 +19,7 @@ from __future__ import absolute_import
import traceback
import logging
from functools import wraps
log = logging.getLogger(__file__)
import salt.utils
@ -44,6 +45,20 @@ def is_proxy(opts):
return salt.utils.is_proxy() and opts.get('proxy', {}).get('proxytype') == 'napalm'
def is_always_alive(opts):
'''
Is always alive required?
'''
return opts.get('proxy', {}).get('always_alive', True)
def not_always_alive(opts):
'''
Should this proxy be always alive?
'''
return (is_proxy(opts) and not is_always_alive(opts)) or is_minion(opts)
def is_minion(opts):
'''
Is this a NAPALM straight minion?
@ -113,6 +128,7 @@ def call(napalm_device, method, *args, **kwargs):
'''
result = False
out = None
opts = napalm_device.get('__opts__', {})
try:
if not napalm_device.get('UP', False):
raise Exception('not connected')
@ -153,6 +169,13 @@ def call(napalm_device, method, *args, **kwargs):
'comment': comment,
'traceback': err_tb
}
finally:
if opts and not_always_alive(opts) and napalm_device.get('CLOSE', True):
# either running in a not-always-alive proxy
# either running in a regular minion
# close the connection when the call is over
# unless the CLOSE is explicitely set as False
napalm_device['DRIVER'].close()
return {
'out': out,
'result': result,
@ -172,7 +195,7 @@ def get_device(opts, salt_obj=None):
device_dict = opts.get('proxy', {}) or opts.get('napalm', {})
if salt_obj and not device_dict:
# get the connection details from the opts
device_dict = salt_obj['config.option']('napalm')
device_dict = salt_obj['config.merge']('napalm')
if not device_dict:
# still not able to setup
log.error('Incorrect minion config. Please specify at least the napalm driver name!')
@ -183,8 +206,9 @@ def get_device(opts, salt_obj=None):
network_device['PASSWORD'] = device_dict.get('passwd') or device_dict.get('password') or device_dict.get('pass')
network_device['TIMEOUT'] = device_dict.get('timeout', 60)
network_device['OPTIONAL_ARGS'] = device_dict.get('optional_args', {})
network_device['ALWAYS_ALIVE'] = device_dict.get('always_alive', True)
network_device['UP'] = False
# get driver object form NAPALM
# get driver object from NAPALM
if 'config_lock' not in list(network_device['OPTIONAL_ARGS'].keys()):
network_device['OPTIONAL_ARGS']['config_lock'] = False
_driver_ = napalm_base.get_network_driver(network_device.get('DRIVER_NAME'))
@ -226,20 +250,25 @@ def proxy_napalm_wrap(func):
:param func:
:return:
'''
@wraps(func)
def func_wrapper(*args, **kwargs):
wrapped_global_namespace = func.__globals__
# get __proxy__ from func_globals
# get __opts__ and __proxy__ from func_globals
proxy = wrapped_global_namespace.get('__proxy__')
opts = wrapped_global_namespace.get('__opts__')
# in any case, will inject the `napalm_device` global
# the execution modules will make use of this variable from now on
# previously they were accessing the device properties through the __proxy__ object
if salt.utils.is_proxy():
always_alive = opts.get('proxy', {}).get('always_alive', True)
if salt.utils.is_proxy() and always_alive:
# if it is running in a proxy and it's using the default always alive behaviour,
# will get the cached copy of the network device
wrapped_global_namespace['napalm_device'] = proxy['napalm.get_device']()
else:
# get __opts__ and __salt__ from func_globals
opts = wrapped_global_namespace.get('__opts__')
_salt_obj = wrapped_global_namespace.get('__salt__')
elif salt.utils.is_proxy() and not always_alive:
# if still proxy, but the user does not want the SSH session always alive
# get a new device instance
# which establishes a new connection
# which is closed just before the call() function defined above returns
if 'inherit_napalm_device' not in kwargs or ('inherit_napalm_device' in kwargs and
not kwargs['inherit_napalm_device']):
# try to open a new connection
@ -247,8 +276,9 @@ def proxy_napalm_wrap(func):
# for configuration management this is very important,
# in order to make sure we are editing the same session.
try:
wrapped_global_namespace['napalm_device'] = get_device(opts, salt_obj=_salt_obj)
wrapped_global_namespace['napalm_device'] = get_device(opts)
except napalm_base.exceptions.ConnectionException as nce:
log.error(nce)
return '{base_msg}. See log for details.'.format(
base_msg=str(nce.msg)
)
@ -260,5 +290,35 @@ def proxy_napalm_wrap(func):
# as all actions must be issued within the same configuration session
# otherwise we risk to open multiple sessions
wrapped_global_namespace['napalm_device'] = kwargs['inherit_napalm_device']
else:
# if no proxy
# thus it is running on a regular minion, directly on the network device
# get __salt__ from func_globals
_salt_obj = wrapped_global_namespace.get('__salt__')
if 'inherit_napalm_device' not in kwargs or ('inherit_napalm_device' in kwargs and
not kwargs['inherit_napalm_device']):
# try to open a new connection
# but only if the function does not inherit the napalm driver
# for configuration management this is very important,
# in order to make sure we are editing the same session.
try:
wrapped_global_namespace['napalm_device'] = get_device(opts, salt_obj=_salt_obj)
except napalm_base.exceptions.ConnectionException as nce:
log.error(nce)
return '{base_msg}. See log for details.'.format(
base_msg=str(nce.msg)
)
else:
# in case the `inherit_napalm_device` is set
# and it also has a non-empty value,
# the global var `napalm_device` will be overriden.
# this is extremely important for configuration-related features
# as all actions must be issued within the same configuration session
# otherwise we risk to open multiple sessions
wrapped_global_namespace['napalm_device'] = kwargs['inherit_napalm_device']
if not_always_alive(opts):
# inject the __opts__ only when not always alive
# otherwise, we don't want to overload the always-alive proxies
wrapped_global_namespace['napalm_device']['__opts__'] = opts
return func(*args, **kwargs)
return func_wrapper