/* * Copyright (c) 2013, 2016 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 * */ /** * @file * Wind River CGTS Platform Guest Heartbeat Server Daemon on Compute */ #include #include #include #include #include #include #include #include #include #include #include /* for hostent */ #include #include #include #include #include #include /* for close and usleep */ #include /* for realtime scheduling api */ using namespace std; #include "nodeBase.h" #include "daemon_ini.h" /* Ini Parser Header */ #include "daemon_common.h" /* Common definitions and types for daemons */ #include "daemon_option.h" /* Common options for daemons */ #include "nodeUtil.h" /* for ... common utilities */ #include "jsonUtil.h" /* for ... jason utilities */ #include "nodeTimers.h" /* for ... maintenance timers */ #include "nodeMacro.h" /* for ... CREATE_NONBLOCK_INET_UDP_RX_SOCKET */ #include "nodeEvent.h" /* for ... set_inotify_watch, set_inotify_close */ #include "guestBase.h" #include "guestUtil.h" /* for ... guestUtil_inst_init */ #include "guestSvrUtil.h" /* for ... guestUtil_inotify_events */ #include "guestVirtio.h" /* for ... virtio_channel_connect */ #include "guestSvrMsg.h" /* for ... send_to_guestAgent */ #include "guestInstClass.h" /* Where to send events */ string guestAgent_ip = "" ; /***************************************************************************** * * The daemon primary instance racking object. * * This object is a dynamically managed linked list of tracked insances * * @see guestInstClass Module control structure in guestInstClass.h * *****************************************************************************/ guestInstClass instInv ; guestInstClass * get_instInv_ptr ( void ) { return(&instInv); } /* @see guestBase.h Module control structure * TODO: Consider obsoleting by moving into class */ ctrl_type ctrl ; ctrl_type * get_ctrl_ptr ( void ) { return(&ctrl); } void daemon_sigchld_hdlr ( void ) { ; /* dlog("Received SIGCHLD ... no action\n"); */ } /** * Daemon Configuration Structure - The allocated struct * @see daemon_common.h for daemon_config_type struct format. */ static daemon_config_type guest_config ; daemon_config_type * daemon_get_cfg_ptr () { return &guest_config ; } /* Cleanup exit handler */ void daemon_exit ( void ) { daemon_dump_info (); daemon_files_fini (); /* Close the messaging sockets */ if ( ctrl.sock.server_rx_sock ) delete (ctrl.sock.server_rx_sock); if ( ctrl.sock.server_tx_sock ) delete (ctrl.sock.server_tx_sock); if ( ctrl.sock.agent_rx_float_sock ) delete (ctrl.sock.agent_rx_float_sock); if ( ctrl.sock.agent_tx_sock ) delete (ctrl.sock.agent_tx_sock); /* Turn off inotify */ set_inotify_close ( ctrl.inotify_dir_fd, ctrl.inotify_dir_wd ); instInv.free_instance_resources (); fflush (stdout); fflush (stderr); exit (0); } /** Client Config mask */ #define CONFIG_MASK (CONFIG_CLIENT_RX_PORT |\ CONFIG_AGENT_RX_PORT) /* Startup config read */ static int _config_handler ( void * user, const char * section, const char * name, const char * value) { daemon_config_type* config_ptr = (daemon_config_type*)user; if (MATCH("agent", "rx_port")) { config_ptr->agent_rx_port = atoi(value); config_ptr->mask |= CONFIG_AGENT_RX_PORT ; } else if (MATCH("client", "rx_port")) { config_ptr->client_rx_port = atoi(value); config_ptr->mask |= CONFIG_CLIENT_RX_PORT ; } else if (MATCH("client", "hbs_pulse_period")) { config_ptr->hbs_pulse_period = atoi(value); } else if (MATCH("client", "hbs_failure_threshold")) { config_ptr->hbs_failure_threshold = atoi(value); } #ifdef WANT_REPORT_DELAY else if (MATCH("timeouts", "start_delay")) { config_ptr->start_delay = atoi(value); } #endif else { return (PASS); } return (FAIL); } /* Read the guest.ini file and load agent */ /* settings into the daemon configuration */ int daemon_configure ( void ) { int rc = FAIL ; /* Read the ini */ char config_fn[100] ; guest_config.mask = 0 ; sprintf ( &config_fn[0], "/etc/mtc/%s.ini", program_invocation_short_name ); if (ini_parse(config_fn, _config_handler, &guest_config) < 0) { elog("Can't load '%s'\n", config_fn ); return (FAIL_LOAD_INI); } get_debug_options ( config_fn, &guest_config ); /* Verify loaded config against an expected mask * as an ini file fault detection method */ if ( guest_config.mask != CONFIG_MASK ) { elog ("Configuration load failed (%x)\n", (( -1 ^ guest_config.mask ) & CONFIG_MASK) ); rc = FAIL_INI_CONFIG ; } else { guest_config.mgmnt_iface = daemon_get_iface_master ( guest_config.mgmnt_iface ); ilog("Guest Agent : %s:%d\n", guest_config.mgmnt_iface, guest_config.client_rx_port ); // get_iface_macaddr ( guest_config.mgmnt_iface, my_macaddr ); get_iface_address ( guest_config.mgmnt_iface, ctrl.address, true ); get_hostname ( &ctrl.hostname[0], MAX_HOST_NAME_SIZE ); ilog("Report Thres: %d\n", guest_config.hbs_failure_threshold ); #ifdef WANT_REPORT_DELAY ilog("Report Delay: %d sec\n", guest_config.start_delay ); #endif ilog("Deflt Period: %d msec\n", guest_config.hbs_pulse_period ); rc = PASS ; } return (rc); } /****************************/ /* Initialization Utilities */ /****************************/ /* Setup UDP messaging to the guestAgent. */ int _socket_init ( void ) { int rc = PASS ; guestAgent_ip = getipbyname ( CONTROLLER ); ilog ("ControllerIP: %s\n", guestAgent_ip.c_str()); /* Read the ports the socket struct */ ctrl.sock.agent_rx_port = guest_config.agent_rx_port ; ctrl.sock.server_rx_port = guest_config.client_rx_port ; /****************************/ /* Setup the Receive Socket */ /****************************/ ctrl.sock.server_rx_sock = new msgClassRx(ctrl.address.c_str(), guest_config.client_rx_port, IPPROTO_UDP); rc = ctrl.sock.server_rx_sock->return_status; if ( rc ) { elog ("Failed to setup 'guestAgent' receiver on port %d\n", ctrl.sock.server_rx_port ); return (rc) ; } ctrl.sock.server_tx_sock = new msgClassTx(guestAgent_ip.c_str(), guest_config.agent_rx_port, IPPROTO_UDP, guest_config.mgmnt_iface); rc = ctrl.sock.server_tx_sock->return_status; if ( rc ) { elog ("Failed to setup 'guestServer' transmiter\n" ); return (rc) ; } return (rc); } /* The main heartbeat service loop */ int daemon_init ( string iface, string nodeType_str ) { int rc = PASS ; ctrl.address.clear() ; ctrl.address_peer.clear(); ctrl.nodetype = CGTS_NODE_NULL ; /* Init the Inotify descriptors */ ctrl.inotify_dir_fd = 0 ; ctrl.inotify_dir_wd = 0 ; /* clear hostname */ memset ( &ctrl.hostname[0], 0, MAX_HOST_NAME_SIZE ); /* Initialize socket construct and pointer to it */ memset ( &ctrl.sock, 0, sizeof(ctrl.sock)); /* Assign interface to config */ guest_config.mgmnt_iface = (char*)iface.data() ; if ( (rc = daemon_files_init ( )) != PASS ) { elog ("Pid, log or other files could not be opened (rc:%d)\n", rc ); rc = FAIL_FILES_INIT ; } /* convert node type to integer */ ctrl.nodetype = get_host_function_mask ( nodeType_str ) ; ilog ("Node Type : %s (%d)\n", nodeType_str.c_str(), ctrl.nodetype ); /* Bind signal handlers */ if ( daemon_signal_init () != PASS ) { elog ("daemon_signal_init failed\n"); return ( FAIL_SIGNAL_INIT ); } /************************************************************************ * There is no point continuing with init ; i.e. running daemon_configure, * initializing sockets and trying to query for an ip address until the * daemon's configuration requirements are met. Here we wait for those * flag files to be present before continuing. ************************************************************************ * Wait for /etc/platform/.initial_config_complete & /var/run/.goenabled */ daemon_wait_for_file ( CONFIG_COMPLETE_FILE , 0); daemon_wait_for_file ( GOENABLED_MAIN_READY , 0); /* Configure the client */ if ( (rc = daemon_configure ()) != PASS ) { elog ("Daemon service configuration failed (rc:%d)\n", rc ); rc = FAIL_DAEMON_CONFIG ; } /* Setup the heartbeat service messaging sockets */ else if ( (rc = _socket_init ( )) != PASS ) { elog ("socket initialization failed (rc:%d)\n", rc ); rc = FAIL_SOCKET_INIT; } /* Ignore this signal */ signal(SIGPIPE, SIG_IGN); return (rc); } /* { hostname" : "" , "instances" : [ { "channel" : "" , "services" : [ { "service":"heartbeat", "admin":"enabled", "oper":"enabled" , "avail":"available" } ], "channel: : "" , "services" : [ { "service":"heartbeat", "admin":"enabled", "oper":"enabled" , "avail":"available"} ] } ] } */ int select_failure_count = 0 ; void guestInstClass::manage_comm_loss ( void ) { int rc ; std::list socks ; socks.clear(); waitd.tv_sec = 0; waitd.tv_usec = GUEST_SOCKET_TO; /* Initialize the master fd_set */ FD_ZERO(&inotify_readfds); /* check for empty list condition */ if ( inst_head ) { for ( struct inst * inst_ptr = inst_head ; inst_ptr != NULL ; inst_ptr = inst_ptr->next ) { if ( inst_ptr->instance.inotify_file_fd ) { //ilog ("adding inotify_fd %d for %s to select list\n", // inst_ptr->instance.inotify_file_fd, // inst_ptr->instance.uuid.c_str()); socks.push_front ( inst_ptr->instance.inotify_file_fd ); FD_SET ( inst_ptr->instance.inotify_file_fd, &inotify_readfds); } if (( inst_ptr->next == NULL ) || ( inst_ptr == inst_tail )) break ; } /* if there are no sockets to monitor then just exit */ if ( socks.empty() ) return ; /* Call select() and wait only up to SOCKET_WAIT */ socks.sort(); rc = select( socks.back()+1, &inotify_readfds, NULL, NULL, &waitd); if (( rc < 0 ) || ( rc == 0 ) || ( rc > (int)socks.size())) { /* Check to see if the select call failed. */ /* ... but filter Interrupt signal */ if (( rc < 0 ) && ( errno != EINTR )) { wlog_throttled ( select_failure_count, 20, "socket select failed (%d:%m)\n", errno); } else if ( rc > (int)socks.size()) { wlog_throttled ( select_failure_count, 100, "Select return exceeds current file descriptors (%ld:%d)\n", socks.size(), rc ); } else { select_failure_count = 0 ; } } else { wlog ( "inotify channel event\n"); for ( struct inst * inst_ptr = inst_head ; inst_ptr != NULL ; inst_ptr = inst_ptr->next ) { if ( inst_ptr->instance.inotify_file_fd ) { if (FD_ISSET(inst_ptr->instance.inotify_file_fd, &inotify_readfds) ) { ilog ("Watch Event on instance %s\n", inst_ptr->instance.uuid.c_str()); guestUtil_inotify_events (inst_ptr->instance.inotify_file_fd); } } if (( inst_ptr->next == NULL ) || ( inst_ptr == inst_tail )) break ; } } } } #define MAX_LEN 300 void daemon_service_run ( void ) { int rc = 0 ; int count = 0 ; int flush_thld = 0 ; string payload = "" ; /* for the ready event */ std::list socks ; guestUtil_load_channels (); /* Setup inotify to watch for new instance serial IO channel creations */ if ( set_inotify_watch ( QEMU_CHANNEL_DIR, ctrl.inotify_dir_fd, ctrl.inotify_dir_wd ) ) { elog ("failed to setup inotify on %s\n", QEMU_CHANNEL_DIR ); } socks.clear(); socks.push_front (ctrl.sock.server_rx_sock->getFD()); if ( ctrl.inotify_dir_fd ) socks.push_front (ctrl.inotify_dir_fd); else { elog ("unable to inotify monitor %s\n", QEMU_CHANNEL_DIR ); // TODO: consider exiting daemon } socks.sort(); mtcTimer_init ( ctrl.timer, ctrl.hostname ); mtcTimer_init ( instInv.search_timer, ctrl.hostname ); mtcTimer_start ( ctrl.timer , guestTimer_handler, 2 ); mtcTimer_start ( instInv.search_timer, guestTimer_handler, SEARCH_AUDIT_TIME ); ilog ("Selects: guestAgent:%d qemuDir:%d\n", ctrl.sock.server_rx_sock->getFD(), ctrl.inotify_dir_fd ); ilog ("-------------------------------------------------------\n"); /* Tell the guestAgent that we started or restarted * so that it can send instance state data */ payload = "{\"hostname\":\"" ; payload.append(ctrl.hostname); payload.append("\"}"); /* Run heartbeat service forever or until stop condition */ for ( ; ; ) { instInv.waitd.tv_sec = 0; instInv.waitd.tv_usec = GUEST_SOCKET_TO; /* Initialize the master fd_set */ FD_ZERO(&instInv.message_readfds); FD_SET ( ctrl.sock.server_rx_sock->getFD(), &instInv.message_readfds); if ( ctrl.inotify_dir_fd ) { FD_SET ( ctrl.inotify_dir_fd, &instInv.message_readfds); } rc = select( socks.back()+1, &instInv.message_readfds, NULL, NULL, &instInv.waitd); if (( rc < 0 ) || ( rc == 0 ) || ( rc > (int)socks.size())) { /* Check to see if the select call failed. */ /* ... but filter Interrupt signal */ if (( rc < 0 ) && ( errno != EINTR )) { wlog_throttled ( count, 20, "socket select failed (%d:%m)\n", errno); } else if ( rc > (int)socks.size()) { wlog_throttled ( count, 100, "Select return exceeds current file descriptors (%ld:%d)\n", socks.size(), rc ); } else { count = 0 ; } } else if (FD_ISSET(ctrl.sock.server_rx_sock->getFD(), &instInv.message_readfds)) { /* clean the rx/tx buffer */ mtc_message_type msg ; memset ((void*)&msg,0,sizeof(mtc_message_type)); int bytes = ctrl.sock.server_rx_sock->read((char*)&msg.hdr[0], sizeof(mtc_message_type)); ctrl.address_peer = ctrl.sock.server_rx_sock->get_src_str() ; mlog1 ("Received %d bytes from %s:%d:guestAgent\n", bytes, ctrl.sock.server_rx_sock->get_src_str(), ctrl.sock.server_rx_sock->get_dst_addr()->getPort() ); print_mtc_message (&msg); if ( bytes > 0 ) { recv_from_guestAgent ( msg.cmd, &msg.buf[0] ); } } else if (FD_ISSET(ctrl.inotify_dir_fd, &instInv.message_readfds)) { dlog ("%s dir change\n", QEMU_CHANNEL_DIR ); guestUtil_inotify_events (ctrl.inotify_dir_fd); } fflush (stdout); fflush (stderr); instInv.guest_fsm_run ( ); if ( ctrl.timer.ring == true ) { /* restart the timer and try again if this call returns a RETRY */ if ( send_to_guestAgent ( MTC_EVENT_MONITOR_READY, payload.data()) == RETRY ) { mtcTimer_start ( ctrl.timer, guestTimer_handler, 5 ); } ctrl.timer.ring = false ; } daemon_signal_hdlr (); /* Support the log flush config option */ if ( guest_config.flush ) { if ( ++flush_thld > guest_config.flush_thld ) { flush_thld = 0 ; fflush (stdout); fflush (stderr); } } } daemon_exit (); } /* Write the daemon /var/log/.dump */ void daemon_dump_info ( void ) { daemon_dump_membuf_banner (); instInv.print_node_info (); instInv.memDumpAllState (); daemon_dump_membuf(); } const char MY_DATA [100] = { "eieio\n" } ; const char * daemon_stream_info ( void ) { return (&MY_DATA[0]); } /*************************************************************************** * * * Module Test Head * * * ***************************************************************************/ /** Teat Head Entry */ int daemon_run_testhead ( void ) { int rc = PASS; return (rc); }