Skip to content

Commit

Permalink
Merge branch 'kderynski-master' into 0.30
Browse files Browse the repository at this point in the history
  • Loading branch information
dbarrosop committed Oct 23, 2015
2 parents 9b2e9eb + dfce33d commit ef98031
Show file tree
Hide file tree
Showing 13 changed files with 477 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('../'))

# -- General configuration ------------------------------------------------

Expand Down
13 changes: 13 additions & 0 deletions docs/ibm.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
IBM Networking Operating System
-------

Rollback
~~~~~~~~

Rollback is simply implemented by reading current running configuration before any load actions. Rollback function executes load replace and commit.


Atomic Changes
~~~~~~~~~~~~~~

IBM plugin uses netconf to load configuration on to device. It seems that configuration is executed line by line but we can be sure that all lines will be executed. There are three options for error handling: stop-on-error, continue-on-error and rollback-on-error. Plugin uses rollback-on-error option in case of merge operation. However replace operation uses continue-on-error option. In case of typo in configuration, device will inform plugin about error but execute all the rest lines. Plugin will revert configuration using rollback function from the plugin. I do not use rollback-on-error for replace operation because in case of error device is left without any configuration. It seems like a bug. It will be investigated further. Moreover it seems that replace option wipe out whole configuration on device at the first step, so this option is good for provisioning of new device and it is not recomended for device in production.
2 changes: 2 additions & 0 deletions napalm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from iosxr import IOSXRDriver
from junos import JunOSDriver
from fortios import FortiOSDriver
from ibm import IBMDriver

def get_network_driver(vendor):
driver_mapping = {
Expand All @@ -26,6 +27,7 @@ def get_network_driver(vendor):
'JUNOS': JunOSDriver,
'JUNIPER': JunOSDriver,
'FORTIOS': FortiOSDriver,
'IBM': IBMDriver,
}
try:
return driver_mapping[vendor.upper()]
Expand Down
143 changes: 143 additions & 0 deletions napalm/ibm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#
# Copyright 2015 Kamil Derynski, Opera Software ASA
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

from base import NetworkDriver
from exceptions import ReplaceConfigException, MergeConfigException
from bnclient import bnclient
import difflib
import sys
from StringIO import StringIO


class IBMDriver(NetworkDriver):

def __init__(self, hostname, username, password, timeout=60):
self.hostname = hostname
self.username = username
self.password = password
self.timeout = timeout
self.argv = ['', '-u', username, '-p', password, hostname]
self.bnc = bnclient.bnclient(self.argv)
self.config_replace = False
self.config = {"running": "", "candidate": "", "rollback": ""}
self.filename_running = "/tmp/" + hostname + "-running.conf"
self.filename_candidate = None
self.filename_rollback = "/tmp/" + hostname + "-rollback.conf"
self.error = StringIO()

def str2argv(self, str=''):
return str.split(' ')

def open(self):
self.bnc.connect()
self.bnc.sendhello()
self._get_config(self.filename_rollback)

def close(self):
self.bnc.close()

def _bnc_cmd(self, file, operation, error_option):
cmd = "-o edit-config -t running -f "
cmd += file
cmd += " -d " + operation
cmd += " -r " + error_option
return cmd

def _write_memory(self):
cmd = "-o copy-config -t startup -s running"
return cmd

def _send_rpc(self, cmd):
self.error = StringIO()
old_stdout = sys.stdout
sys.stdout = self.error
self.bnc.sendrpc(self.str2argv(cmd))
sys.stdout = old_stdout

def _get_config(self, filename):
self.bnc.sendrpc(self.str2argv("-o get -f " + filename))

def _load_config(self, filename, config):
self._get_config(self.filename_rollback)
if filename is None:
configuration = config
else:
with open(filename) as f:
configuration = f.read()
self.config['candidate'] = configuration
self.filename_candidate = filename
f.close()

def load_replace_candidate(self, filename=None, config=None):
self.config_replace = True
self._load_config(filename, config)

def load_merge_candidate(self, filename=None, config=None):
self.config_replace = False
self._load_config(filename, config)

def compare_config(self):
result = ''
if self.config_replace:
self._get_config(self.filename_running)
with open(self.filename_running, 'r') as running:
with open(self.filename_candidate, 'r') as candidate:
diff = difflib.unified_diff(
running.readlines(),
candidate.readlines(),
fromfile='running',
tofile='candidate',
)
for line in diff:
for prefix in ('---', '+++', '@@'):
if line.startswith(prefix):
break
else:
result += line
else:
result = self.config['candidate']
return str(result).strip()

