Skip to content

Commit

Permalink
list instance creation handled during SetRequest
Browse files Browse the repository at this point in the history
fixes #30

Signed-off-by: Martin Volf <[email protected]>
  • Loading branch information
martin-volf committed Dec 6, 2023
1 parent dd04647 commit bfc97ae
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 67 deletions.
64 changes: 35 additions & 29 deletions src/confd_gnmi_api_adapter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import functools
import itertools
import os
import re
Expand Down Expand Up @@ -691,41 +692,14 @@ def get(self, prefix, paths, data_type, use_models):
log.info("<== notifications=%s", notifications)
return notifications

def set_update(self, trans, prefix, path, val):
path_str = self.fix_path_prefixes(make_formatted_path(path, prefix))
if val.string_val:
trans.set_elem(val.string_val, path_str)
elif val.json_ietf_val:
jval = json.loads(val.json_ietf_val)

def update_paths(path, val, parent_node):
# TODO: no support for list instances
csnode = _tm.cs_node_cd(parent_node, path)
if isinstance(val, dict):
trans.pushd(path)
for leaf, value in val.items():
update_paths(self.fix_path_prefixes(leaf), value, csnode)
else:
if csnode.info().shallow_type() == _tm.C_IDENTITYREF:
# in JSON, identityrefs are prefixed by module name
val = self.fix_path_prefixes(val)
if csnode.info().shallow_type() == _tm.C_BOOL:
# ConfD boolean value is lowercase string
val = str(val).lower()
log.debug("path=%s val=%s", path, val)
trans.set_elem(val, path)
update_paths(path_str, jval, None)
op = gnmi_pb2.UpdateResult.UPDATE
return op

def set(self, prefix, updates):
log.info("==> prefix=%s, updates=%s", prefix, updates)
context = "netconf"
groups = [self.username]
with maapi.single_write_trans(self.username, context, groups,
ip=self.addr, port=self.port) as t:
ops = [(up.path, self.set_update(t, prefix, up.path, up.val))
for up in updates]
updater = Updater(self, t, prefix)
ops = [(up.path, updater.update(up.path, up.val)) for up in updates]
t.apply()

log.info("==> ops=%s", ops)
Expand All @@ -745,3 +719,35 @@ def delete(self, prefix, paths):

log.info("==> ops=%s", ops)
return ops


class Updater:
def __init__(self, adapter, t, prefix):
self.adapter = adapter
self.trans = t
self.prefix = prefix

def update(self, path, value):
if value.HasField('json_ietf_val'):
obj = json.loads(value.json_ietf_val)
elif value.HasField('json_val'):
log.warning('using json_val as json_ietf_val')
obj = json.loads(value.json_val)
else:
raise Exception(f'{value.ListFields()[0][0].name} not supported for updates')
elems = [gnmi_pb2.PathElem(name='data')] + list(self.prefix.elem) + list(path.elem)
data = functools.reduce(self.build_obj, reversed(elems), obj)
log.debug('sending %s to JSON load', data)
sid = self.trans.load_config_stream(_tm.maapi.CONFIG_JSON | _tm.maapi.CONFIG_MERGE)
with socket() as sock:
_tm.stream_connect(sock, sid, 0, self.adapter.addr, self.adapter.port)
sock.send(json.dumps(data).encode())
if self.trans.maapi.load_config_stream_result(sid) != _tm.OK:
raise Exception('load_config_stream failed')
return gnmi_pb2.UpdateResult.UPDATE

def build_obj(self, obj, elem):
if elem.key:
assert isinstance(obj, dict), 'cannot apply keys to a non-container'
obj.update(elem.key)
return {elem.name: obj}
66 changes: 35 additions & 31 deletions tests/client_server_test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

@pytest.mark.grpc
@pytest.mark.usefixtures("fix_method")
class GrpcBase(object):
class GrpcBase:
NS_IETF_INTERFACES = GnmiDemoServerAdapter.NS_INTERFACES
NS_OC_INTERFACES = "openconfig-interfaces:"

Expand Down Expand Up @@ -73,6 +73,8 @@ def mk_gnmi_if_path(path_str, if_state_str="", if_id=None):
path_str = path_str.format(if_state_str, if_id)
return make_gnmi_path(path_str)


class AdapterTests(GrpcBase):
def test_capabilities(self, request):
log.info("testing capabilities")
capabilities = self.client.get_capabilities()
Expand Down Expand Up @@ -133,7 +135,7 @@ def assert_set_response(response, path_op):
def assert_updates(updates, path_vals):
assert (len(updates) == len(path_vals))
for i, u in enumerate(updates):
GrpcBase.assert_update(u, path_vals[i])
AdapterTests.assert_update(u, path_vals[i])

