Skip to content

Commit

Permalink
junos_facts refactor to work with resources modules (ansible#58566)
Browse files Browse the repository at this point in the history
* junos_facts refactor to work with resources modules

*  Refactor junos_facts module to work with
   network resource module.

* Fix unit test failures

* Fix review comments
  • Loading branch information
ganeshrn authored Jul 3, 2019
1 parent 6c5de9e commit 1e3034b
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 307 deletions.
11 changes: 6 additions & 5 deletions lib/ansible/module_utils/network/common/facts/facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, module):
self._connection = get_resource_connection(module)

self.ansible_facts = {'ansible_network_resources': {}}
self.ansible_facts['ansible_gather_network_resources'] = list()
self.ansible_facts['ansible_net_gather_network_resources'] = list()
self.ansible_facts['ansible_net_gather_subset'] = list()

if not self._gather_subset:
Expand Down Expand Up @@ -65,7 +65,8 @@ def gen_runable(self, subsets, valid_subsets):
exclude = False

if subset not in valid_subsets:
self._module.fail_json(msg='Bad subset')
self._module.fail_json(msg='Subset must be one of [%s], got %s' %
(', '.join(sorted([item for item in valid_subsets])), subset))

if exclude:
exclude_subsets.add(subset)
Expand All @@ -75,7 +76,6 @@ def gen_runable(self, subsets, valid_subsets):
if not runable_subsets:
runable_subsets.update(valid_subsets)
runable_subsets.difference_update(exclude_subsets)

return runable_subsets

