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)