Skip to content

Commit ed23a36

Browse files
committed
Merge branch 'main' into sodn_yang_connector_protofiles
2 parents 91e2243 + 9d7d785 commit ed23a36

File tree

14 files changed

+592
-8
lines changed

14 files changed

+592
-8
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
April 2025
2+
==========
3+
4+
April 29 - Yang v25.4
5+
------------------------
6+
7+
8+
9+
.. csv-table:: New Module Versions
10+
:header: "Modules", "Version"
11+
12+
``yang.connector``, v25.4
13+
``yang.ncdiff``, v25.4
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
21+
yang.connector
22+
""""""""""""""
23+
24+
yang.ncdiff
25+
"""""""""""
26+
--------------------------------------------------------------------------------
27+
Add
28+
--------------------------------------------------------------------------------
29+
30+
* yang.ncdiff
31+
* Added rc and gnmi functions as we didn't had support for restconf under configdelta.
32+
33+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
February 2025
2+
==========
3+
4+
February 25 - Yang v25.2
5+
------------------------
6+
7+
8+
9+
.. csv-table:: New Module Versions
10+
:header: "Modules", "Version"
11+
12+
``yang.connector``, v25.2
13+
``yang.ncdiff``, v25.2
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
21+
yang.connector
22+
""""""""""""""
23+
24+
yang.ncdiff
25+
"""""""""""
126
--------------------------------------------------------------------------------
227
Fix
328
--------------------------------------------------------------------------------
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
March 2025
2+
==========
3+
4+
March 25 - Yang v25.3
5+
------------------------
6+
7+
8+
9+
.. csv-table:: New Module Versions
10+
:header: "Modules", "Version"
11+
12+
``yang.connector``, v25.3
13+
``yang.ncdiff``, v25.3
14+
15+
16+
17+
18+
Changelogs
19+
^^^^^^^^^^
20+
21+
yang.connector
22+
""""""""""""""
23+
--------------------------------------------------------------------------------
24+
New
25+
--------------------------------------------------------------------------------
26+
27+
* yang
28+
* Modified Gnmi
29+
* Added `skip_verify` property for `gnmi` connection
30+
* if `skip_verify` is set to `true` pyats will establishes a secure connection, using the server credentials.
31+
* Verification of certificate is skipped with this option.
32+
33+
34+
35+
yang.ncdiff
36+
"""""""""""

connector/docs/changelog/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Changelog
44
.. toctree::
55
:maxdepth: 2
66

7+
2025/april
8+
2025/march
9+
2025/february
710
2025/january
811
2024/november
912
2024/october

connector/src/yang/connector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88

99
# metadata
10-
__version__ = '25.1'
10+
__version__ = '25.4'
1111
__author__ = (
1212
'Jonathan Yang <[email protected]>',
1313
'Siming Yuan <[email protected]',

connector/src/yang/connector/gnmi.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from google.protobuf import json_format
77
import grpc
88
from . import proto
9+
import ssl
10+
from cryptography import x509
11+
from cryptography.hazmat.backends import default_backend
912

1013

1114
try:
@@ -228,6 +231,7 @@ def __init__(self, *args, **kwargs):
228231
super().__init__(*args, **kwargs)
229232
self.device = kwargs.get('device')
230233
self.dev_args = self.connection_info
234+
self.skip_verify = kwargs.get('skip_verify')
231235
if self.dev_args.get('protocol', '') != 'gnmi':
232236
msg = 'Invalid protocol {0}'.format(self.dev_args.get('protocol', ''))
233237
raise TypeError(msg)
@@ -266,6 +270,7 @@ def connect(self):
266270
dev_args = self.dev_args
267271
username = dev_args.get('username', '')
268272
password = dev_args.get('password', '')
273+
skip_verify = dev_args.get('skip_verify')
269274

270275
if dev_args.get('custom_log', ''):
271276
self.log = dev_args.get('custom_log')
@@ -331,6 +336,28 @@ def connect(self):
331336
if private_key and os.path.isfile(private_key):
332337
private_key = open(private_key, 'rb').read()
333338

339+
if skip_verify and not root:
340+
try:
341+
ssl_cert = ssl.get_server_certificate((str(host), port)).encode("utf-8")
342+
except Exception as e:
343+
self.log.error(f'The SSH certificate cannot be retrieved from {target}')
344+
raise gNMIException(f'The SSH certificate cannot be retrieved from {target}', e)
345+
346+
ssl_cert_deserialized = x509.load_pem_x509_certificate(
347+
ssl_cert, default_backend()
348+
)
349+
350+
try:
351+
ssl_cert_common_name = ssl_cert_deserialized.subject.get_attributes_for_oid(
352+
(x509.oid.NameOID.COMMON_NAME)
353+
)[0].value
354+
options.append(
355+
('grpc.ssl_target_name_override', ssl_cert_common_name),
356+
)
357+
root = ssl_cert
358+
except BaseException as err:
359+
self.log.warning(f'Unable to get common name: {err}')
360+
334361
if any((root, chain, private_key)):
335362
override_name = dev_args.get('ssl_name_override', '')
336363
if override_name:
@@ -359,7 +386,6 @@ def connect(self):
359386
target, channel_creds, options
360387
)
361388
else:
362-
self.channel = grpc.insecure_channel(target)
363389
self.channel = grpc.insecure_channel(target, options)
364390
self.metadata = [
365391
("username", username),

connector/src/yang/connector/tests/test_gnmi.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from pyats.topology import loader
99
from pyats.datastructures import AttrDict
1010

11+
from cryptography import x509
12+
from cryptography.hazmat.backends import default_backend
1113

1214
class TestGnmi(unittest.TestCase):
1315

@@ -133,6 +135,105 @@ def test_connect_proxy(self):
133135
],
134136
}
135137

138+
@patch('yang.connector.gnmi.grpc.secure_channel')
139+
@patch('yang.connector.gnmi.grpc.composite_channel_credentials')
140+
@patch('yang.connector.gnmi.grpc.metadata_call_credentials')
141+
@patch('yang.connector.gnmi.grpc.ssl_channel_credentials')
142+
@patch('yang.connector.gnmi.x509.load_pem_x509_certificate')
143+
@patch('yang.connector.gnmi.ssl.get_server_certificate')
144+
def test_connect_with_skip_verify(
145+
self, mock_get_server_certificate, mock_load_pem_x509_certificate,
146+
mock_ssl_channel_credentials, mock_metadata_call_credentials,
147+
mock_composite_channel_credentials, mock_secure_channel, *_
148+
):
149+
yaml = """
150+
devices:
151+
dummy:
152+
type: dummy_device
153+
connections:
154+
Gnmi:
155+
class: yang.connector.Gnmi
156+
protocol: gnmi
157+
ip : 1.2.3.4
158+
port: 830
159+
username: admin
160+
password: admin
161+
skip_verify: true
162+
"""
163+
testbed = loader.load(yaml)
164+
device = testbed.devices['dummy']
165+
device.connect(alias='gnmi', via='Gnmi')
166+
mock_get_server_certificate.assert_called_once_with(('1.2.3.4', '830'))
167+
mock_load_pem_x509_certificate.assert_called_once_with(
168+
mock_get_server_certificate.return_value.encode('utf-8'),
169+
default_backend()
170+
)
171+
mock_load_pem_x509_certificate.return_value.subject.get_attributes_for_oid.assert_called_once_with(
172+
(x509.NameOID.COMMON_NAME)
173+
)
174+
mock_ssl_channel_credentials.assert_called_once_with(
175+
mock_get_server_certificate.return_value.encode('utf-8'), None, None
176+
)
177+
self.assertEqual(
178+
mock_secure_channel.call_args[0][2][-1],
179+
(
180+
'grpc.ssl_target_name_override',
181+
mock_load_pem_x509_certificate.return_value.subject.get_attributes_for_oid.return_value[0].value
182+
)
183+
)
184+
185+
@patch('yang.connector.gnmi.ssl.get_server_certificate', side_effect=Exception)
186+
def test_connect_with_skip_verify_get_server_certificate_exception(self, *_):
187+
yaml = """
188+
devices:
189+
dummy:
190+
type: dummy_device
191+
connections:
192+
Gnmi:
193+
class: yang.connector.Gnmi
194+
protocol: gnmi
195+
ip: 1.2.3.4
196+
port: 830
197+
username: admin
198+
password: admin
199+
skip_verify: true
200+
"""
201+
testbed = loader.load(yaml)
202+
device = testbed.devices['dummy']
203+
with self.assertRaises(Exception):
204+
device.connect(alias='gnmi', via='Gnmi')
205+
206+
@patch('yang.connector.gnmi.ssl.get_server_certificate')
207+
@patch('yang.connector.gnmi.grpc.insecure_channel')
208+
@patch('yang.connector.gnmi.x509.load_pem_x509_certificate')
209+
def test_connect_with_skip_verify_get_attributes_for_oid_exception(
210+
self, mock_load_pem_x509_certificate, mock_insecure_channel, *_
211+
):
212+
yaml = """
213+
devices:
214+
dummy:
215+
type: dummy_device
216+
connections:
217+
Gnmi:
218+
class: yang.connector.Gnmi
219+
protocol: gnmi
220+
ip: 1.2.3.4
221+
port: 830
222+
username: admin
223+
password: admin
224+
skip_verify: true
225+
"""
226+
mock_load_pem_x509_certificate.return_value.subject.get_attributes_for_oid.side_effect = BaseException
227+
testbed = loader.load(yaml)
228+
device = testbed.devices['dummy']
229+
device.connect(alias='gnmi', via='Gnmi')
230+
mock_insecure_channel.assert_called_once_with(
231+
'1.2.3.4:830', [
232+
('grpc.max_receive_message_length', 1000000000),
233+
('grpc.max_send_message_length', 1000000000)
234+
]
235+
)
236+
136237
def test_xpath_to_path_elem(self):
137238
"""Test converting Genie content data to cisco_gnmi format."""
138239
modules, message, origin = xpath_util.xml_path_to_path_elem(self.request)

ncdiff/src/yang/ncdiff/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
edit-config message."""
55

66
# metadata
7-
__version__ = '25.1'
7+
__version__ = '25.4'
88
__author__ = 'Jonathan Yang <[email protected]>'
99
__contact__ = '[email protected]'
1010
__copyright__ = 'Cisco Systems, Inc.'

ncdiff/src/yang/ncdiff/config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,20 @@ def nc(self):
576576
replace_depth=self.replace_depth,
577577
replace_xpath=self.replace_xpath,
578578
).sub
579+
580+
@property
581+
def rc(self):
582+
return RestconfCalculator(
583+
self.device,
584+
self.config_dst.ele, self.config_src.ele,
585+
).sub
586+
587+
@property
588+
def gnmi(self):
589+
return gNMICalculator(
590+
self.device,
591+
self.config_dst.ele, self.config_src.ele,
592+
).sub
579593

580594
@property
581595
def ns(self):

0 commit comments

Comments
 (0)