/* * Copyright (c) 2013, 2016 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 * */ /** * @file * Wind River CGTS Platform Controller Maintenance Daemon */ #ifdef __AREA__ #undef __AREA__ #endif #define __AREA__ "svr" #include #include #include #include #include #include #include #include /* for hostent */ #include #include #include #include #include #include #include #include /* for ... close and usleep */ #include /* for ... HTTP_ status definitions */ #include /* for ... RTMGRP_LINK */ using namespace std; #include "daemon_common.h" /* */ #include "nodeBase.h" /* Service header */ #include "nodeTimers.h" /* */ #include "nodeClass.h" /* */ #include "nodeUtil.h" /* */ #include "jsonUtil.h" /* */ #include "mtcHttpSvr.h" #include "mtcNodeMsg.h" /* for ... send_mtc_cmd */ #include "mtcAlarm.h" /* for ... mtcAlarm_log */ #define EVENT_SERVER "HTTP Event Server" #define CLIENT_SYSINV_URL "/v1/hosts/" #define CLIENT_VIM_HOSTS_URL "/v1/hosts/" #define CLIENT_SM_URL "/v1/hosts/" #define CLIENT_VIM_SYSTEMS_URL "/v1/systems" #define CLIENT_HEADER "User-Agent" #define CLIENT_SYSINV_1_0 "sysinv/1.0" #define CLIENT_VIM_1_0 "vim/1.0" #define CLIENT_SM_1_0 "sm/1.0" #define EVENT_METHODS (EVHTTP_REQ_PATCH | \ EVHTTP_REQ_POST | \ EVHTTP_REQ_GET | \ EVHTTP_REQ_PUT | \ EVHTTP_REQ_DELETE) int sequence = 0 ; char log_str [MAX_API_LOG_LEN]; char filename[MAX_FILENAME_LEN]; /* Cleanup */ void mtcHttpSvr_fini ( event_type & mtce_event ) { if ( mtce_event.fd ) { if ( mtce_event.base ) { event_base_free( mtce_event.base); } close ( mtce_event.fd ); mtce_event.fd = 0 ; } } /************************************************************************************ * * event_base_loopcontinue is not supported until version 2.1.2-alpha * It allows processing of events in main loop instead of in the handler. * Theoretically this would be nice to use in conjunction with * event_base_loopexit in the selected fd void mtcHttpSvr_work ( event_type & mtce_event ) { if ( mtce_event.base ) { int rc = event_base_loopcontinue ( mtce_event.base ) ; // EVLOOP_NONBLOCK ); if ( rc ) { ilog ("HTTP event_base_loopcontinue rc:%d\n", rc ); } } } **************************************************************************************/ /* Look for events */ void mtcHttpSvr_look ( event_type & mtce_event ) { /* Look for Events */ if ( mtce_event.base ) { // rc = event_base_loopexit( mtce_event.base, NULL ) ; // EVLOOP_NONBLOCK ); event_base_loop( mtce_event.base, EVLOOP_NONBLOCK ); } } void _create_error_response ( int rc , string & resp_buffer , node_inv_type & inv ) { resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); if ( rc == FAIL_UNIT_ACTIVE ) { resp_buffer.append (",\"reason\" : \"Controller is Active\""); resp_buffer.append (",\"action\" : \"Swact Controller and then Lock\""); } else if ( rc == FAIL_LOW_STORAGE ) { resp_buffer.append (",\"reason\" : \"Storage redundancy check\""); resp_buffer.append (",\"action\" : \"Enable another storage host\""); } else if ( rc == FAIL_PATCH_INPROGRESS ) { resp_buffer.append (",\"reason\" : \"Operation not permitted while software patching is 'In-Progress'\""); resp_buffer.append (",\"action\" : \"Wait for patching to complete and then retry operation\""); } else if ( rc == FAIL_PATCHED_NOREBOOT ) { resp_buffer.append (",\"reason\" : \"Patches have been applied but not loaded on target host'\""); resp_buffer.append (",\"action\" : \"Please 'lock' then 'unlock' host and retry operation\""); } else if ( rc == FAIL_NEED_STORAGE_MON ) { resp_buffer.append (",\"reason\" : \"Failed Backend Monitor Quorum check\""); resp_buffer.append (",\"action\" : \"Enable second controller or additional storage host\""); } else if ( rc == FAIL_NEED_DUPLEX ) { resp_buffer.append (",\"reason\" : \"Controller redundancy check\""); resp_buffer.append (",\"action\" : \"Enable second controller\""); } else if ( rc == FAIL_DEL_UNLOCKED ) { resp_buffer.append (",\"reason\" : \"Host is Unlocked\""); resp_buffer.append (",\"action\" : \"Lock Host and then Delete\""); } else if ( rc == FAIL_ADMIN_ACTION ) { resp_buffer.append (",\"reason\" : \"Unknown admin action\""); resp_buffer.append (",\"action\" : \"Check admin action\""); } else if ( rc == FAIL_NODETYPE ) { resp_buffer.append (",\"reason\" : \"Swact not supported for this Host\""); resp_buffer.append (",\"action\" : \"Re-evaluate selected operation\""); } else if ( rc == FAIL_SWACT_NOINSVMATE ) { resp_buffer.append (",\"reason\" : \"No unlocked-enabled controller available to switch activity to\""); resp_buffer.append (",\"action\" : \"Enable second controller and then retry\""); } else if ( rc == FAIL_OPER_INPROGRESS ) { resp_buffer.append (",\"reason\" : \"User operation on this host already in-progress\""); resp_buffer.append (",\"action\" : \"Wait a moment and then retry\""); } else if ( rc == FAIL_SWACT_INPROGRESS ) { resp_buffer.append (",\"reason\" : \"Swact operation on this host already in-progress\""); resp_buffer.append (",\"action\" : \"Wait for current operation to complete and then retry\""); } else if ( rc == FAIL_JSON_PARSE ) { resp_buffer.append (",\"reason\" : \"Mtce cannot parse key:values from Inventory request\""); resp_buffer.append (",\"action\" : \"Retry operation or contact next level support\""); } else if ( rc == FAIL_RESET_POWEROFF ) { resp_buffer.append (",\"reason\" : \"Cannot reset a powered off host\""); resp_buffer.append (",\"action\" : \"Power-on host and then retry\""); } else if ( rc == FAIL_NO_IP_SUPPORT ) { resp_buffer.append (",\"reason\" : \"Warning: The board management IP address is not provisioned or learned.\""); resp_buffer.append (",\"action\" : \"Make sure the board management controller is powered on, connected to the "); resp_buffer.append ("board management network and the provisioned MAC address is correct. Board management actions "); resp_buffer.append ("such as 'reset' or 'power-on' or 'power-off' will not work until the "); resp_buffer.append ("the host's board management IP is learned.\""); } else if ( rc == FAIL_DUP_HOSTNAME ) { resp_buffer.append (",\"reason\" : \"Rejecting host-edit with duplicate hostname\""); resp_buffer.append (",\"action\" : \"Delete host with hostname '"); resp_buffer.append (inv.name.data()); resp_buffer.append ("' first and then retry or use different hostname\""); } else if ( rc == FAIL_DUP_IPADDR ) { resp_buffer.append (",\"reason\" : \"Rejecting host-edit with duplicate ip address\""); resp_buffer.append (",\"action\" : \"Delete host with ip address '"); resp_buffer.append (inv.ip.data()); resp_buffer.append ("' first and then retry or contact system administrator\""); } else if ( rc == FAIL_DUP_MACADDR ) { resp_buffer.append (",\"reason\" : \"Rejecting host-edit with duplicate mac address\""); resp_buffer.append (",\"action\" : \"Delete host with mac address '"); resp_buffer.append (inv.mac.data()); resp_buffer.append ("' first and then retry or contact system administrator\""); } else if (( rc == FAIL_POWER_CONTROL ) || ( rc == FAIL_RESET_CONTROL )) { resp_buffer.append (",\"reason\" : \"The board management controller for this host is not configured\""); resp_buffer.append (",\"action\" : \"Edit host to configure board management and then retry\""); } else if (( rc == FAIL_RESERVED_NAME ) && ((( !inv.name.compare ("controller-0")) && ( inv.type.compare("controller"))) || (( !inv.name.compare ("controller-1")) && ( inv.type.compare("controller"))))) { resp_buffer.append (",\"reason\" : \"Can only add reserved '"); resp_buffer.append (inv.name.data()); resp_buffer.append ("' hostname with personality set to 'controller'\""); resp_buffer.append (",\"action\" : \"Retry operation with personality set to 'controller'\""); } else if (( rc == FAIL_RESERVED_NAME ) && (( !inv.name.compare ("storage-0")) && ( inv.type.compare("storage")))) { resp_buffer.append (",\"reason\" : \"Can only add reserved '"); resp_buffer.append (inv.name.data()); resp_buffer.append ("' hostname with personality set to 'storage'\""); resp_buffer.append (",\"action\" : \"Retry operation with personality set to 'storage'\""); } else if ( rc == FAIL_NOT_ACCESSIBLE ) { resp_buffer.append (",\"reason\" : \"Maintenance has not yet established communication with the board management controller for this host\""); resp_buffer.append (",\"action\" : \"Verify board management configuration settings and then retry\""); } else if ( rc == FAIL_NOT_CONNECTED ) { resp_buffer.append (",\"reason\" : \"Maintenance does not have an established connection to the board management controller for this host\""); resp_buffer.append (",\"action\" : \"Verify board management configuration settings and then retry. "); resp_buffer.append ("Note: Maintenance is continuously trying to maintain an established connection using the supplied provisioning and credentials\""); } else if ( rc == FAIL_BM_PROVISION_ERR ) { resp_buffer.append (",\"reason\" : \"Request rejected due to provisioning semantic check. \""); resp_buffer.append (",\"action\" : \"Please verify that the board management MAC or IP address being used is "); resp_buffer.append ("formatted correctly or not already provisioned against another host\""); } else { resp_buffer.append (",\"reason\" : \"Unknown\""); resp_buffer.append (",\"action\" : \"Undetermined\""); wlog ("%s no supported reason/action string for error code %d\n", inv.name.c_str(), rc); } resp_buffer.append ("}"); } /***************************************************************************** * * Name: mtcHttpSvr_vim_req * * Handles three 'operations' * * 'delete' - based on uuid * 'modify' - based on list of key - value pairs * 'add' - based on inventory record * ******************************************************************************/ /* Test Commands: * * Test 1: Select host, get uuid and make sure it is unlocked-enabled. * Verify: Host should fail, reset and auto re-enable. curl -i -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2112/v1/hosts/8b216803-c47c-40b3-bf61-ed84ff83754e -d '{"uuid":"8b216803-c47c-40b3-bf61-ed84ff83754e", "hostname": "compute-1", "severity": "failed"}' * Test 2: Lock Host and issue command with correct uuids and hostname. * Verify: The host is rebooted/reset curl -i -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2112/v1/hosts/8b216803-c47c-40b3-bf61-ed84ff83754e -d '{"uuid":"8b216803-c47c-40b3-bf61-ed84ff83754e", "hostname": "compute-1", "severity": "failed"}' * Test 3: curl -i -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2112/v1/hosts/8b216803-c47c-40b3-bf61-ed84ff83754e -d '{"uuid":"8b216803-c47c-40b3-bf61-ed84ff83754e", "hostname": "compute-1", "severity": "degraded"}' * Test 4: curl -i -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2112/v1/hosts/8b216803-c47c-40b3-bf61-ed84ff83754e -d '{"uuid":"8b216803-c47c-40b3-bf61-ed84ff83754e", "hostname": "compute-1", "severity": "cleared"}' * Test 5: Unsuppored VIM Command curl -i -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2112/v1/hosts/8b216803-c47c-40b3-bf61-ed84ff83754e -d '{"uuid":"8b216803-c47c-40b3-bf61-ed84ff83754e", "hostname": "compute-1", "severity": "degradeded"}' */ string mtcHttpSvr_vim_req ( char * buffer_ptr, evhttp_cmd_type http_cmd, int & http_status_code ) { nodeLinkClass * obj_ptr = get_mtcInv_ptr () ; string response = "" ; string severity = "" ; string uuid = "" ; string hostname = "" ; int rc1 = jsonUtil_get_key_val ( buffer_ptr, MTC_JSON_SEVERITY, severity ); int rc2 = jsonUtil_get_key_val ( buffer_ptr, MTC_JSON_INV_NAME, uuid ); int rc3 = jsonUtil_get_key_val ( buffer_ptr, MTC_JSON_INV_NAME, hostname ); jlog ("%s '%s' request\n", hostname.c_str(), getHttpCmdType_str(http_cmd)); if ( rc1 | rc2 | rc3 ) { wlog ("Failed to parse command key values (%d:%d:%d)\n", rc1, rc2, rc3 ); response = "{" ; response.append (" \"status\" : \"fail\""); response.append (",\"reason\" : \"command parse error\""); response.append (",\"action\" : \"retry command or contact next level support\""); response.append ("}"); http_status_code = HTTP_BADREQUEST ; } else { if ( ! severity.compare("failed" )) { if ( obj_ptr->get_adminState ( hostname ) == MTC_ADMIN_STATE__LOCKED ) { /* Test 2 */ ilog ("%s reboot/reset due to failed event (host is locked)\n", hostname.c_str()); obj_ptr->set_rebootStage ( hostname , MTC_RESETPROG__START ); obj_ptr->set_adminAction ( hostname , MTC_ADMIN_ACTION__REBOOT ); } else { /* Test 1 */ ilog ("%s is now failed due to failed event (host is unlocked)\n", hostname.c_str()); obj_ptr->mtcInvApi_update_states ( hostname, get_adminState_str (MTC_ADMIN_STATE__UNLOCKED), get_operState_str (MTC_OPER_STATE__DISABLED ), get_availStatus_str(MTC_AVAIL_STATUS__FAILED)); } response = "{ \"status\" : \"pass\" }" ; http_status_code = HTTP_OK ; } else if ( ! severity.compare("degraded")) { /* Test 3 */ ilog ("%s severity 'degraded' request from not supported\n", hostname.c_str() ); response.append ("{ \"status\" : \"fail\""); response.append (",\"reason\" : \"Controlled host degrade not supported\""); response.append (",\"action\" : \"Upgrade maintenance package containing support and retry\""); response.append ("}"); http_status_code = HTTP_BADMETHOD; } else if ( ! severity.compare("cleared")) { /* Test 4 */ ilog ("%s severity 'cleared' request not supported\n", hostname.c_str() ); response.append ("{\"status\" : \"fail\""); response.append (",\"reason\" : \"Controlled host degrade clear not supported\""); response.append (",\"action\" : \"Upgrade maintenance package containing support and retry\""); response.append ("}"); http_status_code = HTTP_BADMETHOD; } else { /* Test 5 */ ilog ("%s severity '%s' request from not supported\n", hostname.c_str(), severity.c_str()); response.append ("{\"status\" : \"fail\""); response.append (",\"reason\" : \"Unsupported severity request '"); response.append (severity); response.append ("' ,\"action\" : \"Upgrade maintenance package containing support and retry\""); response.append ("}"); http_status_code = HTTP_BADREQUEST; } } return (response); } /***************************************************************************** * * Name: mtcHttpSvr_inv_req * * Handles three 'operations' * * 'delete' - based on uuid * 'modify' - based on list of key - value pairs * 'add' - based on inventory record * ******************************************************************************/ string mtcHttpSvr_inv_req ( char * request_ptr, evhttp_cmd_type event_type, int & http_status_code ) { int rc = PASS ; nodeLinkClass * obj_ptr = get_mtcInv_ptr (); msgSock_type * mtclogd_ptr = get_mtclogd_sockPtr (); event_type = event_type ; /* variable scoping */ string resp_buffer = "" ; string key = "operation" ; string value = "" ; string hostname = "n/a" ; /* Identify the operation */ rc = jsonUtil_get_key_val ( request_ptr, key, value ) ; if ( rc == PASS ) { node_inv_type inv ; node_inv_init (inv); ilog ("%s %s : '%s'\n", obj_ptr->my_hostname.c_str(), key.c_str(), value.c_str()) ; rc = jsonUtil_load_host ( request_ptr, inv ); if ( rc == PASS ) { if ( !inv.name.empty() ) { hostname = inv.name ; } snprintf (&log_str[0], MAX_API_LOG_LEN-1, "%s [%5d] http event seq: %d Payload:%s: %s", pt(), getpid(), sequence, hostname.data(), request_ptr); send_log_message ( mtclogd_ptr, obj_ptr->my_hostname.data(), &filename[0], &log_str[0] ); /* ADD */ if ( ! strncmp ( value.data() , "add" , strlen("add") )) { rc = obj_ptr->add_host ( inv ); if ( rc == PASS ) { ilog ("%s Add Operation\n", inv.name.c_str()); /* generate event=add alarm if the add_host returns a PASS */ mtcAlarm_log ( inv.name, MTC_LOG_ID__EVENT_ADD ); } /* A RETRY return from add_host indicates that the node is * already provisioned. At this point changes can only be * implemented as modification so call mod_host */ if ( rc == RETRY ) { rc = obj_ptr->mod_host ( inv ); } /* handle the http response code/message */ if ( rc == PASS ) { resp_buffer = "{ \"status\" : \"pass\" }" ; } else { elog ("%s Inventory Add failed (%s)\n", inv.name.length() ? inv.name.c_str() : "none", inv.uuid.c_str() ); _create_error_response ( rc , resp_buffer, inv ) ; } } /* MODIFY ? */ else if ( ! strncmp ( value.data() , "modify" , strlen("modify") )) { ilog ("%s Modify Operation\n", inv.name.c_str()); /* If the return value of get_host is empty then we need to add the host */ if ( obj_ptr->get_host ( inv.uuid ).empty() ) { wlog ("%s Missing\n", inv.uuid.c_str() ); ilog ("%s Overriding 'modify' with 'add' operation\n", inv.name.c_str() ); rc = obj_ptr->add_host ( inv ); if ( rc == PASS ) { resp_buffer = "{ \"status\" : \"pass\" }" ; http_status_code = HTTP_OK ; } else { elog ("Inventory Add failed for uuid: %s\n", inv.uuid.c_str()); resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"Rejected - unknown\""); resp_buffer.append (",\"action\" : \"Switch activity\""); resp_buffer.append ("}"); } } else { rc = obj_ptr->mod_host ( inv ); if ( rc != PASS ) { elog ("Inventory Modify failed for uuid: %s\n", inv.uuid.c_str()); _create_error_response ( rc , resp_buffer, inv ) ; } else { resp_buffer = "{ \"status\" : \"pass\" }" ; http_status_code = HTTP_OK ; } } } else { elog ("Unsupported Inventory Event Operation:%s\n", value.data()); resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"Unsupported "); resp_buffer.append (value.data()); resp_buffer.append (" operation\""); resp_buffer.append (",\"action\" : \"Use delete, add or modify only\""); resp_buffer.append ("}"); http_status_code = HTTP_BADREQUEST ; } } else { elog ("JSON key:value parse error: %s\n", request_ptr ); _create_error_response ( FAIL_JSON_PARSE , resp_buffer, inv ) ; http_status_code = HTTP_BADREQUEST ; } } else { elog ("Unable to get key value\n"); resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"String deserialization\""); resp_buffer.append (",\"action\" : \"Fix event dictionary\""); resp_buffer.append ("}"); http_status_code = HTTP_BADREQUEST ; } return resp_buffer ; } /***************************************************************************** * * Name: mtcHttpSvr_sm_req * * Handles only 1 'operation' * * 'event' - based on hostname, to set host state * ******************************************************************************/ string mtcHttpSvr_sm_req ( char * request_ptr, evhttp_cmd_type event_type, int & http_status_code ) { int rc = PASS ; http_status_code = HTTP_BADREQUEST ; nodeLinkClass * obj_ptr = get_mtcInv_ptr (); msgSock_type * mtclogd_ptr = get_mtclogd_sockPtr (); event_type = event_type ; /* variable scoping */ string resp_buffer = "" ; string key = "action" ; string value = "" ; string hostname = "n/a" ; /* Identify the operation */ rc = jsonUtil_get_key_val ( request_ptr, key, value ) ; if ( rc == PASS ) { node_inv_type inv ; node_inv_init (inv); ilog ("%s %s : '%s'\n", obj_ptr->my_hostname.c_str(), key.c_str(), value.c_str()) ; rc = jsonUtil_load_host_state ( request_ptr, inv ); if ( rc == PASS ) { if ( !inv.name.empty() ) { hostname = inv.name ; } snprintf (&log_str[0], MAX_API_LOG_LEN-1, "%s [%5d] http event seq: %d Payload:%s: %s", pt(), getpid(), sequence, hostname.data(), request_ptr); send_log_message ( mtclogd_ptr, obj_ptr->my_hostname.data(), &filename[0], &log_str[0] ); /* state change event */ if ( !value.compare("event") ) { ilog ("%s state change\n", inv.name.c_str()); if ( obj_ptr->get_host (inv.name).empty() ) { string reason_text = "hostname not provided"; if( !inv.name.empty() ) { reason_text = "host " + inv.name + " not found"; } wlog ("%s\n", reason_text.c_str()); resp_buffer = "{"; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"" + reason_text + "\""); resp_buffer.append (",\"action\" : \"event\""); resp_buffer.append ("}"); http_status_code = HTTP_OK ; } else { bool executed = false; if( (inv.avail.compare("failed") == 0) && (inv.oper.compare("disabled")==0) ) { rc = obj_ptr->set_host_failed ( inv ); executed = true; } if (!executed) { resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"Rejected - operation not supported\""); resp_buffer.append (",\"action\" : \"event\""); resp_buffer.append ("}"); }else { if ( rc != PASS ) { char errcode[12]; snprintf(errcode, sizeof(errcode), "%d", rc); resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"Rejected - "); resp_buffer.append ( errcode ); resp_buffer.append ("\""); resp_buffer.append (",\"action\" : \"event\""); resp_buffer.append ("}"); } else { resp_buffer = "{ \"status\" : \"pass\" }" ; http_status_code = HTTP_OK ; } } } } else { elog ("Unsupported Inventory Event Operation:%s\n", value.data()); resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"Unsupported "); resp_buffer.append (value.data()); resp_buffer.append (" operation\""); resp_buffer.append (",\"action\" : \"Use event only\""); resp_buffer.append ("}"); http_status_code = HTTP_BADREQUEST ; } } else { elog ("JSON key:value parse error: %s\n", request_ptr ); _create_error_response ( FAIL_JSON_PARSE , resp_buffer, inv ) ; http_status_code = HTTP_BADREQUEST ; } } else { elog ("Unable to get key value\n"); resp_buffer = "{" ; resp_buffer.append (" \"status\" : \"fail\""); resp_buffer.append (",\"reason\" : \"String deserialization\""); resp_buffer.append (",\"action\" : \"Fix event dictionary\""); resp_buffer.append ("}"); http_status_code = HTTP_BADREQUEST ; } return resp_buffer ; } /******************************************************************** * * Verify this request contains valid client info. * * 1. the URL must have * CLIENT_SYSINV_URL or * CLIENT_VIM_HOSTS_URL or * CLIENT_VIM_SYSTEMS_URL * * 2. the user-Agent header needs to exist and be set to either * CLIENT_SYSINV_1_0 or * CLIENT_VIM_1_0 * ********************************************************************/ mtc_client_enum _get_client_id ( struct evhttp_request *req ) { mtc_client_enum client = CLIENT_NONE ; /* Parse Headers we care about to verify that it also contains the * correct User-Agent header and supported version */ struct evkeyvalq * headers_ptr = evhttp_request_get_input_headers (req); const char * header_value_ptr = evhttp_find_header (headers_ptr, CLIENT_HEADER); if ( header_value_ptr ) { const char * url_ptr = evhttp_request_get_uri (req); hlog2 ("URI: %s\n", url_ptr ); if ( ! strncmp ( header_value_ptr, CLIENT_SYSINV_1_0, 20 ) ) { hlog3 ("%s\n", header_value_ptr ); if ( strstr ( url_ptr, CLIENT_SYSINV_URL) ) { client = CLIENT_SYSINV ; } } else if ( ! strncmp ( header_value_ptr, CLIENT_VIM_1_0, 20 ) ) { hlog3 ("%s\n", header_value_ptr ); if ( strstr ( url_ptr, CLIENT_VIM_HOSTS_URL)) { client = CLIENT_VIM_HOSTS ; } else if ( strstr ( url_ptr, CLIENT_VIM_SYSTEMS_URL) ) { client = CLIENT_VIM_SYSTEMS ; } } else if ( ! strncmp ( header_value_ptr, CLIENT_SM_1_0, 20 ) ) { hlog3 ("%s\n", header_value_ptr); if ( strstr ( url_ptr, CLIENT_SM_URL ) ) { client = CLIENT_SM; } } } else { wlog ("Unknown or mismatched client (%d)\n", client) ; } return (client); } /***************************************************************************** * * Name: mtcHttpSvr_handler * * Description: Receive an http event extract the event type and buffer from * it and call process request handler. * Send the processed message response back to the connection. * * Supported events include: POST, PUT, DELETE * ******************************************************************************/ void mtcHttpSvr_handler (struct evhttp_request *req, void *arg) { struct evbuffer *resp_buf ; mtc_client_enum client = CLIENT_NONE ; int http_status_code = HTTP_NOTFOUND ; string service = "" ; string uuid = "" ; string response = "" ; string hostname = "n/a" ; UNUSED(arg); response = "{" ; response.append (" \"status\" : \"fail\""); response.append (",\"reason\" : \"not found\""); response.append (",\"action\" : \"retry with valid host\""); response.append ("}"); nodeLinkClass * obj_ptr = get_mtcInv_ptr (); msgSock_type * mtclogd_ptr = get_mtclogd_sockPtr (); event_type * event_ptr = get_eventPtr (); event_ptr->req = req ; /* Get sender must be localhost */ const char * host_ptr = evhttp_request_get_host (req); if ( strncmp ( host_ptr , "localhost" , 10 )) { wlog ("Message received from unknown host (%s)\n", host_ptr ); /* TODO: Fail the request if from unknown host */ } const char * url_ptr = evhttp_request_get_uri (req); /* Extract the operation */ evhttp_cmd_type http_cmd = evhttp_request_get_command (req); jlog ("%s request from '%s'\n", getHttpCmdType_str(http_cmd), host_ptr ); /* Acquire the client that sent this event from the url URI */ client = _get_client_id ( req ); if ( client == CLIENT_NONE ) { response = ("{\"status\" : \"fail\""); response.append (",\"reason\" : \"unknown client in User-Agent header\""); response.append (",\"action\" : \"use "); response.append (CLIENT_VIM_1_0); response.append (" or "); response.append (CLIENT_SYSINV_1_0); response.append (" in User-Agent header\"}"); http_status_code = HTTP_BADREQUEST ; elog ("%s\n", response.c_str()); evhttp_send_error (event_ptr->req, MTC_HTTP_FORBIDDEN, response.data() ); return ; } if (( client == CLIENT_VIM_HOSTS ) || ( client == CLIENT_VIM_SYSTEMS )) { service = "vim" ; } else if ( client == CLIENT_SYSINV ) { service = "sysinv" ; } else if ( client == CLIENT_SM ) { service = "sm"; } else service = "unknown" ; snprintf (&log_str[0], MAX_API_LOG_LEN-1, "\n%s [%5d] http event seq: %d with %s %s request from %s:%s", pt(), getpid(), ++sequence, service.c_str(), getHttpCmdType_str(http_cmd), host_ptr, url_ptr ); send_log_message ( mtclogd_ptr, obj_ptr->my_hostname.data(), &filename[0], &log_str[0] ); switch ( http_cmd ) { case EVHTTP_REQ_GET: case EVHTTP_REQ_DELETE: { size_t len = strlen(CLIENT_SYSINV_URL) ; uuid = (url_ptr+len) ; hostname = obj_ptr->get_host(uuid) ; if ( uuid.length() == UUID_LEN ) { hostname = obj_ptr->get_host(uuid) ; } if (( http_cmd == EVHTTP_REQ_GET ) && ( client == CLIENT_VIM_SYSTEMS )) { http_status_code = obj_ptr->mtcVimApi_system_info ( response ); break ; } else { http_status_code = HTTP_OK ; if ( uuid.length() != UUID_LEN ) { wlog ("http '%s' request rejected, invalid uuid size (%ld:%s)\n", getHttpCmdType_str(http_cmd), uuid.length(), uuid.c_str()); response = "{" ; response.append (" \"status\" : \"fail\""); response.append (",\"reason\" : \"Uuid size error\""); response.append (",\"action\" : \"Undetermined\""); response.append ("}"); http_status_code = HTTP_BADREQUEST ; } if (( http_cmd == EVHTTP_REQ_DELETE ) && (( hostname.length() == 0 ) || ( !hostname.compare("none")))) { wlog ("deleting unknown resource: %s\n", uuid.length() ? uuid.c_str() : "(null)" ); response = "{ \"status\" : \"pass\" }" ; http_status_code = HTTP_OK ; } else if (( http_cmd == EVHTTP_REQ_GET ) ) { if ( client == CLIENT_VIM_HOSTS ) { response = "{\"state\": " ; response.append(obj_ptr->mtcVimApi_state_get ( hostname, http_status_code )); } else { elog ("http GET request not from VIM (client:%d)\n", client ); response = "{" ; response.append (" \"status\" : \"fail\""); response.append (",\"reason\" : \"command not supported for specified User-Agent header\""); response.append (",\"action\" : \"use "); response.append (CLIENT_VIM_1_0); response.append (" as User-Agent\""); response.append ("}"); http_status_code = HTTP_BADREQUEST ; } } else { ilog ("%s Delete Request Posted (%s)\n", hostname.c_str(), uuid.c_str()); obj_ptr->set_adminAction ( hostname, MTC_ADMIN_ACTION__DELETE ); response = "{ \"status\" : \"pass\" }" ; http_status_code = HTTP_OK ; } } break ; } case EVHTTP_REQ_PATCH: case EVHTTP_REQ_POST: { response = "{" ; response.append (" \"status\" : \"fail\""); response.append (",\"reason\" : \"no buffer\""); response.append (",\"action\" : \"retry with data\""); response.append ("}"); /* get the payload */ struct evbuffer *in_buf = evhttp_request_get_input_buffer ( req ); if ( in_buf ) { size_t len = evbuffer_get_length(in_buf) ; if ( len ) { ev_ssize_t bytes = 0 ; char * buffer_ptr = (char*)malloc(len+1); memset ( buffer_ptr, 0, len+1 ); bytes = evbuffer_remove(in_buf, buffer_ptr, len ); if ( bytes <= 0 ) { http_status_code = HTTP_BADREQUEST ; wlog ("http event request with no payload\n"); } else { http_status_code = HTTP_OK ; if ( client == CLIENT_VIM_HOSTS ) { response = mtcHttpSvr_vim_req ( buffer_ptr, http_cmd, http_status_code ); } else if ( client == CLIENT_SYSINV ) { response = mtcHttpSvr_inv_req ( buffer_ptr, http_cmd, http_status_code ); } else if ( client == CLIENT_SM ) { response = mtcHttpSvr_sm_req ( buffer_ptr, http_cmd, http_status_code ); } else { http_status_code = HTTP_BADREQUEST ; } } free ( buffer_ptr ); } else { http_status_code = MTC_HTTP_LENGTH_REQUIRED ; wlog ("Http event request has no payload\n"); } } else { http_status_code = HTTP_BADREQUEST ; wlog ("Http event request has no buffer\n"); } break ; } default: { wlog ("Unknown command (%d)\n", http_cmd ); http_status_code = HTTP_NOTFOUND ; } } snprintf (&log_str[0], MAX_API_LOG_LEN-1, "%s [%5d] http event seq: %d Response:%s: %s", pt(), getpid(), sequence, hostname.c_str(), response.c_str() ); send_log_message ( mtclogd_ptr, obj_ptr->my_hostname.data(), &filename[0], &log_str[0] ); if (( http_status_code == HTTP_OK ) || ( http_status_code == MTC_HTTP_ACCEPTED )) { resp_buf = evbuffer_new(); jlog ("Event Response: %s\n", response.c_str()); evbuffer_add_printf (resp_buf, "%s\n", response.data()); evhttp_send_reply (event_ptr->req, http_status_code, "OK", resp_buf ); evbuffer_free ( resp_buf ); } else { elog ("HTTP Event error:%d ; cmd:%s url:%s response:%s\n", http_status_code, getHttpCmdType_str(http_cmd), url_ptr, response.c_str()); evhttp_send_error (event_ptr->req, http_status_code, response.data() ); } } /***************************************************************** * * Name : mtcHttpSvr_bind * * Description : Setup the HTTP server socket * *****************************************************************/ int mtcHttpSvr_bind ( event_type & event ) { int rc ; int flags ; int one = 1; event.fd = socket(AF_INET, SOCK_STREAM, 0); if (event.fd < 0) { elog ("HTTP server socket create failed (%d:%m)\n", errno ); return FAIL_SOCKET_CREATE ; } /* make socket reusable */ rc = setsockopt(event.fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(int)); memset(&event.addr, 0, sizeof(struct sockaddr_in)); event.addr.sin_family = AF_INET; event.addr.sin_addr.s_addr = inet_addr(LOOPBACK_IP) ; /* INADDR_ANY; TODO: Refine this if we can */ // event.addr.sin_addr.s_addr = INADDR_ANY; event.addr.sin_port = htons(event.port); /* bind port */ rc = bind ( event.fd, (struct sockaddr*)&event.addr, sizeof(struct sockaddr_in)); if (rc < 0) { elog ("HTTP server port %d bind failed (%d:%m)\n", event.port, errno ); return FAIL_SOCKET_BIND ; } /* Listen for events */ rc = listen(event.fd, 10 ); if (rc < 0) { elog ("HTTP server listen failed (%d:%m)\n", errno ); return FAIL_SOCKET_LISTEN; } /* make non-blocking */ flags = fcntl ( event.fd, F_GETFL, 0) ; if ( flags < 0 || fcntl(event.fd, F_SETFL, flags | O_NONBLOCK) < 0) { elog ("failed to set HTTP server socket to non-blocking (%d:%m)\n", errno ); return FAIL_SOCKET_OPTION; } return PASS; } /* Setup the http server */ int mtcHttpSvr_setup ( event_type & event ) { int rc = PASS ; if ( ( rc = mtcHttpSvr_bind ( event )) != PASS ) { return rc ; } else if (event.fd < 0) { wlog ("failed to get http server socket file descriptor\n"); return RETRY ; } event.base = event_base_new(); if (event.base == NULL) { elog ("failed to get http server event base\n"); return -1; } event.httpd = evhttp_new(event.base); if (event.httpd == NULL) { elog ("failed to get httpd server handle\n"); return -1; } evhttp_set_allowed_methods (event.httpd, EVENT_METHODS ); rc = evhttp_accept_socket(event.httpd, event.fd); if ( rc == -1) { elog ("failed to accept on http server socket\n"); return -1; } evhttp_set_gencb(event.httpd, mtcHttpSvr_handler, NULL); return PASS ; } /* initialize the mtce http server */ int mtcHttpSvr_init ( event_type & mtce_event ) { int rc = PASS ; snprintf (&filename[0], MAX_FILENAME_LEN, "/var/log/%s_event.log", program_invocation_short_name ); for ( ; ; ) { rc = mtcHttpSvr_setup ( mtce_event ); if ( rc == RETRY ) { wlog ("%s bind failed (%d)\n", EVENT_SERVER, mtce_event.fd ); } else if ( rc != PASS ) { elog ("%s start failed (rc:%d)\n", EVENT_SERVER, rc ); } else if ( mtce_event.fd > 0 ) { ilog ("Listening On: 'http event server ' socket %s:%d\n", inet_ntoa(mtce_event.addr.sin_addr), mtce_event.port ); rc = PASS ; break ; } if ( rc ) mtcWait_secs (5); } return ( rc ) ; }