2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-05-22 11:42:05 +00:00
2016-03-08 02:24:18 -05:00

1079 lines
34 KiB
Perl
Executable File

# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#-------------------------------------------------------
=head1
xCAT plugin package to handle docker
=cut
#-------------------------------------------------------
package xCAT_plugin::docker;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use POSIX qw(WNOHANG nice);
use POSIX qw(WNOHANG setsid :errno_h);
use Errno;
use MIME::Base64 qw(encode_base64);
require IO::Socket::SSL; IO::Socket::SSL->import('inet4');
use Time::HiRes qw(gettimeofday sleep);
use Fcntl qw/:DEFAULT :flock/;
use File::Path;
use File::Copy;
use File::Basename;
use Getopt::Long;
Getopt::Long::Configure("bundling");
use HTTP::Async;
use HTTP::Headers;
use HTTP::Request;
use xCAT::Utils;
use xCAT::MsgUtils;
use Cwd;
use xCAT::Usage;
use JSON;
#use Data::Dumper;
my $verbose;
my $global_callback;
my $async;
#-------------------------------------------------------
=head3 The hash variable to store node related http request id
The structure is like this
%http_session_variable = (
$session_id => $node,
);
=cut
#-------------------------------------------------------
my %http_session_variable = ();
#-------------------------------------------------------
=head3 The hash variable to store node parameters to access docker container
The structure is like this
%node_hash_variable = (
$node => {
image=>$nodetype.provmethod,
cmd=>$nodetype.provmethod,
ip=>$host.ip,
mac=>$mac.mac,
cpu=>$vm.cpus
memory=>$vm.memory
flag=>$vm.othersettings,
hostinfo=>{
name => $host,
port => $port,
},
genreq_ptr => \&genreq;
http_req_method => $init_method,
http_req_url => $node_init_url,
node_app_state => $init_state,
state_machine_engine => $state_machine_engine,
},
);
=cut
#-------------------------------------------------------
my %node_hash_variable = ();
# The num of HTTP requests that is progressing
my $http_requests_in_progress = 0;
#-------------------------------------------------------
=head3 handled_commands
Return list of commands handled by this plugin
=cut
#-------------------------------------------------------
sub handled_commands {
return( {docker=>"docker",
rpower => 'nodehm:mgt',
mkdocker => 'nodehm:mgt',
rmdocker => 'nodehm:mgt',
lsdocker => 'nodehm:mgt=docker|ipmi|kvm',
} );
}
#-------------------------------------------------------
=head3 The hash table to store mapping of commands and its state_machine_engine
The structure is like this:
command => {
option1 => {
state_machine_engine => \&state_machine_engine,
init_method => GET/POST/PUT/DELETE,
init_url => url,
},
},
=cut
#-------------------------------------------------------
my %command_states = (
rpower => {
start => {
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/containers/#NODE#/start",
init_state => "INIT_TO_WAIT_FOR_START_DONE",
},
stop => {
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/containers/#NODE#/stop",
init_state => "INIT_TO_WAIT_FOR_STOP_DONE",
},
restart => {
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/containers/#NODE#/restart",
},
pause => {
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/containers/#NODE#/pause",
},
unpause => {
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/containers/#NODE#/unpause",
},
state => {
state_machine_engine => \&single_state_engine,
init_method => "GET",
init_url => "/containers/#NODE#/json",
init_state => "INIT_TO_WAIT_FOR_QUERY_STATE_DONE",
},
},
mkdocker => {
all => {
genreq_ptr => \&genreq_for_mkdocker,
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/containers/create?name=#NODE#",
init_state => "INIT_TO_WAIT_FOR_CREATE_DONE"
},
pullimage => {
state_machine_engine => \&single_state_engine,
init_method => "POST",
init_url => "/images/create?fromImage=#DOCKER_IMAGE#",
init_state => "INIT_TO_WAIT_FOR_IMAGE_PULL_DONE",
},
},
rmdocker => {
force => {
state_machine_engine => \&single_state_engine,
init_method => "DELETE",
init_url => "/containers/#NODE#?force=1",
},
all => {
state_machine_engine => \&single_state_engine,
init_method => "DELETE",
init_url => "/containers/#NODE#",
},
},
lsdocker => {
all => {
state_machine_engine => \&single_state_engine,
init_method => "GET",
init_url => "/containers/#NODE#/json?",
init_state => "INIT_TO_WAIT_FOR_QUERY_DOCKER_DONE",
},
log => {
state_machine_engine => \&single_state_engine,
init_method => "GET",
init_url => "/containers/#NODE#/logs?stderr=1&stdout=1",
init_state => "INIT_TO_WAIT_FOR_QUERY_LOG_DONE",
},
},
);
#-------------------------------------------------------
=head3 http_state_code_info
The function to deal with http response code
Input:
$state_code: the http response code
$curr_status: the current status for the SSL connection that receive the http response
It is used for rpower start/stop since they use the same state_code 304 to indicate no modification.
Return:
A string to explain the http response code
Usage example:
http_state_code_info('304', "INIT_TO_WAIT_FOR_START_DONE") -> "Already started"
http_state_code_info('304', "INIT_TO_WAIT_FOR_STOP_DONE") -> "Already stoped"
=cut
#-------------------------------------------------------
sub http_state_code_info {
my $state_code = shift;
my $curr_status = shift;
if ($state_code =~ /20\d/) {
return [0, "success"];
}
elsif ($state_code eq '304') {
if (defined $curr_status) {
if ($curr_status eq "INIT_TO_WAIT_FOR_START_DONE") {
return [0, "container already started"];
}
else {
return [0, "container already stoped"];
}
}
else {
return [1, "unknown http status code $state_code"];
}
}
elsif ($state_code eq '404') {
return [1, "no such container"];
}
elsif ($state_code eq '406') {
return [1, "impossible to attach (container not running)"];
}
elsif ($state_code eq '500') {
return [1, "server error"];
}
return [1, "unknown http status code $state_code"];
}
#-------------------------------------------------------
=head3 change_node_state
To change node state to the state specified, and then send out the HTTP request.
Input:
$node: the node to change state
$to_state_hash: the hash which store the destination state info
Return:
Usage example:
change_node_state($node, $command_states{$command}{$option});
=cut
#-------------------------------------------------------
sub change_node_state {
my $node = shift;
my $to_state_hash = shift;
my $node_hash = $node_hash_variable{$node};
$node_hash->{http_req_method} = $to_state_hash->{init_method};
$node_hash->{http_req_url} = $to_state_hash->{init_url};
$node_hash->{node_app_state} = $to_state_hash->{init_state};
$node_hash->{state_machine_engine} = $to_state_hash->{state_machine_engine};
$node_hash->{genreq_ptr} = $to_state_hash->{genreq_ptr};
if (!defined($node_hash->{genreq_ptr})) {
$node_hash->{genreq_ptr} = \&genreq;
}
if ($node_hash->{image} =~ /:/) {
$node_hash->{http_req_url} =~ s/#DOCKER_IMAGE#/$node_hash->{image}/;
} else {
$node_hash->{http_req_url} =~ s/#DOCKER_IMAGE#/$node_hash->{image}:latest/;
}
$node_hash->{http_req_url} =~ s/#NODE#/$node/;
sendreq($node, $node_hash);
return;
}
#-------------------------------------------------------
=head3 single_state_engine
The state_machine_engine to deal with http response
Input:
$id: The http session id when adding HTTP request into HTTP::Async object
$data: The http response
Return:
If there are any errors or msg, they will be outputed directly.
Else, nothing returned.
Usage example:
single_state_engine($id, HTTP Response data);
=cut
#-------------------------------------------------------
sub single_state_engine {
my $id = shift;
my $data = shift;
my $node = $http_session_variable{$id};
if (!defined($node)) {
return;
}
my $node_hash = $node_hash_variable{$node};
my $curr_state = $node_hash->{node_app_state};
my $info_flag = 'data';
if ($data->is_error or (defined($data->header("connection")) and $data->header("connection") =~ /close/)) {
$http_requests_in_progress--;
delete($http_session_variable{$id});
}
my $content = $data->decoded_content;
my @msg = ();
$msg[0] = &http_state_code_info($data->code, $curr_state);
if ($data->is_error) {
if ($content ne '') {
$msg[0]->[1] = "$content";
}
elsif ($data->message ne '') {
$msg[0]->[1] = $data->message;
}
}
my $content_type = $data->header("content-type");
my $content_hash = undef;
if (defined($content_type) and $content_type =~ /json/i) {
if ($curr_state ne "INIT_TO_WAIT_FOR_IMAGE_PULL_DONE") {
$content_hash = decode_json $content;
}
else {
if ($content =~ /Status: Downloaded newer image/) {
}
elsif ($content =~ /\"error\":\"([^\"]*)\"/) {
@msg = ();
$msg[0] = [1, $1];
}
}
}
elsif (!defined($content_type)) {
$content_type = "undefined";
}
if ($curr_state eq "INIT_TO_WAIT_FOR_QUERY_STATE_DONE") {
if ($data->is_success) {
if ($content_type =~ /json/i) {
my $node_state = $content_hash->{'State'}->{'Status'};
if (defined($node_state)) {
$msg[0] = [0, $node_state];
}
else {
$msg[0] = [1, "Can not get status"];
}
}
else {
$msg[0] = [1, "The content type: $content_type is unable to be parsed."];
}
}
}
elsif ($curr_state eq "INIT_TO_WAIT_FOR_QUERY_LOG_DONE") {
if ($data->is_success) {
$info_flag = "base64_data";
@msg = ();
if ($content_type =~ /text\/plain/i) {
$msg[0] = [0,encode_base64($content)];
}
else {
$msg[0] = [1, "The content type: $content_type is unable to be parsed."];
}
}
}
elsif ($curr_state eq "INIT_TO_WAIT_FOR_QUERY_DOCKER_DONE") {
if ($data->is_success) {
@msg = ();
if ($content_type =~ /json/i) {
if (ref($content_hash) eq 'ARRAY') {
foreach (@$content_hash) {
push @msg, [0, parse_docker_list_info($_, 1)];
}
}
else {
push @msg, [0, parse_docker_list_info($content_hash, 0)];
}
}
if (!scalar(@msg)) {
@msg = [0, "No running docker"];
}
}
}
elsif ($curr_state eq 'INIT_TO_WAIT_FOR_CREATE_DONE') {
if ($data->code eq '404') {
# To avoid pulling image loop
if (defined($node_hash_variable{$node}->{have_pulled_image})) {
return;
}
$global_callback->({node=>[{name=>[$node],"$info_flag"=>["Pull image $node_hash_variable{$node}->{image} start"]}]});
change_node_state($node, $command_states{mkdocker}{pullimage});
return;
}
}
elsif ($curr_state eq 'INIT_TO_WAIT_FOR_IMAGE_PULL_DONE') {
if ($data->is_success and !$msg[0]->[0]) {
$global_callback->({node=>[{name=>[$node],"$info_flag"=>["Pull image $node_hash_variable{$node}->{image} done"]}]});
$node_hash_variable{$node}->{have_pulled_image} = 1;
change_node_state($node, $command_states{mkdocker}{all});
return;
}
}
foreach my $tmp (@msg) {
if ($tmp->[0]) {
$global_callback->({node=>[{name=>[$node],error=>["$tmp->[1]"],errorcode=>["$tmp->[0]"]}]});
}
else {
$global_callback->({node=>[{name=>[$node],"$info_flag"=>["$tmp->[1]"]}]});
}
}
return;
}
#-------------------------------------------------------
=head3 deal_with_space_in_array_entry
The function to add '' for entries that have spaces
Input:
$array: The string array whose entires may have spaces
Return:
A string that join the entries in input $array with space,
for entries have spaces, they will be put in "'"
Usage example:
=cut
#-------------------------------------------------------
sub deal_with_space_in_array_entry {
my $array = shift;
my @ret_array = ();
push @ret_array, shift @$array;
foreach (@$array) {
if (/\s/) {
push @ret_array, "'$_'";
}
else {
push @ret_array, $_;
}
}
return join(' ', @ret_array);
}
#-------------------------------------------------------
=head3 parse_docker_list_info
The function to parse the content returned by the lsdocker command
Input:
$docker_info_hash: The hash variable which include docker infos
The variable is decoded from JSON string
$flag: To show the info is get from dockerhost (1) or a speciifed docker (0)
Return:
docker_info_string in the format: $id $image $command $created $status $names;
Usage example:
=cut
#-------------------------------------------------------
sub parse_docker_list_info {
my $docker_info_hash = shift;
my $flag = shift; # Use the flag to check whether need to cut command
my ($id,$image,$command,$created,$status,$names);
$id = substr($docker_info_hash->{'Id'}, 0, 12);
if ($flag) {
$image = $docker_info_hash->{'Image'};
$command = $docker_info_hash->{'Command'};
$created = $docker_info_hash->{'Created'};
$status = $docker_info_hash->{'Status'};
$names = $docker_info_hash->{'Names'}->[0];
my ($sec,$min,$hour,$day,$mon,$year) = localtime($created);
$mon += 1;
$year += 1900;
$created = "$year-$mon-$day - $hour:$min:$sec";
}
else {
$image = $docker_info_hash->{Config}->{'Image'};
$command = deal_with_space_in_array_entry($docker_info_hash->{Config}->{'Cmd'});
if (defined($docker_info_hash->{Config}->{'Entrypoint'})) {
$command = deal_with_space_in_array_entry($docker_info_hash->{Config}->{'Entrypoint'});
}
$names = $docker_info_hash->{'Name'};
$created = $docker_info_hash->{'Created'};
$status = $docker_info_hash->{'State'}->{'Status'};
$created =~ s/\..*$//;
}
my $cmd = sprintf("\"%.20s\"", $command);
my $string = sprintf("%-12s %-30.30s %-22s %-20s %-10s %s", $id, $image, $cmd, $created, $status, $names);
return($string);
}
#-------------------------------------------------------
=head3 deal_with_rsp
The function to deal with SELECT
Input:
%args: a hash which currently only key 'timeout' is using
Return:
The number of response have received
Usage example:
=cut
#-------------------------------------------------------
sub deal_with_rsp
{
my %args = @_;
my $timeout = 0;
if (defined($args{timeout})) {
$timeout = $args{timeout};
}
my $deal_num = 0;
while (my ($response, $id) = $async->wait_for_next_response($timeout)) {
my $node = $http_session_variable{$id};
if (defined($node)) {
$deal_num++;
$node_hash_variable{$node}->{state_machine_engine}->($id, $response);
}
}
return $deal_num;
}
#-------------------------------------------------------
=head3 parse_args
Parse the command line options and operands
=cut
#-------------------------------------------------------
sub parse_args {
my $request = shift;
my $args = $request->{arg};
my $cmd = $request->{command}->[0];
my %opt;
#############################################
# Responds with usage statement
#############################################
local *usage = sub {
my $usage_string = xCAT::Usage->getUsage($cmd);
return( [$_[0], $usage_string] );
};
#############################################
# No command-line arguments - use defaults
#############################################
if ( !defined( $args )) {
if ($cmd eq "rpower") {
return ([1, "No option specified for rpower"]);
}
return(0);
}
#############################################
# Checks case in GetOptions, allows opts
# to be grouped (e.g. -vx), and terminates
# at the first unrecognized option.
#############################################
@ARGV = @$args;
$Getopt::Long::ignorecase = 0;
Getopt::Long::Configure( "bundling" );
#############################################
# Process command-line flags
#############################################
if (!GetOptions( \%opt,
qw(h|help V|Verbose v|version))) {
return( usage() );
}
#############################################
# Option -V for verbose output
#############################################
if ( exists( $opt{V} )) {
$verbose = 1;
}
if ($cmd eq "rpower") {
if (scalar (@ARGV) > 1) {
return ( [1, "Only one option is supportted at the same time"]);
}
elsif (!defined ($command_states{$cmd}{$ARGV[0]})) {
return ( [1, "The option $ARGV[0] not support for $cmd"]);
}
else {
@ARGV = ();
}
}
elsif ($cmd eq 'mkdocker') {
my ($image, $command);
foreach my $op (@ARGV) {
my ($key,$value) = split /=/,$op;
if ($key !~ /image|command|dockerflag/) {
return ( [1, "Option $key is not supported for $cmd"]);
}
elsif (!defined($value)) {
return ( [1, "Must set value for $key"]);
}
else {
if ($key eq 'image') {
$image = $value;
}
elsif ($key eq 'command') {
$command = $value;
}
}
}
if (!defined($image) and defined($command)) {
return ( [1, "Must set 'image' if use 'command'"]);
}
}
elsif ($cmd eq 'rmdocker') {
foreach my $op (@ARGV) {
if ($op ne '-f' and $op ne '--force') {
return ( [1, "Option $op is not supported for $cmd"]);
}
}
$request->{arg}->[0] = "force";
}
elsif ($cmd eq 'lsdocker') {
foreach my $op (@ARGV) {
if ($op ne '-l' and $op ne '--logs') {
return ( [1, "Option $op is not supported for $cmd"]);
}
}
$request->{arg}->[0] = "log";
}
return;
}
#-------------------------------------------------------
=head3 preprocess_request
preprocess the command
=cut
#-------------------------------------------------------
sub preprocess_request {
my $req = shift;
if ($req->{_xcatpreprocessed}->[0] == 1) { return [$req]; }
my $callback=shift;
my $command = $req->{command}->[0];
my $extrargs = $req->{arg};
my @exargs=($req->{arg});
if (ref($extrargs)) {
@exargs=@$extrargs;
}
my $usage_string=xCAT::Usage->parseCommand($command, @exargs);
if ($usage_string) {
$callback->({data=>[$usage_string]});
$req = {};
return;
}
####################################
# Process command-specific options
####################################
my $parse_result = parse_args( $req );
####################################
# Return error
####################################
if ( ref($parse_result) eq 'ARRAY' ) {
$callback->({error=>$parse_result->[1], errorcode=>$parse_result->[0]});
$req = {};
return ;
}
my @result = ();
my $mncopy = {%$req};
push @result, $mncopy;
return \@result;
}
#-------------------------------------------------------
=head3 process_request
Process the command
=cut
#-------------------------------------------------------
sub process_request {
my $req = shift;
my $callback = shift;
my $noderange = $req->{node};
my $command = $req->{command}->[0];
my $args = $req->{arg};
$global_callback = $callback;
# For docker create, the attributes needed are
# vm.host,cpus,memory,othersettings
# nodetype.provmethod -- the image and command the docker will use
# mac.mac
# For other command, get docker host is enough to do operation
my $init_method = undef;
my $init_url = undef;
my $init_state = undef;
my $state_machine_engine = undef;
my $genreq_ptr = undef;
my $mapping_hash = undef;
my $max_concur_session_allow = 20; # A variable can be set by caculated in the future
$mapping_hash = $command_states{$command}{$args->[0]};
unless($mapping_hash) {
$mapping_hash = $command_states{$command}{all};
}
$init_method = $mapping_hash->{init_method};
$init_url = $mapping_hash->{init_url};
$state_machine_engine = $mapping_hash->{state_machine_engine};
$init_state = $mapping_hash->{init_state};
if (!defined($init_state)) {
$init_state = "INIT_TO_WAIT_FOR_RSP";
}
$genreq_ptr = $mapping_hash->{genreq_ptr};
if (!defined($genreq_ptr)) {
$genreq_ptr = \&genreq;
}
if ($command eq 'lsdocker') {
my @new_noderange = ();
my $nodehm = xCAT::Table->new('nodehm');
if ($nodehm) {
my $nodehmhash = $nodehm->getNodesAttribs($noderange, ['mgt']);
foreach my $node (@$noderange) {
if (defined($nodehmhash->{$node}->[0]->{mgt}) and $nodehmhash->{$node}->[0]->{mgt} =~ /ipmi|kvm/) {
if (defined($args) and $args->[0] ne '') {
$callback->({error=>[" $args->[0] is not support for $node"], errorcode=>1});
return;
}
my $node_init_url = $init_url;
$node_init_url =~ s/#NODE#\///;
${$node_hash_variable{$node}}{hostinfo} = {name=>$node,port=>'2375'};
$node_hash_variable{$node}->{http_req_method} = $init_method;
$node_hash_variable{$node}->{http_req_url} = $node_init_url;
$node_hash_variable{$node}->{node_app_state} = $init_state;
$node_hash_variable{$node}->{state_machine_engine} = $state_machine_engine;
$node_hash_variable{$node}->{genreq_ptr} = $genreq_ptr;
}
else {
push @new_noderange, $node;
}
}
}
$noderange = \@new_noderange;
}
# The dockerhost is mapped to vm.host, so open vm table here
my $vmtab = xCAT::Table->new('vm');
if ($vmtab) {
my $vmhashs = $vmtab->getNodesAttribs($noderange, ['host']);
if ($vmhashs) {
my @errornodes = ();
foreach my $node (@$noderange) {
my $vmhash = $vmhashs->{$node}->[0];
if (!defined($vmhash) or !defined($vmhash->{host})) {
push @errornodes, $node;
next;
}
my ($host, $port) = split /:/,$vmhash->{host};
if (!defined($host)) {
push @errornodes, $node;
next;
}
if (!defined($port)) {
$port = 2375;
}
my $node_init_url = $init_url;
$node_init_url =~ s/#NODE#/$node/;
${$node_hash_variable{$node}}{hostinfo} = {name=>$host,port=>$port};
$node_hash_variable{$node}->{http_req_method} = $init_method;
$node_hash_variable{$node}->{http_req_url} = $node_init_url;
$node_hash_variable{$node}->{node_app_state} = $init_state;
$node_hash_variable{$node}->{state_machine_engine} = $state_machine_engine;
$node_hash_variable{$node}->{genreq_ptr} = $genreq_ptr;
}
if (scalar(@errornodes)) {
$callback->({error=>["Docker host not set correct for @errornodes"], errorcode=>1});
return;
}
}
}
else {
$callback->({error=>["Open table 'vm' failed"], errorcode=>1});
return;
}
#parse parameters for mkdocker
if ($command eq 'mkdocker') {
my ($imagearg, $cmdarg, $flagarg);
foreach (@$args) {
if (/image=(.*)$/) {
$imagearg = $1;
}
elsif (/command=(.*)$/) {
$cmdarg = $1;
}
elsif (/dockerflag=(.*)$/) {
$flagarg = $1;
}
}
my $nodetypetab = xCAT::Table->new('nodetype');
if (!defined($nodetypetab)) {
$callback->({error=>["Open table 'nodetype' failed"], errorcode=>1});
return;
}
my $hosttab = xCAT::Table->new('hosts');
if (!defined($hosttab)) {
$callback->({error=>["Open table 'hosts' failed"], errorcode=>1});
return;
}
my $mactab = xCAT::Table->new('mac');
if (!defined($mactab)) {
$callback->({error=>["Open table 'mac' failed"], errorcode=>1});
return;
}
my $nodetypehash = $nodetypetab->getNodesAttribs($noderange, ['provmethod']);
my $hosthash = $hosttab->getNodesAttribs($noderange, ['ip']);
my $machash = $mactab->getNodesAttribs($noderange, ['mac']);
my $vmhash = $vmtab->getNodesAttribs($noderange, ['cpus', 'memory', 'othersettings']);
my @errornodes = ();
foreach my $node (@$noderange) {
if ($imagearg) {
$node_hash_variable{$node}->{image} = $imagearg;
if ($cmdarg) {
$node_hash_variable{$node}->{cmd} = $cmdarg;
$nodetypetab->setNodeAttribs($node,{provmethod=>"$imagearg!$cmdarg"});
}
else {
$nodetypetab->setNodeAttribs($node,{provmethod=>"$imagearg"});
}
}
else {
if (!defined($nodetypehash->{$node}->[0]->{provmethod})) {
push @errornodes, $node;
next;
}
else {
my ($tmp_img,$tmp_cmd) = split /!/, $nodetypehash->{$node}->[0]->{provmethod};
if (!defined($tmp_img)) {
push @errornodes, $node;
next;
}
$node_hash_variable{$node}->{image} = $tmp_img;
$node_hash_variable{$node}->{cmd} = $tmp_cmd;
}
}
if ($flagarg) {
$node_hash_variable{$node}->{flag} = $flagarg;
$vmtab->setNodeAttribs($node,{othersettings=>$flagarg});
}
if (defined($hosthash->{$node}->[0]->{ip})) {
$node_hash_variable{$node}->{ip} = $hosthash->{$node}->[0]->{ip};
}
if (defined($machash->{$node}->[0]->{mac})) {
$node_hash_variable{$node}->{mac} = $machash->{$node}->[0]->{mac};
}
my $vmnodehash = $vmhash->{$node}->[0];
if (defined($vmnodehash)) {
if (defined($vmnodehash->{cpus})) {
$node_hash_variable{$node}->{cpus} = $vmnodehash->{cpus};
}
if (defined($vmnodehash->{memory})) {
$node_hash_variable{$node}->{memory} = $vmnodehash->{memory};
}
if (!defined($flagarg) and defined($vmnodehash->{othersettings})) {
$node_hash_variable{$node}->{flag} = $vmnodehash->{othersettings};
}
}
}
$nodetypetab->close;
$hosttab->close;
$mactab->close;
if (scalar(@errornodes)) {
$callback->({error=>["Docker image not set correct for @errornodes"], errorcode=>1});
return;
}
}
$vmtab->close;
init_async(slots=>$max_concur_session_allow);
my @nodeargs = keys(%node_hash_variable);
while (1) {
while ((scalar @nodeargs) and $http_requests_in_progress < $max_concur_session_allow) {
deal_with_rsp();
my $node = shift @nodeargs;
sendreq($node, $node_hash_variable{$node});
}
if ($async->empty) {
last;
}
deal_with_rsp();
}
return;
}
#-------------------------------------------------------
=head3 init_async
Creates a new HTTP::Async object and sets it up.
Input:
%args: the hash stores params to create the HTTP::Async object
slots: maximum number of parallel requests to make
Usage example:
init_async(slots=><num>)
=cut
#-------------------------------------------------------
sub init_async {
my %args = @_;
my @user = getpwuid($>);
my $homedir = $user[7];
my $ssl_ca_file = $homedir . "/.xcat/ca.pem";
my $ssl_cert_file = $homedir . "/.xcat/client-cred.pem";
my $key_file = $homedir . "/.xcat/client-cred.pem";
$async = HTTP::Async->new(
slots => $args{slots},
ssl_options => {
SSL_verify_mode => "SSL_VERIFY_PEER",
SSL_ca_file => $ssl_ca_file,
SSL_cert_file => $ssl_cert_file,
SSL_key_file => $key_file,
},
);
return;
}
#-------------------------------------------------------
=head3 genreq
Generate the docker REST API http request
Input:
$node: the docker container name
$dockerhost: hash, keys: name, port, user, pw, user, pw
$method: GET, PUT, POST, DELETE
$api: the url of rest api
$content: an xml section which including the data to perform the rest api
Return:
The REST API http request
Usage example:
my $api = "/images/json";
my $method = "GET";
my %dockerhost = ( name => "bybc0604", port => "2375", );
my $request = genreq($node, \%dockerhost, $method,$api, "");
=cut
#-------------------------------------------------------
sub genreq {
my $node = shift;
my $dockerhost = shift;
my $method = shift;
my $api = shift;
my $content = shift;
if (! defined($content)) { $content = ""; }
my $header = HTTP::Headers->new('content-type' => 'application/json',
'Accept' => 'application/json',
#'Connection' => 'keep-alive',
'Host' => $dockerhost->{name}.":".$dockerhost->{port});
$header->authorization_basic($dockerhost->{user}.'@internal', $dockerhost->{pw});
my $ctlen = length($content);
$header->push_header('Content-Length' => $ctlen);
my $url = "https://".$dockerhost->{name}.":".$dockerhost->{port}.$api;
my $request = HTTP::Request->new($method, $url, $header, $content);
$request->protocol('HTTP/1.1');
return $request;
}
#-------------------------------------------------------
=head3 genreq_for_mkdocker
Generate HTTP request for mkdocker
Input: $node: The docker container name
$dockerhost: hash, keys: name, port, user, pw, user, pw, user, pw
$method: the http method to generate the http request
$api: the url to generate the http request
return: 1-No image defined;
2-http response error;
Usage example:
my $res = genreq_for_mkdocker($node,\%dockerhost,'GET','/containers/$node/json');
=cut
#-------------------------------------------------------
sub genreq_for_mkdocker {
my ($node, $dockerhost, $method, $api) = @_;
my $dockerinfo = $node_hash_variable{$node};
my %info_hash = ();
#$info_hash{name} = '/'.$node;
#$info_hash{Hostname} = '';
#$info_hash{Domainname} = '';
$info_hash{Image} = "$dockerinfo->{image}";
@{$info_hash{Cmd}} = split/,/, $dockerinfo->{cmd};
$info_hash{Memory} = $dockerinfo->{mem};
$info_hash{MacAddress} = $dockerinfo->{mac};
$info_hash{CpusetCpus} = $dockerinfo->{cpus};
if (defined($dockerinfo->{flag})) {
my $flag_hash = decode_json($dockerinfo->{flag});
%info_hash = (%info_hash, %$flag_hash);
}
my $content = encode_json \%info_hash;
return genreq($node, $dockerhost, $method, $api, $content);
}
#-------------------------------------------------------
=head3 sendreq
Based on the method, url create a http request and send out on the given SSL connection
Input:
$node: the docker container name
$node_hash: the hash that store information for the $node
return: 0-undefine If no error
1-return generate http request failed;
2-return http request error message;
Usage example:
my $res = sendreq($node, $node_hash);
=cut
#-------------------------------------------------------
sub sendreq {
my ($node, $node_hash) = @_;
my $http_req = $node_hash->{genreq_ptr}->($node, $node_hash->{hostinfo}, $node_hash->{http_req_method}, $node_hash->{http_req_url});
# Need to Dumper to log file later
# print Dumper($http_req);
my $http_session_id = $async->add_with_opts($http_req, {});
$http_session_variable{$http_session_id} = $node;
$http_requests_in_progress++;
return undef;
}
1;