def get_network_resources_facts(self, net_res_choices, facts_resource_obj_map, resource_facts_type=None, data=None):
Expand All @@ -95,7 +95,7 @@ def get_network_resources_facts(self, net_res_choices, facts_resource_obj_map, r

restorun_subsets = self.gen_runable(resource_facts_type, frozenset(net_res_choices))
if restorun_subsets:
self.ansible_facts['gather_network_resources'] = list(restorun_subsets)
self.ansible_facts['ansible_net_gather_network_resources'] = list(restorun_subsets)
instances = list()
for key in restorun_subsets:
fact_cls_obj = facts_resource_obj_map.get(key)
Expand All @@ -112,9 +112,10 @@ def get_network_legacy_facts(self, fact_legacy_obj_map, legacy_facts_type=None):
legacy_facts_type = self._gather_subset

runable_subsets = self.gen_runable(legacy_facts_type, frozenset(fact_legacy_obj_map.keys()))
runable_subsets.add('default')
if runable_subsets:
facts = dict()
facts['gather_subset'] = list(runable_subsets)
facts['ansible_net_gather_subset'] = list(runable_subsets)

instances = list()
for key in runable_subsets:
Expand Down
Empty file.
Empty file.
25 changes: 25 additions & 0 deletions lib/ansible/module_utils/network/junos/argspec/facts/facts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The arg spec for the junos facts module.
"""
CHOICES = [
'all',
]


class FactsArgs(object):
""" The arg spec for the junos facts module
"""

def __init__(self, **kwargs):
pass

argument_spec = {
'gather_subset': dict(default=['!config'], type='list'),
'config_format': dict(default='text', choices=['xml', 'text', 'set', 'json']),
'gather_network_resources': dict(choices=CHOICES, type='list'),
}
Empty file.
Empty file.
64 changes: 64 additions & 0 deletions lib/ansible/module_utils/network/junos/facts/facts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The facts class for junos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""

from ansible.module_utils.network.junos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.junos.facts.legacy.base import Default, Hardware, Config, Interfaces, OFacts, HAS_PYEZ

FACT_LEGACY_SUBSETS = dict(
default=Default,
hardware=Hardware,
config=Config,
interfaces=Interfaces,
)
FACT_RESOURCE_SUBSETS = dict(
)


class Facts(FactsBase):
""" The fact class for junos
"""

VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())

def __init__(self, module):
super(Facts, self).__init__(module)

def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
""" Collect the facts for junos
:param legacy_facts_type: List of legacy facts types
:param resource_facts_type: List of resource fact types
:param data: previously collected conf
:rtype: dict
:return: the facts gathered
"""
netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', [])
if self.VALID_RESOURCE_SUBSETS:
self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS, resource_facts_type, data)

if not legacy_facts_type:
legacy_facts_type = self._gather_subset
# fetch old style facts only when explicitly mentioned in gather_subset option
if 'ofacts' in legacy_facts_type:
if HAS_PYEZ:
self.ansible_facts.update(OFacts(self._module).populate())
else:
self._warnings.extend([
'junos-eznc is required to gather old style facts but does not appear to be installed. '
'It can be installed using `pip install junos-eznc`'])
self.ansible_facts['ansible_net_gather_subset'].append('ofacts')
legacy_facts_type.remove('ofacts')

if self.VALID_LEGACY_GATHER_SUBSETS:
self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)

return self.ansible_facts, self._warnings
Empty file.
226 changes: 226 additions & 0 deletions lib/ansible/module_utils/network/junos/facts/legacy/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The junos interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
import platform

from ansible.module_utils.network.common.netconf import exec_rpc
from ansible.module_utils.network.junos.junos import get_param, tostring
from ansible.module_utils.network.junos.junos import get_configuration, get_capabilities
from ansible.module_utils._text import to_native


try:
from lxml.etree import Element, SubElement
except ImportError:
from xml.etree.ElementTree import Element, SubElement

try:
from jnpr.junos import Device
from jnpr.junos.exception import ConnectError
HAS_PYEZ = True
except ImportError:
HAS_PYEZ = False


class FactsBase(object):

def __init__(self, module):
self.module = module
self.facts = dict()
self.warnings = []

def populate(self):
raise NotImplementedError

def cli(self, command):
reply = command(self.module, command)
output = reply.find('.//output')
if not output:
self.module.fail_json(msg='failed to retrieve facts for command %s' % command)
return str(output.text).strip()

def rpc(self, rpc):
return exec_rpc(self.module, tostring(Element(rpc)))

def get_text(self, ele, tag):
try:
return str(ele.find(tag).text).strip()
except AttributeError:
pass


class Default(FactsBase):

def populate(self):
self.facts.update(self.platform_facts())

reply = self.rpc('get-chassis-inventory')
data = reply.find('.//chassis-inventory/chassis')
self.facts['serialnum'] = self.get_text(data, 'serial-number')

def platform_facts(self):
platform_facts = {}

resp = get_capabilities(self.module)
device_info = resp['device_info']

platform_facts['system'] = device_info['network_os']

for item in ('model', 'image', 'version', 'platform', 'hostname'):
val = device_info.get('network_os_%s' % item)
if val:
platform_facts[item] = val

platform_facts['api'] = resp['network_api']
platform_facts['python_version'] = platform.python_version()

return platform_facts


class Config(FactsBase):

def populate(self):
config_format = self.module.params['config_format']
reply = get_configuration(self.module, format=config_format)

if config_format == 'xml':
config = tostring(reply.find('configuration')).strip()

elif config_format == 'text':
config = self.get_text(reply, 'configuration-text')

elif config_format == 'json':
config = self.module.from_json(reply.text.strip())

elif config_format == 'set':
config = self.get_text(reply, 'configuration-set')

self.facts['config'] = config


class Hardware(FactsBase):

def populate(self):

reply = self.rpc('get-system-memory-information')
data = reply.find('.//system-memory-information/system-memory-summary-information')

self.facts.update({
'memfree_mb': int(self.get_text(data, 'system-memory-free')),
'memtotal_mb': int(self.get_text(data, 'system-memory-total'))
})

reply = self.rpc('get-system-storage')
data = reply.find('.//system-storage-information')

filesystems = list()
for obj in data:
filesystems.append(self.get_text(obj, 'filesystem-name'))
self.facts['filesystems'] = filesystems

reply = self.rpc('get-route-engine-information')
data = reply.find('.//route-engine-information')

routing_engines = dict()
for obj in data:
slot = self.get_text(obj, 'slot')
routing_engines.update({slot: {}})
routing_engines[slot].update({'slot': slot})
for child in obj:
if child.text != "\n":
routing_engines[slot].update({child.tag.replace("-", "_"): child.text})

self.facts['routing_engines'] = routing_engines

if len(data) > 1:
self.facts['has_2RE'] = True
else:
self.facts['has_2RE'] = False

reply = self.rpc('get-chassis-inventory')
data = reply.findall('.//chassis-module')

modules = list()
for obj in data:
mod = dict()
for child in obj:
if child.text != "\n":
mod.update({child.tag.replace("-", "_"): child.text})
modules.append(mod)

self.facts['modules'] = modules


class Interfaces(FactsBase):

def populate(self):
ele = Element('get-interface-information')
SubElement(ele, 'detail')
reply = exec_rpc(self.module, tostring(ele))

interfaces = {}

for item in reply[0]:
name = self.get_text(item, 'name')
obj = {
'oper-status': self.get_text(item, 'oper-status'),
'admin-status': self.get_text(item, 'admin-status'),
'speed': self.get_text(item, 'speed'),
'macaddress': self.get_text(item, 'hardware-physical-address'),
'mtu': self.get_text(item, 'mtu'),
'type': self.get_text(item, 'if-type'),
}

interfaces[name] = obj

self.facts['interfaces'] = interfaces


class OFacts(FactsBase):
def _connect(self, module):
host = get_param(module, 'host')

kwargs = {
'port': get_param(module, 'port') or 830,
'user': get_param(module, 'username')
}

if get_param(module, 'password'):
kwargs['passwd'] = get_param(module, 'password')

if get_param(module, 'ssh_keyfile'):
kwargs['ssh_private_key_file'] = get_param(module, 'ssh_keyfile')

kwargs['gather_facts'] = False
try:
device = Device(host, **kwargs)
device.open()
device.timeout = get_param(module, 'timeout') or 10
except ConnectError as exc:
module.fail_json('unable to connect to %s: %s' % (host, to_native(exc)))

return device

def populate(self):

device = self._connect(self.module)
facts = dict(device.facts)

if '2RE' in facts:
facts['has_2RE'] = facts['2RE']
del facts['2RE']

facts['version_info'] = dict(facts['version_info'])
if 'junos_info' in facts:
for key, value in facts['junos_info'].items():
if 'object' in value:
value['object'] = dict(value['object'])

return facts
Empty file.
Loading

0 comments on commit 1e3034b

Please sign in to comment.