Skip to content

Commit

Permalink
fix: Refacto /now httpd interface to trigger full inventory by default
Browse files Browse the repository at this point in the history
Updated httpd interface index with a link to only trigger inventory task
  • Loading branch information
g-bougard committed Nov 28, 2024
1 parent bfae208 commit f417aac
Show file tree
Hide file tree
Showing 17 changed files with 321 additions and 64 deletions.
12 changes: 12 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ core:
configuration value including a dash from configuration file
* fix #790: Fix server URL parsing when it doesn't include scheme
* Add glpi_version configuration support to handle inventory_format dependent features
* Handle /now request as an event. If inventory task run is triggered, it will now
always by default generate a full inventory. /now supports a query string with the
following possible values:
- partial=yes or partial=1 to force a partial inventory
- full=yes or full=1 to force a full inventory (the default)
- task=all (the default) or "all" replaced by a comma-separated list of tasks
- delay=0 (the default) can be set with the number of seconds to delay the tasks run
* Changes to index page from httpd interface:
- "Force an inventory" link text is replaced by "Force running all targets planned tasks"
to reflect the real behavior of the related action
- Added a "Force an inventory" to only trigger inventory task for all targets
- Targets list also show target planned tasks for trusted clients

inventory:
* Fix rare windows perl error during drives, ipv6 network or videos inventory
Expand Down
12 changes: 10 additions & 2 deletions lib/GLPI/Agent.pm
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ sub terminate {
}

