Skip to content
This repository was archived by the owner on Feb 10, 2018. It is now read-only.

Commit 16cbdb8

Browse files
authored
Merge pull request #79 from napalm-automation/develop
Merging develop into master for the new testing framework
2 parents dd1587d + cef6b23 commit 16cbdb8

File tree

78 files changed

+64186
-56
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+64186
-56
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ env
5959
*.swp
6060

6161
test/unit/test_devices.py
62-
62+
report.json

.travis.yml

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,8 @@ deploy:
1313
tags: true
1414
branch: master
1515
script:
16-
- pylama .
17-
- cd test/unit
18-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_bgp_config
19-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_bgp_neighbors
20-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_bgp_neighbors_detail
21-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_environment
22-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_facts
23-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_interfaces
24-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_interfaces_counters
25-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_lldp_neighbors
26-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_lldp_neighbors_detail
27-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_arp_table
28-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_ntp_peers
29-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_ntp_stats
30-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_ntp_servers
31-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_interfaces_ip
32-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_mac_address_table
33-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_route_to
34-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_snmp_information
35-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_probes_config
36-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_probes_results
37-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_traceroute
38-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_users
39-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_optics
40-
- nosetests --with-coverage --cover-package napalm_junos -v TestJunOSDriver:TestGetterJunOSDriver.test_get_config
41-
- cd ../..
42-
- coverage combine test/unit/.coverage
43-
after_success: coveralls
16+
- py.test --cov-report= --cov=napalm_junos test/
17+
- pylama .
18+
after_success:
19+
- coveralls
20+
- if [ $TRAVIS_TAG ]; then curl -X POST https://readthedocs.org/build/napalm; fi

