diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 870d7cfda..5a353b110 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -1,666 +1,666 @@
 # Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 """
 A library for retrieving value dicts from VyOS configs in a declarative fashion.
 """
 import os
 import json
 
 from vyos.utils.dict import dict_search
 from vyos.utils.process import cmd
 
 def retrieve_config(path_hash, base_path, config):
     """
     Retrieves a VyOS config as a dict according to a declarative description
 
     The description dict, passed in the first argument, must follow this format:
     ``field_name : <path, type, [inner_options_dict]>``.
 
     Supported types are: ``str`` (for normal nodes),
     ``list`` (returns a list of strings, for multi nodes),
     ``bool`` (returns True if valueless node exists),
     ``dict`` (for tag nodes, returns a dict indexed by node names,
     according to description in the third item of the tuple).
 
     Args:
         path_hash (dict): Declarative description of the config to retrieve
         base_path (list): A base path to prepend to all option paths
         config (vyos.config.Config): A VyOS config object
 
     Returns:
         dict: config dict
     """
     config_hash = {}
 
     for k in path_hash:
 
         if type(path_hash[k]) != tuple:
             raise ValueError("In field {0}: expected a tuple, got a value {1}".format(k, str(path_hash[k])))
         if len(path_hash[k]) < 2:
             raise ValueError("In field {0}: field description must be a tuple of at least two items, path (list) and type".format(k))
 
         path = path_hash[k][0]
         if type(path) != list:
             raise ValueError("In field {0}: path must be a list, not a {1}".format(k, type(path)))
 
         typ = path_hash[k][1]
         if type(typ) != type:
             raise ValueError("In field {0}: type must be a type, not a {1}".format(k, type(typ)))
 
         path = base_path + path
 
         path_str = " ".join(path)
 
         if typ == str:
             config_hash[k] = config.return_value(path_str)
         elif typ == list:
             config_hash[k] = config.return_values(path_str)
         elif typ == bool:
             config_hash[k] = config.exists(path_str)
         elif typ == dict:
             try:
                 inner_hash = path_hash[k][2]
             except IndexError:
                 raise ValueError("The type of the \'{0}\' field is dict, but inner options hash is missing from the tuple".format(k))
             config_hash[k] = {}
             nodes = config.list_nodes(path_str)
             for node in nodes:
                 config_hash[k][node] = retrieve_config(inner_hash, path + [node], config)
 
     return config_hash
 
 
 def dict_merge(source, destination):
     """ Merge two dictionaries. Only keys which are not present in destination
     will be copied from source, anything else will be kept untouched. Function
     will return a new dict which has the merged key/value pairs. """
     from copy import deepcopy
     tmp = deepcopy(destination)
 
     for key, value in source.items():
         if key not in tmp:
             tmp[key] = value
         elif isinstance(source[key], dict):
             tmp[key] = dict_merge(source[key], tmp[key])
 
     return tmp
 
 def list_diff(first, second):
     """ Diff two dictionaries and return only unique items """
     second = set(second)
     return [item for item in first if item not in second]
 
 def is_node_changed(conf, path):
    """
    Check if any key under path has been changed and return True.
    If nothing changed, return false
    """
    from vyos.configdiff import get_config_diff
    D = get_config_diff(conf, key_mangling=('-', '_'))
    return D.is_node_changed(path)
 
 def leaf_node_changed(conf, path):
     """
     Check if a leaf node was altered. If it has been altered - values has been
     changed, or it was added/removed, we will return a list containing the old
     value(s). If nothing has been changed, None is returned.
 
     NOTE: path must use the real CLI node name (e.g. with a hyphen!)
     """
     from vyos.configdiff import get_config_diff
     D = get_config_diff(conf, key_mangling=('-', '_'))
     (new, old) = D.get_value_diff(path)
     if new != old:
         if isinstance(old, dict):
             # valueLess nodes return {} if node is deleted
             return True
         if old is None and isinstance(new, dict):
             # valueLess nodes return {} if node was added
             return True
         if old is None:
             return []
         if isinstance(old, str):
             return [old]
         if isinstance(old, list):
             if isinstance(new, str):
                 new = [new]
             elif isinstance(new, type(None)):
                 new = []
             return list_diff(old, new)
 
     return None
 
 def node_changed(conf, path, key_mangling=None, recursive=False, expand_nodes=None) -> list:
     """
     Check if node under path (or anything under path if recursive=True) was changed. By default
     we only check if a node or subnode (recursive) was deleted from path. If expand_nodes
     is set to Diff.ADD we can also check if something was added to the path.
 
     If nothing changed, an empty list is returned.
     """
     from vyos.configdiff import get_config_diff
     from vyos.configdiff import Diff
     # to prevent circular dependencies we assign the default here
     if not expand_nodes: expand_nodes = Diff.DELETE
     D = get_config_diff(conf, key_mangling)
     # get_child_nodes_diff() will return dict_keys()
     tmp = D.get_child_nodes_diff(path, expand_nodes=expand_nodes, recursive=recursive)
     output = []
     if expand_nodes & Diff.DELETE:
         output.extend(list(tmp['delete'].keys()))
     if expand_nodes & Diff.ADD:
         output.extend(list(tmp['add'].keys()))
 
     # remove duplicate keys from list, this happens when a node (e.g. description) is altered
     output = list(dict.fromkeys(output))
     return output
 
 def get_removed_vlans(conf, path, dict):
     """
     Common function to parse a dictionary retrieved via get_config_dict() and
     determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q.
     """
     from vyos.configdiff import get_config_diff, Diff
 
     # Check vif, vif-s/vif-c VLAN interfaces for removal
     D = get_config_diff(conf, key_mangling=('-', '_'))
     D.set_level(conf.get_level())
 
     # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
     keys = D.get_child_nodes_diff(path + ['vif'], expand_nodes=Diff.DELETE)['delete'].keys()
     if keys: dict['vif_remove'] = [*keys]
 
     # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
     keys = D.get_child_nodes_diff(path + ['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys()
     if keys: dict['vif_s_remove'] = [*keys]
 
     for vif in dict.get('vif_s', {}).keys():
         keys = D.get_child_nodes_diff(path + ['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys()
         if keys: dict['vif_s'][vif]['vif_c_remove'] = [*keys]
 
     return dict
 
 def is_member(conf, interface, intftype=None):
     """
     Checks if passed interface is member of other interface of specified type.
     intftype is optional, if not passed it will search all known types
     (currently bridge and bonding)
 
     Returns: dict
     empty -> Interface is not a member
     key -> Interface is a member of this interface
     """
     ret_val = {}
     intftypes = ['bonding', 'bridge']
 
     if intftype not in intftypes + [None]:
         raise ValueError((
             f'unknown interface type "{intftype}" or it cannot '
             f'have member interfaces'))
 
     intftype = intftypes if intftype == None else [intftype]
 
     for iftype in intftype:
         base = ['interfaces', iftype]
         for intf in conf.list_nodes(base):
             member = base + [intf, 'member', 'interface', interface]
             if conf.exists(member):
                 tmp = conf.get_config_dict(member, key_mangling=('-', '_'),
                                            get_first_key=True,
                                            no_tag_node_value_mangle=True)
                 ret_val.update({intf : tmp})
 
     return ret_val
 
 def is_mirror_intf(conf, interface, direction=None):
     """
     Check whether the passed interface is used for port mirroring. Direction
     is optional, if not passed it will search all known direction
     (currently ingress and egress)
 
     Returns:
     None -> Interface is not a monitor interface
     Array() -> This interface is a monitor interface of interfaces
     """
     from vyos.ifconfig import Section
 
     directions = ['ingress', 'egress']
     if direction not in directions + [None]:
         raise ValueError(f'Unknown interface mirror direction "{direction}"')
 
     direction = directions if direction == None else [direction]
 
     ret_val = None
     base = ['interfaces']
 
     for dir in direction:
         for iftype in conf.list_nodes(base):
             iftype_base = base + [iftype]
             for intf in conf.list_nodes(iftype_base):
                 mirror = iftype_base + [intf, 'mirror', dir, interface]
                 if conf.exists(mirror):
                     path = ['interfaces', Section.section(intf), intf]
                     tmp = conf.get_config_dict(path, key_mangling=('-', '_'),
                                                get_first_key=True)
                     ret_val = {intf : tmp}
 
     return ret_val
 
 def has_address_configured(conf, intf):
     """
     Checks if interface has an address configured.
     Checks the following config nodes:
     'address', 'ipv6 address eui64', 'ipv6 address autoconf'
 
     Returns True if interface has address configured, False if it doesn't.
     """
     from vyos.ifconfig import Section
     ret = False
 
     old_level = conf.get_level()
     conf.set_level([])
 
     intfpath = ['interfaces', Section.get_config_path(intf)]
     if (conf.exists([intfpath, 'address']) or
         conf.exists([intfpath, 'ipv6', 'address', 'autoconf']) or
         conf.exists([intfpath, 'ipv6', 'address', 'eui64'])):
         ret = True
 
     conf.set_level(old_level)
     return ret
 
 def has_vrf_configured(conf, intf):
     """
     Checks if interface has a VRF configured.
 
     Returns True if interface has VRF configured, False if it doesn't.
     """
     from vyos.ifconfig import Section
     ret = False
 
     old_level = conf.get_level()
     conf.set_level([])
 
     if conf.exists(['interfaces', Section.get_config_path(intf), 'vrf']):
         ret = True
 
     conf.set_level(old_level)
     return ret
 
 def has_vlan_subinterface_configured(conf, intf):
     """
     Checks if interface has an VLAN subinterface configured.
     Checks the following config nodes:
     'vif', 'vif-s'
 
     Return True if interface has VLAN subinterface configured.
     """
     from vyos.ifconfig import Section
     ret = False
 
     intfpath = ['interfaces', Section.section(intf), intf]
     if (conf.exists(intfpath + ['vif']) or conf.exists(intfpath + ['vif-s'])):
         ret = True
 
     return ret
 
 def is_source_interface(conf, interface, intftype=None):
     """
     Checks if passed interface is configured as source-interface of other
     interfaces of specified type. intftype is optional, if not passed it will
     search all known types (currently pppoe, macsec, pseudo-ethernet, tunnel
     and vxlan)
 
     Returns:
     None -> Interface is not a member
     interface name -> Interface is a member of this interface
     False -> interface type cannot have members
     """
     ret_val = None
     intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan']
     if not intftype:
         intftype = intftypes
 
     if isinstance(intftype, str):
         intftype = [intftype]
     elif not isinstance(intftype, list):
         raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!')
 
     if not all(x in intftypes for x in intftype):
         raise ValueError(f'unknown interface type "{intftype}" or it can not '
             'have a source-interface')
 
     for it in intftype:
         base = ['interfaces', it]
         for intf in conf.list_nodes(base):
             src_intf = base + [intf, 'source-interface']
             if conf.exists(src_intf) and interface in conf.return_values(src_intf):
                 ret_val = intf
                 break
 
     return ret_val
 
 def get_dhcp_interfaces(conf, vrf=None):
     """ Common helper functions to retrieve all interfaces from current CLI
     sessions that have DHCP configured. """
     dhcp_interfaces = {}
     dict = conf.get_config_dict(['interfaces'], get_first_key=True)
     if not dict:
         return dhcp_interfaces
 
     def check_dhcp(config):
         ifname = config['ifname']
         tmp = {}
         if 'address' in config and 'dhcp' in config['address']:
             options = {}
             if dict_search('dhcp_options.default_route_distance', config) != None:
                 options.update({'dhcp_options' : config['dhcp_options']})
             if 'vrf' in config:
                 if vrf == config['vrf']: tmp.update({ifname : options})
             else:
                 if vrf is None: tmp.update({ifname : options})
 
         return tmp
 
     for section, interface in dict.items():
         for ifname in interface:
             # always reset config level, as get_interface_dict() will alter it
             conf.set_level([])
             # we already have a dict representation of the config from get_config_dict(),
             # but with the extended information from get_interface_dict() we also
             # get the DHCP client default-route-distance default option if not specified.
             _, ifconfig = get_interface_dict(conf, ['interfaces', section], ifname)
 
             tmp = check_dhcp(ifconfig)
             dhcp_interfaces.update(tmp)
             # check per VLAN interfaces
             for vif, vif_config in ifconfig.get('vif', {}).items():
                 tmp = check_dhcp(vif_config)
                 dhcp_interfaces.update(tmp)
             # check QinQ VLAN interfaces
             for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items():
                 tmp = check_dhcp(vif_s_config)
                 dhcp_interfaces.update(tmp)
                 for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
                     tmp = check_dhcp(vif_c_config)
                     dhcp_interfaces.update(tmp)
 
     return dhcp_interfaces
 
 def get_pppoe_interfaces(conf, vrf=None):
     """ Common helper functions to retrieve all interfaces from current CLI
     sessions that have DHCP configured. """
     pppoe_interfaces = {}
     conf.set_level([])
     for ifname in conf.list_nodes(['interfaces', 'pppoe']):
         # always reset config level, as get_interface_dict() will alter it
         conf.set_level([])
         # we already have a dict representation of the config from get_config_dict(),
         # but with the extended information from get_interface_dict() we also
         # get the DHCP client default-route-distance default option if not specified.
         _, ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname)
 
         options = {}
         if 'default_route_distance' in ifconfig:
             options.update({'default_route_distance' : ifconfig['default_route_distance']})
         if 'no_default_route' in ifconfig:
             options.update({'no_default_route' : {}})
         if 'vrf' in ifconfig:
             if vrf == ifconfig['vrf']: pppoe_interfaces.update({ifname : options})
         else:
             if vrf is None: pppoe_interfaces.update({ifname : options})
 
     return pppoe_interfaces
 
 def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pki=False):
     """
     Common utility function to retrieve and mangle the interfaces configuration
     from the CLI input nodes. All interfaces have a common base where value
     retrival is identical. This function must be used whenever possible when
     working on the interfaces node!
 
     Return a dictionary with the necessary interface config keys.
     """
     if not ifname:
         from vyos import ConfigError
         # determine tagNode instance
         if 'VYOS_TAGNODE_VALUE' not in os.environ:
             raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
         ifname = os.environ['VYOS_TAGNODE_VALUE']
 
     # Check if interface has been removed. We must use exists() as
     # get_config_dict() will always return {} - even when an empty interface
     # node like the following exists.
     # +macsec macsec1 {
     # +}
     if not config.exists(base + [ifname]):
         dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'),
                                       get_first_key=True,
                                       no_tag_node_value_mangle=True)
         dict.update({'deleted' : {}})
     else:
         # Get config_dict with default values
         dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'),
                                       get_first_key=True,
                                       no_tag_node_value_mangle=True,
                                       with_defaults=True,
                                       with_recursive_defaults=recursive_defaults,
                                       with_pki=with_pki)
 
         # If interface does not request an IPv4 DHCP address there is no need
         # to keep the dhcp-options key
         if 'address' not in dict or 'dhcp' not in dict['address']:
             if 'dhcp_options' in dict:
                 del dict['dhcp_options']
 
     # Add interface instance name into dictionary
     dict.update({'ifname': ifname})
 
     # Check if QoS policy applied on this interface - See ifconfig.interface.set_mirror_redirect()
     if config.exists(['qos', 'interface', ifname]):
         dict.update({'traffic_policy': {}})
 
     address = leaf_node_changed(config, base + [ifname, 'address'])
     if address: dict.update({'address_old' : address})
 
     # Check if we are a member of a bridge device
     bridge = is_member(config, ifname, 'bridge')
     if bridge: dict.update({'is_bridge_member' : bridge})
 
     # Check if it is a monitor interface
     mirror = is_mirror_intf(config, ifname)
     if mirror: dict.update({'is_mirror_intf' : mirror})
 
     # Check if we are a member of a bond device
     bond = is_member(config, ifname, 'bonding')
     if bond: dict.update({'is_bond_member' : bond})
 
     # Check if any DHCP options changed which require a client restat
     dhcp = is_node_changed(config, base + [ifname, 'dhcp-options'])
     if dhcp: dict.update({'dhcp_options_changed' : {}})
 
     # Changine interface VRF assignemnts require a DHCP restart, too
     dhcp = is_node_changed(config, base + [ifname, 'vrf'])
     if dhcp: dict.update({'dhcp_options_changed' : {}})
 
     # Some interfaces come with a source_interface which must also not be part
     # of any other bond or bridge interface as it is exclusivly assigned as the
     # Kernels "lower" interface to this new "virtual/upper" interface.
     if 'source_interface' in dict:
         # Check if source interface is member of another bridge
         tmp = is_member(config, dict['source_interface'], 'bridge')
         if tmp: dict.update({'source_interface_is_bridge_member' : tmp})
 
         # Check if source interface is member of another bridge
         tmp = is_member(config, dict['source_interface'], 'bonding')
         if tmp: dict.update({'source_interface_is_bond_member' : tmp})
 
     mac = leaf_node_changed(config, base + [ifname, 'mac'])
     if mac: dict.update({'mac_old' : mac})
 
     eui64 = leaf_node_changed(config, base + [ifname, 'ipv6', 'address', 'eui64'])
     if eui64:
         tmp = dict_search('ipv6.address', dict)
         if not tmp:
             dict.update({'ipv6': {'address': {'eui64_old': eui64}}})
         else:
             dict['ipv6']['address'].update({'eui64_old': eui64})
 
     for vif, vif_config in dict.get('vif', {}).items():
         # Add subinterface name to dictionary
         dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'})
 
         if config.exists(['qos', 'interface', f'{ifname}.{vif}']):
             dict['vif'][vif].update({'traffic_policy': {}})
 
         if 'deleted' not in dict:
             address = leaf_node_changed(config, base + [ifname, 'vif', vif, 'address'])
             if address: dict['vif'][vif].update({'address_old' : address})
 
             # If interface does not request an IPv4 DHCP address there is no need
             # to keep the dhcp-options key
             if 'address' not in dict['vif'][vif] or 'dhcp' not in dict['vif'][vif]['address']:
                 if 'dhcp_options' in dict['vif'][vif]:
                     del dict['vif'][vif]['dhcp_options']
 
         # Check if we are a member of a bridge device
         bridge = is_member(config, f'{ifname}.{vif}', 'bridge')
         if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge})
 
         # Check if any DHCP options changed which require a client restat
         dhcp = is_node_changed(config, base + [ifname, 'vif', vif, 'dhcp-options'])
         if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : {}})
 
     for vif_s, vif_s_config in dict.get('vif_s', {}).items():
         # Add subinterface name to dictionary
         dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'})
 
         if config.exists(['qos', 'interface', f'{ifname}.{vif_s}']):
             dict['vif_s'][vif_s].update({'traffic_policy': {}})
 
         if 'deleted' not in dict:
             address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'address'])
             if address: dict['vif_s'][vif_s].update({'address_old' : address})
 
             # If interface does not request an IPv4 DHCP address there is no need
             # to keep the dhcp-options key
             if 'address' not in dict['vif_s'][vif_s] or 'dhcp' not in \
                 dict['vif_s'][vif_s]['address']:
                 if 'dhcp_options' in dict['vif_s'][vif_s]:
                     del dict['vif_s'][vif_s]['dhcp_options']
 
         # Check if we are a member of a bridge device
         bridge = is_member(config, f'{ifname}.{vif_s}', 'bridge')
         if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge})
 
         # Check if any DHCP options changed which require a client restat
         dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'dhcp-options'])
         if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : {}})
 
         for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
             # Add subinterface name to dictionary
             dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'})
 
             if config.exists(['qos', 'interface', f'{ifname}.{vif_s}.{vif_c}']):
                 dict['vif_s'][vif_s]['vif_c'][vif_c].update({'traffic_policy': {}})
 
             if 'deleted' not in dict:
                 address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'address'])
                 if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update(
                         {'address_old' : address})
 
                 # If interface does not request an IPv4 DHCP address there is no need
                 # to keep the dhcp-options key
                 if 'address' not in dict['vif_s'][vif_s]['vif_c'][vif_c] or 'dhcp' \
                     not in dict['vif_s'][vif_s]['vif_c'][vif_c]['address']:
                     if 'dhcp_options' in dict['vif_s'][vif_s]['vif_c'][vif_c]:
                         del dict['vif_s'][vif_s]['vif_c'][vif_c]['dhcp_options']
 
             # Check if we are a member of a bridge device
             bridge = is_member(config, f'{ifname}.{vif_s}.{vif_c}', 'bridge')
             if bridge: dict['vif_s'][vif_s]['vif_c'][vif_c].update(
                 {'is_bridge_member' : bridge})
 
             # Check if any DHCP options changed which require a client restat
             dhcp = is_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'dhcp-options'])
             if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : {}})
 
     # Check vif, vif-s/vif-c VLAN interfaces for removal
     dict = get_removed_vlans(config, base + [ifname], dict)
     return ifname, dict
 
 def get_vlan_ids(interface):
     """
     Get the VLAN ID of the interface bound to the bridge
     """
     vlan_ids = set()
 
     bridge_status = cmd('bridge -j vlan show', shell=True)
     vlan_filter_status = json.loads(bridge_status)
 
     if vlan_filter_status is not None:
         for interface_status in vlan_filter_status:
             ifname = interface_status['ifname']
             if interface == ifname:
                 vlans_status = interface_status['vlans']
                 for vlan_status in vlans_status:
                     vlan_id = vlan_status['vlan']
                     vlan_ids.add(vlan_id)
 
     return vlan_ids
 
 def get_accel_dict(config, base, chap_secrets, with_pki=False):
     """
     Common utility function to retrieve and mangle the Accel-PPP configuration
     from different CLI input nodes. All Accel-PPP services have a common base
     where value retrival is identical. This function must be used whenever
     possible when working with Accel-PPP services!
 
     Return a dictionary with the necessary interface config keys.
     """
-    from vyos.cpu import get_core_count
+    from vyos.utils.cpu import get_core_count
     from vyos.template import is_ipv4
 
     dict = config.get_config_dict(base, key_mangling=('-', '_'),
                                   get_first_key=True,
                                   no_tag_node_value_mangle=True,
                                   with_recursive_defaults=True,
                                   with_pki=with_pki)
 
     # set CPUs cores to process requests
     dict.update({'thread_count' : get_core_count()})
     # we need to store the path to the secrets file
     dict.update({'chap_secrets_file' : chap_secrets})
 
     # We can only have two IPv4 and three IPv6 nameservers - also they are
     # configured in a different way in the configuration, this is why we split
     # the configuration
     if 'name_server' in dict:
         ns_v4 = []
         ns_v6 = []
         for ns in dict['name_server']:
             if is_ipv4(ns): ns_v4.append(ns)
             else: ns_v6.append(ns)
 
         dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6})
         del dict['name_server']
 
     # Check option "disable-accounting" per server and replace default value from '1813' to '0'
     for server in (dict_search('authentication.radius.server', dict) or []):
         if 'disable_accounting' in dict['authentication']['radius']['server'][server]:
             dict['authentication']['radius']['server'][server]['acct_port'] = '0'
 
     return dict
diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py
index 12ef2d3b8..1cd062a11 100644
--- a/python/vyos/utils/__init__.py
+++ b/python/vyos/utils/__init__.py
@@ -1,30 +1,31 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 from vyos.utils import assertion
 from vyos.utils import auth
 from vyos.utils import boot
 from vyos.utils import commit
 from vyos.utils import convert
+from vyos.utils import cpu
 from vyos.utils import dict
 from vyos.utils import file
 from vyos.utils import io
 from vyos.utils import kernel
 from vyos.utils import list
 from vyos.utils import misc
 from vyos.utils import network
 from vyos.utils import permission
 from vyos.utils import process
 from vyos.utils import system
diff --git a/python/vyos/cpu.py b/python/vyos/utils/cpu.py
similarity index 99%
rename from python/vyos/cpu.py
rename to python/vyos/utils/cpu.py
index cae5f5f4d..3bea5ac12 100644
--- a/python/vyos/cpu.py
+++ b/python/vyos/utils/cpu.py
@@ -1,102 +1,101 @@
 # Copyright (C) 2022-2024 VyOS maintainers and contributors
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 """
 Retrieves (or at least attempts to retrieve) the total number of real CPU cores
 installed in a Linux system.
 
 The issue of core count is complicated by existence of SMT, e.g. Intel's Hyper Threading.
 GNU nproc returns the number of LOGICAL cores,
 which is 2x of the real cores if SMT is enabled.
 
 The idea is to find all physical CPUs and add up their core counts.
 It has special cases for x86_64 and MAY work correctly on other architectures,
 but nothing is certain.
 """
 
 import re
 
-
 def _read_cpuinfo():
     with open('/proc/cpuinfo', 'r') as f:
         lines = f.read().strip()
         return re.split(r'\n+', lines)
 
 def _split_line(l):
     l = l.strip()
     parts = re.split(r'\s*:\s*', l)
     return (parts[0], ":".join(parts[1:]))
 
 def _find_cpus(cpuinfo_lines):
     # Make a dict because it's more convenient to work with later,
     # when we need to find physicall distinct CPUs there.
     cpus = {}
 
     cpu_number = 0
 
     for l in cpuinfo_lines:
         key, value = _split_line(l)
         if key == 'processor':
             cpu_number = value
             cpus[cpu_number] = {}
         else:
             cpus[cpu_number][key] = value
 
     return cpus
 
 def _find_physical_cpus():
     cpus = _find_cpus(_read_cpuinfo())
 
     phys_cpus = {}
 
     for num in cpus:
         if 'physical id' in cpus[num]:
             # On at least some architectures, CPUs in different sockets
             # have different 'physical id' field, e.g. on x86_64.
             phys_id = cpus[num]['physical id']
             if phys_id not in phys_cpus:
                 phys_cpus[phys_id] = cpus[num]
         else:
             # On other architectures, e.g. on ARM, there's no such field.
             # We just assume they are different CPUs,
             # whether single core ones or cores of physical CPUs.
             phys_cpus[num] = cpus[num]
 
     return phys_cpus
 
 def get_cpus():
     """ Returns a list of /proc/cpuinfo entries that belong to different CPUs.
     """
     cpus_dict = _find_physical_cpus()
     return list(cpus_dict.values())
 
 def get_core_count():
     """ Returns the total number of physical CPU cores
         (even if Hyper-Threading or another SMT is enabled and has inflated
         the number of cores in /proc/cpuinfo)
     """
     physical_cpus = _find_physical_cpus()
 
     core_count = 0
 
     for num in physical_cpus:
         # Some architectures, e.g. x86_64, include a field for core count.
         # Since we found unique physical CPU entries, we can sum their core counts.
         if 'cpu cores' in physical_cpus[num]:
             core_count += int(physical_cpus[num]['cpu cores'])
         else:
             core_count += 1
 
     return core_count
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py
index 212dc58ab..c6f6cb804 100644
--- a/smoketest/scripts/cli/base_accel_ppp_test.py
+++ b/smoketest/scripts/cli/base_accel_ppp_test.py
@@ -1,648 +1,648 @@
 # Copyright (C) 2020-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 from configparser import ConfigParser
 
 from vyos.configsession import ConfigSessionError
 from vyos.template import is_ipv4
-from vyos.cpu import get_core_count
+from vyos.utils.cpu import get_core_count
 from vyos.utils.process import process_named_running
 from vyos.utils.process import cmd
 
 class BasicAccelPPPTest:
     class TestCase(VyOSUnitTestSHIM.TestCase):
         @classmethod
         def setUpClass(cls):
             cls._process_name = "accel-pppd"
 
             super(BasicAccelPPPTest.TestCase, cls).setUpClass()
 
             # ensure we can also run this test on a live system - so lets clean
             # out the current configuration :)
             cls.cli_delete(cls, cls._base_path)
 
         def setUp(self):
             self._gateway = "192.0.2.1"
             # ensure we can also run this test on a live system - so lets clean
             # out the current configuration :)
             self.cli_delete(self._base_path)
 
         def tearDown(self):
             # Check for running process
             self.assertTrue(process_named_running(self._process_name))
 
             self.cli_delete(self._base_path)
             self.cli_commit()
 
             # Check for running process
             self.assertFalse(process_named_running(self._process_name))
 
         def set(self, path):
             self.cli_set(self._base_path + path)
 
         def delete(self, path):
             self.cli_delete(self._base_path + path)
 
         def basic_protocol_specific_config(self):
             """
             An astract method.
             Initialize protocol scpecific configureations.
             """
             self.assertFalse(True, msg="Function must be defined")
 
         def initial_auth_config(self):
             """
             Initialization of default authentication for all protocols
             """
             self.set(
                 [
                     "authentication",
                     "local-users",
                     "username",
                     "vyos",
                     "password",
                     "vyos",
                 ]
             )
             self.set(["authentication", "mode", "local"])
 
         def initial_gateway_config(self):
             """
             Initialization of default gateway
             """
             self.set(["gateway-address", self._gateway])
 
         def initial_pool_config(self):
             """
             Initialization of default client ip pool
             """
             first_pool = "SIMPLE-POOL"
             self.set(["client-ip-pool", first_pool, "range", "192.0.2.0/24"])
             self.set(["default-pool", first_pool])
 
         def basic_config(self, is_auth=True, is_gateway=True, is_client_pool=True):
             """
             Initialization of basic configuration
             :param is_auth: authentication initialization
             :type is_auth: bool
             :param is_gateway: gateway initialization
             :type is_gateway: bool
             :param is_client_pool: client ip pool initialization
             :type is_client_pool: bool
             """
             self.basic_protocol_specific_config()
             if is_auth:
                 self.initial_auth_config()
             if is_gateway:
                 self.initial_gateway_config()
             if is_client_pool:
                 self.initial_pool_config()
 
         def getConfig(self, start, end="cli"):
             """
             Return part of configuration from line
             where the first injection of start keyword to the line
             where the first injection of end keyowrd
             :param start: start keyword
             :type start: str
             :param end: end keyword
             :type end: str
             :return: part of config
             :rtype: str
             """
             command = f'cat {self._config_file} | sed -n "/^\[{start}/,/^\[{end}/p"'
             out = cmd(command)
             return out
 
         def verify(self, conf):
             self.assertEqual(conf["core"]["thread-count"], str(get_core_count()))
 
         def test_accel_name_servers(self):
             # Verify proper Name-Server configuration for IPv4 and IPv6
             self.basic_config()
 
             nameserver = ["192.0.2.1", "192.0.2.2", "2001:db8::1"]
             for ns in nameserver:
                 self.set(["name-server", ns])
 
             # commit changes
             self.cli_commit()
 
             # Validate configuration values
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
 
             # IPv4 and IPv6 nameservers must be checked individually
             for ns in nameserver:
                 if is_ipv4(ns):
                     self.assertIn(ns, [conf["dns"]["dns1"], conf["dns"]["dns2"]])
                 else:
                     self.assertEqual(conf["ipv6-dns"][ns], None)
 
         def test_accel_local_authentication(self):
             # Test configuration of local authentication
             self.basic_config()
 
             # upload / download limit
             user = "test"
             password = "test2"
             static_ip = "100.100.100.101"
             upload = "5000"
             download = "10000"
             self.set(
                 [
                     "authentication",
                     "local-users",
                     "username",
                     user,
                     "password",
                     password,
                 ]
             )
             self.set(
                 [
                     "authentication",
                     "local-users",
                     "username",
                     user,
                     "static-ip",
                     static_ip,
                 ]
             )
             self.set(
                 [
                     "authentication",
                     "local-users",
                     "username",
                     user,
                     "rate-limit",
                     "upload",
                     upload,
                 ]
             )
 
             # upload rate-limit requires also download rate-limit
             with self.assertRaises(ConfigSessionError):
                 self.cli_commit()
             self.set(
                 [
                     "authentication",
                     "local-users",
                     "username",
                     user,
                     "rate-limit",
                     "download",
                     download,
                 ]
             )
 
             # commit changes
             self.cli_commit()
 
             # Validate configuration values
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
 
             # check proper path to chap-secrets file
             self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets)
 
             # basic verification
             self.verify(conf)
 
             # check local users
             tmp = cmd(f"sudo cat {self._chap_secrets}")
             regex = f"{user}\s+\*\s+{password}\s+{static_ip}\s+{download}/{upload}"
             tmp = re.findall(regex, tmp)
             self.assertTrue(tmp)
 
             # Check local-users default value(s)
             self.delete(
                 ["authentication", "local-users", "username", user, "static-ip"]
             )
             # commit changes
             self.cli_commit()
 
             # check local users
             tmp = cmd(f"sudo cat {self._chap_secrets}")
             regex = f"{user}\s+\*\s+{password}\s+\*\s+{download}/{upload}"
             tmp = re.findall(regex, tmp)
             self.assertTrue(tmp)
 
         def test_accel_radius_authentication(self):
             # Test configuration of RADIUS authentication for PPPoE server
             self.basic_config()
 
             radius_server = "192.0.2.22"
             radius_key = "secretVyOS"
             radius_port = "2000"
             radius_port_acc = "3000"
             acct_interim_jitter = '10'
             acct_interim_interval = '10'
             acct_timeout = '30'
 
             self.set(["authentication", "mode", "radius"])
             self.set(
                 ["authentication", "radius", "server", radius_server, "key", radius_key]
             )
             self.set(
                 [
                     "authentication",
                     "radius",
                     "server",
                     radius_server,
                     "port",
                     radius_port,
                 ]
             )
             self.set(
                 [
                     "authentication",
                     "radius",
                     "server",
                     radius_server,
                     "acct-port",
                     radius_port_acc,
                 ]
             )
             self.set(
                 [
                     "authentication",
                     "radius",
                     "acct-interim-jitter",
                     acct_interim_jitter,
                 ]
             )
             self.set(
                 [
                     "authentication",
                     "radius",
                     "accounting-interim-interval",
                     acct_interim_interval,
                 ]
             )
             self.set(
                 [
                     "authentication",
                     "radius",
                     "acct-timeout",
                     acct_timeout,
                 ]
             )
 
             coa_server = "4.4.4.4"
             coa_key = "testCoA"
             self.set(
                 ["authentication", "radius", "dynamic-author", "server", coa_server]
             )
             self.set(["authentication", "radius", "dynamic-author", "key", coa_key])
 
             nas_id = "VyOS-PPPoE"
             nas_ip = "7.7.7.7"
             self.set(["authentication", "radius", "nas-identifier", nas_id])
             self.set(["authentication", "radius", "nas-ip-address", nas_ip])
 
             source_address = "1.2.3.4"
             self.set(["authentication", "radius", "source-address", source_address])
 
             # commit changes
             self.cli_commit()
 
             # Validate configuration values
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
 
             # basic verification
             self.verify(conf)
 
             # check auth
             self.assertTrue(conf["radius"].getboolean("verbose"))
             self.assertEqual(conf["radius"]["acct-timeout"], acct_timeout)
             self.assertEqual(conf["radius"]["acct-interim-interval"], acct_interim_interval)
             self.assertEqual(conf["radius"]["acct-interim-jitter"], acct_interim_jitter)
             self.assertEqual(conf["radius"]["timeout"], "3")
             self.assertEqual(conf["radius"]["max-try"], "3")
 
             self.assertEqual(
                 conf["radius"]["dae-server"], f"{coa_server}:1700,{coa_key}"
             )
             self.assertEqual(conf["radius"]["nas-identifier"], nas_id)
             self.assertEqual(conf["radius"]["nas-ip-address"], nas_ip)
             self.assertEqual(conf["radius"]["bind"], source_address)
 
             server = conf["radius"]["server"].split(",")
             self.assertEqual(radius_server, server[0])
             self.assertEqual(radius_key, server[1])
             self.assertEqual(f"auth-port={radius_port}", server[2])
             self.assertEqual(f"acct-port={radius_port_acc}", server[3])
             self.assertEqual(f"req-limit=0", server[4])
             self.assertEqual(f"fail-time=0", server[5])
 
             #
             # Disable Radius Accounting
             #
             self.delete(
                 ["authentication", "radius", "server", radius_server, "acct-port"]
             )
             self.set(
                 [
                     "authentication",
                     "radius",
                     "server",
                     radius_server,
                     "disable-accounting",
                 ]
             )
 
             self.set(
                 [
                     "authentication",
                     "radius",
                     "server",
                     radius_server,
                     "backup",
                 ]
             )
 
             self.set(
                 [
                     "authentication",
                     "radius",
                     "server",
                     radius_server,
                     "priority",
                     "10",
                 ]
             )
 
             # commit changes
             self.cli_commit()
 
             conf.read(self._config_file)
 
             server = conf["radius"]["server"].split(",")
             self.assertEqual(radius_server, server[0])
             self.assertEqual(radius_key, server[1])
             self.assertEqual(f"auth-port={radius_port}", server[2])
             self.assertEqual(f"acct-port=0", server[3])
             self.assertEqual(f"req-limit=0", server[4])
             self.assertEqual(f"fail-time=0", server[5])
             self.assertIn('weight=10', server)
             self.assertIn('backup', server)
 
         def test_accel_ipv4_pool(self):
             self.basic_config(is_gateway=False, is_client_pool=False)
             gateway = "192.0.2.1"
             subnet = "172.16.0.0/24"
             first_pool = "POOL1"
             second_pool = "POOL2"
             range = "192.0.2.10-192.0.2.20"
             range_config = "192.0.2.10-20"
 
             self.set(["gateway-address", gateway])
             self.set(["client-ip-pool", first_pool, "range", subnet])
             self.set(["client-ip-pool", first_pool, "next-pool", second_pool])
             self.set(["client-ip-pool", second_pool, "range", range])
             self.set(["default-pool", first_pool])
             # commit changes
 
             self.cli_commit()
 
             # Validate configuration values
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
 
             self.assertEqual(
                 f"{first_pool},next={second_pool}", conf["ip-pool"][f"{subnet},name"]
             )
             self.assertEqual(second_pool, conf["ip-pool"][f"{range_config},name"])
             self.assertEqual(gateway, conf["ip-pool"]["gw-ip-address"])
             self.assertEqual(first_pool, conf[self._protocol_section]["ip-pool"])
 
         def test_accel_next_pool(self):
             # T5099 required specific order
             self.basic_config(is_gateway=False, is_client_pool=False)
 
             gateway = "192.0.2.1"
             first_pool = "VyOS-pool1"
             first_subnet = "192.0.2.0/25"
             second_pool = "Vyos-pool2"
             second_subnet = "203.0.113.0/25"
             third_pool = "Vyos-pool3"
             third_subnet = "198.51.100.0/24"
 
             self.set(["gateway-address", gateway])
             self.set(["client-ip-pool", first_pool, "range", first_subnet])
             self.set(["client-ip-pool", first_pool, "next-pool", second_pool])
             self.set(["client-ip-pool", second_pool, "range", second_subnet])
             self.set(["client-ip-pool", second_pool, "next-pool", third_pool])
             self.set(["client-ip-pool", third_pool, "range", third_subnet])
 
             # commit changes
             self.cli_commit()
 
             config = self.getConfig("ip-pool")
 
             pool_config = f"""gw-ip-address={gateway}
 {third_subnet},name={third_pool}
 {second_subnet},name={second_pool},next={third_pool}
 {first_subnet},name={first_pool},next={second_pool}"""
             self.assertIn(pool_config, config)
 
         def test_accel_ipv6_pool(self):
             # Test configuration of IPv6 client pools
             self.basic_config(is_gateway=False, is_client_pool=False)
 
             # Enable IPv6
             allow_ipv6 = 'allow'
             self.set(['ppp-options', 'ipv6', allow_ipv6])
 
             pool_name = 'ipv6_test_pool'
             prefix_1 = '2001:db8:fffe::/56'
             prefix_mask = '64'
             prefix_2 = '2001:db8:ffff::/56'
             client_prefix_1 = f'{prefix_1},{prefix_mask}'
             client_prefix_2 = f'{prefix_2},{prefix_mask}'
             self.set(
                 ['client-ipv6-pool', pool_name, 'prefix', prefix_1, 'mask',
                  prefix_mask])
             self.set(
                 ['client-ipv6-pool', pool_name, 'prefix', prefix_2, 'mask',
                  prefix_mask])
 
             delegate_1_prefix = '2001:db8:fff1::/56'
             delegate_2_prefix = '2001:db8:fff2::/56'
             delegate_mask = '64'
             self.set(
                 ['client-ipv6-pool', pool_name, 'delegate', delegate_1_prefix,
                  'delegation-prefix', delegate_mask])
             self.set(
                 ['client-ipv6-pool', pool_name, 'delegate', delegate_2_prefix,
                  'delegation-prefix', delegate_mask])
 
             # commit changes
             self.cli_commit()
 
             # Validate configuration values
             conf = ConfigParser(allow_no_value=True, delimiters='=',
                                 strict=False)
             conf.read(self._config_file)
 
             for tmp in ['ipv6pool', 'ipv6_nd', 'ipv6_dhcp']:
                 self.assertEqual(conf['modules'][tmp], None)
 
             self.assertEqual(conf['ppp']['ipv6'], allow_ipv6)
 
             config = self.getConfig("ipv6-pool")
             pool_config = f"""{client_prefix_1},name={pool_name}
 {client_prefix_2},name={pool_name}
 delegate={delegate_1_prefix},{delegate_mask},name={pool_name}
 delegate={delegate_2_prefix},{delegate_mask},name={pool_name}"""
             self.assertIn(pool_config, config)
 
         def test_accel_ppp_options(self):
             # Test configuration of local authentication for PPPoE server
             self.basic_config()
 
             # other settings
             mppe = 'require'
             self.set(['ppp-options', 'disable-ccp'])
             self.set(['ppp-options', 'mppe', mppe])
 
             # min-mtu
             min_mtu = '1400'
             self.set(['ppp-options', 'min-mtu', min_mtu])
 
             # mru
             mru = '9000'
             self.set(['ppp-options', 'mru', mru])
 
             # interface-cache
             interface_cache = '128000'
             self.set(['ppp-options', 'interface-cache', interface_cache])
 
             # ipv6
             allow_ipv6 = 'allow'
             allow_ipv4 = 'require'
             random = 'random'
             lcp_failure = '4'
             lcp_interval = '40'
             lcp_timeout = '100'
             self.set(['ppp-options', 'ipv4', allow_ipv4])
             self.set(['ppp-options', 'ipv6', allow_ipv6])
             self.set(['ppp-options', 'ipv6-interface-id', random])
             self.set(['ppp-options', 'ipv6-accept-peer-interface-id'])
             self.set(['ppp-options', 'ipv6-peer-interface-id', random])
             self.set(['ppp-options', 'lcp-echo-failure', lcp_failure])
             self.set(['ppp-options', 'lcp-echo-interval', lcp_interval])
             self.set(['ppp-options', 'lcp-echo-timeout', lcp_timeout])
             # commit changes
             self.cli_commit()
 
             # Validate configuration values
             conf = ConfigParser(allow_no_value=True, delimiters='=')
             conf.read(self._config_file)
 
             self.assertEqual(conf['chap-secrets']['gw-ip-address'], self._gateway)
 
             # check ppp
             self.assertEqual(conf['ppp']['mppe'], mppe)
             self.assertEqual(conf['ppp']['min-mtu'], min_mtu)
             self.assertEqual(conf['ppp']['mru'], mru)
 
             self.assertEqual(conf['ppp']['ccp'],'0')
 
             # check interface-cache
             self.assertEqual(conf['ppp']['unit-cache'], interface_cache)
 
             #check ipv6
             for tmp in ['ipv6pool', 'ipv6_nd', 'ipv6_dhcp']:
                 self.assertEqual(conf['modules'][tmp], None)
 
             self.assertEqual(conf['ppp']['ipv6'], allow_ipv6)
             self.assertEqual(conf['ppp']['ipv6-intf-id'], random)
             self.assertEqual(conf['ppp']['ipv6-peer-intf-id'], random)
             self.assertTrue(conf['ppp'].getboolean('ipv6-accept-peer-intf-id'))
             self.assertEqual(conf['ppp']['lcp-echo-failure'], lcp_failure)
             self.assertEqual(conf['ppp']['lcp-echo-interval'], lcp_interval)
             self.assertEqual(conf['ppp']['lcp-echo-timeout'], lcp_timeout)
 
 
         def test_accel_wins_server(self):
             self.basic_config()
             winsservers = ["192.0.2.1", "192.0.2.2"]
             for wins in winsservers:
                 self.set(["wins-server", wins])
             self.cli_commit()
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
             for ws in winsservers:
                 self.assertIn(ws, [conf["wins"]["wins1"], conf["wins"]["wins2"]])
 
         def test_accel_snmp(self):
             self.basic_config()
             self.set(['snmp', 'master-agent'])
             self.cli_commit()
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
             self.assertEqual(conf['modules']['net-snmp'], None)
             self.assertEqual(conf['snmp']['master'],'1')
 
         def test_accel_shaper(self):
             self.basic_config()
             fwmark = '2'
             self.set(['shaper', 'fwmark', fwmark])
             self.cli_commit()
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
             self.assertEqual(conf['modules']['shaper'], None)
             self.assertEqual(conf['shaper']['verbose'], '1')
             self.assertEqual(conf['shaper']['down-limiter'], 'tbf')
             self.assertEqual(conf['shaper']['fwmark'], fwmark)
 
         def test_accel_limits(self):
             self.basic_config()
             burst = '100'
             timeout = '20'
             limits = '1/min'
             self.set(['limits', 'connection-limit', limits])
             self.set(['limits', 'timeout', timeout])
             self.set(['limits', 'burst', burst])
             self.cli_commit()
             conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
             conf.read(self._config_file)
             self.assertEqual(conf['modules']['connlimit'], None)
             self.assertEqual(conf['connlimit']['limit'], limits)
             self.assertEqual(conf['connlimit']['burst'], burst)
             self.assertEqual(conf['connlimit']['timeout'], timeout)
 
         def test_accel_log_level(self):
             self.basic_config()
             self.cli_commit()
 
             # check default value
             conf = ConfigParser(allow_no_value=True)
             conf.read(self._config_file)
             self.assertEqual(conf['log']['level'], '3')
 
             for log_level in range(0, 5):
                 self.set(['log', 'level', str(log_level)])
                 self.cli_commit()
                 # Validate configuration values
                 conf = ConfigParser(allow_no_value=True)
                 conf.read(self._config_file)
 
                 self.assertEqual(conf['log']['level'], str(log_level))
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index a969626a9..ded370a7a 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -1,521 +1,521 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from decimal import Decimal
 from hashlib import sha256
 from ipaddress import ip_address
 from ipaddress import ip_network
 from json import dumps as json_write
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.configdict import dict_merge
 from vyos.configdict import node_changed
 from vyos.configdict import is_node_changed
 from vyos.configverify import verify_vrf
 from vyos.ifconfig import Interface
-from vyos.cpu import get_core_count
+from vyos.utils.cpu import get_core_count
 from vyos.utils.file import write_file
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.process import run
 from vyos.utils.network import interface_exists
 from vyos.template import bracketize_ipv6
 from vyos.template import inc_ip
 from vyos.template import is_ipv4
 from vyos.template import is_ipv6
 from vyos.template import render
 from vyos.xml_ref import default_value
 from vyos import ConfigError
 from vyos import airbag
 
 airbag.enable()
 
 config_containers = '/etc/containers/containers.conf'
 config_registry = '/etc/containers/registries.conf'
 config_storage = '/etc/containers/storage.conf'
 systemd_unit_path = '/run/systemd/system'
 
 
 def _cmd(command):
     if os.path.exists('/tmp/vyos.container.debug'):
         print(command)
     return cmd(command)
 
 
 def network_exists(name):
     # Check explicit name for network, returns True if network exists
     c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
     return bool(c)
 
 
 # Common functions
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     base = ['container']
     container = conf.get_config_dict(base, key_mangling=('-', '_'),
                                      no_tag_node_value_mangle=True,
                                      get_first_key=True,
                                      with_recursive_defaults=True)
 
     for name in container.get('name', []):
         # T5047: Any container related configuration changed? We only
         # wan't to restart the required containers and not all of them ...
         tmp = is_node_changed(conf, base + ['name', name])
         if tmp:
             if 'container_restart' not in container:
                 container['container_restart'] = [name]
             else:
                 container['container_restart'].append(name)
 
     # registry is a tagNode with default values - merge the list from
     # default_values['registry'] into the tagNode variables
     if 'registry' not in container:
         container.update({'registry': {}})
         default_values = default_value(base + ['registry'])
         for registry in default_values:
             tmp = {registry: {}}
             container['registry'] = dict_merge(tmp, container['registry'])
 
     # Delete container network, delete containers
     tmp = node_changed(conf, base + ['network'])
     if tmp: container.update({'network_remove': tmp})
 
     tmp = node_changed(conf, base + ['name'])
     if tmp: container.update({'container_remove': tmp})
 
     return container
 
 
 def verify(container):
     # bail out early - looks like removal from running config
     if not container:
         return None
 
     # Add new container
     if 'name' in container:
         for name, container_config in container['name'].items():
             # Container image is a mandatory option
             if 'image' not in container_config:
                 raise ConfigError(f'Container image for "{name}" is mandatory!')
 
             # Check if requested container image exists locally. If it does not
             # exist locally - inform the user. This is required as there is a
             # shared container image storage accross all VyOS images. A user can
             # delete a container image from the system, boot into another version
             # of VyOS and then it would fail to boot. This is to prevent any
             # configuration error when container images are deleted from the
             # global storage. A per image local storage would be a super waste
             # of diskspace as there will be a full copy (up tu several GB/image)
             # on upgrade. This is the "cheapest" and fastest solution in terms
             # of image upgrade and deletion.
             image = container_config['image']
             if run(f'podman image exists {image}') != 0:
                 Warning(f'Image "{image}" used in container "{name}" does not exist ' \
                         f'locally. Please use "add container image {image}" to add it ' \
                         f'to the system! Container "{name}" will not be started!')
 
             if 'cpu_quota' in container_config:
                 cores = get_core_count()
                 if Decimal(container_config['cpu_quota']) > cores:
                     raise ConfigError(f'Cannot set limit to more cores than available "{name}"!')
 
             if 'network' in container_config:
                 if len(container_config['network']) > 1:
                     raise ConfigError(f'Only one network can be specified for container "{name}"!')
 
                 # Check if the specified container network exists
                 network_name = list(container_config['network'])[0]
                 if network_name not in container.get('network', {}):
                     raise ConfigError(f'Container network "{network_name}" does not exist!')
 
                 if 'address' in container_config['network'][network_name]:
                     cnt_ipv4 = 0
                     cnt_ipv6 = 0
                     for address in container_config['network'][network_name]['address']:
                         network = None
                         if is_ipv4(address):
                             try:
                                 network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
                                 cnt_ipv4 += 1
                             except:
                                 raise ConfigError(f'Network "{network_name}" does not contain an IPv4 prefix!')
                         elif is_ipv6(address):
                             try:
                                 network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
                                 cnt_ipv6 += 1
                             except:
                                 raise ConfigError(f'Network "{network_name}" does not contain an IPv6 prefix!')
 
                         # Specified container IP address must belong to network prefix
                         if ip_address(address) not in ip_network(network):
                             raise ConfigError(f'Used container address "{address}" not in network "{network}"!')
 
                         # We can not use the first IP address of a network prefix as this is used by podman
                         if ip_address(address) == ip_network(network)[1]:
                             raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
                                               'reserved for the container engine!')
 
                     if cnt_ipv4 > 1 or cnt_ipv6 > 1:
                         raise ConfigError(f'Only one IP address per address family can be used for ' \
                                           f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
 
             if 'device' in container_config:
                 for dev, dev_config in container_config['device'].items():
                     if 'source' not in dev_config:
                         raise ConfigError(f'Device "{dev}" has no source path configured!')
 
                     if 'destination' not in dev_config:
                         raise ConfigError(f'Device "{dev}" has no destination path configured!')
 
                     source = dev_config['source']
                     if not os.path.exists(source):
                         raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
 
             if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
                 for var, cfg in container_config['sysctl']['parameter'].items():
                     if 'value' not in cfg:
                         raise ConfigError(f'sysctl parameter {var} has no value assigned!')
                     if var.startswith('net.') and 'allow_host_networks' in container_config:
                         raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!')
 
             if 'environment' in container_config:
                 for var, cfg in container_config['environment'].items():
                     if 'value' not in cfg:
                         raise ConfigError(f'Environment variable {var} has no value assigned!')
 
             if 'label' in container_config:
                 for var, cfg in container_config['label'].items():
                     if 'value' not in cfg:
                         raise ConfigError(f'Label variable {var} has no value assigned!')
 
             if 'volume' in container_config:
                 for volume, volume_config in container_config['volume'].items():
                     if 'source' not in volume_config:
                         raise ConfigError(f'Volume "{volume}" has no source path configured!')
 
                     if 'destination' not in volume_config:
                         raise ConfigError(f'Volume "{volume}" has no destination path configured!')
 
                     source = volume_config['source']
                     if not os.path.exists(source):
                         raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!')
 
             if 'port' in container_config:
                 for tmp in container_config['port']:
                     if not {'source', 'destination'} <= set(container_config['port'][tmp]):
                         raise ConfigError(f'Both "source" and "destination" must be specified for a port mapping!')
 
             # If 'allow-host-networks' or 'network' not set.
             if 'allow_host_networks' not in container_config and 'network' not in container_config:
                 raise ConfigError(f'Must either set "network" or "allow-host-networks" for container "{name}"!')
 
             # Can not set both allow-host-networks and network at the same time
             if {'allow_host_networks', 'network'} <= set(container_config):
                 raise ConfigError(
                     f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
 
             # gid cannot be set without uid
             if 'gid' in container_config and 'uid' not in container_config:
                 raise ConfigError(f'Cannot set "gid" without "uid" for container')
 
     # Add new network
     if 'network' in container:
         for network, network_config in container['network'].items():
             v4_prefix = 0
             v6_prefix = 0
             # If ipv4-prefix not defined for user-defined network
             if 'prefix' not in network_config:
                 raise ConfigError(f'prefix for network "{network}" must be defined!')
 
             for prefix in network_config['prefix']:
                 if is_ipv4(prefix):
                     v4_prefix += 1
                 elif is_ipv6(prefix):
                     v6_prefix += 1
 
             if v4_prefix > 1:
                 raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
             if v6_prefix > 1:
                 raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!')
 
             # Verify VRF exists
             verify_vrf(network_config)
 
     # A network attached to a container can not be deleted
     if {'network_remove', 'name'} <= set(container):
         for network in container['network_remove']:
             for c, c_config in container['name'].items():
                 if 'network' in c_config and network in c_config['network']:
                     raise ConfigError(f'Can not remove network "{network}", used by container "{c}"!')
 
     if 'registry' in container:
         for registry, registry_config in container['registry'].items():
             if 'authentication' not in registry_config:
                 continue
             if not {'username', 'password'} <= set(registry_config['authentication']):
                 raise ConfigError('Container registry requires both username and password to be set!')
 
     return None
 
 
 def generate_run_arguments(name, container_config):
     image = container_config['image']
     cpu_quota = container_config['cpu_quota']
     memory = container_config['memory']
     shared_memory = container_config['shared_memory']
     restart = container_config['restart']
 
     # Add sysctl options
     sysctl_opt = ''
     if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
         for k, v in container_config['sysctl']['parameter'].items():
             sysctl_opt += f" --sysctl {k}={v['value']}"
 
     # Add capability options. Should be in uppercase
     capabilities = ''
     if 'capability' in container_config:
         for cap in container_config['capability']:
             cap = cap.upper().replace('-', '_')
             capabilities += f' --cap-add={cap}'
 
     # Add a host device to the container /dev/x:/dev/x
     device = ''
     if 'device' in container_config:
         for dev, dev_config in container_config['device'].items():
             source_dev = dev_config['source']
             dest_dev = dev_config['destination']
             device += f' --device={source_dev}:{dest_dev}'
 
     # Check/set environment options "-e foo=bar"
     env_opt = ''
     if 'environment' in container_config:
         for k, v in container_config['environment'].items():
             env_opt += f" --env \"{k}={v['value']}\""
 
     # Check/set label options "--label foo=bar"
     label = ''
     if 'label' in container_config:
         for k, v in container_config['label'].items():
             label += f" --label \"{k}={v['value']}\""
 
     hostname = ''
     if 'host_name' in container_config:
         hostname = container_config['host_name']
         hostname = f'--hostname {hostname}'
 
     # Publish ports
     port = ''
     if 'port' in container_config:
         protocol = ''
         for portmap in container_config['port']:
             protocol = container_config['port'][portmap]['protocol']
             sport = container_config['port'][portmap]['source']
             dport = container_config['port'][portmap]['destination']
             listen_addresses = container_config['port'][portmap].get('listen_address', [])
 
             # If listen_addresses is not empty, include them in the publish command
             if listen_addresses:
                 for listen_address in listen_addresses:
                     port += f' --publish {bracketize_ipv6(listen_address)}:{sport}:{dport}/{protocol}'
             else:
                 # If listen_addresses is empty, just include the standard publish command
                 port += f' --publish {sport}:{dport}/{protocol}'
 
     # Set uid and gid
     uid = ''
     if 'uid' in container_config:
         uid = container_config['uid']
         if 'gid' in container_config:
             uid += ':' + container_config['gid']
         uid = f'--user {uid}'
 
     # Bind volume
     volume = ''
     if 'volume' in container_config:
         for vol, vol_config in container_config['volume'].items():
             svol = vol_config['source']
             dvol = vol_config['destination']
             mode = vol_config['mode']
             prop = vol_config['propagation']
             volume += f' --volume {svol}:{dvol}:{mode},{prop}'
 
     host_pid = ''
     if 'allow_host_pid' in container_config:
       host_pid = '--pid host'
 
     container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
                          f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
                          f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}'
 
     entrypoint = ''
     if 'entrypoint' in container_config:
         # it needs to be json-formatted with single quote on the outside
         entrypoint = json_write(container_config['entrypoint'].split()).replace('"', "&quot;")
         entrypoint = f'--entrypoint &apos;{entrypoint}&apos;'
 
     command = ''
     if 'command' in container_config:
         command = container_config['command'].strip()
 
     command_arguments = ''
     if 'arguments' in container_config:
         command_arguments = container_config['arguments'].strip()
 
     if 'allow_host_networks' in container_config:
         return f'{container_base_cmd} --net host {entrypoint} {image} {command} {command_arguments}'.strip()
 
     ip_param = ''
     networks = ",".join(container_config['network'])
     for network in container_config['network']:
         if 'address' not in container_config['network'][network]:
             continue
         for address in container_config['network'][network]['address']:
             if is_ipv6(address):
                 ip_param += f' --ip6 {address}'
             else:
                 ip_param += f' --ip {address}'
 
     return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
 
 
 def generate(container):
     # bail out early - looks like removal from running config
     if not container:
         for file in [config_containers, config_registry, config_storage]:
             if os.path.exists(file):
                 os.unlink(file)
         return None
 
     if 'network' in container:
         for network, network_config in container['network'].items():
             tmp = {
                 'name': network,
                 'id': sha256(f'{network}'.encode()).hexdigest(),
                 'driver': 'bridge',
                 'network_interface': f'pod-{network}',
                 'subnets': [],
                 'ipv6_enabled': False,
                 'internal': False,
                 'dns_enabled': True,
                 'ipam_options': {
                     'driver': 'host-local'
                 }
             }
             for prefix in network_config['prefix']:
                 net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
                 tmp['subnets'].append(net)
 
                 if is_ipv6(prefix):
                     tmp['ipv6_enabled'] = True
 
             write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2))
 
     render(config_containers, 'container/containers.conf.j2', container)
     render(config_registry, 'container/registries.conf.j2', container)
     render(config_storage, 'container/storage.conf.j2', container)
 
     if 'name' in container:
         for name, container_config in container['name'].items():
             if 'disable' in container_config:
                 continue
 
             file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
             run_args = generate_run_arguments(name, container_config)
             render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, },
                    formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
 
     return None
 
 
 def apply(container):
     # Delete old containers if needed. We can't delete running container
     # Option "--force" allows to delete containers with any status
     if 'container_remove' in container:
         for name in container['container_remove']:
             file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
             call(f'systemctl stop vyos-container-{name}.service')
             if os.path.exists(file_path):
                 os.unlink(file_path)
 
     call('systemctl daemon-reload')
 
     # Delete old networks if needed
     if 'network_remove' in container:
         for network in container['network_remove']:
             call(f'podman network rm {network} >/dev/null 2>&1')
 
     # Add container
     disabled_new = False
     if 'name' in container:
         for name, container_config in container['name'].items():
             image = container_config['image']
 
             if run(f'podman image exists {image}') != 0:
                 # container image does not exist locally - user already got
                 # informed by a WARNING in verfiy() - bail out early
                 continue
 
             if 'disable' in container_config:
                 # check if there is a container by that name running
                 tmp = _cmd('podman ps -a --format "{{.Names}}"')
                 if name in tmp:
                     file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
                     call(f'systemctl stop vyos-container-{name}.service')
                     if os.path.exists(file_path):
                         disabled_new = True
                         os.unlink(file_path)
                 continue
 
             if 'container_restart' in container and name in container['container_restart']:
                 cmd(f'systemctl restart vyos-container-{name}.service')
 
     if disabled_new:
         call('systemctl daemon-reload')
 
     # Start network and assign it to given VRF if requested. this can only be done
     # after the containers got started as the podman network interface will
     # only be enabled by the first container and yet I do not know how to enable
     # the network interface in advance
     if 'network' in container:
         for network, network_config in container['network'].items():
             network_name = f'pod-{network}'
             # T5147: Networks are started only as soon as there is a consumer.
             # If only a network is created in the first place, no need to assign
             # it to a VRF as there's no consumer, yet.
             if interface_exists(network_name):
                 tmp = Interface(network_name)
                 tmp.set_vrf(network_config.get('vrf', ''))
                 tmp.add_ipv6_eui64_address('fe80::/64')
 
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/op_mode/cpu.py b/src/op_mode/cpu.py
index d53663c17..1a0f7392f 100755
--- a/src/op_mode/cpu.py
+++ b/src/op_mode/cpu.py
@@ -1,82 +1,82 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2016-2022 VyOS maintainers and contributors
+# Copyright (C) 2016-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sys
 
-import vyos.cpu
 import vyos.opmode
+from vyos.utils.cpu import get_cpus
+from vyos.utils.cpu import get_core_count
 
 from jinja2 import Template
 
 cpu_template = Template("""
 {% for cpu in cpus %}
 {% if 'physical id' in cpu %}CPU socket: {{cpu['physical id']}}{% endif %}
 {% if 'vendor_id' in cpu %}CPU Vendor:       {{cpu['vendor_id']}}{% endif %}
 {% if 'model name' in cpu %}Model:            {{cpu['model name']}}{% endif %}
 {% if 'cpu cores' in cpu %}Cores:            {{cpu['cpu cores']}}{% endif %}
 {% if 'cpu MHz' in cpu %}Current MHz:      {{cpu['cpu MHz']}}{% endif %}
 {% endfor %}
 """)
 
 cpu_summary_template = Template("""
 Physical CPU cores: {{count}}
 CPU model(s): {{models | join(", ")}}
 """)
 
 def _get_raw_data():
-    return vyos.cpu.get_cpus()
+    return get_cpus()
 
 def _format_cpus(cpu_data):
     env = {'cpus': cpu_data}
     return cpu_template.render(env).strip()
 
 def _get_summary_data():
-    count = vyos.cpu.get_core_count()
-    cpu_data = vyos.cpu.get_cpus()
+    count = get_core_count()
+    cpu_data = get_cpus()
     models = [c['model name'] for c in cpu_data]
     env = {'count': count, "models": models}
 
     return env
 
 def _format_cpu_summary(summary_data):
     return cpu_summary_template.render(summary_data).strip()
 
 def show(raw: bool):
     cpu_data = _get_raw_data()
 
     if raw:
         return cpu_data
     else:
         return _format_cpus(cpu_data)
 
 def show_summary(raw: bool):
     cpu_summary_data = _get_summary_data()
 
     if raw:
         return cpu_summary_data
     else:
         return _format_cpu_summary(cpu_summary_data)
 
 
 if __name__ == '__main__':
     try:
         res = vyos.opmode.run(sys.modules[__name__])
         if res:
             print(res)
     except (ValueError, vyos.opmode.Error) as e:
         print(e)
         sys.exit(1)
-
diff --git a/src/op_mode/uptime.py b/src/op_mode/uptime.py
index 059a4c3f6..559eed24c 100755
--- a/src/op_mode/uptime.py
+++ b/src/op_mode/uptime.py
@@ -1,82 +1,82 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sys
 
 import vyos.opmode
 
 def _get_uptime_seconds():
   from re import search
   from vyos.utils.file import read_file
 
   data = read_file("/proc/uptime")
   seconds = search("([0-9\.]+)\s", data).group(1)
 
   return int(float(seconds))
 
 def _get_load_averages():
     from re import search
+    from vyos.utils.cpu import get_core_count
     from vyos.utils.process import cmd
-    from vyos.cpu import get_core_count
 
     data = cmd("uptime")
     matches = search(r"load average:\s*(?P<one>[0-9\.]+)\s*,\s*(?P<five>[0-9\.]+)\s*,\s*(?P<fifteen>[0-9\.]+)\s*", data)
 
     core_count = get_core_count()
 
     res = {}
     res[1]  = float(matches["one"]) / core_count
     res[5]  = float(matches["five"]) / core_count
     res[15] = float(matches["fifteen"]) / core_count
 
     return res
 
 def _get_raw_data():
     from vyos.utils.convert import seconds_to_human
 
     res = {}
     res["uptime_seconds"] = _get_uptime_seconds()
     res["uptime"] = seconds_to_human(_get_uptime_seconds(), separator=' ')
     res["load_average"] = _get_load_averages()
 
     return res
 
 def _get_formatted_output(data):
     out = "Uptime: {}\n\n".format(data["uptime"])
     avgs = data["load_average"]
     out += "Load averages:\n"
     out += "1  minute:   {:.01f}%\n".format(avgs[1]*100)
     out += "5  minutes:  {:.01f}%\n".format(avgs[5]*100)
     out += "15 minutes:  {:.01f}%\n".format(avgs[15]*100)
 
     return out
 
 def show(raw: bool):
     uptime_data = _get_raw_data()
 
     if raw:
         return uptime_data
     else:
         return _get_formatted_output(uptime_data)
 
 if __name__ == '__main__':
     try:
         res = vyos.opmode.run(sys.modules[__name__])
         if res:
             print(res)
     except (ValueError, vyos.opmode.Error) as e:
         print(e)
         sys.exit(1)