What is NSoT?
Network Source of Truth (NSoT) is a Django based opensource application for the management of IP addresses (IPAM), network devices and network interfaces.
NSoT provides the following for the administration of the database inventory:
- REST-based API
- CLI client
- Python modules
- UI
As you will shortly see the true benefit of NSoT comes with being able to easily add metadata/attributes and perform various query/filters on your datasets.
NSoT Installation
The architecture of NSoT is based upon the installation of the NSoT service which provides the UI and API. A client - pynsot
- is then installed, and used for the interaction with the NSoT API via either a CLI client or Python modules.
Install Service
The easiest way to install the service is via Docker, then once the container is running a superuser is created.
$ docker run -p 8990:8990 -d --name=nsot nsot/nsot start --noinput
$ docker exec -it nsot bash
# nsot-server --config=/etc/nsot/nsot.conf.py createsuperuser —email admin@nsot.local
Further details around installing NSoT can be found at NSoT.readthedocs.io/en/latest/install/docker.html
Install PyNSoT
Using Python2.7 install pynsot
like so,
pip install pynsot
Further details around installing pynsot
can be found at pynsot.readthedocs.io/en/latest/#installation
Objects and Resources
The NSoT datamodel is comprised of various object-types, such as:
- Sites
- Attributes
- Resources
- Users
- Permissions
We have the following resource types:
- Devices
- Networks
- Interfaces
- Circuits
- ProtocolTypes
- Protocols
Based on the above, a site is created, and the various resource types are created within the site. Custom attributes can also be created for our site and resources, for example for a device resource - os_version, vendor, vun_cve etc.
Listing Resources
First of all, let us list our devices, like so. As you can see I have created some attributes for my device resource.
# nsot devices list | head -n 30
+----------------------------------------------------------------------------------+
| ID Hostname (Key) Attributes |
+----------------------------------------------------------------------------------+
| 14 access-sw-001 hw_type=switch |
| model=qfx1000 |
| position=u8 |
| rack=r1 |
| vendor=juniper |
| 15 access-sw-002 hw_type=switch |
| model=qfx1000 |
| position=u9 |
| rack=r1 |
| vendor=juniper |
| 16 access-sw-003 hw_type=switch |
| model=qfx1000 |
| position=u10 |
| rack=r1 |
| vendor=juniper |
| 17 access-sw-004 hw_type=switch |
| model=qfx1000 |
| position=u11 |
| rack=r1 |
| vendor=juniper |
| 18 access-sw-005 hw_type=switch |
| model=qfx1000 |
| position=u12 |
| rack=r1 |
| vendor=juniper |
| 9 core-rtr-001 hw_type=router |
| model=asr1000 |
Let’s list our networks as well.
# nsot networks list
+---------------------------------------------------------------------------------------------------------+
| ID CIDR (Key) Is IP? IP Ver. Parent State Attributes |
+---------------------------------------------------------------------------------------------------------+
| 5 10.0.0.0/8 False 4 None allocated description=super_block |
| vlan_desc=none |
| vlan_id=none |
| vlan_name=none,vrf=none |
| 6 10.1.0.0/16 False 4 10.0.0.0/8 allocated description=infra_block |
| vlan_desc=none |
| vlan_id=none |
| vlan_name=none,vrf=none |
| 41 10.1.0.144/29 False 4 10.1.0.0/16 allocated description=backup |
| vlan_desc=oob-mgmt |
| vlan_id=104 |
| vlan_name=VLAN-104-BACKUP |
| vrf=infra |
| 7 10.1.1.0/24 False 4 10.1.0.0/16 allocated description=vmotion |
| vlan_desc=vmotion |
| vlan_id=100 |
| vlan_name=VLAN-100-VMOTION,vrf=infra |
Creating a Device
Let’s now create a device
object:
# nsot devices add --hostname access-sw-010 \
--attributes rack=r1 \
--attributes position=u20 \
--attributes vendor=dell \
--attributes model=n3048 \
--attributes hw_type=switch
[SUCCESS] Added device!
Querying and Filtering
NSoT provides some pretty cool features to allow you to select which data you want to see from your datasets. These features come in the form of queries and filters.
Filters
Filters allow you to match based upon attributes. As shown below, where we are select the attributes of rack
and hw_type
and the values we are interested in.
# nsot devices list -a rack=r1 -a hw_type=switch -a vendor=dell
+--------------------------------------+
| ID Hostname (Key) Attributes |
+--------------------------------------+
| 40 access-sw-010 hw_type=switch |
| model=n3048 |
| position=u20 |
| rack=r1 |
| vendor=dell |
+--------------------------------------+
Queries
Queries allow you to perform set operations against your attributes and output the matching hostnames. Below provides an example, based on the logic,
provide all devices within rack r1, excluding a vendor type of cisco.
# nsot devices list -q 'rack=r1 -vendor=cisco'
access-sw-001
access-sw-002
access-sw-003
access-sw-004
access-sw-005
access-sw-010
dcgw-001
dcgw-002
mgmt-sw-001
Full details around the NSoT queries can be found at pynsot.readthedocs.io/en/latest/cli.html#set-queries
NSoT Python Module
Let’s now dive into the Python module.
Imports
First let's perform our imports.
from pprint import pprint as pp
from pynsot.client import get_api_client
c = get_api_client()
Note: pprint
is used to display the output in a readable format, and is a separate module, i.e not part of the NSoT project.
Fetching the Next Network or Address
Now we have import pynsot
, we can obtain the next set of addresses from a given network.
Like so,
>>> pp(c.sites(7).networks('10.1.5.0/24').next_address().get(num=10))
[u'10.1.5.21/32',
u'10.1.5.22/32',
u'10.1.5.23/32',
u'10.1.5.24/32',
u'10.1.5.25/32',
u'10.1.5.26/32',
u'10.1.5.27/32',
u'10.1.5.28/32',
u'10.1.5.29/32',
u'10.1.5.30/32']
We can also do the same for obtaining the next set of networks. Like so,
>>> pp(c.sites(7).networks('10.1.0.0/16').next_network().get(prefix_length=29,num=20))
[u'10.1.0.0/29',
u'10.1.0.8/29',
u'10.1.0.16/29',
u'10.1.0.24/29',
u'10.1.0.32/29',
u'10.1.0.40/29',
u'10.1.0.48/29',
u'10.1.0.56/29',
u'10.1.0.64/29',
u'10.1.0.72/29',
u'10.1.0.80/29',
u'10.1.0.88/29',
u'10.1.0.96/29',
u'10.1.0.104/29',
u'10.1.0.112/29',
u'10.1.0.120/29',
u'10.1.0.128/29',
u'10.1.0.136/29',
u'10.1.0.144/29',
u'10.1.0.152/29']
Create Network
Ok, so let’s create a network. We will then perform the next_networks again to ensure that the network is no longer available.
First, we define our network via a dict()
.
>>> net = {
'attributes': {
'description': 'backup',
'vlan_id': '104',
'vlan_name': 'VLAN-104-BACKUP',
'vlan_desc': 'backup',
'vrf': 'infra'
},
'network_address': '10.1.0.144',
'prefix_length': 29
}
Next we create the network, like so:
>>> pp(c.sites(7).networks.post(net))
{u'attributes': {u'description': u'backup',
u'vlan_desc': u'backup',
u'vlan_id': u'104',
u'vlan_name': u'VLAN-104-BACKUP',
u'vrf': u'infra'},
u'cidr': u'10.1.0.144/29',
u'id': 41,
u'ip_version': u'4',
u'is_ip': False,
u'network_address': u'10.1.0.144',
u'parent': u'10.1.0.0/16',
u'parent_id': 6,
u'prefix_length': 29,
u'site_id': 7,
u'state': u'allocated'}
Finally we perform next_network
again to ensure 10.1.0.144/29
is no longer available:
>>> pp(c.sites(7).networks('10.1.0.0/16').next_network().get(prefix_length=29,num=20))
[u'10.1.0.0/29',
u'10.1.0.8/29',
u'10.1.0.16/29',
u'10.1.0.24/29',
u'10.1.0.32/29',
u'10.1.0.40/29',
u'10.1.0.48/29',
u'10.1.0.56/29',
u'10.1.0.64/29',
u'10.1.0.72/29',
u'10.1.0.80/29',
u'10.1.0.88/29',
u'10.1.0.96/29',
u'10.1.0.104/29',
u'10.1.0.112/29',
u'10.1.0.120/29',
u'10.1.0.128/29',
u'10.1.0.136/29',
u'10.1.0.152/29',
u'10.1.0.160/29']
Debugging
In order to print debugging output the following variable can be exported.
# export PYNSOT_DEBUG=1
Below shows an example of the type of output that it can provide:
# nsot devices list -q 'rack=r1 -vendor=cisco'
DEBUG:pynsot.client:Reading dotfile.
DEBUG:pynsot.dotfile:Enforcing permissions 600 on /root/.pynsotrc
DEBUG:pynsot.client:Validating auth_method: auth_token
DEBUG:pynsot.client:Skipping 'debug' in config for auth_method 'auth_token'
DEBUG:pynsot.client:Using api_version = 1.0
DEBUG:pynsot.client:Getting token for user data: {u'secret_key': u'XXXXXXXX', u'email': 'rick@nsot.local'}
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 172.29.236.139:8990
DEBUG:urllib3.connectionpool:http://172.29.236.139:8990 "POST /api/authenticate/ HTTP/1.1" 200 137
DEBUG:pynsot.client:Got response: <Response [200]>
DEBUG:pynsot.commands.callbacks:GOT DEFAULT_SITE: 7
DEBUG:pynsot.commands.callbacks:GOT PROVIDED SITE_ID: None
DEBUG:pynsot.app:rebase: Got site_id: 7
DEBUG:pynsot.app:rebase: Site_id found; rebasing API URL!
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 172.29.236.139:8990
DEBUG:urllib3.connectionpool:http://172.29.236.139:8990 "GET /api/sites/7/devices/query/?grep=False&natural_key=False&query=rack%3Dr1+-vendor%3Dcisco HTTP/1.1" 200 1312
access-sw-001
access-sw-002
access-sw-003
access-sw-004
access-sw-005
access-sw-010
dcgw-001
dcgw-002
mgmt-sw-001
Other than troubleshooting, this can help massively when building the Python (along with pynsot.readthedocs.io/en/latest/python_api.html#fetching-resources) or REST API NSoT calls.