[Gelöst] Wie eine Aktion für 170 clients per script setzen

Antworten
dark alex
Beiträge: 312
Registriert: 11 Mär 2015, 10:09

[Gelöst] Wie eine Aktion für 170 clients per script setzen

Beitrag von dark alex »

Hallo zusammen!

Ich brauch ein Brainstorming :D

Ich schreibe ein Script, das regelmäßig eine Menge Clients (0-170 Stück) auswählt und zwei Produkte auf "setup" setzen soll.

Wie gehe ich da am dümmsten ran?
Ich schreibe das Script in PHP aber für CLI-Nutzung. d.h. exec() mit opsi-admin wäre denkbar.

Was ist der beste Weg? 170 mal opsi-admin aufrufen kanns ja nich sein...

Eckdaten:
Es sind immer zwischen 0 und 170 Clients.
Die Clients sind durch einen Algorithmus ausgewählt, die Listen jedes mal unterschiedlich. Es ist nie gewollt 2x die selbe Clientliste.
Wir haben derzeit nur ein Depot
Wir haben ca 1300 Clients
Wir haben das MySQL-Backend lizenziert und im Einsatz
Zuletzt geändert von dark alex am 01 Apr 2016, 13:27, insgesamt 1-mal geändert.
Benutzeravatar
n.wenselowski
Ex-uib-Team
Beiträge: 3195
Registriert: 04 Apr 2013, 12:15

Re: Wie eine Aktion für 170 clients per script setzen

Beitrag von n.wenselowski »

Hi,

anstatt opsi-admin kannst du ja auch einen Request an die Webschnittstelle machen - dann tauchen die Sachen bspw. auf der Info-Page auch auf.
opsi-admin ist leider nicht sehr schnell (Start von Python-Interpreter + Modulimports...) und gerade bei vielen wiederholten Aufrufen bevorzuge ich opsi-admin zu vermeiden.

Du könntest es mit nur einem Call umsetzen, wenn du bspw. dir eine eigene Erweiterung baust, die für eine Menge von Clients ein Produkt auf setup setzt. Dabei kannst du dich bspw. am vorhandenen setProductActionRequest orientieren.

Zugriff direkt auf die Datenbank empfehle ich nicht, weil es eine API gibt, die bspw. auch bei Änderungen des Schemata stabil ist ;)


Gruß

Niko

Code: Alles auswählen

import OPSI
dark alex
Beiträge: 312
Registriert: 11 Mär 2015, 10:09

Re: Wie eine Aktion für 170 clients per script setzen

Beitrag von dark alex »

Hi Niko,

hast du eine Idee, warum ein URL-Aufruf am Webbrowser funktioniert aber über Curl einen 400 Bad Request wirft?

Code: Alles auswählen