sub runTarget {
my ($self, $target) = @_;
my ($self, $target, $responses_only) = @_;

if ($target->isType('local') || $target->isType('server')) {
$self->{logger}->info("target $target->{id}: " . $target->getType() . " " . $target->getName());
Expand Down Expand Up @@ -405,6 +405,14 @@ sub runTarget {
}
}

# Used when running tasks after a taskrun event
if ($responses_only) {
return {
contact => $contact_response,
response => $response
};
}

foreach my $name (@plannedTasks) {
my $server_response = $response;
if ($contact_response) {
Expand Down Expand Up @@ -465,7 +473,7 @@ sub runTaskReal {
# init event first initiates maintenance event on deploy task
if ($self->{event} && $self->{event}->init) {
my $event = $task->newEvent();
$target->addEvent($event) if $event && $event->name;
$target->addEvent($event, 1) if $event && $event->name;
return;
}

Expand Down
65 changes: 56 additions & 9 deletions lib/GLPI/Agent/Daemon.pm
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ sub run {

# background mode: work on a targets list copy, but loop while
# the list really exists so we can stop quickly when asked for
my $responses;
while ($self->getTargets()) {
my $time = time();

Expand All @@ -126,14 +127,28 @@ sub run {
$self->_reloadConfIfNeeded();

if ($target->paused()) {

undef $responses;

# Leave immediately if we passed in terminate method
last if $self->{_terminate};

} elsif (my $event = $target->getEvent()) {

# Contact server if required and cache responses
if ($event->taskrun) {
if (!defined($responses)) {
eval {
$responses = $self->runTarget($target, "contact-only");
};
}
} else {
undef $responses;
}

my $net_error = 0;
eval {
$net_error = $self->runTargetEvent($target, $event);
$net_error = $self->runTargetEvent($target, $event, $responses);
};
$logger->error($EVAL_ERROR) if ($EVAL_ERROR && $logger);
if ($net_error) {
Expand All @@ -150,6 +165,8 @@ sub run {

} elsif ($time >= $target->getNextRunDate()) {

undef $responses;

my $net_error = 0;
eval {
$net_error = $self->runTarget($target);
Expand Down Expand Up @@ -210,26 +227,56 @@ sub _reloadConfIfNeeded {
}

sub runTargetEvent {
my ($self, $target, $event) = @_;

return unless $event->name && $event->task;
my ($self, $target, $event, $responses) = @_;

# Just ignore event if invalid
return 0 unless $event && $event->name && $event->task;

my $task = $event->task;
my %modulesmap = qw(
netdiscovery NetDiscovery
netinventory NetInventory
remoteinventory RemoteInventory
esx ESX
wakeonlan WakeOnLan
);
my $realtask = $modulesmap{$task} || ucfirst($task);

$self->{logger}->debug("target ".$target->id().": ".$event->name()." event for ".$event->task()." task");
$self->{logger}->debug("target ".$target->id().": ".$event->name()." event for $realtask task")
unless $event->runnow;

$self->{event} = $event;

if ($event && $event->init) {
if ($event->init) {
eval {
# We don't need to fork for init event
$self->runTaskReal($target, ucfirst($event->task));
$self->runTaskReal($target, $realtask);
};

} elsif ($event->runnow) {
$target->triggerRunTasksNow($event);

} elsif ($responses) {
my $server_response = $responses->{response};
if ($responses->{contact}) {
# Be sure to use expected response for task
my $task_server = $target->getTaskServer($task) // 'glpi';
$server_response = $responses->{contact}
if $task_server eq 'glpi';
}
eval {
$self->runTask($target, $realtask, $server_response);
};
$self->{logger}->error($EVAL_ERROR) if $EVAL_ERROR;
$self->setStatus($target->paused() ? 'paused' : 'waiting');

} else {
# Simulate CONTACT server response
my $contact = GLPI::Agent::Protocol::Contact->new(
tasks => { $event->task => { params => [ $event->params ] }}
tasks => { $task => { params => [ $event->params ] }}
);
eval {
$self->runTask($target, ucfirst($event->task), $contact);
$self->runTask($target, $realtask, $contact);
};
$self->{logger}->error($EVAL_ERROR) if $EVAL_ERROR;
$self->setStatus($target->paused() ? 'paused' : 'waiting');
Expand Down
81 changes: 78 additions & 3 deletions lib/GLPI/Agent/Event.pm
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,80 @@ sub new {
# Check for supported events
# 1. init:
# - init event are triggered when the service starts
# 2. partial inventory:
# - can't be triggered via http request
# 2. runnow:
# - can be trigerred by /now http requests from agent HTTP UI or GLPI
# - must be evaluated before partial inventory as it can use "partial" param
# - can be used to trigger one or more tasks via task or tasks param
# - can be delayed using delay param to provide a delay value in seconds
# - inventory task can be forced to full by setting full param to "1" or "yes"
# 3. taskrun:
# - can be trigerred by /now http requests or following a runnow event
# - must be evaluated before partial inventory as it can use "partial" param
# - task is mandatory and can only be used to trigger a planned task
# - can be delayed using delay param to provide a delay value in seconds
# - for inventory task:
# - full or partial params can be to "1" or "yes"
# - full default is 1 and has precedence other partial if the 2 are defined
# 4. partial inventory:
# - can be trigerred by /now http requests
# - can be requested by glpi-inventory run via --partial parameter
# 3. maintenance: internal event to trigger maintenance needs
# 5. maintenance: internal event to trigger maintenance needs
# - for deploy, it cleans up storage from file parts when too old
# 4. job:
# - can't be triggered via http request
# 6. job:
# - job events are created by toolbox managed inventory tasks
# - can't be triggered via http request
if ($params{init} && $params{init} =~ /^yes|1$/i) {
# Partial inventory request on given categories
$self = {
_init => 1,
_task => $params{task} // '',
_name => "init",
_rundate => $params{rundate} // 0,
_httpd => 0,
};
} elsif ($params{runnow} && $params{runnow} =~ /^yes|1$/i) {
# Run now request
$self = {
_runnow => 1,
_name => "run now",
_task => $params{task} || $params{tasks} || 'all',
_delay => $params{delay} // 0,
_httpd => 1,
};
# Store any other params
foreach my $key (grep { $_ !~ /^runnow|tasks?|delay$/ } keys(%params)) {
$self->{_params}->{$key} = $params{$key} // "";
}
} elsif ($params{taskrun} && $params{taskrun} =~ /^yes|1$/i) {
# Run task request eventually following a runnow event
$self = {
_taskrun => 1,
_name => "run",
_task => $params{task} // '',
_delay => $params{delay} // 0,
_httpd => 1,
};
# Store any other supported params
if ($params{task} && $params{task} eq "inventory") {
if (defined($params{full})) {
$self->{_params}->{full} = $params{full} =~ /^yes|1$/i ? 1 : 0;
} elsif (defined($params{partial})) {
$self->{_params}->{full} = $params{partial} =~ /^yes|1$/i ? 0 : 1;
} else {
# Always default to full when full and partial are not set
$self->{_params}->{full} = 1;
}
}
} elsif ($params{partial} && $params{partial} =~ /^yes|1$/i) {
# Partial inventory request on given categories
$self = {
_partial => 1,
_task => "inventory",
_name => "partial inventory",
_category => $params{category} // '',
_httpd => 1,
};
# Store any other params (can be used by database partial inventory requests)
foreach my $key (grep { $_ ne 'partial' } keys(%params)) {
Expand All @@ -50,13 +102,15 @@ sub new {
_task => $params{task} // '',
_name => $params{name} // "maintenance",
_delay => $params{delay} // 0,
_httpd => 0,
};
} elsif ($params{name} && $params{job} && $params{job} =~ /^yes|1$/i) {
$self = {
_job => 1,
_name => $params{name},
_rundate => $params{rundate} // 0,
_task => $params{task} // 'unknown',
_httpd => 0,
};
}

Expand Down Expand Up @@ -95,6 +149,16 @@ sub job {
return $self->{_job} // 0;
}

sub runnow {
my ($self) = @_;
return $self->{_runnow} // 0;
}

sub taskrun {
my ($self) = @_;
return $self->{_taskrun} // 0;
}

# Event attributes

sub task {
Expand All @@ -117,6 +181,12 @@ sub params {
return $self->{_params} // {};
}

sub get {
my ($self, $key) = @_;
return unless $key && ref($self->{_params}) eq "HASH";
return $self->{_params}->{$key};
}

sub delay {
my ($self) = @_;
return $self->{_delay} // 0;
Expand All @@ -130,6 +200,11 @@ sub rundate {
return $self->{_rundate} // 0;
}

sub httpd_support {
my ($self) = @_;
return $self->{_httpd} // 0;
}

sub dump_as_string {
my ($self) = @_;
my $dump = $self->dump_for_message();
Expand Down
50 changes: 31 additions & 19 deletions lib/GLPI/Agent/HTTP/Server.pm
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ sub _handle_root {
grep { $_->isType('local') }
$self->{agent}->getTargets();

my %planned_tasks = ();
if ($trust) {
map {
$planned_tasks{$_->id()} = join(", ", $_->plannedTasks());
} $self->{agent}->getTargets();
}

my @listening_plugins = ();
my %plugins_url = ();
if ($trust) {
Expand Down Expand Up @@ -376,6 +383,7 @@ sub _handle_root {
server_targets => \@server_targets,
local_targets => \@local_targets,
sessions => \@sessions,
planned_tasks => \%planned_tasks,
};

my $response = HTTP::Response->new(
Expand Down Expand Up @@ -478,26 +486,30 @@ sub _handle_now {
$headers->header('Content-Type' => 'text/html');

if (@targets) {
my $query = uri_unescape($request->uri()->query());
if ($query) {
my %event = map { /^([^=]+)=(.*)$/ } grep { /[^=]=/ } split('&', $query);
foreach my $target (@targets) {
my $id = $target->id;
my $event = GLPI::Agent::Event->new(%event);
next if $event->target && $event->target ne $target->id;
# Only support partial event requests via /now
if ($event->name && $event->partial && $target->addEvent($event)) {
$logger->debug($log_prefix.$event->name." triggering event on $id");
} else {
($code, $message, $trace) = (
400, "Bad request",
"unsupported event for $id target: ".($event->name ? $event->dump_as_string() : substr($query, 0, 255))
);
}
}
my $query = uri_unescape($request->uri()->query()) || "";
my %event = map { /^([^=]+)=(.*)$/ } grep { /[^=]=/ } split('&', $query);
# Support runnow with partial set without category
$event{runnow} = "yes" if empty($query) || !$event{"partial"} || !$event{category};
my $event = GLPI::Agent::Event->new(%event);
if ($event->runnow) {
$trace = "rescheduling next contact for all targets";
$trace .= $event->delay > 0 ? " in ".$event->delay."s" : " right now";
$trace .= " for ".$event->task unless $event->task && $event->task eq "all";
} else {
map { $_->setNextRunDateFromNow() } @targets;
$trace = "rescheduling next contact for all targets right now";
$trace = "rescheduling next run for ".$event->name." event";
}
foreach my $target (@targets) {
my $id = $target->id // "";
next if $event->target && $event->target ne $id;
if ($event->name && $event->httpd_support && $target->addEvent($event)) {
$logger->debug($log_prefix.$event->name." triggering event on $id");
} else {
($code, $message, $trace) = (
400, "Bad request",
"unsupported event for $id target: ".($event->name ? $event->dump_as_string() : substr($query, 0, 255))
);
last
}
}
} else {
$code = 403;
Expand Down
Loading

0 comments on commit f417aac

Please sign in to comment.