diff --git a/src/confd/bin/bootstrap b/src/confd/bin/bootstrap
index 193ef412..1d30829b 100755
--- a/src/confd/bin/bootstrap
+++ b/src/confd/bin/bootstrap
@@ -160,7 +160,7 @@ sysrepoctl -s $SEARCH \
-i infix-lldp@2023-08-23.yang -g wheel -p 0660 \
-i infix-dhcp-client@2023-05-22.yang -g wheel -p 0660 \
-i infix-shell-type@2023-08-21.yang -g wheel -p 0660 \
- -i infix-system@2023-08-15.yang -g wheel -p 0660 \
+ -i infix-system@2023-10-19.yang -g wheel -p 0660 \
-i infix-services@2023-10-16.yang -g wheel -p 0660 \
-i ieee802-ethernet-interface@2019-06-21.yang -g wheel -p 0660 \
-I "${INIT_DATA}"
diff --git a/src/confd/src/ietf-system.c b/src/confd/src/ietf-system.c
index f9aa2b2d..3d8426e4 100644
--- a/src/confd/src/ietf-system.c
+++ b/src/confd/src/ietf-system.c
@@ -301,6 +301,8 @@ static int change_clock(sr_session_ctx_t *session, uint32_t sub_id, const char *
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
char *timezone;
+ char *tz_utc_offset;
+ char tz_name[14];
switch (event) {
case SR_EV_ENABLED: /* first time, on register. */
@@ -329,13 +331,20 @@ static int change_clock(sr_session_ctx_t *session, uint32_t sub_id, const char *
default:
return SR_ERR_OK;
+
+ }
+ tz_utc_offset = srx_get_str(session, XPATH_BASE_"/clock/timezone-utc-offset");
+ timezone = srx_get_str(session, XPATH_BASE_"/clock/timezone-name");
+ if (!timezone && !tz_utc_offset) {
+ snprintf(tz_name,sizeof(tz_name),"Etc/UTC");
+ timezone = tz_name;
}
- /* XXX: add support also for /ietf-system:system/clock/timezone-utc-offset (deviation) */
- timezone = srx_get_str(session, XPATH_BASE_"/clock/timezone-name");
- if (!timezone) {
- ERROR("Failed reading timezone-name");
- return SR_ERR_VALIDATION_FAILED;
+ if (tz_utc_offset) {
+ int8_t offset = atol(tz_utc_offset);
+ /* When using Etc/GMT offsets, the +/- is inverted in tzdata. */
+ snprintf(tz_name,sizeof(tz_name), "Etc/GMT%s%.2d", offset>-1?"":"+", -offset);
+ timezone = tz_name;
}
remove("/etc/localtime+");
diff --git a/src/confd/yang/infix-system@2023-08-15.yang b/src/confd/yang/infix-system@2023-10-19.yang
similarity index 83%
rename from src/confd/yang/infix-system@2023-08-15.yang
rename to src/confd/yang/infix-system@2023-10-19.yang
index 2d3ee932..7dcdeb50 100644
--- a/src/confd/yang/infix-system@2023-08-15.yang
+++ b/src/confd/yang/infix-system@2023-10-19.yang
@@ -19,6 +19,13 @@ module infix-system {
contact "kernelkit@googlegroups.com";
description "Infix augments and deviations to ietf-system.";
+ revision 2023-10-19 {
+ description "Change deviation for timezone-utc-offset from unsupported to Etc+/-HOUR
+ - Unit is set to hours (tzdata compatibility)
+ - Range is -12 .. 14";
+
+ reference "internal";
+ }
revision 2023-08-15 {
description "Add support for user login shell.
@@ -76,8 +83,14 @@ module infix-system {
}
}
- deviation "/sys:system/sys:clock/sys:timezone/sys:timezone-utc-offset" {
- deviate not-supported;
+ deviation "/sys:system/sys:clock/sys:timezone/sys:timezone-utc-offset/sys:timezone-utc-offset" {
+ description "Timezone UTC offset should be set in hours, not minutes";
+ deviate replace {
+ type int16 {
+ range "-12 .. 14";
+ }
+ units "hours";
+ }
}
deviation "/sys:system/sys:radius" {
diff --git a/test/case/ietf_system/all.yaml b/test/case/ietf_system/all.yaml
index c188aaef..5bfe5867 100644
--- a/test/case/ietf_system/all.yaml
+++ b/test/case/ietf_system/all.yaml
@@ -1,3 +1,5 @@
---
- case: hostname.py
- case: add_delete_user.py
+- case: timezone.py
+- case: timezone_utc_offset.py
diff --git a/test/case/ietf_system/timezone.py b/test/case/ietf_system/timezone.py
new file mode 100755
index 00000000..b1da7131
--- /dev/null
+++ b/test/case/ietf_system/timezone.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+import random, string
+import time
+import infamy
+import lxml
+with infamy.Test() as test:
+ with test.step("Initialize"):
+ env = infamy.Env(infamy.std_topology("1x1"))
+ target = env.attach("target", "mgmt")
+
+ with test.step("Set timezone"):
+ target.put_config_dict("ietf-system", {
+ "system": {
+ "clock": {
+ "timezone-name": "Australia/Perth" # always +8:00, no DTS
+ }
+ }
+ })
+
+ 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
+ offset=current_datetime[-6:]
+
+ assert(offset == "+08:00")
+
+ test.succeed()
diff --git a/test/case/ietf_system/timezone_utc_offset.py b/test/case/ietf_system/timezone_utc_offset.py
new file mode 100755
index 00000000..f6ed87ad
--- /dev/null
+++ b/test/case/ietf_system/timezone_utc_offset.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+import infamy
+import lxml
+
+with infamy.Test() as test:
+ with test.step("Initialize"):
+ env = infamy.Env(infamy.std_topology("1x1"))
+ target = env.attach("target", "mgmt")
+
+ with test.step("Set timezone UTC offset"):
+ target.put_config_dict("ietf-system", {
+ "system": {
+ "clock": {
+ "timezone-utc-offset": "12"
+ }
+ }
+ })
+
+ 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
+ offset=current_datetime[-6:]
+
+ assert(offset == "+12:00")
+
+ test.succeed()
diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py
index 56303d96..e5d12e9b 100644
--- a/test/infamy/netconf.py
+++ b/test/infamy/netconf.py
@@ -147,7 +147,7 @@ class Device(object):
pieces.append("")
return self._ncc_make_rpc("".join(pieces), msg_id=msg_id)
- def _get(self, xpath, getter):
+ def _get(self, xpath, getter, as_xml=False):
# Figure out which modules we are referencing
mods = self._modules_in_xpath(xpath)
@@ -155,30 +155,42 @@ class Device(object):
xmlns = " ".join([f"xmlns:{m['name']}=\"{m['namespace']}\"" for m in mods])
filt = f""
# pylint: disable=c-extension-no-member
- cfg = lxml.etree.tostring(getter(filter=filt).data_ele[0])
+ data=getter(filter=filt).data_ele[0]
+ if as_xml:
+ return data
+ else:
+ cfg = lxml.etree.tostring(data)
+ return self.ly.parse_data_mem(cfg, "xml", parse_only=True)
- return self.ly.parse_data_mem(cfg, "xml", parse_only=True)
-
- def _get_data(self, xpath):
+ def _get_data(self, xpath,as_xml=False):
"""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)
- # pylint: disable=c-extension-no-member
- cfg = lxml.etree.tostring(data.data_ele[0])
- return self.ly.parse_data_mem(cfg, "xml", parse_only=True)
+ if(as_xml):
+ return data
+ else:
+ # pylint: disable=c-extension-no-member
+ cfg = lxml.etree.tostring(data.data_ele[0])
+ return self.ly.parse_data_mem(cfg, "xml", parse_only=True)
- def get(self, xpath):
+ def get(self, xpath, as_xml=False):
"""RPC (legacy NETCONF) fetches config:false data"""
- return self._get(xpath, self.ncc.get)
+ return self._get(xpath, self.ncc.get, as_xml)
- def get_dict(self, xpath):
+ def get_dict(self, xpath,as_xml=False):
"""Return Python dictionary of RPC data"""
- return self.get(xpath).print_dict()
+ if(as_xml):
+ return self.get(xpath, as_xml=True)
+ else:
+ return self.get(xpath).print_dict()
- def get_data(self, xpath):
+ def get_data(self, xpath, as_xml=False):
"""RPC to fetch operational data"""
- return self._get_data(xpath).print_dict()
+ if(as_xml):
+ return self._get_data(xpath,as_xml)
+ else:
+ return self._get_data(xpath).print_dict()
def get_config(self, xpath):
return self._get(xpath, self.ncc.get_config)