diff --git a/test/case/ietf_system/timezone.py b/test/case/ietf_system/timezone.py
index b1da7131..32edfc24 100755
--- a/test/case/ietf_system/timezone.py
+++ b/test/case/ietf_system/timezone.py
@@ -19,8 +19,7 @@ with infamy.Test() as test:
})
with test.step("Verify current time."):
- root = target.get_dict("/ietf-system:system-state/clock",as_xml=True)
- current_datetime = root.find('.//{urn:ietf:params:xml:ns:yang:ietf-system}current-datetime').text
+ current_datetime=target.get_current_time_with_offset()
offset=current_datetime[-6:]
assert(offset == "+08:00")
diff --git a/test/case/ietf_system/timezone_utc_offset.py b/test/case/ietf_system/timezone_utc_offset.py
index f6ed87ad..a4952b11 100755
--- a/test/case/ietf_system/timezone_utc_offset.py
+++ b/test/case/ietf_system/timezone_utc_offset.py
@@ -18,8 +18,7 @@ with infamy.Test() as test:
})
with test.step("Verify current time."):
- root = target.get_dict("/ietf-system:system-state/clock",as_xml=True)
- current_datetime = root.find('.//{urn:ietf:params:xml:ns:yang:ietf-system}current-datetime').text
+ current_datetime=target.get_current_time_with_offset()
offset=current_datetime[-6:]
assert(offset == "+12:00")
diff --git a/test/case/misc/operational_all.py b/test/case/misc/operational_all.py
index 6f09534d..f83ca295 100755
--- a/test/case/misc/operational_all.py
+++ b/test/case/misc/operational_all.py
@@ -11,6 +11,6 @@ with infamy.Test() as test:
target = env.attach("target", "mgmt")
with test.step("Get all Operational data"):
- target.get_data(as_xml=True)
+ target.get_data(parse=False)
test.succeed()
diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py
index 5c776098..7496ddb4 100644
--- a/test/infamy/netconf.py
+++ b/test/infamy/netconf.py
@@ -233,7 +233,7 @@ class Device(Transport):
"system-restart": {}
})
- def _get(self, xpath, getter, as_xml=False):
+ def _get(self, xpath, getter, parse=True):
# Figure out which modules we are referencing
mods = self._modules_in_xpath(xpath)
@@ -243,17 +243,17 @@ class Device(Transport):
filt = f""
# pylint: disable=c-extension-no-member
data = getter(filter=filt).data_ele[0]
- if as_xml:
+ if parse==False:
return data
cfg = lxml.etree.tostring(data)
return self.ly.parse_data_mem(cfg, "xml", parse_only=True)
- def _get_data(self, xpath, as_xml=False):
+ def _get_data(self, xpath, parse=True):
"""Local member wrapper for netconf-client RPC"""
# pylint: disable=protected-access
(raw, ele) = self.ncc._send_rpc(self._ncc_get_data_rpc(filter=xpath))
data = NccGetDataReply(raw, ele)
- if(as_xml):
+ if(parse==False):
return data
if len(data.data_ele) == 0:
@@ -263,14 +263,12 @@ class Device(Transport):
cfg = lxml.etree.tostring(data.data_ele[0])
return self.ly.parse_data_mem(cfg, "xml", parse_only=True)
- def get(self, xpath, as_xml=False):
+ def get(self, xpath, parse=True):
"""RPC (legacy NETCONF) fetches config:false data"""
- return self._get(xpath, self.ncc.get, as_xml)
+ return self._get(xpath, self.ncc.get, parse)
- def get_dict(self, xpath, as_xml=False):
+ def get_dict(self, xpath):
"""Return Python dictionary of RPC data"""
- if as_xml:
- return self.get(xpath, as_xml=True)
data = self.get(xpath)
if not data:
@@ -278,10 +276,11 @@ class Device(Transport):
return data.print_dict()
- def get_data(self, xpath=None, as_xml=False):
+ def get_data(self, xpath=None, parse=True):
"""RPC to fetch operational data"""
- if as_xml:
- return self._get_data(xpath, as_xml)
+
+ if parse==False:
+ return self._get_data(xpath,parse)
data = self._get_data(xpath)
if not data:
@@ -419,3 +418,8 @@ class Device(Transport):
lyd = oldd.diff(newd)
return self.put_config(lyd.print_mem("xml", with_siblings=True, pretty=False))
+
+ def get_current_time_with_offset(self):
+ root = self.get_data("/ietf-system:system-state/clock", parse=False).data_ele
+ current_datetime = root.find('.//{urn:ietf:params:xml:ns:yang:ietf-system}current-datetime').text
+ return current_datetime
diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py
index 1cd0314a..6d63aeee 100644
--- a/test/infamy/restconf.py
+++ b/test/infamy/restconf.py
@@ -29,19 +29,19 @@ class Device(Transport):
mapping: dict,
yangdir: None | str = None,
factory_default = True):
- self.location = location
+ self.location=location
self.url_base=f"https://[{location.host}]:{location.port}"
- self.restconf_url = f"{self.url_base}/restconf"
- self.yang_url = f"{self.url_base}/yang/"
- self.rpc_url= f"{self.url_base}/restconf/operations"
- self.headers = {
+ self.restconf_url=f"{self.url_base}/restconf"
+ self.yang_url=f"{self.url_base}/yang/"
+ self.rpc_url=f"{self.url_base}/restconf/operations"
+ self.headers={
'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'
}
- self.auth = HTTPBasicAuth(location.username, location.password)
- self.modules = {}
+ self.auth=HTTPBasicAuth(location.username, location.password)
+ self.modules={}
- self.lyctx = libyang.Context(yangdir)
+ self.lyctx=libyang.Context(yangdir)
self._ly_bootstrap(yangdir)
self._ly_init(yangdir)
@@ -68,7 +68,7 @@ class Device(Transport):
return os.path.exists("{schema_path}")
def _ly_bootstrap(self, yangdir):
- schemas = self.get_schemas_list()
+ schemas=self.get_schemas_list()
for schema in schemas:
if not self.schema_exist(schema["name"], schema["revision"],yangdir):
self.get_schema(schema["name"], schema["revision"], yangdir)
@@ -84,14 +84,14 @@ class Device(Transport):
print("YANG models downloaded.")
def _ly_init(self, yangdir):
- lib = self.lyctx.load_module("ietf-yang-library")
- ns = libyang.util.c2str(lib.cdata.ns)
+ lib=self.lyctx.load_module("ietf-yang-library")
+ ns=libyang.util.c2str(lib.cdata.ns)
for ms in self.modules.values():
if ms["conformance-type"] != "implement":
continue
- mod = self.lyctx.load_module(ms["name"])
+ mod=self.lyctx.load_module(ms["name"])
# TODO: ms["feature"] contains the list of enabled
# features, so ideally we should only enable the supported
@@ -101,31 +101,31 @@ class Device(Transport):
def _get_raw(self, url, parse=True):
"""Actually send a GET to RESTCONF server"""
- response = requests.get(url, headers=self.headers, auth=self.auth, verify=False)
+ response=requests.get(url, headers=self.headers, auth=self.auth, verify=False)
response.raise_for_status() # Raise an exception for HTTP errors
if parse:
- data = response.json()
+ data=response.json()
data=self.lyctx.parse_data_mem(json.dumps(data), "json", parse_only=True)
return data
else:
return response.content
- def get_datastore(self, datastore="operational" , xpath=""):
+ def get_datastore(self, datastore="operational" , xpath="", parse=True):
"""Get a datastore"""
- path = f"/ds/ietf-datastores:{datastore}"
+ path=f"/ds/ietf-datastores:{datastore}"
if not xpath is None:
path=f"{path}/{xpath}"
- path = quote(path, safe="/:")
- url = f"{self.restconf_url}{path}"
- return self._get_raw(url)
+ path=quote(path, safe="/:")
+ url=f"{self.restconf_url}{path}"
+ return self._get_raw(url, parse)
def get_running(self, xpath=None):
"""Wrapper function to get running datastore"""
return self.get_datastore("running", xpath)
- def get_operational(self, xpath=None):
+ def get_operational(self, xpath=None, parse=True):
"""Wrapper function to get operational datastore"""
- return self.get_datastore("operational", xpath)
+ return self.get_datastore("operational", xpath, parse)
def get_factory(self, xpath=None):
"""Wrapper function to get factory defaults"""
@@ -133,7 +133,7 @@ class Device(Transport):
def post_datastore(self, datastore, data):
"""Actually send a POST to RESTCONF server"""
- response = requests.post(
+ response=requests.post(
f"{self.restconf_url}/ds/ietf-datastores:{datastore}/",
json=data, # Directly pass the dictionary without using json.dumps
headers=self.headers,
@@ -144,7 +144,7 @@ class Device(Transport):
def put_datastore(self, datastore, data):
"""Actually send a PUT to RESTCONF server"""
- response = requests.put(
+ response=requests.put(
f"{self.restconf_url}/ds/ietf-datastores:{datastore}/",
json=data, # Directly pass the dictionary without using json.dumps
headers=self.headers,
@@ -157,15 +157,15 @@ class Device(Transport):
"""Get all configuration for module @modname as dictionary"""
ds=self.get_running(modname)
ds=json.loads(ds.print_mem("json", with_siblings=True, pretty=False))
- model, container = modname.split(":")
+ model, container=modname.split(":")
for k, v in ds.items():
return {container: v}
def put_config_dict(self, modname, edit):
"""Add @edit to running config and put the whole configuration"""
- running = self.get_running()
- mod = self.lyctx.get_module(modname)
- change = mod.parse_data_dict(edit, no_state=True, validate=False)
+ running=self.get_running()
+ mod=self.lyctx.get_module(modname)
+ change=mod.parse_data_dict(edit, no_state=True, validate=False)
running.merge_module(change)
return self.put_datastore("running", json.loads(running.print_mem("json", with_siblings=True, pretty=False)))
@@ -173,7 +173,7 @@ class Device(Transport):
def call_rpc(self, rpc):
url=f"{self.rpc_url}/{rpc}"
"""Actually send a POST to RESTCONF server"""
- response = requests.post(
+ response=requests.post(
url,
headers=self.headers,
auth=self.auth,
@@ -181,30 +181,27 @@ class Device(Transport):
)
response.raise_for_status() # Raise an exception for HTTP errors
- def get_dict(self, xpath=None, as_xml=False):
+ def get_dict(self, xpath=None, parse=True):
"""NETCONF compat function, just wraps get_data"""
- return self.get_data(xpath, as_xml)
+ return self.get_data(xpath, parse)
- def get_data(self, xpath=None, as_xml=False):
+ def get_data(self, xpath=None, parse=True):
"""Get operational data"""
- data=self.get_operational(xpath)
- if as_xml:
- data=data.print_mem("xml", with_siblings=True, pretty=False)
- return etree.fromstring(data)
- if xpath is None:
+ data=self.get_operational(xpath, parse)
+ if parse==False:
return data
data=json.loads(data.print_mem("json", with_siblings=True, pretty=False))
for k,v in data.items():
- model, container = k.split(":")
+ model, container=k.split(":")
break
return {container: v}
def copy(self, source, target):
factory=self.get_datastore(source)
- data = factory.print_mem("json", with_siblings=True, pretty=False)
+ data=factory.print_mem("json", with_siblings=True, pretty=False)
self.put_datastore(target, json.loads(data))
def reboot(self):
@@ -223,9 +220,15 @@ class Device(Transport):
return xpath
+ def get_current_time_with_offset(self):
+ data=self.get_data("/ietf-system:system-state/clock", parse=False)
+ data=json.loads(data)
+ print(data)
+ return data["ietf-system:system-state"]["clock"]["current-datetime"]
+
def get_iface(self, iface):
"""Fetch target dict for iface and extract param from JSON"""
- content = self.get_data(self.get_iface_xpath(iface))
+ content=self.get_data(self.get_iface_xpath(iface))
interface=content.get("interfaces", {}).get("interface", None)
if interface is None:
return None
@@ -233,16 +236,11 @@ class Device(Transport):
# This is a bug in rousette, should be able to use the same code as NETCONF
return interface[0]
- def get_iface_xpath(self, iface, path=None):
- """Compose complete XPath to a YANG node in /ietf-interfaces"""
- xpath = f"/ietf-interfaces:interfaces/interface"
- return self.get_xpath(xpath, "", iface, path)
-
def delete_xpath(self, xpath):
"""Delete XPath from running config"""
- path = f"/ds/ietf-datastores:running/{xpath}"
- path = quote(path, safe="/:")
- url = f"{self.restconf_url}{path}"
- response = requests.delete(url, headers=self.headers, auth=self.auth, verify=False)
+ path=f"/ds/ietf-datastores:running/{xpath}"
+ path=quote(path, safe="/:")
+ url=f"{self.restconf_url}{path}"
+ response=requests.delete(url, headers=self.headers, auth=self.auth, verify=False)
response.raise_for_status() # Raise an exception for HTTP errors
return True
diff --git a/test/infamy/transport.py b/test/infamy/transport.py
index d326a374..e75418a9 100644
--- a/test/infamy/transport.py
+++ b/test/infamy/transport.py
@@ -5,7 +5,7 @@ class Transport(ABC):
"""Common functions for NETCONF/RESTCONF"""
@abstractmethod
- def get_data(self, xpath=None, as_xml=False):
+ def get_data(self, xpath=None, parse=True):
pass
@abstractmethod
def get_config_dict(self, modname):
@@ -14,18 +14,12 @@ class Transport(ABC):
def put_config_dict(self, modname, edit):
pass
@abstractmethod
- def get_dict(self, xpath=None, as_xml=False):
+ def get_dict(self, xpath=None):
pass
@abstractmethod
def get_xpath(self, xpath, key, value, path=None):
pass
@abstractmethod
- def get_iface(self, iface):
- pass
- @abstractmethod
- def get_iface_xpath(self, iface, path=None):
- pass
- @abstractmethod
def delete_xpath(self, xpath):
pass
@abstractmethod
@@ -34,6 +28,16 @@ class Transport(ABC):
@abstractmethod
def reboot(self):
pass
+ @abstractmethod
+ def get_current_time_with_offset(self):
+ # This is needed since libyang is too nice and removes the original offset
+ pass
+ @abstractmethod
+ def get_iface_xpath(self, iface, path=None):
+ pass
+ @abstractmethod
+ def get_iface(self, iface): # Should be common, but is not due to bug in rousette
+ pass
def get_mgmt_ip(self):
"""Return managment IP address used for RESTCONF"""
@@ -51,3 +55,8 @@ class Transport(ABC):
"""Check if the device reachable on ll6"""
neigh = ll6ping(self.location.interface, flags=["-w1", "-c1", "-L", "-n"])
return bool(neigh)
+
+ def get_iface_xpath(self, iface, path=None):
+ """Compose complete XPath to a YANG node in /ietf-interfaces"""
+ xpath = f"/ietf-interfaces:interfaces/interface"
+ return self.get_xpath(xpath, "name", iface, path)