class Manager(object): INTF = "cmc_eth0" DHCP_SOCK_ADDRESS = 'ipc:///tmp/zmq-dhcp.ipc' TPC_SOCK_ADDRESS = 'ipc:///tmp/zmq-tpc.ipc' RCP_SOCK_ADDRESS = 'ipc:///tmp/zmq-rcp.ipc' HAL_SOCK_ADDRESS = 'ipc:///tmp/zmq-hal.ipc' EXAMPLE_PROC_SOCK_ADDRESS = 'ipc:///tmp/zmq-example.ipc' IF_UP_TIMEOUT = 60 # If process should respond in exponential backoff timer, this is safety # catch for cases, when something goes wrong BACKOFF_TIMEOUT = 600 dhcp_data_path = ['oper', 'DhcpData'] hw_version_path = ['oper', 'HwVersion'] hw_version = "OPENWRT v1" __metaclass__ = AddLoggerToClass def __init__(self): self.dhcpv4_process = None self.dhcpv6_process = None self.sfd_stream = None self.db = RPD_DB() self.db_adapter = CfgDbAdapter(self.db) self.dhcp_data = t_DhcpData() self.disp = Dispatcher() self.signal_mask = self.create_signal_mask() self.dhcp_sock = None self.dhcp_timer = None self.processes = {} ProcessInfo.dispatcher = self.disp self.processes['dhcpv6'] = ProcessInfo( [ "odhcp6c", # Args - start "-s", "/lib/netifd/dhcpv6.script", "-P", "0", # Request IPv6 Prefix = auto "-t", "256", # Random backoff <1, 256> "-v", # Verbose "-I", self.DHCP_SOCK_ADDRESS ], # Args - end self.DHCP_SOCK_ADDRESS, # IPC address self.BACKOFF_TIMEOUT, # Timeout in seconds self._dhcp_no_lease # Timeout callback ) self.processes['dhcpv4'] = ProcessInfo( [ "udhcpc", # Args - start "-p", # Create PID file "/var/run/udhcpc-" + self.INTF + ".pid", "-f", "-t", "8", # Random backoff <1, 256> "-i", self.INTF, "-C", # Don't send MAC address as client-id "-B", # Enable broadcast "-S", # Enable logging to syslog "-n", # Exit if lease is not obtained "-I", self.DHCP_SOCK_ADDRESS ], # Args - end self.DHCP_SOCK_ADDRESS, # IPC address self.BACKOFF_TIMEOUT, # Timeout in seconds self._dhcp_no_lease # Timeout callback ) self.processes['rcp'] = ProcessInfo( [ "python", # Args - start "-m", "rpd.rcp.rcp_process", "--ipc-address", self.RCP_SOCK_ADDRESS ], # Args - end self.RCP_SOCK_ADDRESS # IPC address ) confFile = '/etc/config/hal.conf' self.processes['hal'] = ProcessInfo( ("python -m rpd.hal.src.HalMain --conf=" + confFile).split(" "), "") self.processes['tpc'] = ProcessInfo( [ "python", # Args - start "-m", "rpd.tpc", "--ipc-address", self.TPC_SOCK_ADDRESS ], # Args - end self.TPC_SOCK_ADDRESS, # IPC address self.BACKOFF_TIMEOUT, # Timeout in seconds self.reboot # Timeout callback ) self.processes['example'] = ProcessInfo( [ "python", # Args - start "-m", "rpd.example", "--ipc-address", self.EXAMPLE_PROC_SOCK_ADDRESS ], # Args - end self.EXAMPLE_PROC_SOCK_ADDRESS, # IPC address 60, # Timeout in seconds self.reboot # Timeout callback ) self.processes['example'].start(self.example_msg_cb) # Source of subTLV codes - section 6.4.1 of # http://www.cablelabs.com/wp-content/uploads/specdocs/CM-SP-R-PHY-I01_150615.pdf rpd_ident = ['cfg', 'RpdCapabilities', 'RpdIdentification'] self.dhcp_args_mapping = { '0x02': rpd_ident + ['DeviceDescription'], '0x04': rpd_ident + ['SerialNumber'], '0x05': Manager.hw_version_path, '0x06': rpd_ident + ['CurrentSwVersion'], '0x07': rpd_ident + ['BootRomVersion'], '0x08': "".join(SysTools.get_mac_address( self.INTF).split(':')[0:3]), # vendor ID '0x09': rpd_ident + ['ModelNumber'], '0x0A': rpd_ident + ['VendorName'] } # Fill device information to DB, if not loaded mac_addr_str = rpd_ident + ['DeviceMacAddress'] if self.db_adapter.get_leaf(mac_addr_str) is None: # TODO negative case handling self.db_adapter.set_leaf(mac_addr_str, SysTools.get_mac_address(self.INTF), True) hostname_str = rpd_ident + ['DeviceAlias'] if self.db_adapter.get_leaf(hostname_str) is None: # TODO negative case handling self.db_adapter.set_leaf(hostname_str, SysTools.get_host_name(), True) # TODO get from HW if self.db_adapter.get_leaf(Manager.hw_version_path) is None: # TODO negative case handling self.db_adapter.set_leaf(Manager.hw_version_path, Manager.hw_version, True) self.fsm = Fysom({ 'initial': { 'state': 'init' }, 'events': [{ 'name': 'init_done', 'src': 'init', 'dst': 'if_up_waiting' }, { 'name': 'if_is_up', 'src': 'if_up_waiting', 'dst': 'dhcpv6_waiting' }, { 'name': 'dhcpv6_failed', 'src': 'dhcpv6_waiting', 'dst': 'dhcpv4_waiting' }, { 'name': 'dhcp_ack', 'src': [ 'dhcpv6_waiting', 'dhcpv4_waiting', 'time_waiting', 'log_waiting', 'gcp_started' ], 'dst': 'time_waiting' }, { 'name': 'time_cfged', 'src': 'time_waiting', 'dst': 'log_waiting' }, { 'name': 'log_done', 'src': 'log_waiting', 'dst': 'gcp_started' }, { 'name': 'fatal_failure', 'src': [ 'if_up_waiting', 'dhcpv6_waiting', 'dhcpv4_waiting', 'time_waiting', 'gcp_started' ], 'dst': 'reboot' }], 'callbacks': { 'onchangestate': self._onchangestate, 'onif_is_up': self._on_iface_is_up, 'ondhcp_ack': self._ondhcp_ack, 'ontime_cfged': self._ontime_cfged, 'onlog_done': self._onlog_done, 'onfatal_failure': self.reboot, } }) def delete_dhcp_data(self): """Delete DHCP data structure from DB and also clear cached copy of it. :return: """ # TODO negative case handling self.db_adapter.del_leaf(Manager.dhcp_data_path) def store_dhcp_data(self): """Save updated cached copy of DHCP data to DB. This must be called after each set operation to this cached structure (to keep it synchronized). :return: """ # TODO negative case handling - clear self.DhcpData self.db_adapter.set_leaf(Manager.dhcp_data_path, self.dhcp_data, True) def _dhcp_timeout_cb(self, _): """DHCP process haven't responded in limited time (backoff timer + extra time), so probably something wrong happened (DHCP process crashed, was killed, stuck in a loop, ...) :return: """ self.logger.warn("DHCP timer expired") self._dhcp_no_lease() def _dhcp_data_ready(self): """Received new DHCP data, do necessary cleanup if needed (start/update) (old GCP sessions must be closed). Keep remote logging enabled, so we don't loose syslog messages in case of DHCP update. :return: """ if not self.fsm.can('dhcp_ack'): raise ValueError("Wrong state '%s' for dhcp_ack event", self.fsm.current) self.fsm.dhcp_ack() def _dhcp_no_lease(self): """DHCP client failed to get required information (backoff timer increased to maximum value without success) - If DHCPv6 failed -> try DHCPv4 - If DHCPv4 failed -> reboot :return: """ dhcpv6_proc = self.processes['dhcpv6'] dhcpv4_proc = self.processes['dhcpv4'] if dhcpv6_proc.process is not None: # Kill DHCPv6 process if it still running, cleanup all related # stuff, but keep ipc_sock - it will be reused for dhcpv4 dhcpv6_proc.cleanup(close_ipc_sock=False) dhcpv6_proc.process = None if not self.fsm.can('dhcpv6_failed'): raise ValueError("Wrong state '%s' for dhcpv6_failed event", self.fsm.current) self.fsm.dhcpv6_failed() # Prepare "runtime" args args = [] for code, attr in self.dhcp_args_mapping.iteritems(): if isinstance(attr, basestring): attr_val = attr else: attr_val = self.db_adapter.get_leaf(attr) if attr_val is None or not isinstance(attr_val, basestring): self.logger.warning( "Attribute: %s not set in DB, ignoring ", attr) continue # append args in format: -x 0x0A:value args.extend(['-c', '{}:{}'.format(code, attr_val)]) self.logger.info("Starting DHCPv4 client ...") dhcpv4_proc.start(self.dhcp_msg_cb, args, dhcpv6_proc.ipc_sock) elif dhcpv4_proc.process is not None: dhcpv4_proc.cleanup() dhcpv4_proc.process = None self.logger.error("Both DHCPv6 & DHCPv4 failed - exiting ...") self.fsm.fatal_failure() else: raise ValueError("Received unexpected DHCP failed message") def _cleanup(self): """Cleanup method, this should be used only in scenario, when Manager is killed directly. :return: """ for process in self.processes.values(): process.cleanup() if self.sfd_stream is not None: self.sfd_stream.close() self.configure_remote_logging(None) exit(0) def testing_cleanup(self): """Cleanup method for testing purposes.""" for process in self.processes.values(): process.cleanup() if self.sfd_stream is not None: self.sfd_stream.close() # let the cleanup finish time.sleep(2) def fd_event_handler(self, signal_fd, eventmask): """Callback called by dispatcher when any signal is received on signalfd Reads signal info from fd and calls applicable signal handler. :return: """ del eventmask siginfo = signalfd_siginfo() if self.sfd_stream is None: try: self.sfd_stream = os.fdopen(signal_fd, 'rb', 0) except IOError, ex: self.logger.error("Failed to open signal fd - %s", os.strerror(ex.errno)) self._cleanup() return self.sfd_stream.readinto(siginfo) self._signal_handler(siginfo.ssi_signo, None)