To enable SaltStack to control devices that cannot run standard salt-minion software, we turn to proxy minions. The proxy minion is a process that Salt controls as if it were a minion. In turn, the proxy minion controls a device through the API or through CLI commands.
When we want to have SaltStack control Juniper devices, we can choose from the following proxy minion software: netmiko
, napalm
or junos
.
This article is about the Junos proxy minion. The Junos
proxy minion is leveraging PyEZ to communicate with the Junos API over NETCONF. The proxy minion comes with an execution module that provides you several functions. These functions will enable you to get most of the basic things done. Sending a CLI command, issuing an RPC, changing the configuration, etc.
But before diving into all that, let’s get a proxy minion started first.
Setting up the proxy minion
Setting up the proxy minion requires that you create a file for the proxy minion where you specify the connection details. To check the file we use for the proxy minion in our example, we use cat /srv/pillar/ar01_lab.sls
:
proxy:
username: admin
proxytype: junos
host: 198.18.60.41
password: salt123
port: 830
The proxy minion needs to be referenced in the top file as well. Using cat /srv/pillar/top.sls
, we see our example contains the following:
base:
ar01-lab:
- ar01_lab
- lab_pillar
Next, we start start the proxy minion as a daemon. To this end, we issue the following command:
salt-proxy -d --proxyid=ar01-lab
We can now see that the proxy minion process is running:
/ $ ps ax| grep ar
20862 ? Sl 88:13 /usr/bin/python2 /usr/bin/salt-proxy -d --proxyid=ar01-lab
Interacting with a proxy minion is done using the following syntax:
salt <proxy-minion> <execution-module.function> <arguments>
We can break this down in the following:
- salt: this invokes the
salt
CLI - proxy-minion: this is the name of the proxy minion you want to target
- execution-module.function: here we specify the name of the execution module we want to use and then the function we want to call, both separated by a dot
- arguments: the arguments you want to pass to the function
To test and see if the Salt master can communicate with the proxy minion process, we use the ping
function from the test
execution module. This would be done by issuing salt ar01-lab test.ping
:
ar01-lab:
True
This is not an ICMP ping. It is a test that proves the Salt master can communicate with the proxy minion. It does not prove that the proxy minion can communicate with the device. To see if the proxy minion process can communicate with the device, we can send a CLI command (next section).
When the proxy minion is not running or in case it fails to respond, consider running the proxy minion in debug mode. Running the proxy minion in debug mode will output all proxy minion actions to screen. You can see the proxy minion process starting, logging in, issuing RPCs, etc. To run the proxy minion in debug mode, use the following:
salt-proxy --proxyid=ar01-lab -l debug
If running the proxy minion in debug mode does not offer you any clues, consider looking into the proxy minion logs and the log of the Salt master. These log files are the following:
- /var/log/salt/proxy
- /var/log/salt/master
Exploring the proxy minion
Now that we have the proxy minion running, the first thing you’ll probably be interested in doing is sending some commands to the device.
Let’s start by issuing the salt ar01-lab junos.cli 'show ospf neighbor interface ae2.2'
Salt CLI command:
ar01-lab:
----------
message:
Address Interface State ID Pri Dead
192.22.118.159 ae2.2 Full 192.22.118.241 0 30
out:
True
Let’s break down the salt ar01-lab junos.cli 'show ospf neighbor interface ae2.2'
command. In our interaction with the proxy minion, we:
- invoked the Salt CLI using
salt
- targeted the
ar01-lab
proxy minion - called the
cli
function from thejunos
execution module - passed
show ospf neighbor interface ae2.2
as an argument to the function
Instead of a CLI command, we can also execute an RPC. Let issue the following salt ar01-lab junos.rpc get-ospf-neighbor-information interface='ae2.2'
Salt CLI command:
ar01-lab:
----------
out:
True
rpc_reply:
----------
ospf-neighbor-information:
----------
ospf-neighbor:
----------
activity-timer:
30
interface-name:
ae2.2
neighbor-address:
192.22.118.159
neighbor-id:
192.22.118.241
neighbor-priority:
0
ospf-neighbor-state:
Full
SaltStack does have to parse the XML Juniper returns because the Juniper proxy minion uses jxmlease
to ‘dictify’ the RPC return. Anyway, we get structured data in dictionary format.
Another ‘basic’ thing would be to have the proxy minion proces make the managed device send ICMPs to another device using salt ar01-lab junos.ping '192.22.118.15' count=5 rapid=True
:
ar01-lab:
----------
message:
----------
ping-results:
----------
packet-size:
56
ping-failure:
no response
probe-results-summary:
----------
packet-loss:
100
probes-sent:
5
responses-received:
0
target-host:
192.22.118.15
target-ip:
192.22.118.15
out:
True
When the Junos proxy minion starts, it will use RPCs to gather some information about the device it is managing. This information is gathered using the facts
function and this data is stored as grains data. To display the facts from the device, you can use salt ar01-lab junos.facts
. These facts do no change that often, but you can refresh them using salt ar01-lab junos.facts_refresh
.
The following sample is part of the output we get after issuing salt ar01-lab junos.facts
:
ar01-lab:
----------
facts:
----------
2RE:
False
RE0:
----------
last_reboot_reason:
Router rebooted after a normal shutdown.
model:
RE-S-1800x4
up_time:
482 days, 17 hours, 3 minutes, 32 seconds
model:
MX240
serialnumber:
JN124999CAFC
version:
16.1R3-S8
These grains are provided right out of the box. Though it makes for a nice start, I do think it is worthwhile to consider writing your own functions that set additional grains for templating and targeting purposes. More on that here.
Working with the configuration
As far as working with the configuration goes, the junos
execution module has all the basics covered. Let’s check the template we will be working with in this example using cat /srv/salt/templates/juniper/arp.j2
:
set system arp passive-learning
This basic example template will be enough to demonstrate the use of the execution modules. For more information on how to template in Salt, check this article: Templating for network engineers in SaltStack .
Back to our example. The configuration from the template is not (yet) present on the device we are working with. Let’s render the template and load the configuration using the junos.load
function:
/ $ salt ar01-lab junos.load salt://templates/juniper/arp.j2 format='set'
ar01-lab:
----------
message:
Successfully loaded the configuration.
out:
True
We used the junos.load
function and passed it 2 arguments. The first one is the template that we want to load and the second is the format the template is in. In this case, the file has a .j2
extension. If we would have given the file a .set
extension, Salt would still render the template as Jinja but we would not need to pass the format='set'
argument as the proxy minion software would have ‘figured out’ the configuration would be in ‘set’ format.
After running the function, the template was rendered and the configuration was loaded as a candidate configuration. The configuration was not applied. When we check what happened on the device, we can see the following:
admin@ar01-lab> configure
Entering configuration mode
The configuration has been changed but not committed
[edit]
admin@ar01-lab# show | compare
[edit system]
+ arp {
+ passive-learning;
+ }
[edit]
We logged in and we performed a show | compare
, which is an easy way of seeing what the difference is between the candidate configuration and the active configuration. But we can do the same thing using the Salt CLI:
/ $ salt ar01-lab junos.diff
ar01-lab:
----------
message:
[edit system]
+ arp {
+ passive-learning;
+ }
out:
True
Before doing an actual commit, we can have the Junos management daemon (mgd
) see if we can commit
our configuration. Using a commit-check
, we can have mgd
verify the candidate configuration:
/ $ salt ar01-lab junos.commit_check
ar01-lab:
----------
message:
Commit check succeeded.
out:
True
We see here that mgd
has no problems with our configuration. Two examples of errors checked for here are syntax errors and references to non-existing configuration constructs. An example of the latter would be referencing a non-existing community in a routing-policy.
For now, let’s undo what we have done so far:
/ $ salt ar01-lab junos.rollback
ar01-lab:
----------
message:
Rollback successful
out:
True
The junos.rollback
can be used to load a previous configuration that is available on the device. When we issue a junos.rollback
, we effectively do a rollback 0
. This means we return to the current active configuration and we discard our candidate configuration. By default, any Juniper will allow us to rollback to anything ranging from 0-49.
After performing the rollback, the candidate configuration no longer exists:
admin@ar01-lab> configure
Entering configuration mode
[edit]
admin@ar01-lab# quit
Exiting configuration mode
From the start of the configuration section, I followed along with the interactive-commands
log on the device. From the logs in there, we can see what exactly how the different functions interacted with the device:
admin@ar01-lab> monitor start interactive-commands | match NETCONF
admin@ar01-lab>
*** interactive-commands ***
Aug 11 20:13:55 ar01-lab mgd[29316]: UI_NETCONF_CMD: User 'admin' used NETCONF client to run command 'load-configuration action="set" format="text"'
Aug 11 20:14:00 ar01-lab mgd[29316]: UI_NETCONF_CMD: User 'admin' used NETCONF client to run command 'get-configuration compare="rollback" rollback="0" format="text"'
Aug 11 20:14:08 ar01-lab mgd[29316]: UI_NETCONF_CMD: User 'admin' used NETCONF client to run command 'commit-configuration check'
Aug 11 20:14:16 ar01-lab mgd[29316]: UI_NETCONF_CMD: User 'admin' used NETCONF client to run command 'load-configuration compare="rollback" rollback="0"'
monitor stop
admin@ar01-lab>
Instead of performing a rollback
, we could have also used junos.commit
to commit the candidate configuration and make our changes take effect. Let’s take a different approach here and do everything in one go. The following Salt CLI command will render the template and apply it:
/ $ salt ar01-lab junos.install_config 'salt://templates/juniper/arp.j2' mode='private' comment='salty' format='set'
ar01-lab:
----------
message:
Successfully loaded and committed!
out:
True
/ $
Notice the 2 additional and optional keywords I used here. The first one is mode. By setting the mode to private, we ensure that the candidate configuration we create is for our current user only. This means we cannot inadvertently include configuration statements that other users have put into their candidate configuration.
Second is the comment. This an optional message for others to read in the commit log using show system commit
.
After the change, we can see the following on the device:
admin@ar01-lab> show configuration system arp
passive-learning;
admin@ar01-lab> show system commit
0 2019-08-09 20:28:42 UTC by admin via netconf
salty
Now, let’s rollback:
/ $ salt ar01-lab junos.rollback id='1'
ar01-lab:
----------
message:
Rollback successful
out:
True
/ $
Notice that when we used junos.rollback
, a rollback 1
was done and it was followed by a commit:
admin@ar01-lab> show configuration system arp
admin@ar01-lab> show system commit
0 2019-08-09 20:31:33 UTC by admin via netconf
1 2019-08-09 20:28:42 UTC by admin via netconf
salty
Wrapping up
In this article we explored the Junos proxy minion and the functions that the junos
execution module provides us with.
The proxy minion works really well and allows for a seamless interaction with the Juniper API. I think it is pretty smart to tie the whole thing in with the PyEZ microframework. It gives some maturity to the proxy minion and it will give people that have worked with PyEZ a running start.
The execution module functions are great for several reasons. First of all, they are great to play around with and will help you better understand the Junos proxy minion. Additionally, you can use them for ad-hoc information gathering leveraging CLI/RPCs you already know. Add Salt’s 0MQ, and you have quite a powerful tool to instantly check what is going on in your network. Also note that we did not touch all execution module function. Using salt ar01-lab junos
, you can see what other functions exists. Another way to see what is available is to check the execution module in github, right here.
Apart from applying the execution module functions directly like we have done here, the functions can be seen as ‘low-level’ building blocks. When you get to writing states, the main thing you will use are the states that the Junos proxy minion comes with. Interestingly enough, these junos state modules leverage the custom execution modules also.
All in all, building some familiarity with the execution module will help you better understand the states that come with the minion and studying the execution modules in more detail will help you imagine how you can start writing you own custom execution modules and/or custom states to do things 100% your own way.