1093 lines
38 KiB
C++
1093 lines
38 KiB
C++
/*
|
|
* Copyright (c) 2015-2017 Wind River Systems, Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
*/
|
|
|
|
/** @file Wind River Titanium Cloud Guest Daemon's HTTP Server */
|
|
|
|
#ifdef __AREA__
|
|
#undef __AREA__
|
|
#endif
|
|
#define __AREA__ "gst"
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h> /* for ... inet_addr , inet_ntoa */
|
|
#include <arpa/inet.h> /* for ... LOOPBACK_IP */
|
|
#include <evhttp.h> /* for ... HTTP_ status definitions */
|
|
#include <fcntl.h>
|
|
|
|
using namespace std;
|
|
|
|
#include "daemon_common.h" /* */
|
|
|
|
#include "nodeBase.h" /* Service header */
|
|
#include "nodeTimers.h" /* */
|
|
#include "nodeUtil.h" /* */
|
|
#include "jsonUtil.h" /* for ... jsonUtil_get_key_val */
|
|
|
|
#include "guestUtil.h" /* for ... guestUtil_inst_init */
|
|
#include "guestClass.h" /* */
|
|
#include "guestHttpSvr.h" /* for ... this module */
|
|
#include "guestVimApi.h" /* for ... guestVimApi_inst_action */
|
|
|
|
extern int send_event_to_mtcAgent ( string hostname, unsigned int event);
|
|
|
|
/* Used for log messages */
|
|
#define GUEST_SERVER "HTTP Guest Server"
|
|
|
|
/**
|
|
* HTTP commands level is specified in the URL as either
|
|
* of the following ; both are at v1
|
|
**/
|
|
#define HOST_LEVEL_URL "/v1/hosts/"
|
|
#define INST_LEVEL_URL "/v1/instances/"
|
|
|
|
/* Commands require the "User Agent" to be set to SERVICE_VERSION */
|
|
#define USER_AGENT "User-Agent"
|
|
#define SERVICE_VERSION "vim/1.0"
|
|
|
|
/* This servers's request structure */
|
|
static request_type guest_request ;
|
|
|
|
int sequence = 0 ;
|
|
char log_str [MAX_API_LOG_LEN];
|
|
char filename[MAX_FILENAME_LEN];
|
|
|
|
/* Module Cleanup */
|
|
void guestHttpSvr_fini ( void )
|
|
{
|
|
if ( guest_request.fd )
|
|
{
|
|
if ( guest_request.base )
|
|
{
|
|
event_base_free( guest_request.base);
|
|
}
|
|
close ( guest_request.fd );
|
|
}
|
|
}
|
|
|
|
/* Look for events */
|
|
void guestHttpSvr_look ( void )
|
|
{
|
|
/* Look for INV Events */
|
|
if ( guest_request.base )
|
|
event_base_loop( guest_request.base, EVLOOP_NONBLOCK );
|
|
}
|
|
|
|
/**
|
|
* Formulates and updates the resp_buffer reference
|
|
* variable based on the specified error code
|
|
**/
|
|
string _create_error_response ( int error_code )
|
|
{
|
|
string resp_buffer = "{" ;
|
|
resp_buffer.append (" \"status\" : \"fail\"");
|
|
switch (error_code)
|
|
{
|
|
case FAIL_KEY_VALUE_PARSE:
|
|
{
|
|
resp_buffer.append (",\"reason\" : \"command parse error\"");
|
|
break ;
|
|
}
|
|
case FAIL_JSON_ZERO_LEN:
|
|
{
|
|
resp_buffer.append (",\"reason\" : \"no buffer\"");
|
|
break ;
|
|
}
|
|
case FAIL_NOT_FOUND:
|
|
{
|
|
resp_buffer.append (",\"reason\" : \"entity not found\"");
|
|
break ;
|
|
|
|
}
|
|
case FAIL_INVALID_DATA:
|
|
{
|
|
resp_buffer.append (",\"reason\" : \"invalid data\"");
|
|
break ;
|
|
}
|
|
case FAIL_BAD_STATE:
|
|
{
|
|
resp_buffer.append (",\"reason\" : \"bad state\"");
|
|
break ;
|
|
}
|
|
case FAIL_BAD_CASE:
|
|
{
|
|
resp_buffer.append (",\"reason\" : \"unsupported http command\"");
|
|
break ;
|
|
}
|
|
default:
|
|
{
|
|
;
|
|
}
|
|
}
|
|
resp_buffer.append ("}");
|
|
|
|
return (resp_buffer);
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Name : _get_service_level
|
|
*
|
|
* Description: Verify this request contains
|
|
*
|
|
* 1. valid service level specification in the URL and
|
|
* 2. the expected User-Agent value
|
|
*
|
|
********************************************************************/
|
|
service_level_enum _get_service_level ( struct evhttp_request *req )
|
|
{
|
|
service_level_enum service_level = SERVICE_LEVEL_NONE ;
|
|
|
|
/* Parse Headers we care about to verify that it also contains the correct User-Agent header */
|
|
struct evkeyvalq * headers_ptr = evhttp_request_get_input_headers (req);
|
|
const char * header_value_ptr = evhttp_find_header (headers_ptr, USER_AGENT);
|
|
if ( header_value_ptr )
|
|
{
|
|
if ( strncmp ( header_value_ptr, SERVICE_VERSION, 20 ) )
|
|
{
|
|
elog ("Request missing required '%s=%s' (%s)\n",
|
|
USER_AGENT, SERVICE_VERSION, header_value_ptr );
|
|
return (service_level);
|
|
}
|
|
}
|
|
|
|
/* get the URL string */
|
|
const char * url_ptr = evhttp_request_get_uri (req);
|
|
jlog1 ("URI: %s\n", url_ptr );
|
|
|
|
/* look for the supported service levels in the url */
|
|
const char * service_level_ptr = strstr ( url_ptr, HOST_LEVEL_URL);
|
|
if ( service_level_ptr )
|
|
{
|
|
service_level = SERVICE_LEVEL_HOST ;
|
|
}
|
|
else
|
|
{
|
|
service_level_ptr = strstr ( url_ptr, INST_LEVEL_URL);
|
|
if ( service_level_ptr )
|
|
{
|
|
service_level = SERVICE_LEVEL_INST ;
|
|
}
|
|
}
|
|
if ( service_level == SERVICE_LEVEL_NONE )
|
|
{
|
|
elog ("Unsupported service level (url:%s)\n", url_ptr );
|
|
return (service_level);
|
|
}
|
|
return (service_level);
|
|
}
|
|
|
|
|
|
|
|
string _update_services_response ( string hostname, string uuid, instInfo * instinfo_ptr )
|
|
{
|
|
string response = ("{");
|
|
response.append ("\"uuid\":\"");
|
|
response.append (uuid);
|
|
response.append ("\",");
|
|
response.append ("\"hostname\":\"");
|
|
response.append (hostname);
|
|
response.append ("\",");
|
|
response.append ("\"services\": [{ \"service\":\"heartbeat\",");
|
|
|
|
response.append ("\"state\":\"");
|
|
if ( instinfo_ptr->heartbeat.reporting == true )
|
|
response.append ("enabled");
|
|
else
|
|
response.append ("disabled");
|
|
|
|
response.append ("\",\"restart-timeout\":\"");
|
|
if ( instinfo_ptr->heartbeating == true )
|
|
{
|
|
response.append (instinfo_ptr->restart_to_str);
|
|
response.append ("\",\"status\":\"");
|
|
response.append ("enabled\"}]}");
|
|
}
|
|
else
|
|
{
|
|
response.append ("0\",\"status\":\"");
|
|
response.append ("disabled\"}]}");
|
|
}
|
|
return (response);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Name: guestHttpSvr_vim_req
|
|
*
|
|
* Handles three 'operations'
|
|
*
|
|
* 'delete' - based on uuid
|
|
* 'modify' - based on list of key - value pairs
|
|
* 'add' - based on inventory record
|
|
*
|
|
******************************************************************************
|
|
* Test Commands:
|
|
*
|
|
|
|
Add Instance:
|
|
curl -i -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2410/v1/instances/8d80875b-fa73-4ccb-bce3-1cd4df10449d -d '{"hostname": "compute-1", "uuid" : "8d80875b-fa73-4ccb-bce3-1cd4df10449d", "channel" : "cgts-instance000001", "services" : ["heartbeat"]}'
|
|
|
|
|
|
|
|
Disable Instance: heartbeat
|
|
curl -i -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2410/v1/instances/8d80875b-fa73-4ccb-bce3-1cd4df10449d -d '{"hostname": "compute-1", "uuid" : "8d80875b-fa73-4ccb-bce3-1cd4df10449d", "channel" : "cgts-instance000001", "services" : [{"service":"heartbeat" , "state":"disabled"}]}'
|
|
|
|
Delete Host:
|
|
curl -i -X DELETE -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2410/v1/hosts/8aee436e-d564-459e-a0d8-26c44792a9df
|
|
|
|
Enable Host: heartbeat
|
|
curl -i -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2410/v1/hosts/8aee436e-d564-459e-a0d8-26c44792a9df/enable -d '{"hostname": "compute-1", "uuid" : "8d80875b-fa73-4ccb-bce3-1cd4df10449d"}'
|
|
|
|
Enable Host: heartbeat
|
|
curl -i -X GET -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: vim/1.0' http://localhost:2410/v1/instances/8d80875b-fa73-4ccb-bce3-1cd4df10449d
|
|
|
|
*/
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Name : guestHttpSvr_host_req
|
|
*
|
|
* Description : Handles host level VIM requests
|
|
*
|
|
********************************************************************************/
|
|
string guestHttpSvr_host_req ( char * buffer_ptr,
|
|
mtc_cmd_enum command,
|
|
evhttp_cmd_type http_cmd,
|
|
int & http_status_code )
|
|
{
|
|
string response = "" ;
|
|
string hostname = "" ;
|
|
|
|
int rc = jsonUtil_get_key_val ( buffer_ptr, MTC_JSON_INV_NAME, hostname );
|
|
|
|
if ( rc )
|
|
{
|
|
wlog ("Failed to parse command key values (%d)\n", rc );
|
|
ilog ("... %s\n", buffer_ptr );
|
|
|
|
response = _create_error_response ( FAIL_KEY_VALUE_PARSE );
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
}
|
|
else
|
|
{
|
|
guestHostClass * obj_ptr = get_hostInv_ptr ();
|
|
|
|
string instance_info = "" ;
|
|
string instance_uuid = "" ;
|
|
string instance_chan = "" ;
|
|
|
|
/* WARNING: We only support a single list element for now */
|
|
list<string> services_list ;
|
|
services_list.clear() ;
|
|
|
|
switch ( http_cmd )
|
|
{
|
|
case EVHTTP_REQ_PUT:
|
|
{
|
|
qlog ("%s VIM CMD: Enable Host\n", hostname.c_str());
|
|
|
|
rc = obj_ptr->host_inst ( hostname, command );
|
|
if ( rc )
|
|
{
|
|
elog ("%s Host Enable Request (vim) - Host Not Found\n", hostname.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
|
|
/* Ask mtce for an inventory update */
|
|
send_event_to_mtcAgent ( obj_ptr->hostBase.my_hostname, MTC_EVENT_MONITOR_READY ) ;
|
|
|
|
}
|
|
else
|
|
{
|
|
http_status_code = HTTP_OK ;
|
|
response = " { \"status\" : \"pass\" }" ;
|
|
}
|
|
break ;
|
|
}
|
|
default:
|
|
{
|
|
wlog ("%s Unsupported http command '%s'\n",
|
|
hostname.c_str(), getHttpCmdType_str(http_cmd));
|
|
response = _create_error_response ( FAIL_BAD_CASE );
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
}
|
|
}
|
|
}
|
|
return (response);
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Name : _get_key_val
|
|
*
|
|
* Description : Get valid value from http message and generate error if failed
|
|
*
|
|
********************************************************************************/
|
|
int _get_key_val ( char * buffer_ptr,
|
|
string key,
|
|
string & value,
|
|
int & http_status_code,
|
|
string & response )
|
|
{
|
|
int rc = jsonUtil_get_key_val ( buffer_ptr, key, value );
|
|
|
|
if ( rc )
|
|
{
|
|
wlog ("Failed to extract %s from message\n", key.c_str());
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
response = _create_error_response ( FAIL_KEY_VALUE_PARSE );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Name : _get_list
|
|
*
|
|
* Description : Get valid list from http message and generate error if failed
|
|
*
|
|
********************************************************************************/
|
|
int _get_list ( char * buffer_ptr,
|
|
string key,
|
|
list<string> & list,
|
|
int & http_status_code,
|
|
string & response )
|
|
{
|
|
int rc = jsonUtil_get_list ( buffer_ptr, key, list );
|
|
|
|
if ( rc )
|
|
{
|
|
wlog ("Failed to extract %s from message\n", key.c_str());
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
response = _create_error_response ( FAIL_KEY_VALUE_PARSE );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
#define EVENT_VOTE "vote"
|
|
#define EVENT_STOP "stop"
|
|
#define EVENT_REBOOT "reboot"
|
|
#define EVENT_PAUSE "pause"
|
|
#define EVENT_UNPAUSE "unpause"
|
|
#define EVENT_SUSPEND "suspend"
|
|
#define EVENT_RESUME "resume"
|
|
#define EVENT_LIVE_MIGRATE_BEGIN "live_migrate_begin"
|
|
#define EVENT_LIVE_MIGRATE_END "live_migrate_end"
|
|
#define EVENT_COLD_MIGRATE_BEGIN "cold_migrate_begin"
|
|
#define EVENT_COLD_MIGRATE_END "cold_migrate_end"
|
|
|
|
string _get_action_timeout ( instInfo * instInfo_ptr, string action )
|
|
{
|
|
if ( instInfo_ptr->heartbeating == false )
|
|
{
|
|
ilog ("%s returning timeout of zero while not heartbeating for action '%s'\n",
|
|
log_prefix(instInfo_ptr).c_str(), action.c_str());
|
|
return ("0");
|
|
}
|
|
if ( !action.compare (EVENT_VOTE) )
|
|
return (instInfo_ptr->vote_to_str);
|
|
|
|
if ( !action.compare (EVENT_STOP) )
|
|
return (instInfo_ptr->shutdown_to_str);
|
|
if ( !action.compare (EVENT_REBOOT) )
|
|
return (instInfo_ptr->shutdown_to_str);
|
|
if ( !action.compare (EVENT_PAUSE) )
|
|
return (instInfo_ptr->suspend_to_str);
|
|
if ( !action.compare (EVENT_UNPAUSE) )
|
|
return (instInfo_ptr->resume_to_str);
|
|
if ( !action.compare (EVENT_SUSPEND) )
|
|
return (instInfo_ptr->suspend_to_str);
|
|
if ( !action.compare (EVENT_RESUME) )
|
|
return (instInfo_ptr->resume_to_str);
|
|
|
|
if ( !action.compare (EVENT_LIVE_MIGRATE_BEGIN) )
|
|
return (instInfo_ptr->suspend_to_str);
|
|
if ( !action.compare (EVENT_LIVE_MIGRATE_END) )
|
|
return (instInfo_ptr->resume_to_str);
|
|
if ( !action.compare (EVENT_COLD_MIGRATE_BEGIN) )
|
|
return (instInfo_ptr->suspend_to_str);
|
|
if ( !action.compare (EVENT_COLD_MIGRATE_END) )
|
|
return (instInfo_ptr->resume_to_str);
|
|
|
|
ilog ("%s returning timeout of zero for invalid action '%s'\n",
|
|
log_prefix(instInfo_ptr).c_str(), action.c_str());
|
|
|
|
return ("0");
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Name : guestHttpSvr_inst_req
|
|
*
|
|
* Description : Handles instance level VIM requests
|
|
*
|
|
********************************************************************************/
|
|
string guestHttpSvr_inst_req ( char * buffer_ptr,
|
|
mtc_cmd_enum command,
|
|
evhttp_cmd_type http_cmd,
|
|
int & http_status_code )
|
|
{
|
|
string response = "" ;
|
|
string hostname = "" ;
|
|
string instance_uuid = "" ;
|
|
|
|
_get_key_val ( buffer_ptr, MTC_JSON_INV_NAME, hostname, http_status_code, response);
|
|
if ( _get_key_val ( buffer_ptr, MTC_JSON_INV_NAME, hostname, http_status_code, response ))
|
|
return (response);
|
|
|
|
if ( _get_key_val ( buffer_ptr, "uuid", instance_uuid, http_status_code, response ))
|
|
return (response);
|
|
|
|
instInfo instance_info ; guestUtil_inst_init ( &instance_info );
|
|
instance_info.uuid = instance_uuid;
|
|
|
|
guestHostClass * obj_ptr = get_hostInv_ptr ();
|
|
|
|
/* WARNING: We only support a single list element for now */
|
|
list<string> services_list ;
|
|
services_list.clear() ;
|
|
|
|
switch ( http_cmd )
|
|
{
|
|
case EVHTTP_REQ_POST:
|
|
{
|
|
if ( MTC_CMD_VOTE == command )
|
|
{
|
|
jlog ("vote instance Info: %s", buffer_ptr );
|
|
|
|
string action = "";
|
|
if ( _get_key_val (buffer_ptr, "action", action, http_status_code, response ) )
|
|
return (response);
|
|
|
|
qlog ("VIM CMD: Vote instance %s\n",
|
|
instance_info.uuid.c_str());
|
|
|
|
instInfo * instInfo_ptr = obj_ptr->get_inst ( instance_uuid );
|
|
if ( instInfo_ptr )
|
|
{
|
|
response = ("{\"uuid\":\"");
|
|
response.append (instance_uuid);
|
|
response.append ("\",\"hostname\":\"");
|
|
response.append (hostname);
|
|
response.append ("\",\"action\":\"");
|
|
response.append (action);
|
|
response.append ("\",\"timeout\":\"");
|
|
response.append (_get_action_timeout ( instInfo_ptr, EVENT_VOTE ));
|
|
response.append ("\"}");
|
|
|
|
jlog ("%s %s Vote Response: %s\n", hostname.c_str(),
|
|
instance_uuid.c_str(),
|
|
response.c_str());
|
|
|
|
|
|
if ( instInfo_ptr->heartbeating )
|
|
send_cmd_to_guestServer (hostname, MTC_CMD_VOTE_INST, instance_uuid, true, action);
|
|
}
|
|
else
|
|
{
|
|
elog ("%s %s vote request (from vim) - Instance Not Found\n", hostname.c_str(), instance_uuid.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
}
|
|
}
|
|
else if ( MTC_CMD_NOTIFY == command )
|
|
{
|
|
jlog ("notify instance Info: %s", buffer_ptr );
|
|
|
|
string action = "";
|
|
if ( _get_key_val (buffer_ptr, "action", action, http_status_code, response ))
|
|
return (response);
|
|
|
|
qlog ("%s %s VIM CMD: Notify instance\n",
|
|
hostname.c_str(), instance_info.uuid.c_str());
|
|
|
|
instInfo * instInfo_ptr = obj_ptr->get_inst ( instance_uuid );
|
|
if ( instInfo_ptr )
|
|
{
|
|
response = ("{\"uuid\":\"");
|
|
response.append (instance_uuid);
|
|
response.append ("\",\"hostname\":\"");
|
|
response.append (hostname);
|
|
response.append ("\",\"action\":\"");
|
|
response.append (action);
|
|
response.append ("\",\"timeout\":\"");
|
|
response.append (_get_action_timeout ( instInfo_ptr , action ));
|
|
response.append ("\"}");
|
|
|
|
jlog ("%s %s Notify Response: %s\n", hostname.c_str(), instInfo_ptr->uuid.c_str(), response.c_str());
|
|
|
|
if ( instInfo_ptr->heartbeating )
|
|
send_cmd_to_guestServer (hostname, MTC_CMD_NOTIFY_INST, instance_uuid, true, action);
|
|
}
|
|
else
|
|
{
|
|
elog ("%s %s notify request (vim) - Instance Not Found\n", hostname.c_str(), instance_uuid.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
}
|
|
}
|
|
/* Add instance */
|
|
else
|
|
{
|
|
if ( _get_list (buffer_ptr, "services", services_list, http_status_code, response ))
|
|
return (response);
|
|
|
|
string service = services_list.front();
|
|
qlog ("%s %s VIM CMD: Add Instance\n",
|
|
hostname.c_str(),
|
|
instance_info.uuid.c_str());
|
|
|
|
ilog ("%s %s add request (from vim) (%s)\n",
|
|
hostname.c_str(),
|
|
instance_info.uuid.c_str(),
|
|
service.c_str());
|
|
|
|
if ( obj_ptr->add_inst ( hostname , instance_info ) != PASS )
|
|
{
|
|
response = _create_error_response ( FAIL_INVALID_DATA );
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
}
|
|
else
|
|
{
|
|
instance_info.heartbeat.provisioned = true ;
|
|
response = " { \"status\" : \"pass\" }" ;
|
|
}
|
|
}
|
|
break ;
|
|
}
|
|
/* PATCH is used to control service states ; enable or disable */
|
|
case EVHTTP_REQ_PATCH:
|
|
{
|
|
if ( _get_list (buffer_ptr, "services", services_list, http_status_code, response ) )
|
|
return (response);
|
|
|
|
jlog ("%s modify instance (%s)", hostname.c_str(), buffer_ptr );
|
|
|
|
string service , state ;
|
|
string services = services_list.front() ;
|
|
jsonUtil_get_key_val ( (char*)services.data(), "service", service );
|
|
jsonUtil_get_key_val ( (char*)services.data(), "state" , state );
|
|
|
|
qlog ("%s %s VIM CMD: Modify Instance\n",
|
|
hostname.c_str(),
|
|
instance_info.uuid.c_str());
|
|
|
|
if ( service.compare("heartbeat"))
|
|
{
|
|
response = _create_error_response ( FAIL_INVALID_DATA );
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
return (response);
|
|
}
|
|
else if ( !state.compare("enabled"))
|
|
instance_info.heartbeat.reporting = true;
|
|
else if ( !state.compare("disabled"))
|
|
instance_info.heartbeat.reporting = false;
|
|
else
|
|
{
|
|
elog ("%s modify request (vim) - invalid instance state '%s'\n", hostname.c_str(), state.c_str());
|
|
response = _create_error_response ( FAIL_BAD_STATE );
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
return (response);
|
|
}
|
|
int rc = obj_ptr->mod_inst ( hostname, instance_info );
|
|
if ( rc )
|
|
{
|
|
elog ("%s %s modify request (vim) - Instance Not Found\n",
|
|
hostname.c_str(), instance_info.uuid.c_str());
|
|
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
return (response);
|
|
}
|
|
else
|
|
{
|
|
instInfo * instInfo_ptr = obj_ptr->get_inst ( instance_info.uuid );
|
|
if ( instInfo_ptr )
|
|
{
|
|
response = _update_services_response ( hostname , instInfo_ptr->uuid, instInfo_ptr );
|
|
http_status_code = HTTP_OK ;
|
|
}
|
|
else
|
|
{
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
}
|
|
}
|
|
break ;
|
|
}
|
|
default:
|
|
{
|
|
wlog ("%s Unsupported HTTP '%s' request\n", instance_uuid.c_str(), getHttpCmdType_str(http_cmd));
|
|
}
|
|
}
|
|
return (response);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Name: guestHttpSvr_vimCmdHdlr
|
|
*
|
|
* Description: Receive an http request extract the request type and buffer from
|
|
* it and call process request handler.
|
|
* Send the processed message response back to the connection.
|
|
*
|
|
* Supported requests include: POST, PUT, DELETE
|
|
*
|
|
******************************************************************************/
|
|
|
|
int _get_url_info ( struct evhttp_request * req,
|
|
const char * url_ptr,
|
|
url_info_type & url_info )
|
|
{
|
|
size_t len = 0 ;
|
|
|
|
/* Extract the service level from the request URL ; host or instance */
|
|
url_info.service_level = _get_service_level ( req );
|
|
if ( url_info.service_level == SERVICE_LEVEL_NONE )
|
|
{
|
|
return ( FAIL_INVALID_DATA );
|
|
}
|
|
|
|
/* Remove the service level from the URL */
|
|
if ( url_info.service_level == SERVICE_LEVEL_HOST )
|
|
{
|
|
url_ptr = strstr ( url_ptr, HOST_LEVEL_URL );
|
|
len = strlen ( HOST_LEVEL_URL );
|
|
}
|
|
else
|
|
{
|
|
url_ptr = strstr ( url_ptr, INST_LEVEL_URL);
|
|
len = strlen ( INST_LEVEL_URL );
|
|
}
|
|
|
|
if ( url_ptr )
|
|
{
|
|
url_ptr += len ;
|
|
url_info.temp = url_ptr ;
|
|
url_info.uuid = url_info.temp.substr ( 0 , UUID_LEN );
|
|
}
|
|
else
|
|
{
|
|
ilog ("Failed to parse URL (%s)", url_ptr); // DLOG
|
|
return (FAIL_INVALID_UUID) ;
|
|
}
|
|
/**
|
|
* Check to see if there is a command enable/disable/etc after the UUID
|
|
* ... If what is left on the URL is longer than a UUID then
|
|
* there must be a command so lets get that
|
|
**/
|
|
if ( url_info.temp.length() > UUID_LEN )
|
|
{
|
|
url_info.command = url_info.temp.substr(url_info.uuid.length()+1, string::npos );
|
|
dlog ("Command:%s\n", url_info.command.c_str());
|
|
}
|
|
return (PASS);
|
|
}
|
|
|
|
void guestHttpSvr_vimCmdHdlr (struct evhttp_request *req, void *arg)
|
|
{
|
|
int rc ;
|
|
struct evbuffer *resp_buf ;
|
|
url_info_type url_info ;
|
|
|
|
int http_status_code = HTTP_NOTFOUND ;
|
|
guestHostClass * obj_ptr = get_hostInv_ptr () ;
|
|
string response = _create_error_response ( FAIL_JSON_ZERO_LEN );
|
|
|
|
guest_request.req = req ;
|
|
jlog1 ("HTTP Request:%p base:%p Req:%p arg:%p\n", &guest_request,
|
|
guest_request.base,
|
|
guest_request.req,
|
|
arg );
|
|
|
|
/* 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);
|
|
jlog1 ("%s request from '%s'\n", getHttpCmdType_str(http_cmd), host_ptr );
|
|
|
|
/* Log the request */
|
|
snprintf (&filename[0], MAX_FILENAME_LEN, "/var/log/%s_request.log", program_invocation_short_name );
|
|
// snprintf (&log_str[0], MAX_API_LOG_LEN-1, "\n%s [%5d] http request seq: %d with %d request from %s:%s",
|
|
snprintf (&log_str[0], MAX_API_LOG_LEN-1, "guest services request '%s' (%d) %s:%s",
|
|
getHttpCmdType_str(http_cmd), ++sequence, host_ptr, url_ptr );
|
|
// send_log_message ( "controller-0", &filename[0], &log_str[0] );
|
|
jlog ( "%s", log_str );
|
|
|
|
/* Fill in the url_info struct from the url string in the request */
|
|
rc = _get_url_info ( req, url_ptr, url_info );
|
|
if ( rc )
|
|
{
|
|
evhttp_send_error ( req, MTC_HTTP_FORBIDDEN, response.data() );
|
|
return ;
|
|
}
|
|
|
|
switch ( http_cmd )
|
|
{
|
|
case EVHTTP_REQ_DELETE:
|
|
{
|
|
qlog ("%s VIM CMD: Delete Host or Instance\n", url_info.uuid.c_str());
|
|
if ( url_info.service_level == SERVICE_LEVEL_HOST )
|
|
{
|
|
/* Nothing to do at the host level for delete.
|
|
* Don't try and do a hostname lookup as it may already have been deleted */
|
|
ilog ("%s delete host services request (vim)\n", url_info.uuid.c_str());
|
|
}
|
|
else
|
|
{
|
|
ilog ("%s delete instance request (vim)\n", url_info.uuid.c_str());
|
|
|
|
rc = obj_ptr->del_inst ( url_info.uuid );
|
|
}
|
|
if ( rc )
|
|
{
|
|
elog ("%s instance not found\n", url_info.uuid.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
}
|
|
else
|
|
{
|
|
http_status_code = HTTP_NOCONTENT ;
|
|
response = "{ \"status\" : \"pass\" }" ;
|
|
}
|
|
break ;
|
|
}
|
|
|
|
/**
|
|
* GET is handled at this level because
|
|
* there is no payload with it.
|
|
**/
|
|
case EVHTTP_REQ_GET:
|
|
{
|
|
if ( url_info.service_level == SERVICE_LEVEL_INST )
|
|
{
|
|
instInfo * instance_ptr = obj_ptr->get_inst ( url_info.uuid );
|
|
qlog ("%s VIM CMD: Query Instance - Reporting State\n", url_info.uuid.c_str());
|
|
if ( !instance_ptr )
|
|
{
|
|
elog ("%s query instance reporting state (vim) failed - Instance Not Found\n", url_info.uuid.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
}
|
|
else
|
|
{
|
|
string hostname = obj_ptr->get_inst_host_name(url_info.uuid);
|
|
response = _update_services_response ( hostname , url_info.uuid, instance_ptr );
|
|
http_status_code = HTTP_OK ;
|
|
}
|
|
}
|
|
/* GET the host level reporting state */
|
|
else if ( url_info.service_level == SERVICE_LEVEL_HOST )
|
|
{
|
|
string hostname = obj_ptr->get_host_name(url_info.uuid) ;
|
|
qlog ("%s VIM CMD: Query Host - Reporting State\n", hostname.c_str());
|
|
if ( hostname.length() )
|
|
{
|
|
|
|
response = ("{");
|
|
response.append ("\"uuid\":\"");
|
|
response.append (url_info.uuid);
|
|
response.append ("\",");
|
|
response.append ("\"hostname\":\"");
|
|
response.append (hostname);
|
|
response.append ("\",");
|
|
response.append ("\"state\":\"");
|
|
|
|
if ( obj_ptr->get_reporting_state(hostname) == true )
|
|
response.append ("enabled\"}");
|
|
else
|
|
response.append ("disabled\"}");
|
|
|
|
http_status_code = HTTP_OK ;
|
|
}
|
|
else
|
|
{
|
|
wlog ("%s query host reporting state (vim) failed - Host Not Found\n", url_info.uuid.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
|
|
/* Ask mtce for an inventory update */
|
|
send_event_to_mtcAgent ( obj_ptr->hostBase.my_hostname, MTC_EVENT_MONITOR_READY ) ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
slog ("invalid service level\n");
|
|
}
|
|
break ;
|
|
}
|
|
|
|
case EVHTTP_REQ_PUT:
|
|
case EVHTTP_REQ_POST:
|
|
case EVHTTP_REQ_PATCH:
|
|
{
|
|
/* GET the host level reporting state */
|
|
if (( http_cmd == EVHTTP_REQ_POST ) &&
|
|
( url_info.service_level == SERVICE_LEVEL_HOST ))
|
|
{
|
|
string hostname = obj_ptr->get_host_name(url_info.uuid) ;
|
|
qlog ("%s VIM CMD: Create Host\n", hostname.c_str());
|
|
if ( hostname.length() )
|
|
{
|
|
ilog ("%s create host services (vim)\n", hostname.c_str());
|
|
|
|
http_status_code = HTTP_OK ;
|
|
response = " { \"status\" : \"pass\" }" ;
|
|
}
|
|
else
|
|
{
|
|
wlog ("%s create host (vim) failed - Host Not Found\n", url_info.uuid.c_str());
|
|
response = _create_error_response ( FAIL_NOT_FOUND );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
|
|
/* Ask mtce for an inventory update */
|
|
send_event_to_mtcAgent ( obj_ptr->hostBase.my_hostname, MTC_EVENT_MONITOR_READY ) ;
|
|
}
|
|
break ;
|
|
}
|
|
|
|
/* Otherwise for PUTs and instances ; 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);
|
|
jlog1 ("Buffer @ %p contains %ld bytes\n", &in_buf, len );
|
|
|
|
memset ( buffer_ptr, 0, len+1 );
|
|
bytes = evbuffer_remove(in_buf, buffer_ptr, len );
|
|
|
|
if ( bytes <= 0 )
|
|
{
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
wlog ("http request with no payload\n");
|
|
}
|
|
else
|
|
{
|
|
http_status_code = HTTP_OK ;
|
|
mtc_cmd_enum mtc_cmd;
|
|
|
|
jlog("%s\n", buffer_ptr );
|
|
|
|
if (!url_info.command.compare("enable") )
|
|
mtc_cmd = MTC_CMD_ENABLE ;
|
|
else if (!url_info.command.compare("disable") )
|
|
mtc_cmd = MTC_CMD_DISABLE ;
|
|
else if (!url_info.command.compare("vote") )
|
|
mtc_cmd = MTC_CMD_VOTE ;
|
|
else if (!url_info.command.compare("notify") )
|
|
mtc_cmd = MTC_CMD_NOTIFY ;
|
|
else
|
|
mtc_cmd = MTC_CMD_NOT_SET ;
|
|
|
|
if ( url_info.service_level == SERVICE_LEVEL_INST )
|
|
{
|
|
response = guestHttpSvr_inst_req ( buffer_ptr,
|
|
mtc_cmd,
|
|
http_cmd,
|
|
http_status_code );
|
|
}
|
|
else if ( url_info.service_level == SERVICE_LEVEL_HOST )
|
|
{
|
|
response = guestHttpSvr_host_req ( buffer_ptr,
|
|
mtc_cmd,
|
|
http_cmd,
|
|
http_status_code );
|
|
}
|
|
else
|
|
{
|
|
slog ("invalid service level\n");
|
|
}
|
|
}
|
|
free ( buffer_ptr );
|
|
}
|
|
else
|
|
{
|
|
http_status_code = MTC_HTTP_LENGTH_REQUIRED ;
|
|
wlog ("http request has no length\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
http_status_code = HTTP_BADREQUEST ;
|
|
wlog ("Http request has no buffer\n");
|
|
}
|
|
break ;
|
|
}
|
|
default:
|
|
{
|
|
wlog ("Unknown command (%d)\n", http_cmd );
|
|
http_status_code = HTTP_NOTFOUND ;
|
|
}
|
|
}
|
|
|
|
if (( http_status_code >= HTTP_OK) && (http_status_code <= HTTP_NOCONTENT ))
|
|
{
|
|
resp_buf = evbuffer_new();
|
|
jlog ("Response: %s\n", response.c_str());
|
|
evbuffer_add_printf (resp_buf, "%s\n", response.data());
|
|
evhttp_send_reply (guest_request.req, http_status_code, "OK", resp_buf );
|
|
evbuffer_free ( resp_buf );
|
|
}
|
|
else
|
|
{
|
|
if ( http_status_code == HTTP_NOTFOUND )
|
|
{
|
|
wlog ("%s not found\n", url_ptr );
|
|
}
|
|
else
|
|
{
|
|
elog ("HTTP request error:%d ; cmd:%s url:%s\n",
|
|
http_status_code,
|
|
getHttpCmdType_str(http_cmd),
|
|
url_ptr);
|
|
elog ("... response:%s\n", response.c_str());
|
|
}
|
|
evhttp_send_error (guest_request.req, http_status_code, response.data() );
|
|
}
|
|
}
|
|
|
|
/*****************************************************************
|
|
*
|
|
* Name : guestHttpSvr_bind
|
|
*
|
|
* Description : Setup the HTTP server socket
|
|
*
|
|
*****************************************************************/
|
|
int guestHttpSvr_bind ( request_type & request )
|
|
{
|
|
int rc ;
|
|
int flags ;
|
|
int one = 1;
|
|
|
|
request.fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (request.fd < 0)
|
|
{
|
|
elog ("HTTP server socket create failed (%d:%m)\n", errno);
|
|
return FAIL_SOCKET_CREATE ;
|
|
}
|
|
|
|
/* make socket reusable */
|
|
rc = setsockopt(request.fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(int));
|
|
|
|
memset(&request.addr, 0, sizeof(struct sockaddr_in));
|
|
request.addr.sin_family = AF_INET;
|
|
request.addr.sin_addr.s_addr = inet_addr(LOOPBACK_IP) ;
|
|
request.addr.sin_port = htons(request.port);
|
|
|
|
/* bind port */
|
|
rc = bind ( request.fd, (struct sockaddr*)&request.addr, sizeof(struct sockaddr_in));
|
|
if (rc < 0)
|
|
{
|
|
elog ("HTTP bind failure for port %d (%d:%m)\n", request.port, errno );
|
|
return FAIL_SOCKET_BIND ;
|
|
}
|
|
|
|
/* Listen for requests */
|
|
rc = listen(request.fd, 10 );
|
|
if (rc < 0)
|
|
{
|
|
elog ("HTTP listen failed (%d:%m)\n", errno );
|
|
return FAIL_SOCKET_LISTEN;
|
|
}
|
|
|
|
/* make non-blocking */
|
|
flags = fcntl ( request.fd, F_GETFL, 0) ;
|
|
if ( flags < 0 || fcntl(request.fd, F_SETFL, flags | O_NONBLOCK) < 0)
|
|
{
|
|
elog ("HTTP set to non-blocking failed (%d:%m)\n", errno );
|
|
return FAIL_SOCKET_OPTION;
|
|
}
|
|
|
|
return PASS;
|
|
}
|
|
|
|
/* Setup the http server */
|
|
int guestHttpSvr_setup ( request_type & request )
|
|
{
|
|
int rc = PASS ;
|
|
if ( ( rc = guestHttpSvr_bind ( request )) != PASS )
|
|
{
|
|
return rc ;
|
|
}
|
|
else if (request.fd < 0)
|
|
{
|
|
wlog ("failed to get http server socket file descriptor\n");
|
|
return RETRY ;
|
|
}
|
|
|
|
request.base = event_base_new();
|
|
if (request.base == NULL)
|
|
{
|
|
elog ("failed to get http server request base\n");
|
|
return -1;
|
|
}
|
|
request.httpd = evhttp_new(request.base);
|
|
if (request.httpd == NULL)
|
|
{
|
|
elog ("failed to get httpd server handle\n");
|
|
return -1;
|
|
}
|
|
|
|
evhttp_set_allowed_methods (request.httpd, EVENT_METHODS );
|
|
|
|
rc = evhttp_accept_socket(request.httpd, request.fd);
|
|
if ( rc == -1)
|
|
{
|
|
elog ("failed to accept on http server socket\n");
|
|
return -1;
|
|
}
|
|
evhttp_set_gencb(request.httpd, guestHttpSvr_vimCmdHdlr, NULL);
|
|
|
|
return PASS ;
|
|
}
|
|
|
|
/* initialize the mtce http server */
|
|
int guestHttpSvr_init ( int port )
|
|
{
|
|
int rc = PASS ;
|
|
memset ( &guest_request, 0, sizeof(request_type));
|
|
guest_request.port = port ;
|
|
|
|
for ( ; ; )
|
|
{
|
|
rc = guestHttpSvr_setup ( guest_request );
|
|
if ( rc == RETRY )
|
|
{
|
|
wlog ("%s bind failed (%d)\n", GUEST_SERVER, guest_request.fd );
|
|
}
|
|
else if ( rc != PASS )
|
|
{
|
|
elog ("%s start failed (rc:%d)\n", GUEST_SERVER, rc );
|
|
}
|
|
else if ( guest_request.fd > 0 )
|
|
{
|
|
ilog ("Listening for 'http command' messages on %s:%d\n",
|
|
inet_ntoa(guest_request.addr.sin_addr), guest_request.port );
|
|
rc = PASS ;
|
|
break ;
|
|
}
|
|
if ( rc ) mtcWait_secs (5);
|
|
}
|
|
return ( rc ) ;
|
|
}
|