@staticmethod
def assert_one_in_update(updates, pv):
Expand All @@ -145,14 +147,14 @@ def assert_in_updates(updates, path_vals):
log.debug("==> updates=%s path_vals=%s", updates, path_vals)
assert (len(updates) == len(path_vals))
for pv in path_vals:
GrpcBase.assert_one_in_update(updates, pv)
AdapterTests.assert_one_in_update(updates, pv)
log.debug("<==")


def verify_get_response_updates(self, prefix, paths, path_value,
datatype, encoding, assert_fun=None):
if assert_fun is None:
assert_fun = GrpcBase.assert_updates
assert_fun = AdapterTests.assert_updates
log.debug("prefix=%s paths=%s pv_list=%s datatype=%s encoding=%s",
prefix, paths, path_value, datatype, encoding)
time_before = get_timestamp_ns()
Expand Down Expand Up @@ -192,7 +194,7 @@ def verify_sub_sub_response_updates(self, prefix, paths, path_value,
:param allow_aggregation: allow aggregation of results in updates
'''
if assert_fun is None:
assert_fun = GrpcBase.assert_updates
assert_fun = AdapterTests.assert_updates

stream_mode = gnmi_pb2.SubscriptionMode.ON_CHANGE
if sample_interval is not None:
Expand Down Expand Up @@ -278,7 +280,7 @@ def _test_get_subscribe(self, is_subscribe=False,
encoding = gnmi_pb2.Encoding.JSON_IETF,
allow_aggregation=True):

kwargs = {"assert_fun": GrpcBase.assert_updates}
kwargs = {"assert_fun": AdapterTests.assert_updates}
if_state_str = prefix_state_str = ""
db = GnmiDemoServerAdapter.get_adapter().demo_db
if datatype == gnmi_pb2.GetRequest.DataType.STATE:
Expand All @@ -289,15 +291,15 @@ def _test_get_subscribe(self, is_subscribe=False,
prefix = make_gnmi_path("/ietf-interfaces:interfaces{}".format(prefix_state_str))
kwargs["prefix"] = prefix
if_id = 8
leaf_paths = [GrpcBase.mk_gnmi_if_path(leaf_paths_str,
if_state_str,
if_id)
leaf_paths = [AdapterTests.mk_gnmi_if_path(leaf_paths_str,
if_state_str,
if_id)
for leaf_paths_str in self.leaf_paths_str]
list_paths = [
GrpcBase.mk_gnmi_if_path(self.list_paths_str[0], if_state_str,
if_id),
GrpcBase.mk_gnmi_if_path(self.list_paths_str[1]),
GrpcBase.mk_gnmi_if_path(self.list_paths_str[2].format(prefix_state_str)),
AdapterTests.mk_gnmi_if_path(self.list_paths_str[0], if_state_str,
if_id),
AdapterTests.mk_gnmi_if_path(self.list_paths_str[1]),
AdapterTests.mk_gnmi_if_path(self.list_paths_str[2].format(prefix_state_str)),
]
ifname = "{}if_{}".format(if_state_str, if_id)

Expand Down Expand Up @@ -342,22 +344,24 @@ def _test_get_subscribe(self, is_subscribe=False,
verify_response_updates(**kwargs)
kwargs["paths"] = [list_paths[1]]
if allow_aggregation:
pv = [(GrpcBase.mk_gnmi_if_path(self.list_paths_str[0], if_state_str, i),
pv = [(AdapterTests.mk_gnmi_if_path(self.list_paths_str[0], if_state_str, i),
dict(zip(leaves, [f"{if_state_str}if_{i}"] + vals)))
for i in range(1, GnmiDemoServerAdapter.num_of_ifs+1)]
else:
pv = []
for i in range(1, GnmiDemoServerAdapter.num_of_ifs+1):
pv.append((GrpcBase.mk_gnmi_if_path(self.leaf_paths_str[0], if_state_str, i),
pv.append((AdapterTests.mk_gnmi_if_path(self.leaf_paths_str[0], if_state_str, i),
f"{if_state_str}if_{i}"))
pv.append((GrpcBase.mk_gnmi_if_path(self.leaf_paths_str[1], if_state_str, i),
pv.append((AdapterTests.mk_gnmi_if_path(self.leaf_paths_str[1], if_state_str, i),
"iana-if-type:gigabitEthernet"))
if datatype != gnmi_pb2.GetRequest.DataType.STATE:
pv.append((GrpcBase.mk_gnmi_if_path(self.leaf_paths_str[2], if_state_str, i),
pv.append((AdapterTests.mk_gnmi_if_path(self.leaf_paths_str[2],
if_state_str,
i),
True))

kwargs["path_value"] = pv
kwargs["assert_fun"] = GrpcBase.assert_in_updates
kwargs["assert_fun"] = AdapterTests.assert_in_updates
verify_response_updates(**kwargs)
if allow_aggregation:
kwargs["paths"] = [list_paths[2]]
Expand Down Expand Up @@ -514,8 +518,8 @@ def test_subscribe_stream(self, request, data_type):
log.info("change_list=%s", changes_list)

prefix_str = "{{prefix}}interfaces{}".format(prefix_state_str)
paths = [GrpcBase.mk_gnmi_if_path(self.list_paths_str[1], if_state_str,
"N/A")]
paths = [AdapterTests.mk_gnmi_if_path(self.list_paths_str[1], if_state_str,
"N/A")]
self._test_subscribe(prefix_str, self.NS_IETF_INTERFACES,
paths, changes_list)

Expand All @@ -524,13 +528,13 @@ def _test_subscribe(self, prefix_str, ns_prefix, paths, changes_list):
path_value.extend(self._changes_list_to_pv(changes_list))
prefix = make_gnmi_path("/" + prefix_str.format(prefix=ns_prefix))

kwargs = {"assert_fun": GrpcBase.assert_in_updates}
kwargs = {"assert_fun": AdapterTests.assert_in_updates}
kwargs["prefix"] = prefix
kwargs["paths"] = paths
kwargs["path_value"] = path_value
kwargs["subscription_mode"] = gnmi_pb2.SubscriptionList.STREAM
kwargs["read_count"] = len(path_value)
kwargs["assert_fun"] = GrpcBase.assert_in_updates
kwargs["assert_fun"] = AdapterTests.assert_in_updates

if self.adapter_type == AdapterType.DEMO:
prefix_pfx = prefix_str.format(prefix='')
Expand Down Expand Up @@ -561,15 +565,15 @@ def test_set(self, request):
log.info("testing set")
if_id = 8
prefix = make_gnmi_path("/ietf-interfaces:interfaces")
paths = [GrpcBase.mk_gnmi_if_path(self.leaf_paths_str[1], "", if_id)]
paths = [AdapterTests.mk_gnmi_if_path(self.leaf_paths_str[1], "", if_id)]
vals = [gnmi_pb2.TypedValue(json_ietf_val=b"\"iana-if-type:fastEther\"")]
time_before = get_timestamp_ns()
response = self.client.set(prefix, list(zip(paths, vals)))
time_after = get_timestamp_ns()
assert (time_before <= response.timestamp and response.timestamp <= time_after)
assert (response.prefix == prefix)
GrpcBase.assert_set_response(response.response[0],
(paths[0], gnmi_pb2.UpdateResult.UPDATE))
AdapterTests.assert_set_response(response.response[0],
(paths[0], gnmi_pb2.UpdateResult.UPDATE))

# fetch with get and see value has changed
datatype = gnmi_pb2.GetRequest.DataType.CONFIG
Expand All @@ -578,19 +582,19 @@ def test_set(self, request):
for n in get_response.notification:
log.debug("n=%s", n)
assert (n.prefix == prefix)
GrpcBase.assert_updates(n.update, [(paths[0], "iana-if-type:fastEther")])
AdapterTests.assert_updates(n.update, [(paths[0], "iana-if-type:fastEther")])

# put value back
vals = [gnmi_pb2.TypedValue(json_ietf_val=b"\"iana-if-type:gigabitEthernet\"")]
response = self.client.set(prefix, list(zip(paths, vals)))
GrpcBase.assert_set_response(response.response[0],
(paths[0], gnmi_pb2.UpdateResult.UPDATE))
AdapterTests.assert_set_response(response.response[0],
(paths[0], gnmi_pb2.UpdateResult.UPDATE))

def test_set_encoding(self, request):
log.info("testing set_encoding")
if_id = 8
prefix = make_gnmi_path("/ietf-interfaces:interfaces")
paths = [GrpcBase.mk_gnmi_if_path(self.leaf_paths_str[1], "", if_id)]
paths = [AdapterTests.mk_gnmi_if_path(self.leaf_paths_str[1], "", if_id)]

@self.encoding_test_decorator
def test_it(encoding):
Expand All @@ -616,5 +620,5 @@ def test_it(encoding):
vals = [gnmi_pb2.TypedValue(
json_ietf_val=b"\"iana-if-type:gigabitEthernet\"")]
response = self.client.set(prefix, list(zip(paths, vals)))
GrpcBase.assert_set_response(response.response[0],
(paths[0], gnmi_pb2.UpdateResult.UPDATE))
AdapterTests.assert_set_response(response.response[0],
(paths[0], gnmi_pb2.UpdateResult.UPDATE))
Loading

0 comments on commit bfc97ae

Please sign in to comment.