def _commit_replace(self):
cmd = self._bnc_cmd(self.filename_candidate, "replace", "continue-on-error")
self._send_rpc(cmd)
if self.error.getvalue():
self.rollback()
raise ReplaceConfigException(self.error.getvalue())

def _commit_merge(self):
cmd = self._bnc_cmd(self.filename_candidate, "merge", "rollback-on-error")
self._send_rpc(cmd)
if self.error.getvalue():
self.discard_config()
raise MergeConfigException(self.error.getvalue())

def commit_config(self):
if self.config_replace:
self._commit_replace()
else:
self._commit_merge()
cmd = self._write_memory()
self._send_rpc(cmd)

def discard_config(self):
self.filename_candidate = self.filename_running
self.config['candidate'] = self.config['running']

def rollback(self):
cmd = self._bnc_cmd(self.filename_rollback, "replace", "rollback-on-error")
self._send_rpc(cmd)
cmd = self._write_memory()
self._send_rpc(cmd)
33 changes: 33 additions & 0 deletions test/unit/TestIBMDriver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2015 Spotify AB. All rights reserved.
#
# The contents of this file are licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import unittest

from napalm.ibm import IBMDriver
from base import TestNetworkDriver


class TestIBMDriver(unittest.TestCase, TestNetworkDriver):

@classmethod
def setUpClass(cls):
hostname = '10.20.18.253'
username = 'admin'
password = 'admin'
cls.vendor = 'ibm'

cls.device = IBMDriver(hostname, username, password, timeout=60)
cls.device.open()
cls.device.load_replace_candidate(filename='%s/initial.conf' % cls.vendor)
cls.device.commit_config()
1 change: 1 addition & 0 deletions test/unit/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def test_merge_configuration_typo_and_rollback(self):
try:
self.device.load_merge_candidate(filename='%s/merge_typo.conf' % self.vendor)
diff = self.device.compare_config()
self.device.commit_config()
raise Exception("We shouldn't be here")
except exceptions.MergeConfigException:
# We load the original config as candidate. If the commit failed cleanly the compare_config should be empty
Expand Down
50 changes: 50 additions & 0 deletions test/unit/ibm/initial.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
version "7.11.2"
switch-type "IBM Networking Operating System RackSwitch G8052"
iscli-new
!
!

!
!
!
no system dhcp
no system default-ip
hostname "test-sw2"
!
!
interface port XGE2
description "port 50 uplink"
switchport mode trunk
exit
!
vlan 18
name "sysadmin"
!
!
spanning-tree stp 18 vlan 18
!
!
!
!
!
!
!
interface ip 18
ip address 10.20.18.253 255.255.255.0
vlan 18
enable
exit
!
ip gateway 1 address 10.20.18.1
ip gateway 1 enable
!
!
!
!
!
!
!
!
!
end

15 changes: 15 additions & 0 deletions test/unit/ibm/merge_good.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface port 1
description "test description"
switchport mode trunk
switchport trunk allowed vlan 27,2000
switchport trunk native vlan 27
spanning-tree portfast
exit
!

vlan 27
name "install"
!
vlan 2000
name "another test"
!
33 changes: 33 additions & 0 deletions test/unit/ibm/merge_good.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
hostname "test-sw2"
!
!
-interface port 1
- description "test description"
- switchport mode trunk
- switchport trunk allowed vlan 27,2000
- switchport trunk native vlan 27
- spanning-tree portfast
- exit
-!
interface port XGE2
description "port 50 uplink"
switchport mode trunk
vlan 18
name "sysadmin"
!
-vlan 27
- name "install"
-!
-vlan 2000
- name "another test"
-!
-!
!
spanning-tree stp 18 vlan 18
-!
-spanning-tree stp 27 vlan 27
-!
-spanning-tree stp 95 vlan 2000
!
!
!
15 changes: 15 additions & 0 deletions test/unit/ibm/merge_typo.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
inrface port 1
description "test description"
switchport mode trunk
switchport trunk allowed vlan 27,2000
switchport trunk native vlan 27
spanning-tree portfast
exit
!

vlan 27
name "install"
!
vlan 2000
name "another test"
!

0 comments on commit ef98031

Please sign in to comment.