napalm_junos/constants.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Constants for the JunOS driver."""
2+
3+
from __future__ import unicode_literals
4+
5+
from napalm_base.constants import * # noqa
6+
7+
# OpenConfig mapping
8+
# ref: https://github.com/openconfig/public/blob/master/release/models/network-instance/openconfig-network-instance-types.yang # noqa
9+
OC_NETWORK_INSTANCE_TYPE_MAP = {
10+
'default': 'DEFAULT_INSTANCE',
11+
'l2vpn': 'L2VPN',
12+
'vrf': 'L3VRF',
13+
'evpn': 'BGP_EVPN',
14+
'vpls': 'BGP_VPLS',
15+
'forwarding': 'L2P2P'
16+
}

napalm_junos/junos.py

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from napalm_base.base import NetworkDriver
3737
from napalm_base.utils import string_parsers
3838
from napalm_base.utils import py23_compat
39+
import napalm_junos.constants as C
3940
from napalm_base.exceptions import ConnectionException
4041
from napalm_base.exceptions import MergeConfigException
4142
from napalm_base.exceptions import CommandErrorException
@@ -84,26 +85,33 @@ def open(self):
8485
del self.device.cu
8586
self.device.bind(cu=Config)
8687
if self.config_lock:
87-
self.lock()
88+
self._lock()
8889

8990
def close(self):
9091
"""Close the connection."""
9192
if self.config_lock:
92-
self.unlock()
93+
self._unlock()
9394
self.device.close()
9495

95-
def lock(self):
96+
def _lock(self):
9697
"""Lock the config DB."""
9798
if not self.locked:
9899
self.device.cu.lock()
99100
self.locked = True
100101

101-
def unlock(self):
102+
def _unlock(self):
102103
"""Unlock the config DB."""
103104
if self.locked:
104105
self.device.cu.unlock()
105106
self.locked = False
106107

108+
def is_alive(self):
109+
# evaluate the state of the underlying SSH connection
110+
# and also the NETCONF status from PyEZ
111+
return {
112+
'is_alive': self.device._conn._session.transport.is_active() and self.device.connected
113+
}
114+
107115
def _load_candidate(self, filename, config, overwrite):
108116
if filename is None:
109117
configuration = config
@@ -114,7 +122,7 @@ def _load_candidate(self, filename, config, overwrite):
114122
if not self.config_lock:
115123
# if not locked during connection time
116124
# will try to lock it if not already aquired
117-
self.lock()
125+
self._lock()
118126
# and the device will be locked till first commit/rollback
119127

120128
try:
@@ -148,13 +156,13 @@ def commit_config(self):
148156
"""Commit configuration."""
149157
self.device.cu.commit()
150158
if not self.config_lock:
151-
self.unlock()
159+
self._unlock()
152160

153161
def discard_config(self):
154162
"""Discard changes (rollback 0)."""
155163
self.device.cu.rollback(rb_id=0)
156164
if not self.config_lock:
157-
self.unlock()
165+
self._unlock()
158166

159167
def rollback(self):
160168
"""Rollback to previous commit."""
@@ -468,7 +476,7 @@ def get_lldp_neighbors_detail(self, interface=''):
468476

469477
return lldp_neighbors
470478

471-
def cli(self, commands=None):
479+
def cli(self, commands):
472480
"""Execute raw CLI commands and returns their output."""
473481
cli_output = {}
474482

@@ -951,21 +959,24 @@ def get_mac_address_table(self):
951959

952960
return mac_address_table
953961

954-
def get_route_to(self, destination=None, protocol=None):
962+
def get_route_to(self, destination='', protocol=''):
955963
"""Return route details to a specific destination, learned from a certain protocol."""
956964
routes = {}
957965

958966
if not isinstance(destination, py23_compat.string_types):
959967
raise TypeError('Please specify a valid destination!')
960968

961-
if not isinstance(protocol, py23_compat.string_types) or \
962-
protocol.lower() not in ('static', 'bgp', 'isis'):
969+
if protocol and (not isinstance(protocol, py23_compat.string_types) or
970+
protocol.lower() not in ('static', 'bgp', 'isis', 'connected', 'direct')):
963971
raise TypeError("Protocol not supported: {protocol}.".format(
964972
protocol=protocol
965973
))
966974

967975
protocol = protocol.lower()
968976

977+
if protocol == 'connected':
978+
protocol = 'direct' # this is how is called on JunOS
979+
969980
_COMMON_PROTOCOL_FIELDS_ = [
970981
'destination',
971982
'prefix_length',
@@ -1003,18 +1014,19 @@ def get_route_to(self, destination=None, protocol=None):
10031014
'level',
10041015
'metric',
10051016
'local_as'
1006-
],
1007-
'static': [ # nothing specific to static routes
10081017
]
10091018
}
10101019

10111020
routes_table = junos_views.junos_protocol_route_table(self.device)
10121021

1022+
rt_kargs = {
1023+
'destination': destination
1024+
}
1025+
if protocol:
1026+
rt_kargs['protocol'] = protocol
1027+
10131028
try:
1014-
routes_table.get(
1015-
destination=destination,
1016-
protocol=protocol
1017-
)
1029+
routes_table.get(**rt_kargs)
10181030
except RpcTimeoutError:
10191031
# on devices with milions of routes
10201032
# in case the destination is too generic (e.g.: 10/8)
@@ -1030,7 +1042,7 @@ def get_route_to(self, destination=None, protocol=None):
10301042

10311043
for route in routes_items:
10321044
d = {}
1033-
next_hop = route[0]
1045+
# next_hop = route[0]
10341046
d = {elem[0]: elem[1] for elem in route[1]}
10351047
destination = napalm_base.helpers.ip(d.pop('destination', ''))
10361048
prefix_length = d.pop('prefix_length', 32)
@@ -1048,10 +1060,13 @@ def get_route_to(self, destination=None, protocol=None):
10481060
# to be sure that contains only AS Numbers
10491061
if d.get('inactive_reason') is None:
10501062
d['inactive_reason'] = u''
1063+
route_protocol = d.get('protocol').lower()
1064+
if protocol and protocol != route_protocol:
1065+
continue
10511066
communities = d.get('communities')
10521067
if communities is not None and type(communities) is not list:
10531068
d['communities'] = [communities]
1054-
d['next_hop'] = unicode(next_hop)
1069+
# d['next_hop'] = unicode(next_hop)
10551070
d_keys = d.keys()
10561071
# fields that are not in _COMMON_PROTOCOL_FIELDS_ are supposed to be protocol specific
10571072
all_protocol_attributes = {
@@ -1061,7 +1076,7 @@ def get_route_to(self, destination=None, protocol=None):
10611076
}
10621077
protocol_attributes = {
10631078
key: value for key, value in all_protocol_attributes.iteritems()
1064-
if key in _PROTOCOL_SPECIFIC_FIELDS_.get(protocol)
1079+
if key in _PROTOCOL_SPECIFIC_FIELDS_.get(route_protocol, [])
10651080
}
10661081
d['protocol_attributes'] = protocol_attributes
10671082
if destination not in routes.keys():
@@ -1170,7 +1185,11 @@ def get_probes_results(self):
11701185

11711186
return probes_results
11721187

1173-
def traceroute(self, destination, source='', ttl=0, timeout=0):
1188+
def traceroute(self,
1189+
destination,
1190+
source=C.TRACEROUTE_SOURCE,
1191+
ttl=C.TRACEROUTE_TTL,
1192+
timeout=C.TRACEROUTE_TIMEOUT):
11741193
"""Execute traceroute and return results."""
11751194
traceroute_result = {}
11761195

@@ -1285,9 +1304,10 @@ def get_optics(self):
12851304
# Formatting data into return data structure
12861305
optics_detail = {}
12871306
for intf_optic_item in optics_items:
1307+
interface_name = py23_compat.text_type(intf_optic_item[0])
12881308
optics = dict(intf_optic_item[1])
1289-
if intf_optic_item[0] not in optics_detail:
1290-
optics_detail[intf_optic_item[0]] = {}
1309+
if interface_name not in optics_detail:
1310+
optics_detail[interface_name] = {}
12911311

12921312
# Defaulting avg, min, max values to 0.0 since device does not
12931313
# return these values
@@ -1327,7 +1347,7 @@ def get_optics(self):
13271347
}]
13281348
}
13291349
}
1330-
optics_detail[intf_optic_item[0]] = intf_optics
1350+
optics_detail[interface_name] = intf_optics
13311351

13321352
return optics_detail
13331353

@@ -1353,3 +1373,60 @@ def get_config(self, retrieve='all'):
13531373
rv['running'] = py23_compat.text_type(config.text.encode('ascii', 'replace'))
13541374

13551375
return rv
1376+
1377+
def get_network_instances(self, name=''):
1378+
1379+
network_instances = {}
1380+
1381+
ri_table = junos_views.junos_nw_instances_table(self.device)
1382+
ri_table.get()
1383+
ri_entries = ri_table.items()
1384+
1385+
vrf_interfaces = []
1386+
1387+
for ri_entry in ri_entries:
1388+
ri_name = py23_compat.text_type(ri_entry[0])
1389+
ri_details = {
1390+
d[0]: d[1] for d in ri_entry[1]
1391+
}
1392+
ri_type = ri_details['instance_type']
1393+
if ri_type is None:
1394+
ri_type = 'default'
1395+
ri_rd = ri_details['route_distinguisher']
1396+
ri_interfaces = ri_details['interfaces']
1397+
network_instances[ri_name] = {
1398+
'name': ri_name,
1399+
'type': C.OC_NETWORK_INSTANCE_TYPE_MAP.get(ri_type, ri_type), # default: return raw
1400+
'state': {
1401+
'route_distinguisher': ri_rd if ri_rd else ''
1402+
},
1403+
'interfaces': {
1404+
'interface': {
1405+
intrf_name: {} for intrf_name in ri_interfaces if intrf_name
1406+
}
1407+
}
1408+
}
1409+
vrf_interfaces.extend(network_instances[ri_name]['interfaces']['interface'].keys())
1410+
1411+
all_interfaces = self.get_interfaces().keys()
1412+
default_interfaces = list(set(all_interfaces) - set(vrf_interfaces))
1413+
if 'default' not in network_instances:
1414+
network_instances['default'] = {
1415+
'name': 'default',
1416+
'type': C.OC_NETWORK_INSTANCE_TYPE_MAP.get('default'),
1417+
'state': {
1418+
'route_distinguisher': ''
1419+
},
1420+
'interfaces': {
1421+
'interface': {
1422+
py23_compat.text_type(intrf_name): {}
1423+
for intrf_name in default_interfaces
1424+
}
1425+
}
1426+
}
1427+
1428+
if not name:
1429+
return network_instances
1430+
if name not in network_instances:
1431+
return {}
1432+
return {name: network_instances[name]}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% if (servers is defined) and servers %}
2+
system {
3+
ntp {
4+
{% for server in servers %}
5+
{% if server %}
6+
delete: server {{server}};
7+
{% endif %}
8+
{% endfor %}
9+
}
10+
}
11+
{% endif %}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
snmp {
2+
{% if (location is defined) and location %}
3+
delete: location "{{location}}";
4+
{% endif %}
5+
{% if (contact is defined) and contact %}
6+
delete: contact "{{contact}}";
7+
{% endif %}
8+
{% if (community is defined) and community %}
9+
{% for comm_name, comm_details in community.iteritems() %}
10+
delete: community {{comm_name}} {
11+
{% if (comm_details is defined) and comm_details %}
12+
{% if (comm_details.get('mode') is defined) and comm_details.get('mode') == 'rw' %}
13+
authorization read-write;
14+
{% else %}
15+
authorization read-only;
16+
{% endif %}
17+
{% else %}
18+
authorization read-only;
19+
{% endif %}
20+
}
21+
{% endfor %}
22+
{% endif %}
23+
}
24+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% if (servers is defined) and servers %}
2+
system {
3+
ntp {
4+
{% for server in servers %}
5+
{% if server %}
6+
server {{server}};
7+
{% endif %}
8+
{% endfor %}
9+
}
10+
}
11+
{% endif %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
snmp {
2+
{% if (location is defined) and location %}
3+
location "{{location}}";
4+
{% endif %}
5+
{% if (contact is defined) and contact %}
6+
contact "{{contact}}";
7+
{% endif %}
8+
{% if (community is defined) and community %}
9+
{% for comm_name, comm_details in community.iteritems() %}
10+
community {{comm_name}} {
11+
{% if (comm_details is defined) and comm_details %}
12+
{% if (comm_details.get('mode') is defined) and comm_details.get('mode') == 'rw' %}
13+
authorization read-write;
14+
{% else %}
15+
authorization read-only;
16+
{% endif %}
17+
{% else %}
18+
authorization read-only;
19+
{% endif %}
20+
}
21+
{% endfor %}
22+
{% endif %}
23+
}

0 commit comments

Comments
 (0)