Skip to content

Commit

Permalink
feat(ncclient): Add commit_comment capability support for SROS devices
Browse files Browse the repository at this point in the history
- Allows SORS users to add descriptive comments during NETCONF commit operations.
  • Loading branch information
Mohammad Torkashvand committed Oct 13, 2023
1 parent e003f76 commit 731cf98
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 4 deletions.
77 changes: 77 additions & 0 deletions examples/vendor/nokia/sros/commit.py
@@ -0,0 +1,77 @@
import logging
import sys

from ncclient import manager

# Global constants
DEVICE_HOST = 'localhost'
DEVICE_PORT = 830
DEVICE_USER = 'admin'
DEVICE_PASS = 'admin'

EDIT_CONFIG_PAYLOAD = """
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:alu="urn:ietf:params:xml:ns:netconf:base:1.0">
<configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
<system>
<security>
<snmp>
<community alu:operation="replace">
<community-string>Sample Community</community-string>
<access-permissions>r</access-permissions>
<version>v2c</version>
</community>
</snmp>
</security>
</system>
</configure>
</config>
"""


def create_session(host, port, username, password):
"""Creates and returns an ncclient manager session."""
return manager.connect(
host=host,
port=port,
username=username,
password=password,
hostkey_verify=False,
device_params={'name': 'sros'}
)


def edit_config(m, config_payload):
"""Edits the configuration with the given payload."""
m.edit_config(target='candidate', config=config_payload)


def commit_changes(m, comment=''):
"""Commits changes with an optional comment."""
response = m.commit(comment=comment)
logging.info(response)


def main():
LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s'
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT)

try:
with create_session(DEVICE_HOST, DEVICE_PORT, DEVICE_USER, DEVICE_PASS) as m:
# Lock the configuration
m.lock(target='candidate')

# Edit the configuration
edit_config(m, EDIT_CONFIG_PAYLOAD)

# Commit the configuration
commit_changes(m, comment='A sample commit comment goes here')

# Unlock the configuration
m.unlock(target='candidate')

except Exception as e:
logging.error(f"Encountered an error: {e}")


if __name__ == '__main__':
main()
5 changes: 3 additions & 2 deletions ncclient/devices/sros.py
@@ -1,7 +1,7 @@
from lxml import etree

from .default import DefaultDeviceHandler
from ncclient.operations.third_party.sros.rpc import MdCliRawCommand
from ncclient.operations.third_party.sros.rpc import MdCliRawCommand, Commit
from ncclient.xml_ import BASE_NS_1_0


Expand Down Expand Up @@ -43,7 +43,8 @@ def get_xml_extra_prefix_kwargs(self):

def add_additional_operations(self):
operations = {
'md_cli_raw_command': MdCliRawCommand
'md_cli_raw_command': MdCliRawCommand,
'commit': Commit,
}
return operations

Expand Down
42 changes: 42 additions & 0 deletions ncclient/operations/third_party/sros/rpc.py
@@ -1,3 +1,4 @@
from ncclient.operations import OperationError
from ncclient.xml_ import *

from ncclient.operations.rpc import RPC
Expand All @@ -24,3 +25,44 @@ def request(self, command=None):
sub_ele(raw_cmd_node, 'md-cli-input-line').text = command
self._huge_tree = True
return self._request(node)


class Commit(RPC):
"`commit` RPC. Depends on the `:candidate` capability, and the `:confirmed-commit`."

DEPENDS = [':candidate']

def request(self, confirmed=False, timeout=None, persist=None, persist_id=None, comment=None):
"""Commit the candidate configuration as the device's new current configuration. Depends on the `:candidate` capability.
A confirmed commit (i.e. if *confirmed* is `True`) is reverted if there is no followup commit within the *timeout* interval. If no timeout is specified the confirm timeout defaults to 600 seconds (10 minutes). A confirming commit may have the *confirmed* parameter but this is not required. Depends on the `:confirmed-commit` capability.
*confirmed* whether this is a confirmed commit
*timeout* specifies the confirm timeout in seconds
*persist* make the confirmed commit survive a session termination, and set a token on the ongoing confirmed commit
*persist_id* value must be equal to the value given in the <persist> parameter to the original <commit> operation.
*comment* a descriptive text comment that will be associated with the commit action. This is useful for logging or audit purposes.
"""

node = new_ele("commit")
if comment and comment.strip():
sub_ele(node, "comment",
attrs={'xmlns': "urn:nokia.com:sros:ns:yang:sr:ietf-netconf-augments"}).text = comment

if persist and persist_id:
raise OperationError("Invalid operation as persist cannot be present with persist-id")
if confirmed:
self._assert(":confirmed-commit")
sub_ele(node, "confirmed")
if timeout is not None:
sub_ele(node, "confirm-timeout").text = timeout
if persist is not None:
sub_ele(node, "persist").text = persist
if persist_id:
sub_ele(node, "persist-id").text = persist_id

return self._request(node)
6 changes: 4 additions & 2 deletions test/unit/devices/test_sros.py
Expand Up @@ -39,8 +39,10 @@ def setUp(self):
self.obj = SrosDeviceHandler({'name': 'sros'})

def test_add_additional_operations(self):
expected = dict()
expected['md_cli_raw_command'] = MdCliRawCommand
expected = {
'md_cli_raw_command': MdCliRawCommand,
'commit': Commit,
}
self.assertDictEqual(expected, self.obj.add_additional_operations())

def test_transform_reply(self):
Expand Down
25 changes: 25 additions & 0 deletions test/unit/operations/third_party/sros/test_rpc.py
@@ -1,11 +1,14 @@
import unittest
from xml.etree import ElementTree

try:
from unittest.mock import patch # Python 3.4 and later
except ImportError:
from mock import patch
from ncclient import manager
import ncclient.transport
from ncclient.operations.third_party.sros.rpc import *
from ncclient.operations import RaiseMode


class TestRPC(unittest.TestCase):
Expand All @@ -21,3 +24,25 @@ def test_MdCliRawCommand(self, mock_request):
command = 'show version'
actual = obj.request(command=command)
self.assertEqual(expected, actual)

@patch('ncclient.transport.SSHSession')
@patch('ncclient.operations.third_party.sros.rpc.RPC._request')
@patch('ncclient.operations.third_party.sros.rpc.RPC._assert')
def test_commit(self, mock_assert, mock_request, mock_session):
device_handler = manager.make_device_handler({'name': 'sros'})
session = ncclient.transport.SSHSession(device_handler)
obj = Commit(session, device_handler, raise_mode=RaiseMode.ALL)

obj.request(confirmed=True, comment="This is a comment", timeout="50")

node = new_ele("commit")
sub_ele(node, "comment",
attrs={'xmlns': "urn:nokia.com:sros:ns:yang:sr:ietf-netconf-augments"}).text = "This is a comment"
sub_ele(node, "confirmed")
sub_ele(node, "confirm-timeout").text = "50"

xml = ElementTree.tostring(node)
call = mock_request.call_args_list[0][0][0]
call = ElementTree.tostring(call)

self.assertEqual(call, xml)

0 comments on commit 731cf98

Please sign in to comment.