Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/execute shutdown command #7709

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/icinga/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ set(icinga_SOURCES
timeperiod.cpp timeperiod.hpp timeperiod-ti.hpp
user.cpp user.hpp user-ti.hpp
usergroup.cpp usergroup.hpp usergroup-ti.hpp
commandutils.cpp commandutils.hpp
)

if(ICINGA2_UNITY_BUILD)
Expand Down
80 changes: 80 additions & 0 deletions lib/icinga/apiactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "icinga/hostgroup.hpp"
#include "icinga/pluginutility.hpp"
#include "icinga/checkcommand.hpp"
#include "icinga/commandutils.hpp"
#include "icinga/eventcommand.hpp"
#include "icinga/notificationcommand.hpp"
#include "remote/apiaction.hpp"
Expand All @@ -31,6 +32,85 @@ REGISTER_APIACTION(remove_downtime, "Service;Host;Downtime", &ApiActions::Remove
REGISTER_APIACTION(shutdown_process, "", &ApiActions::ShutdownProcess);
REGISTER_APIACTION(restart_process, "", &ApiActions::RestartProcess);
REGISTER_APIACTION(generate_ticket, "", &ApiActions::GenerateTicket);
REGISTER_APIACTION(shutdown_host, "Host", &ApiActions::ShutdownHost)


Dictionary::Ptr ApiActions::ShutdownHost(const ConfigObject::Ptr& object,
const Dictionary::Ptr& params)
{
// Monitored Objects
Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
if (!checkable)
return ApiActions::CreateResult(404,
"Cannot perform shutdown for non-existent object.");

Host::Ptr host;
Service::Ptr service;
tie(host, service) = GetHostService(checkable);

if (service) {
return ApiActions::CreateResult(404, "Can shutdown only Host object.");
}

Checkable::Ptr custom_checkable;
custom_checkable = host->GetServiceByShortName("shutdown-service");
if (! custom_checkable){
return ApiActions::CreateResult(404, "Checkable service not found.");
}

CheckCommand::Ptr cmd = custom_checkable->GetCheckCommand();
// Checks if command is registered or not
if(!cmd) {
return ApiActions::CreateResult(404, "CheckCommand for service '" +custom_checkable->GetName()+ "' service not found.");
}

// Parameters
if (!params->Contains("shutdown_command")) {
return ApiActions::CreateResult(404, "Parameter 'shutdown_command' is required.");
}

// commands can be executed locally or on an agent
Endpoint::Ptr endpoint = custom_checkable->GetCommandEndpoint();
bool local = !endpoint || endpoint == Endpoint::GetLocalEndpoint();
if(local) {
CommandUtils::ExecuteCommandLocally(custom_checkable, cmd, params);
return ApiActions::CreateResult(
200,
"Executed local command for host '"
+ host->ToString()
);
}
else {
ApiListener::Ptr listener = ApiListener::GetInstance();

if (!listener){
return ApiActions::CreateResult(404, "No ApiListener instance available. Can't relay Custom Command to host.");
}

Dictionary::Ptr message = new Dictionary();
message->Set("jsonrpc", "2.0");
message->Set("method", "event::ExecuteCommandWithMacros");

Dictionary::Ptr newParams = new Dictionary();
newParams->Set("command_type", "check_command");
newParams->Set("command", custom_checkable->GetCheckCommand()->GetName());
newParams->Set("endpoint", endpoint->GetName());
newParams->Set("host", host->GetName());
for(String k : params->GetKeys()) {
newParams->Set(k, params->Get(k));
}
message->Set("params", newParams);

CommandUtils::SendCommandMessageToEndpoints(endpoint, listener, message);

return ApiActions::CreateResult(
200,
"Sent message to endpoints connected for host '"
+ host->ToString() + "'."
);
}
}


Dictionary::Ptr ApiActions::CreateResult(int code, const String& status,
const Dictionary::Ptr& additional)
Expand Down
1 change: 1 addition & 0 deletions lib/icinga/apiactions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ApiActions
static Dictionary::Ptr ShutdownProcess(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
static Dictionary::Ptr RestartProcess(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
static Dictionary::Ptr GenerateTicket(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
static Dictionary::Ptr ShutdownHost(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);

private:
static Dictionary::Ptr CreateResult(int code, const String& status, const Dictionary::Ptr& additional = nullptr);
Expand Down
67 changes: 67 additions & 0 deletions lib/icinga/clusterevents.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */

#include "icinga/clusterevents.hpp"
#include "icinga/commandutils.hpp"
#include "icinga/service.hpp"
#include "remote/apilistener.hpp"
#include "remote/endpoint.hpp"
Expand Down Expand Up @@ -34,6 +35,7 @@ REGISTER_APIFUNCTION(ExecuteCommand, event, &ClusterEvents::ExecuteCommandAPIHan
REGISTER_APIFUNCTION(SendNotifications, event, &ClusterEvents::SendNotificationsAPIHandler);
REGISTER_APIFUNCTION(NotificationSentUser, event, &ClusterEvents::NotificationSentUserAPIHandler);
REGISTER_APIFUNCTION(NotificationSentToAllUsers, event, &ClusterEvents::NotificationSentToAllUsersAPIHandler);
REGISTER_APIFUNCTION(ExecuteCommandWithMacros, event, &ClusterEvents::ExecuteCommandWithMacrosAPIHandler);

void ClusterEvents::StaticInitialize()
{
Expand Down Expand Up @@ -234,6 +236,71 @@ Value ClusterEvents::NextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin
return Empty;
}


Value ClusterEvents::ExecuteCommandWithMacrosAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params){
Endpoint::Ptr remote_endpoint = origin->FromClient->GetEndpoint();

if (!remote_endpoint) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'execute command with macros' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
return Empty;
}

if (!params->Contains("endpoint")) {
Log(LogNotice, "ClusterEvents")
<< "Got no endpoint for 'execute command with macros' message.";
return Empty;
}

if (!params->Contains("command")) {
Log(LogNotice, "ClusterEvents")
<< "No command for 'execute command with macros' message.";
return Empty;
}
CheckCommand::Ptr cmd = CheckCommand::GetByName(params->Get("command"));
if (!cmd) {
Log(LogNotice, "ClusterEvents")
<< "No shutdown command definition for '" + params->Get("command") +"' found.";
return Empty;
}

String endpoint = params->Get("endpoint");
if(endpoint != Endpoint::GetLocalEndpoint()->GetName()) {
Log(LogNotice, "ClusterEvents")
<< "Wrong destination fo 'execute command with macros' message as '" << endpoint << "', retransmitting.";
ApiListener::Ptr listener = ApiListener::GetInstance();

if (!listener) {
Log(LogNotice, "ClusterEvents" ) << "No ApiListener instance available. Can't relay Custom Command to host.";
return Empty;
}
Dictionary::Ptr message = new Dictionary();
message->Set("jsonrpc", "2.0");
message->Set("method", "event::ExecuteCommandWithMacros");
message->Set("params", params);

Endpoint::Ptr remote_endpoint = Endpoint::GetByName(endpoint);

CommandUtils::SendCommandMessageToEndpoints(remote_endpoint, listener, message);

return Empty;
}

Host::Ptr virtual_host = new Host();
Dictionary::Ptr attrs = new Dictionary();
attrs->Set("__name", "FakeCheckCommandHost");
attrs->Set("type", "Host");
Deserialize(virtual_host, attrs, false, FAConfig);

attrs->Set("check_command", cmd->GetName());
Deserialize(virtual_host, attrs, false, FAConfig);

CommandUtils::ExecuteCommandLocally(virtual_host, cmd, params);

return Empty;
}


void ClusterEvents::SuppressedNotificationsChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
{
ApiListener::Ptr listener = ApiListener::GetInstance();
Expand Down
1 change: 1 addition & 0 deletions lib/icinga/clusterevents.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class ClusterEvents
static Value AcknowledgementClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);

static Value ExecuteCommandAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
static Value ExecuteCommandWithMacrosAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);

static Dictionary::Ptr MakeCheckResultMessage(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);

Expand Down
73 changes: 73 additions & 0 deletions lib/icinga/commandutils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "icinga/commandutils.hpp"
#include "icinga/host.hpp"
#include "icinga/checkcommand.hpp"
#include "remote/apilistener.hpp"
#include "remote/httputility.hpp"

using namespace icinga;


void CommandUtils::ExecuteCommandLocally(const Checkable::Ptr& checkable, const CheckCommand::Ptr& command, const Dictionary::Ptr& params)
{

Dictionary::Ptr macros;
if (!params->Contains("macros"))
macros = new Dictionary();
else
macros = params->Get("macros");

Array::Ptr command_line_tmp = command->GetCommandLine();
Value shutdown_command_value = params->Get("shutdown_command");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value is retrieved in various places, but it never gets set. Is that a new command object attribute or is the PR incomplete?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint is called by passing a parameter called shutdown_command, which represents a valid shutdown command for the target host. An example follows:

curl -s -k -u root:admin -H 'X-HTTP-Method-Override: POST' -H 'Accept: application/json' https://localhost:5665/v1/actions/shutdown-host -d '{ "type": "Host", "filter": "host.name==\"<hostname>\"", "shutdown_command": ["systemctl", "poweroff"]}'

Array::Ptr full_command;
if (shutdown_command_value.IsObjectType<Array>()){
full_command = shutdown_command_value;
} else{
Log(LogNotice, "CommandUtils")
<< "Shutdown command must be of type Array.";
return;
}

if(full_command->GetLength() == 0){
Log(LogNotice, "CommandUtils")
<< "Shutdown command must not be empty.";
return;
}
CheckResult::Ptr cr = new CheckResult();
command->SetCommandLine(full_command);
command->Execute(checkable, cr, macros, true);
command->SetCommandLine(command_line_tmp);
}


void CommandUtils::SendCommandMessageToEndpoints(const Endpoint::Ptr& endpoint, const ApiListener::Ptr& listener, const Dictionary::Ptr& message)
{

Zone::Ptr local_zone = Zone::GetLocalZone();
Endpoint::Ptr local_endpoint = Endpoint::GetLocalEndpoint();

std::set<Endpoint::Ptr> target_endpoints;
for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
if (zone->GetParent() == local_zone) {
std::set<Endpoint::Ptr> endpoints = zone->GetEndpoints();
target_endpoints.insert(endpoints.begin(), endpoints.end());
}
}

for (const Endpoint::Ptr& e : target_endpoints) {
if (e == local_endpoint || ! e->GetConnected())
continue;
if (e == endpoint) {
Log(LogNotice, "CommandUtils") << "Sending message to target endpoint '" << e->GetName() << "'.";
listener->SyncSendMessage(e, message);
return;
}
}

for (const Endpoint::Ptr& e : target_endpoints) {
if (e == local_endpoint || ! e->GetConnected())
continue;
Log(LogNotice, "CommandUtils") << "Broadcasting message to connected endpoint '" << e->GetName() << "'.";
listener->SyncSendMessage(e, message);
}

}
23 changes: 23 additions & 0 deletions lib/icinga/commandutils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef COMMANDUTILS_H
#define COMMANDUTILS_H

#include "remote/apilistener.hpp"
#include "icinga/checkcommand.hpp"
#include "icinga/host.hpp"

namespace icinga
{

class CommandUtils
{
public:
static void ExecuteCommandLocally(const Checkable::Ptr& checkable, const CheckCommand::Ptr& command, const Dictionary::Ptr& params);
static void SendCommandMessageToEndpoints(const Endpoint::Ptr& endpoint, const ApiListener::Ptr& listener, const Dictionary::Ptr& message);

private:
CommandUtils();
};

}

#endif /* COMMANDUTILS_H */