[INFO]  [01.04.2016 11:30:43] Processing: ***confidential***
* Hostname was NOT found in DNS cache
*   Trying 172.16.10.10...
* Connected to 172.16.10.10 (172.16.10.10) port 4447 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSL connection using TLSv1.2 / AES256-GCM-SHA384
* Server certificate:
*        subject: C=DE; ST=BY; L=***confidential***; O=***confidential***; OU=EDV; CN=OpsiSrv.***confidential***
*        start date: 2015-02-06 13:22:49 GMT
*        expire date: 2017-11-02 13:22:49 GMT
*        issuer: C=DE; ST=BY; L=***confidential***; O=***confidential***; OU=EDV; CN=OpsiSrv.***confidential***
*        SSL certificate verify result: self signed certificate (18), continuing anyway.
* Server auth using Basic with user 'cronscript'
> GET /interface?[ { "method": "setProductActionRequest","params": ["swaudit", "***confidential***", "setup"],"id": 1 }, { "method": "setProductActionRequest","params": ["hwaudit", "***confidential***", "setup"],"id": 2 }, { "method": "hostControlSafe_fireEvent","params": ["request_graceful_reboot", "***confidential***"],"id": 3 } ] HTTP/1.1
Authorization: Basic ***confidential***
Host: 172.16.10.10:4447
Accept: */*

< HTTP/1.1 400 Bad Request
< Content-Length: 417
< Connection: close
<
* Closing connection 0
[ERROR] RPC-Api-Error 400
[DEBUG] Bad request line: GET /interface?[ { "method": "setProductActionRequest","params": ["swaudit", "***confidential***", "setup"],"id": 1 }, { "method": "setProductActionRequest","params": ["hwaudit", "***confidential***", "setup"],"id": 2 }, { "method": "hostControlSafe_fireEvent","params": ["request_graceful_reboot", "***confidential***"],"id": 3 } ] HTTP/1.1
[DEBUG]

Diese URL klappt:

Code: Alles auswählen

https://opsisrv:4447/interface?[ { "method": "setProductActionRequest","params": ["swaudit", "***confidential***", "setup"],"id": 1 }, { "method": "setProductActionRequest","params": ["hwaudit", "***confidential***", "setup"],"id": 2 }, { "method": "hostControlSafe_fireEvent","params": ["request_graceful_reboot", "***confidential***"],"id": 3 } ]

PHP-Snippet:

Code: Alles auswählen

function rpc($call, $base='interface') {
	$rpc = curl_init('https://172.16.10.10:4447/'.$base.'?'.$call);
	curl_setopt($rpc, CURLOPT_USERPWD, "cronscript:***confidential***");
	curl_setopt($rpc, CURLOPT_TIMEOUT, 15);
	curl_setopt($rpc, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($rpc, CURLOPT_VERBOSE, TRUE);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYPEER, false);
	$return = curl_exec($rpc);
	$httpcode = curl_getinfo($rpc, CURLINFO_HTTP_CODE);
	if (curl_errno($rpc)) {
		e('Curl-Error '.curl_errno($rpc).': '.curl_error($rpc));
		d(print_r(curl_getinfo($rpc), true));
		return false;
	}
	if ($httpcode != 200) e('RPC-Api-Error '.$httpcode);
	curl_close($rpc);
	return $return;
}

[...]

	d(rpc('[ { "method": "setProductActionRequest","params": ["swaudit", "'.$host.'", "setup"],"id": 1 }, { "method": "setProductActionRequest","params": ["hwaudit", "'.$host.'", "setup"],"id": 2 }, { "method": "hostControlSafe_fireEvent","params": ["request_graceful_reboot", "'.$host.'"],"id": 3 } ]'));
Benutzeravatar
n.wenselowski
Ex-uib-Team
Beiträge: 3195
Registriert: 04 Apr 2013, 12:15

Re: Wie eine Aktion für 170 clients per script setzen

Beitrag von n.wenselowski »

Hi,

kannst du mir noch den CURL-Aufruf zeigen?


Gruß

Niko

Code: Alles auswählen

import OPSI
dark alex
Beiträge: 312
Registriert: 11 Mär 2015, 10:09

Re: Wie eine Aktion für 170 clients per script setzen

Beitrag von dark alex »

Hallo Niko,

ist alles im dritten Feld versteckt :)

//Edit:

d($str) i($str) und e($str, $die=false) sind Funktionen, die effektiv nur ein Wrapper für echo sind.

$str wird farbig auf der Konsole ausgegeben und $die steht bei e(...) dafür, ob danach das Skript abgebrochen werden soll.

//Tante Edit(h) 2:
Man sollte noch erwähnen, dass ich zum testen einfach den String von /interface bis "id" : 3 } ] aus der Fehlermeldung kopiert und infirefox eingefügt habe...

//Und der dritte:
urlencode() um den JSON-Teil hilft bedingt... Jetzt bekomme ich immer noch einen 400 OpsiBadRpcError
Habs jetzt mal mit nochmals korrigiertem Aufruf gemacht:

Code: Alles auswählen

function rpc($call, $base='rpc') {
	$rpc = curl_init('https://172.16.10.10:4447/'.$base.'?'.urlencode($call));
	curl_setopt($rpc, CURLOPT_USERPWD, "cronscript:***confidential***");
	curl_setopt($rpc, CURLOPT_TIMEOUT, 15);
	curl_setopt($rpc, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($rpc, CURLOPT_VERBOSE, TRUE);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYPEER, false);
	$return = curl_exec($rpc);
	$httpcode = curl_getinfo($rpc, CURLINFO_HTTP_CODE);
	if (curl_errno($rpc)) {
		e('Curl-Error '.curl_errno($rpc).': '.curl_error($rpc));
		d(print_r(curl_getinfo($rpc), true));
		return false;
	}
	if ($httpcode != 200) e('RPC-Api-Error '.$httpcode);
	curl_close($rpc);
	return $return;
}
[TRACE] {"result": null, "id": null, "error": {"message": "Opsi bad rpc error: Failed to decode rpc: No JSON object could be decoded", "class": "OpsiBadRpcError"}}
dark alex
Beiträge: 312
Registriert: 11 Mär 2015, 10:09

Re: Wie eine Aktion für 170 clients per script setzen

Beitrag von dark alex »

Also zusammengefasst nochmals:

Funktion:

Code: Alles auswählen

function rpc($call, $base='rpc') {
	t('Calling URL: https://172.16.10.10:4447/'.$base.'?'.urlencode($call));
	$rpc = curl_init('https://172.16.10.10:4447/'.$base.'?'.urlencode($call));
	curl_setopt($rpc, CURLOPT_USERPWD, "cronscript:***oooops***");
	curl_setopt($rpc, CURLOPT_TIMEOUT, 15);
	curl_setopt($rpc, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYPEER, false);
	$return = curl_exec($rpc);
	$httpcode = curl_getinfo($rpc, CURLINFO_HTTP_CODE);
	if (curl_errno($rpc)) {
		e('Curl-Error '.curl_errno($rpc).': '.curl_error($rpc));
		d(print_r(curl_getinfo($rpc), true));
		return false;
	}
	if ($httpcode != 200) e('RPC-Api-Error '.$httpcode);
	curl_close($rpc);
	return $return;
}
Aufruf:

Code: Alles auswählen

	t(rpc('[ { "method": "setProductActionRequest","params": ["swaudit", "'.$host.'", "setup"],"id": 1 }, { "method": "setProductActionRequest","params": ["hwaudit", "'.$host.'", "setup"],"id": 2 }, { "method": "hostControlSafe_fireEvent","params": ["request_graceful_reboot", "'.$host.'"],"id": 3 } ]'));
Fehler:

Code: Alles auswählen

[INFO]  [01.04.2016 13:16:40] Processing: ***HOSTNAME***
[TRACE] Calling URL: https://172.16.10.10:4447/rpc?%5B+%7B+%22method%22%3A+%22setProductActionRequest%22%2C%22params%22%3A+%5B%22swaudit%22%2C+%22***HOSTNAME***%22%2C+%22setup%22%5D%2C%22id%22%3A+1+%7D%2C+%7B+%22method%22%3A+%22setProductActionRequest%22%2C%22params%22%3A+%5B%22hwaudit%22%2C+%22***HOSTNAME***%22%2C+%22setup%22%5D%2C%22id%22%3A+2+%7D%2C+%7B+%22method%22%3A+%22hostControlSafe_fireEvent%22%2C%22params%22%3A+%5B%22request_graceful_reboot%22%2C+%22***HOSTNAME***%22%5D%2C%22id%22%3A+3+%7D+%5D
[ERROR] RPC-Api-Error 400
[TRACE] {"result": null, "id": null, "error": {"message": "Opsi bad rpc error: Failed to decode rpc: No JSON object could be decoded", "class": "OpsiBadRpcError"}}
root@OpsiSrv:~#
Und nun die Lösung:

urlencode codiert die URL in einem Format, mit dme OPSI nichts anfangen kann... z.B. wird aus einem Leerzeichen ein + statt %20

Die Lösung ist folgende:
http://stackoverflow.com/questions/4929 ... hp/6059053

Code: Alles auswählen

function encodeURI($uri)
{
    return preg_replace_callback("{[^0-9a-z_.!~*'();,/?:@&=+$#-]}i", function ($m) {
        return sprintf('%%%02X', ord($m[0]));
    }, $uri);
}
und damit wie folgt umgesetzt:

Code: Alles auswählen

function rpc($call, $base='rpc') {
	t('Calling URL: https://172.16.10.10:4447/'.$base.'?'.encodeURI($call));
	$rpc = curl_init('https://172.16.10.10:4447/'.$base.'?'.encodeURI($call));
	curl_setopt($rpc, CURLOPT_USERPWD, "cronscript:***oooops***");
	curl_setopt($rpc, CURLOPT_TIMEOUT, 15);
	curl_setopt($rpc, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYPEER, false);
	$return = curl_exec($rpc);
	$httpcode = curl_getinfo($rpc, CURLINFO_HTTP_CODE);
	if (curl_errno($rpc)) {
		e('Curl-Error '.curl_errno($rpc).': '.curl_error($rpc));
		d(print_r(curl_getinfo($rpc), true));
		return false;
	}
	if ($httpcode != 200) e('RPC-Api-Error '.$httpcode);
	curl_close($rpc);
	return $return;
}
=> Funzt!
dark alex
Beiträge: 312
Registriert: 11 Mär 2015, 10:09

Re: [Gelöst] Wie eine Aktion für 170 clients per script setzen

Beitrag von dark alex »

So, da das Wiki mich ärgert, poste ich den Code jetzt hier...

Description

This Script can be used as a cronjob to make all clients do a hwaudit and swaudit on a regular basis.
The script does only work with MySQL-Backend (probably also works when using free variant with only inventory in DB!)

Hint: In my variant I am using an event "request_graceful_reboot" which issues a popup requesting the user to reboot. This event has to be defined on every client. You could also remove that singe RPC-Call below.

Usage
./opsi-audit-cron.php [-d] [-t]
* -d Enables Debug-Output for diagnostic
* -t Enables Trace-Output (e.g. returned Data from RPC-Call) - Does not imply Debug!

For Cronjobs you should use some output redirection. For example like that:

Code: Alles auswählen

./opsi-audit-cron.php >> /var/log/opsi/opsi-audit-cron.log

Code

cli.lib.php

Code: Alles auswählen

<?php

if (defined('DEBUG') && DEBUG) d('Debug-Mode ist aktiv!');
if (defined('TRACE') && TRACE) t('TRACE-Mode ist aktiv!');

function d($str) {
	if (!defined('DEBUG') || !DEBUG) return false;
	$ary = explode("\n", str_replace("\r", '', $str));
	foreach ($ary as $line) {
		print "\033[1;34m[DEBUG] ".$line."\033[0m\r\n";
	}
	unset ($str, $ary, $line);
}

function t($str) {
	if (!defined('TRACE') || !TRACE) return false;
	$ary = explode("\n", str_replace("\r", '', $str));
	foreach ($ary as $line) {
		print "\033[0;34m[TRACE] ".$line."\033[0m\r\n";
	}
	unset ($str, $ary, $line);
}

function i($str) {
	$ary = explode("\n", str_replace("\r", '', $str));
	foreach ($ary as $line) {
		print "\033[0;32m[INFO]  ".$line."\033[0m\r\n";
	}
	unset ($str, $ary, $line);
}

function e($str, $die=false) {
	$ary = explode("\n", str_replace("\r", '', $str));
	foreach ($ary as $line) {
		print "\033[0;31m[ERROR] ".$line."\033[0m\r\n";
	}
	if ($die){
		e("Exiting because of previous error...");
		exit(255);
	}
	unset ($str, $ary, $line);
}

//borrowed from http://stackoverflow.com/questions/4929584/encodeuri-in-php/6059053
function encodeURI($uri)
{
    return preg_replace_callback("{[^0-9a-z_.!~*'();,/?:@&=+$#-]}i", function ($m) {
        return sprintf('%%%02X', ord($m[0]));
    }, $uri);
}

?>
opsi-audit-cron.php

Code: Alles auswählen

#!/usr/bin/php
<?php
if (in_array('-d', $argv)) define('DEBUG', true);
if (in_array('-t', $argv)) define('TRACE', true);

require "cli.lib.php";
d("Connecting to DB");
$db = new mysqli('localhost', '**dbuser**', '**dbpass**', '**dbname**');

function rpc($call, $base='rpc') {
	t('Calling URL: https://**172.16.10.10**:4447/'.$base.'?'.encodeURI($call));
	$rpc = curl_init('https://**172.16.10.10**:4447/'.$base.'?'.encodeURI($call));
	curl_setopt($rpc, CURLOPT_USERPWD, "**adminuser:password**");
	curl_setopt($rpc, CURLOPT_TIMEOUT, 15);
	curl_setopt($rpc, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($rpc, CURLOPT_SSL_VERIFYPEER, false);
	$return = curl_exec($rpc);
	$httpcode = curl_getinfo($rpc, CURLINFO_HTTP_CODE);
	if (curl_errno($rpc)) {
		e('Curl-Error '.curl_errno($rpc).': '.curl_error($rpc));
		d(print_r(curl_getinfo($rpc), true));
		return false;
	}
	if ($httpcode != 200) e('RPC-Api-Error '.$httpcode);
	curl_close($rpc);
	return $return;
}

//Healthy check
d("Checking healthy of runtime");
if (mysqli_connect_errno()) e(mysqli_connect_error(), true);

d("Checking for too many clients waiting for swaudit");
$sql = $db->prepare("SELECT COUNT(clientId) as cnt FROM PRODUCT_ON_CLIENT WHERE productId='swaudit' AND actionRequest='setup' AND modificationTime > TIMESTAMP(DATE_SUB(NOW(), INTERVAL 62 DAY))");
if ($db->error) e('MySQL-Error: '.$db->error, true);
$sql->execute();
$sql->bind_result($cnt);
$sql->fetch();
if ($cnt > 350) e("Zu viele Clients ($cnt, Limit 350) warten derzeit bereits auf Software-Inventarisierung", true);
$sql->close();

d("Querying MySQL for data");
$sql = $db->prepare("SELECT c.hostId FROM HOST c LEFT JOIN PRODUCT_ON_CLIENT p ON p.clientId = c.hostId WHERE p.productId = 'swaudit' AND p.actionRequest <> 'setup' AND p.modificationTime < TIMESTAMP(DATE_SUB(NOW(), INTERVAL 14 DAY)) order by RAND() ASC LIMIT 175");
if ($db->error) e('MySQL-Error: '.$db->error, true);
$sql->execute();
$sql->bind_result($host);
d("And today's winners are...");
$pcs='';
while ($sql->fetch()) {
	set_time_limit(40); //Max. 40 Sekunden pro Client
	i('['.date('d.m.Y H:i:s').'] Processing: '.$host);
	t(rpc('[ { "method": "setProductActionRequest","params": ["swaudit", "'.$host.'", "setup"],"id": 1 }, { "method": "setProductActionRequest","params": ["hwaudit", "'.$host.'", "setup"],"id": 2 }, { "method": "hostControlSafe_fireEvent","params": ["request_graceful_reboot", "'.$host.'"],"id": 3 } ]'));
	sleep (20);
}
$sql->close();

?>

Note: you have to change ServerIP, passwords, usernames, dbname... those values are marked with asterisks
Wiki: wiki/doku.php?id=userspace:opsi-audit-cron
Benutzeravatar
n.wenselowski
Ex-uib-Team
Beiträge: 3195
Registriert: 04 Apr 2013, 12:15

Re: [Gelöst] Wie eine Aktion für 170 clients per script setzen

Beitrag von n.wenselowski »

Schön, dass es doch noch klappt!

Wer mal auf der Fehlersuche bei sowas ist: Loglevel 8 auf dem opsiconfd zeigt auch mehr Infos zu den Sachen die reinkommen und welche der opsiconfd dort wahrnimmt.


Gruß

Niko

Code: Alles auswählen

import OPSI
Antworten