mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge pull request #40473 from cloudflare/napalm-yang
New execution module: Napalm YANG
This commit is contained in:
commit
595888a439
@ -258,6 +258,7 @@ execution modules
|
||||
napalm_route
|
||||
napalm_snmp
|
||||
napalm_users
|
||||
napalm_yang_mod
|
||||
netaddress
|
||||
netbsd_sysctl
|
||||
netbsdservice
|
||||
|
5
doc/ref/modules/all/salt.modules.napalm_yang_mod.rst
Normal file
5
doc/ref/modules/all/salt.modules.napalm_yang_mod.rst
Normal file
@ -0,0 +1,5 @@
|
||||
salt.modules.napalm_yang_mod module
|
||||
===================================
|
||||
|
||||
.. automodule:: salt.modules.napalm_yang_mod
|
||||
:members:
|
@ -253,6 +253,10 @@ New modules:
|
||||
load ACL (firewall) configuration on network devices.
|
||||
- :mod:`Network ACL state <salt.states.netacl>` - Manage the firewall
|
||||
configuration. It only requires writing the pillar structure correctly!
|
||||
- :mod:`NAPALM YANG execution module <salt.modules.napalm_yang_mod>` - Parse,
|
||||
generate and load native device configuration in a standard way,
|
||||
using the OpenConfig/IETF models. This module cotains also helpers for
|
||||
the states.
|
||||
- :mod:`NET finder <salt.runners.net>` - Runner to find details easily and
|
||||
fast. It's smart enough to know what you are looking for. It will search
|
||||
in the details of the network interfaces, IP addresses, MAC address tables,
|
||||
|
@ -166,3 +166,78 @@ def call(method, *args, **kwargs):
|
||||
*args,
|
||||
**clean_kwargs
|
||||
)
|
||||
|
||||
|
||||
@proxy_napalm_wrap
|
||||
def compliance_report(filepath, **kwargs):
|
||||
'''
|
||||
Return the compliance report.
|
||||
|
||||
filepath
|
||||
The absolute path to the validation file.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm.compliance_report ~/validate.yml
|
||||
|
||||
Validation File Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- get_facts:
|
||||
os_version: 4.17
|
||||
|
||||
- get_interfaces_ip:
|
||||
Management1:
|
||||
ipv4:
|
||||
10.0.2.14:
|
||||
prefix_length: 24
|
||||
_mode: strict
|
||||
|
||||
Output Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
device1:
|
||||
----------
|
||||
comment:
|
||||
out:
|
||||
----------
|
||||
complies:
|
||||
False
|
||||
get_facts:
|
||||
----------
|
||||
complies:
|
||||
False
|
||||
extra:
|
||||
missing:
|
||||
present:
|
||||
----------
|
||||
os_version:
|
||||
----------
|
||||
actual_value:
|
||||
15.1F6-S1.4
|
||||
complies:
|
||||
False
|
||||
nested:
|
||||
False
|
||||
get_interfaces_ip:
|
||||
----------
|
||||
complies:
|
||||
False
|
||||
extra:
|
||||
missing:
|
||||
- Management1
|
||||
present:
|
||||
----------
|
||||
skipped:
|
||||
result:
|
||||
True
|
||||
'''
|
||||
return salt.utils.napalm.call(
|
||||
napalm_device, # pylint: disable=undefined-variable
|
||||
'compliance_report',
|
||||
validation_file=filepath
|
||||
)
|
||||
|
597
salt/modules/napalm_yang_mod.py
Normal file
597
salt/modules/napalm_yang_mod.py
Normal file
@ -0,0 +1,597 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
NAPALM YANG
|
||||
===========
|
||||
|
||||
NAPALM YANG basic operations.
|
||||
|
||||
.. versionadded:: Nitrogen
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import python stdlib
|
||||
import logging
|
||||
|
||||
# Import third party libs
|
||||
try:
|
||||
import napalm_yang
|
||||
HAS_NAPALM_YANG = True
|
||||
except ImportError:
|
||||
HAS_NAPALM_YANG = False
|
||||
|
||||
# import NAPALM utils
|
||||
import salt.utils.napalm
|
||||
from salt.utils.napalm import proxy_napalm_wrap
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# module properties
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
__virtualname__ = 'napalm_yang'
|
||||
__proxyenabled__ = ['napalm']
|
||||
# uses NAPALM-based proxy to interact with network devices
|
||||
|
||||
log = logging.getLogger(__file__)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# property functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
NAPALM library must be installed for this module to work and run in a (proxy) minion.
|
||||
This module in particular requires also napalm-yang.
|
||||
'''
|
||||
if not HAS_NAPALM_YANG:
|
||||
return (False, 'Unable to load napalm_yang execution module: please install napalm-yang!')
|
||||
return salt.utils.napalm.virtual(__opts__, __virtualname__, __file__)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# helper functions -- will not be exported
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _get_root_object(*models):
|
||||
'''
|
||||
Read list of models and returns a Root object with the proper models added.
|
||||
'''
|
||||
root = napalm_yang.base.Root()
|
||||
for model in models:
|
||||
current = napalm_yang
|
||||
for part in model.split('.'):
|
||||
current = getattr(current, part)
|
||||
root.add_model(current)
|
||||
return root
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# callable functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def diff(candidate, running, *models):
|
||||
'''
|
||||
Returns the difference between two configuration entities structured
|
||||
according to the YANG model.
|
||||
|
||||
.. note::
|
||||
This function is recommended to be used mostly as a state helper.
|
||||
|
||||
candidate
|
||||
First model to compare.
|
||||
|
||||
running
|
||||
Second model to compare.
|
||||
|
||||
models
|
||||
A list of models to be used when comparing.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm_yang.diff {} {} models.openconfig_interfaces
|
||||
|
||||
Output Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
"interfaces": {
|
||||
"interface": {
|
||||
"both": {
|
||||
"Port-Channel1": {
|
||||
"config": {
|
||||
"mtu": {
|
||||
"first": "0",
|
||||
"second": "9000"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"first_only": [
|
||||
"Loopback0"
|
||||
],
|
||||
"second_only": [
|
||||
"Loopback1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
first = _get_root_object(*models)
|
||||
first.load_dict(candidate)
|
||||
second = _get_root_object(*models)
|
||||
second.load_dict(running)
|
||||
return napalm_yang.utils.diff(first, second)
|
||||
|
||||
|
||||
@proxy_napalm_wrap
|
||||
def parse(*models, **kwargs):
|
||||
'''
|
||||
Parse configuration from the device.
|
||||
|
||||
models
|
||||
A list of models to be used when parsing.
|
||||
|
||||
config: ``False``
|
||||
Parse config.
|
||||
|
||||
state: ``False``
|
||||
Parse state.
|
||||
|
||||
profiles: ``None``
|
||||
Use certain profiles to parse. If not specified, will use the device
|
||||
default profile(s).
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm_yang.parse models.openconfig_interfaces
|
||||
|
||||
Output Example:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"interfaces": {
|
||||
"interface": {
|
||||
".local.": {
|
||||
"name": ".local.",
|
||||
"state": {
|
||||
"admin-status": "UP",
|
||||
"counters": {
|
||||
"in-discards": 0,
|
||||
"in-errors": 0,
|
||||
"out-errors": 0
|
||||
},
|
||||
"enabled": True,
|
||||
"ifindex": 0,
|
||||
"last-change": 0,
|
||||
"oper-status": "UP",
|
||||
"type": "softwareLoopback"
|
||||
},
|
||||
"subinterfaces": {
|
||||
"subinterface": {
|
||||
".local..0": {
|
||||
"index": ".local..0",
|
||||
"state": {
|
||||
"ifindex": 0,
|
||||
"name": ".local..0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ae0": {
|
||||
"name": "ae0",
|
||||
"state": {
|
||||
"admin-status": "UP",
|
||||
"counters": {
|
||||
"in-discards": 0,
|
||||
"in-errors": 0,
|
||||
"out-errors": 0
|
||||
},
|
||||
"enabled": True,
|
||||
"ifindex": 531,
|
||||
"last-change": 255203,
|
||||
"mtu": 1518,
|
||||
"oper-status": "DOWN"
|
||||
},
|
||||
"subinterfaces": {
|
||||
"subinterface": {
|
||||
"ae0.0": {
|
||||
"index": "ae0.0",
|
||||
"state": {
|
||||
"description": "ASDASDASD",
|
||||
"ifindex": 532,
|
||||
"name": "ae0.0"
|
||||
}
|
||||
}
|
||||
"ae0.32767": {
|
||||
"index": "ae0.32767",
|
||||
"state": {
|
||||
"ifindex": 535,
|
||||
"name": "ae0.32767"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dsc": {
|
||||
"name": "dsc",
|
||||
"state": {
|
||||
"admin-status": "UP",
|
||||
"counters": {
|
||||
"in-discards": 0,
|
||||
"in-errors": 0,
|
||||
"out-errors": 0
|
||||
},
|
||||
"enabled": True,
|
||||
"ifindex": 5,
|
||||
"last-change": 0,
|
||||
"oper-status": "UP"
|
||||
}
|
||||
},
|
||||
"ge-0/0/0": {
|
||||
"name": "ge-0/0/0",
|
||||
"state": {
|
||||
"admin-status": "UP",
|
||||
"counters": {
|
||||
"in-broadcast-pkts": 0,
|
||||
"in-discards": 0,
|
||||
"in-errors": 0,
|
||||
"in-multicast-pkts": 0,
|
||||
"in-unicast-pkts": 16877,
|
||||
"out-broadcast-pkts": 0,
|
||||
"out-errors": 0,
|
||||
"out-multicast-pkts": 0,
|
||||
"out-unicast-pkts": 15742
|
||||
},
|
||||
"description": "management interface",
|
||||
"enabled": True,
|
||||
"ifindex": 507,
|
||||
"last-change": 258467,
|
||||
"mtu": 1400,
|
||||
"oper-status": "UP"
|
||||
},
|
||||
"subinterfaces": {
|
||||
"subinterface": {
|
||||
"ge-0/0/0.0": {
|
||||
"index": "ge-0/0/0.0",
|
||||
"state": {
|
||||
"description": "ge-0/0/0.0",
|
||||
"ifindex": 521,
|
||||
"name": "ge-0/0/0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"irb": {
|
||||
"name": "irb",
|
||||
"state": {
|
||||
"admin-status": "UP",
|
||||
"counters": {
|
||||
"in-discards": 0,
|
||||
"in-errors": 0,
|
||||
"out-errors": 0
|
||||
},
|
||||
"enabled": True,
|
||||
"ifindex": 502,
|
||||
"last-change": 0,
|
||||
"mtu": 1514,
|
||||
"oper-status": "UP",
|
||||
"type": "ethernetCsmacd"
|
||||
}
|
||||
},
|
||||
"lo0": {
|
||||
"name": "lo0",
|
||||
"state": {
|
||||
"admin-status": "UP",
|
||||
"counters": {
|
||||
"in-discards": 0,
|
||||
"in-errors": 0,
|
||||
"out-errors": 0
|
||||
},
|
||||
"description": "lo0",
|
||||
"enabled": True,
|
||||
"ifindex": 6,
|
||||
"last-change": 0,
|
||||
"oper-status": "UP",
|
||||
"type": "softwareLoopback"
|
||||
},
|
||||
"subinterfaces": {
|
||||
"subinterface": {
|
||||
"lo0.0": {
|
||||
"index": "lo0.0",
|
||||
"state": {
|
||||
"description": "lo0.0",
|
||||
"ifindex": 16,
|
||||
"name": "lo0.0"
|
||||
}
|
||||
},
|
||||
"lo0.16384": {
|
||||
"index": "lo0.16384",
|
||||
"state": {
|
||||
"ifindex": 21,
|
||||
"name": "lo0.16384"
|
||||
}
|
||||
},
|
||||
"lo0.16385": {
|
||||
"index": "lo0.16385",
|
||||
"state": {
|
||||
"ifindex": 22,
|
||||
"name": "lo0.16385"
|
||||
}
|
||||
},
|
||||
"lo0.32768": {
|
||||
"index": "lo0.32768",
|
||||
"state": {
|
||||
"ifindex": 248,
|
||||
"name": "lo0.32768"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
config = kwargs.pop('config', False)
|
||||
state = kwargs.pop('state', False)
|
||||
profiles = kwargs.pop('profiles', [])
|
||||
if not profiles and hasattr(napalm_device, 'profile'): # pylint: disable=undefined-variable
|
||||
profiles = napalm_device.profile # pylint: disable=undefined-variable
|
||||
root = _get_root_object(*models)
|
||||
parser_kwargs = {
|
||||
'device': napalm_device, # pylint: disable=undefined-variable
|
||||
'profile': profiles
|
||||
}
|
||||
if config:
|
||||
root.parse_config(**parser_kwargs)
|
||||
if state:
|
||||
root.parse_state(**parser_kwargs)
|
||||
return root
|
||||
|
||||
|
||||
@proxy_napalm_wrap
|
||||
def get_config(data, *models, **kwargs):
|
||||
'''
|
||||
Return the native config.
|
||||
|
||||
data
|
||||
Dictionary structured with respect to the models referenced.
|
||||
|
||||
models
|
||||
A list of models to be used when generating the config.
|
||||
|
||||
profiles: ``None``
|
||||
Use certain profiles to generate the config.
|
||||
If not specified, will use the platform default profile(s).
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm_yang.get_config {} models.openconfig_interfaces
|
||||
|
||||
Output Example:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
interface et1
|
||||
ip address 192.168.1.1/24
|
||||
description Uplink1
|
||||
mtu 9000
|
||||
interface et2
|
||||
ip address 192.168.2.1/24
|
||||
description Uplink2
|
||||
mtu 9000
|
||||
'''
|
||||
profiles = kwargs.pop('profiles', [])
|
||||
if not profiles and hasattr(napalm_device, 'profile'): # pylint: disable=undefined-variable
|
||||
profiles = napalm_device.profile # pylint: disable=undefined-variable
|
||||
parser_kwargs = {
|
||||
'profile': profiles
|
||||
}
|
||||
root = _get_root_object(*models)
|
||||
root.load_dict(data)
|
||||
return root.translate_config(**parser_kwargs)
|
||||
|
||||
|
||||
@proxy_napalm_wrap
|
||||
def load_config(data, *models, **kwargs):
|
||||
'''
|
||||
Generate and load the config on the device using the OpenConfig or IETF
|
||||
models and device profiles.
|
||||
|
||||
data
|
||||
Dictionary structured with respect to the models referenced.
|
||||
|
||||
models
|
||||
A list of models to be used when generating the config.
|
||||
|
||||
profiles: ``None``
|
||||
Use certain profiles to generate the config.
|
||||
If not specified, will use the platform default profile(s).
|
||||
|
||||
test: ``False``
|
||||
Dry run? If set as ``True``, will apply the config, discard
|
||||
and return the changes. Default: ``False`` and will commit
|
||||
the changes on the device.
|
||||
|
||||
commit: ``True``
|
||||
Commit? Default: ``True``.
|
||||
|
||||
debug: ``False``
|
||||
Debug mode. Will insert a new key under the output dictionary,
|
||||
as ``loaded_config`` contaning the raw configuration loaded on the device.
|
||||
|
||||
replace: ``False``
|
||||
Should replace the config with the new generate one?
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm_yang.load_config {} models.openconfig_interfaces test=True debug=True
|
||||
|
||||
Output Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
device1:
|
||||
----------
|
||||
already_configured:
|
||||
False
|
||||
comment:
|
||||
diff:
|
||||
[edit interfaces ge-0/0/0]
|
||||
- mtu 1400;
|
||||
[edit interfaces ge-0/0/0 unit 0 family inet]
|
||||
- dhcp;
|
||||
[edit interfaces lo0]
|
||||
- unit 0 {
|
||||
- description lo0.0;
|
||||
- }
|
||||
+ unit 1 {
|
||||
+ description "new loopback";
|
||||
+ }
|
||||
loaded_config:
|
||||
<configuration>
|
||||
<interfaces replace="replace">
|
||||
<interface>
|
||||
<name>ge-0/0/0</name>
|
||||
<unit>
|
||||
<name>0</name>
|
||||
<family>
|
||||
<inet/>
|
||||
</family>
|
||||
<description>ge-0/0/0.0</description>
|
||||
</unit>
|
||||
<description>management interface</description>
|
||||
</interface>
|
||||
<interface>
|
||||
<name>ge-0/0/1</name>
|
||||
<disable/>
|
||||
<description>ge-0/0/1</description>
|
||||
</interface>
|
||||
<interface>
|
||||
<name>ae0</name>
|
||||
<unit>
|
||||
<name>0</name>
|
||||
<vlan-id>100</vlan-id>
|
||||
<family>
|
||||
<inet>
|
||||
<address>
|
||||
<name>192.168.100.1/24</name>
|
||||
</address>
|
||||
<address>
|
||||
<name>172.20.100.1/24</name>
|
||||
</address>
|
||||
</inet>
|
||||
</family>
|
||||
<description>a description</description>
|
||||
</unit>
|
||||
<vlan-tagging/>
|
||||
<unit>
|
||||
<name>1</name>
|
||||
<vlan-id>1</vlan-id>
|
||||
<family>
|
||||
<inet>
|
||||
<address>
|
||||
<name>192.168.101.1/24</name>
|
||||
</address>
|
||||
</inet>
|
||||
</family>
|
||||
<disable/>
|
||||
<description>ae0.1</description>
|
||||
</unit>
|
||||
<vlan-tagging/>
|
||||
<unit>
|
||||
<name>2</name>
|
||||
<vlan-id>2</vlan-id>
|
||||
<family>
|
||||
<inet>
|
||||
<address>
|
||||
<name>192.168.102.1/24</name>
|
||||
</address>
|
||||
</inet>
|
||||
</family>
|
||||
<description>ae0.2</description>
|
||||
</unit>
|
||||
<vlan-tagging/>
|
||||
</interface>
|
||||
<interface>
|
||||
<name>lo0</name>
|
||||
<unit>
|
||||
<name>1</name>
|
||||
<description>new loopback</description>
|
||||
</unit>
|
||||
<description>lo0</description>
|
||||
</interface>
|
||||
</interfaces>
|
||||
</configuration>
|
||||
result:
|
||||
True
|
||||
'''
|
||||
config = get_config(data, *models, **kwargs)
|
||||
test = kwargs.pop('test', False)
|
||||
debug = kwargs.pop('debug', False)
|
||||
commit = kwargs.pop('commit', True)
|
||||
replace = kwargs.pop('replace', False)
|
||||
return __salt__['net.load_config'](config=config,
|
||||
test=test,
|
||||
debug=debug,
|
||||
commit=commit,
|
||||
replace=replace,
|
||||
inherit_napalm_device=napalm_device) # pylint: disable=undefined-variable
|
||||
|
||||
|
||||
@proxy_napalm_wrap
|
||||
def compliance_report(data, *models, **kwargs):
|
||||
'''
|
||||
Return the compliance report using YANG objects.
|
||||
|
||||
data
|
||||
Dictionary structured with respect to the models referenced.
|
||||
|
||||
models
|
||||
A list of models to be used when generating the config.
|
||||
|
||||
filepath
|
||||
The absolute path to the validation file.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' napalm_yang.compliance_report {} models.openconfig_interfaces filepath=~/validate.yml
|
||||
|
||||
Output Example:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"skipped": [],
|
||||
"complies": true,
|
||||
"get_interfaces_ip": {
|
||||
"missing": [],
|
||||
"complies": true,
|
||||
"present": {
|
||||
"ge-0/0/0.0": {
|
||||
"complies": true,
|
||||
"nested": true
|
||||
}
|
||||
},
|
||||
"extra": []
|
||||
}
|
||||
}
|
||||
'''
|
||||
filepath = kwargs.pop('filepath', '')
|
||||
root = _get_root_object(*models)
|
||||
root.load_dict(data)
|
||||
return root.compliance_report(validation_file=filepath)
|
Loading…
Reference in New Issue
Block a user