ansible-later/env_27/lib/python2.7/site-packages/ansible/module_utils/network/junos/junos.py
2019-04-11 13:00:36 +02:00

446 lines
15 KiB
Python

#
# (c) 2017 Red Hat, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import collections
import json
from contextlib import contextmanager
from copy import deepcopy
from ansible.module_utils.basic import env_fallback, return_values
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.module_utils.network.common.netconf import NetconfConnection
from ansible.module_utils._text import to_text
try:
from lxml.etree import Element, SubElement, tostring as xml_to_string
HAS_LXML = True
except ImportError:
from xml.etree.ElementTree import Element, SubElement, tostring as xml_to_string
HAS_LXML = False
ACTIONS = frozenset(['merge', 'override', 'replace', 'update', 'set'])
JSON_ACTIONS = frozenset(['merge', 'override', 'update'])
FORMATS = frozenset(['xml', 'text', 'json'])
CONFIG_FORMATS = frozenset(['xml', 'text', 'json', 'set'])
junos_provider_spec = {
'host': dict(),
'port': dict(type='int'),
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
'timeout': dict(type='int'),
'transport': dict(default='netconf', choices=['cli', 'netconf'])
}
junos_argument_spec = {
'provider': dict(type='dict', options=junos_provider_spec),
}
junos_top_spec = {
'host': dict(removed_in_version=2.9),
'port': dict(removed_in_version=2.9, type='int'),
'username': dict(removed_in_version=2.9),
'password': dict(removed_in_version=2.9, no_log=True),
'ssh_keyfile': dict(removed_in_version=2.9, type='path'),
'timeout': dict(removed_in_version=2.9, type='int'),
'transport': dict(removed_in_version=2.9)
}
junos_argument_spec.update(junos_top_spec)
def tostring(element, encoding='UTF-8'):
if HAS_LXML:
return xml_to_string(element, encoding='unicode')
else:
return to_text(xml_to_string(element, encoding), encoding=encoding)
def get_provider_argspec():
return junos_provider_spec
def get_connection(module):
if hasattr(module, '_junos_connection'):
return module._junos_connection
capabilities = get_capabilities(module)
network_api = capabilities.get('network_api')
if network_api == 'cliconf':
module._junos_connection = Connection(module._socket_path)
elif network_api == 'netconf':
module._junos_connection = NetconfConnection(module._socket_path)
else:
module.fail_json(msg='Invalid connection type %s' % network_api)
return module._junos_connection
def get_capabilities(module):
if hasattr(module, '_junos_capabilities'):
return module._junos_capabilities
try:
capabilities = Connection(module._socket_path).get_capabilities()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
module._junos_capabilities = json.loads(capabilities)
return module._junos_capabilities
def is_netconf(module):
capabilities = get_capabilities(module)
return True if capabilities.get('network_api') == 'netconf' else False
def _validate_rollback_id(module, value):
try:
if not 0 <= int(value) <= 49:
raise ValueError
except ValueError:
module.fail_json(msg='rollback must be between 0 and 49')
def load_configuration(module, candidate=None, action='merge', rollback=None, format='xml'):
if all((candidate is None, rollback is None)):
module.fail_json(msg='one of candidate or rollback must be specified')
elif all((candidate is not None, rollback is not None)):
module.fail_json(msg='candidate and rollback are mutually exclusive')
if format not in FORMATS:
module.fail_json(msg='invalid format specified')
if format == 'json' and action not in JSON_ACTIONS:
module.fail_json(msg='invalid action for format json')
elif format in ('text', 'xml') and action not in ACTIONS:
module.fail_json(msg='invalid action format %s' % format)
if action == 'set' and not format == 'text':
module.fail_json(msg='format must be text when action is set')
conn = get_connection(module)
try:
if rollback is not None:
_validate_rollback_id(module, rollback)
obj = Element('load-configuration', {'rollback': str(rollback)})
conn.execute_rpc(tostring(obj))
else:
return conn.load_configuration(config=candidate, action=action, format=format)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
def get_configuration(module, compare=False, format='xml', rollback='0', filter=None):
if format not in CONFIG_FORMATS:
module.fail_json(msg='invalid config format specified')
conn = get_connection(module)
try:
if compare:
xattrs = {'format': format}
_validate_rollback_id(module, rollback)
xattrs['compare'] = 'rollback'
xattrs['rollback'] = str(rollback)
reply = conn.execute_rpc(tostring(Element('get-configuration', xattrs)))
else:
reply = conn.get_configuration(format=format, filter=filter)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return reply
def commit_configuration(module, confirm=False, check=False, comment=None, confirm_timeout=None, synchronize=False, at_time=None):
conn = get_connection(module)
try:
if check:
reply = conn.validate()
else:
if is_netconf(module):
reply = conn.commit(confirmed=confirm, timeout=confirm_timeout, comment=comment, synchronize=synchronize, at_time=at_time)
else:
reply = conn.commit(comment=comment, confirmed=confirm, at_time=at_time, synchronize=synchronize)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return reply
def command(module, cmd, format='text', rpc_only=False):
conn = get_connection(module)
if rpc_only:
cmd += ' | display xml rpc'
try:
response = conn.command(command=cmd, format=format)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return response
def lock_configuration(module):
conn = get_connection(module)
try:
response = conn.lock()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return response
def unlock_configuration(module):
conn = get_connection(module)
try:
response = conn.unlock()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return response
@contextmanager
def locked_config(module):
try:
lock_configuration(module)
yield
finally:
unlock_configuration(module)
def discard_changes(module):
conn = get_connection(module)
try:
response = conn.discard_changes()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
return response
def get_diff(module, rollback='0'):
reply = get_configuration(module, compare=True, format='text', rollback=rollback)
# if warning is received from device diff is empty.
if isinstance(reply, list):
return None
output = reply.find('.//configuration-output')
if output is not None:
return to_text(output.text, encoding='latin-1').strip()
def load_config(module, candidate, warnings, action='merge', format='xml'):
get_connection(module)
if not candidate:
return
if isinstance(candidate, list):
candidate = '\n'.join(candidate)
reply = load_configuration(module, candidate, action=action, format=format)
if isinstance(reply, list):
warnings.extend(reply)
module._junos_connection.validate()
return get_diff(module)
def get_param(module, key):
if module.params.get(key):
value = module.params[key]
elif module.params.get('provider'):
value = module.params['provider'].get(key)
else:
value = None
return value
def map_params_to_obj(module, param_to_xpath_map, param=None):
"""
Creates a new dictionary with key as xpath corresponding
to param and value is a list of dict with metadata and values for
the xpath.
Acceptable metadata keys:
'value': Value of param.
'tag_only': Value is indicated by tag only in xml hierarchy.
'leaf_only': If operation is to be added at leaf node only.
'value_req': If value(text) is requried for leaf node.
'is_key': If the field is key or not.
eg: Output
{
'name': [{'value': 'ge-0/0/1'}]
'disable': [{'value': True, tag_only': True}]
}
:param module:
:param param_to_xpath_map: Modules params to xpath map
:return: obj
"""
if not param:
param = module.params
obj = collections.OrderedDict()
for key, attribute in param_to_xpath_map.items():
if key in param:
is_attribute_dict = False
value = param[key]
if not isinstance(value, (list, tuple)):
value = [value]
if isinstance(attribute, dict):
xpath = attribute.get('xpath')
is_attribute_dict = True
else:
xpath = attribute
if not obj.get(xpath):
obj[xpath] = list()
for val in value:
if is_attribute_dict:
attr = deepcopy(attribute)
del attr['xpath']
attr.update({'value': val})
obj[xpath].append(attr)
else:
obj[xpath].append({'value': val})
return obj
def map_obj_to_ele(module, want, top, value_map=None, param=None):
if not HAS_LXML:
module.fail_json(msg='lxml is not installed.')
if not param:
param = module.params
root = Element('root')
top_ele = top.split('/')
ele = SubElement(root, top_ele[0])
if len(top_ele) > 1:
for item in top_ele[1:-1]:
ele = SubElement(ele, item)
container = ele
state = param.get('state')
active = param.get('active')
if active:
oper = 'active'
else:
oper = 'inactive'
# build xml subtree
if container.tag != top_ele[-1]:
node = SubElement(container, top_ele[-1])
else:
node = container
for fxpath, attributes in want.items():
for attr in attributes:
tag_only = attr.get('tag_only', False)
leaf_only = attr.get('leaf_only', False)
value_req = attr.get('value_req', False)
is_key = attr.get('is_key', False)
parent_attrib = attr.get('parent_attrib', True)
value = attr.get('value')
field_top = attr.get('top')
# operation 'delete' is added as element attribute
# only if it is key or leaf only node
if state == 'absent' and not (is_key or leaf_only):
continue
# convert param value to device specific value
if value_map and fxpath in value_map:
value = value_map[fxpath].get(value)
if (value is not None) or tag_only or leaf_only:
ele = node
if field_top:
# eg: top = 'system/syslog/file'
# field_top = 'system/syslog/file/contents'
# <file>
# <name>test</name>
# <contents>
# </contents>
# </file>
ele_list = root.xpath(top + '/' + field_top)
if not len(ele_list):
fields = field_top.split('/')
ele = node
for item in fields:
inner_ele = root.xpath(top + '/' + item)
if len(inner_ele):
ele = inner_ele[0]
else:
ele = SubElement(ele, item)
else:
ele = ele_list[0]
if value is not None and not isinstance(value, bool):
value = to_text(value, errors='surrogate_then_replace')
if fxpath:
tags = fxpath.split('/')
for item in tags:
ele = SubElement(ele, item)
if tag_only:
if state == 'present':
if not value:
# if value of tag_only node is false, delete the node
ele.set('delete', 'delete')
elif leaf_only:
if state == 'present':
ele.set(oper, oper)
ele.text = value
else:
ele.set('delete', 'delete')
# Add value of leaf node if required while deleting.
# in some cases if value is present while deleting, it
# can result in error, hence the check
if value_req:
ele.text = value
if is_key:
par = ele.getparent()
par.set('delete', 'delete')
else:
ele.text = value
par = ele.getparent()
if parent_attrib:
if state == 'present':
# set replace attribute at parent node
if not par.attrib.get('replace'):
par.set('replace', 'replace')
# set active/inactive at parent node
if not par.attrib.get(oper):
par.set(oper, oper)
else:
par.set('delete', 'delete')
return root.getchildren()[0]
def to_param_list(module):
aggregate = module.params.get('aggregate')
if aggregate:
if isinstance(aggregate, dict):
return [aggregate]
else:
return aggregate
else:
return [module.params]