예제 #1
0
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)