#!/usr/bin/perl use strict; use CGI qw/:standard/; use JSON; use Data::Dumper; #added the line: #ScriptAlias /xcatws /var/www/cgi-bin/xcatws.cgi #to /etc/httpd/conf/httpd.conf to hid the cgi-bin and .cgi extension in the uri # # also upgraded CGI to 3.52 #take the JSON or XML and put it into a data structure #all data input will be done from the common structure #turn on or off the debugging output my $DEBUGGING = 0; my $q = CGI->new; my $url = $q->url; my $pathInfo = $q->path_info; my $requestType = $ENV{'REQUEST_METHOD'}; my $queryString = $ENV{'QUERY_STRING'}; my @path = split(/\//, $pathInfo); shift(@path); my $resource = $path[0]; #print $q->header('text/html'); my $request = {clienttype =>'ws'}; #error status codes my $STATUS_BAD_REQUEST = "400 Bad Request"; my $STATUS_UNAUTH = "401 Unauthorized"; my $STATUS_FORBIDDEN = "403 Forbidden"; my $STATUS_NOT_FOUND= "404 Not Found"; my $STATUS_NOT_ALLOWED = "405 Method Not Allowed"; my $STATUS_NOT_ACCEPTABLE = "406 Not Acceptable"; my $STATUS_TIMEOUT = "408 Request Timeout"; my $STATUS_EXPECT_FAILED = "417 Expectation Failed"; my $STATUS_TEAPOT = "418 I'm a teapot"; my $STATUS_SERVICE_UNAVAILABLE = "503 Service Unavailable"; #good status codes my $STATUS_OK = "200 OK"; my $STATUS_CREATED = "201 Created"; sub sendStatusMsg{ my $code = shift; my $message = shift; print $q->header(-status => $code); print $message; } sub unsupportedRequestType{ sendStatusMsg($STATUS_NOT_ALLOWED, "request method '$requestType' is not supported on resource '$resource'"); } use XML::Simple; $XML::Simple::PREFERRED_PARSER='XML::Parser'; sub genRequest{ if($DEBUGGING){ print $q->p("request ".Dumper($request)); } my $xml = XMLout($request, RootName=>'xcatrequest',NoAttr=>1,KeyAttr=>[]); } #default format my $format = 'html'; #data formatters. To add one simple copy the format of an existing one # and add it to this hash my %formatters = ('html' => \&wrapHtml, 'json' => \&wrapJson, 'xml' => \&wrapXml, ); if($q->param('format')) { $format = $q->param('format'); if(!exists $formatters{$format}){ sendStatusMsg($STATUS_BAD_REQUEST, "The format '$format' is not valid"); exit(0); } } my $XCAT_PATH = '/opt/xcat/bin'; #resource handlers my %resources = (groups => \&groupsHandler, images => \&imagesHandler, logs => \&logsHandler, monitors => \&monitorsHandler, networks => \&networksHandler, nodes => \&nodesHandler, notifications => \¬ificationsHandler, policies => \&policiesHandler, site => \&siteHandler, tables => \&tablesHandler, accounts => \&accountsHandler, objects => \&objectsHandler, vms => \&vmsHandler); #if no resource was specified if($pathInfo =~ /^\/$/ || $pathInfo =~ /^$/){ print $q->header('text/html'); print $q->p("This is the root page for the xCAT Rest Web Service. Available resources are:"); foreach (sort keys %resources){ print $q->p($_); } exit(0); } sub doesResourceExist { my $res = shift; return exists $resources{$res}; } if($DEBUGGING){ if(defined $q->param('PUTDATA')){ print "put data ".$q->p($q->param('PUTDATA')."\n"); } if(defined $q->param('POSTDATA')){ print "post data ".$q->p($q->param('POSTDATA')."\n"); } print $q->p("Parameters "); my @params = $q->param; foreach (@params) { print "$_ = ".$q->param($_)."\n"; } print $q->p("Query String $queryString"."\n"); print $q->p("HTTP Method $requestType"."\n"); print $q->p("URI $url"."\n"); print $q->p("path ".Dumper(@path)."\n"); } my $userName; my $password; sub handleRequest{ if(defined $q->param('userName')){ $userName = $q->param('userName') } if(defined $q->param('password')){ $password = $q->param('password') } if($userName && $password){ $request->{becomeuser}->[0]->{username}->[0] = $userName; $request->{becomeuser}->[0]->{password}->[0] = $password; } my @data = $resources{$resource}->(); wrapData(\@data); } my @groupFields = ('groupname', 'grouptype', 'members', 'wherevals', 'comments', 'disable'); #get is done #post and delete are done but not tested #groupfiles4dsh is done but not tested sub groupsHandler{ my @responses; my @args; my $groupName; #is the group name in the URI? if(defined $path[1]){ $groupName = $path[1]; } #in the query string? else{ $groupName = $q->param('groupName'); } if(isGet()){ if(defined $groupName){ $request->{command} = 'tabget'; push @args, "groupname=$groupName"; if(defined $q->param('field')){ foreach ($q->param('field')){ push @args, "nodegroup.$_"; } } else{ foreach (@groupFields){ push @args, "nodegroup.$_"; } } } else { $request->{command} = 'tabdump'; push @args, 'nodegroup'; } } #does it make sense to even have this? elsif(isPost()){ my $nodeRange = $q->param('nodeRange'); if(defined $groupName && defined $nodeRange){ $request->{command} = 'mkdef'; push @args, '-t'; push @args, 'group'; push @args, '-o'; push @args, $groupName; push @args, "members=$nodeRange"; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "A node range and group name must be specified for creating a group"); exit(0); } } elsif(isPut()){ #handle groupfiles4dsh -p /tmp/nodegroupfiles if($q->param('command') eq "4dsh"){ if($q->param('path')){ $request->{command} = 'groupfiles4dsh'; push @args, "p=$q->param('path')"; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The path must be specified for creating directories for dsh"); exit(0); } } else{ if(defined $groupName && defined $q->param('fields')){ $request->{command} = 'nodegrpch'; push @args, $groupName; push @args, $q->param('field'); } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The group and fields must be specified to update groups"); exit(0); } } } elsif(isDelete()){ if(defined $groupName){ $request->{command} = 'rmdef'; push @args, '-d'; push @args, 'group'; push @args, '-o'; push @args, $groupName; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The group must be specified to delete a group"); exit(0); } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } my @imageFields = ('imagename','profile','imagetype','provmethod','osname','osvers','osdistro','osarch','synclists','comments','disable'); #get is done, nothing else sub imagesHandler{ my @responses; my @args; my $image; if(defined($path[1])){ $image = $path[1]; } else{ $image = $q->param('imageName'); } if(isGet()){ if(defined $image){ #call chkosimage, but should only be used for AIX images if($q->param('checkAixImage')){ $request->{command} = 'chkosimage'; push @args, $image; } else{ $request->{command} = 'tabget'; push @args, "imagename=$image"; if(defined $q->param('field')){ foreach ($q->param('field')){ push @args, "osimage.$_"; } } else{ foreach (@groupFields){ push @args, "osimage.$_"; } } } } #no image indicated, so list all else{ $request->{command} = 'tabdump'; push @args, 'osimage'; } } elsif(isPost()){ ####genimage and related commands do not go through xcatd.... ####not supported at the moment #if($q->param('type') eq /stateless/){ #if(!defined $image){ #sendStatusMsg($STATUS_BAD_REQUEST, "The image name is required to create a stateless image"); #exit(0); #} #$request->{command} = 'genimage'; #foreach(param->{'field'}){ #} #} #else{ #if(defined $q->param('path')){ #$request->{command} = 'copycds'; #push @args, $q->param('path'); #} #} } elsif(isPut() || isPatch()){ #use chkosimage to remove any older versions of the rpms. should only be used for AIX if($q->param('cleanAixImage')){ if(defined $image){ $request->{command} = 'chkosimage'; push @args, '-c'; push @args, $image; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The image name is required to clean an os image"); } } } elsif(isDelete()){ if(defined $image){ $request->{command} = 'rmimage'; if(defined $q->param('verbose')){ push @args, '-v'; } push @args, $image; } elsif(defined $q->param('os') && defined $q->param('arch') && defined $q->param('profile')){ push @args, '-o'; push @args, $q->param('os'); push @args, '-a'; push @args, $q->param('arch'); push @args, '-p'; push @args, $q->param('profile'); } else{ sendStatusMsg($STATUS_BAD_REQUEST, "Either the image name or the os, architecture and profile must be specified to remove an image"); exit(0); } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } #complete sub logsHandler{ my @responses; my @args; my $logType; if(defined $path[1]){ $logType = $path[1]; } #in the query string? else{ $logType = $q->param('logType'); } my $nodeRange = $q->param('nodeRange'); #no real output unless the log type is defined if(!defined $logType){ print $q->p("Current logs available are auditlog and eventlog"); exit(0); } if(isGet()){ if($logType eq "reventLog"){ if(defined $nodeRange){ $request->{command} = 'reventlog'; push @args, $nodeRange; if(defined $q->param('count')){ push @args, $q->param('count'); } } else{ sendStatusMsg($STATUS_BAD_REQUEST, "nodeRange must be specified to GET remote event logs"); } } else{ $request->{command} = 'tabdump'; push @args, $logType; } } #this clears the log elsif(isPut()){ if($logType eq "reventlog"){ if(defined $nodeRange){ $request->{command} = 'reventlog'; push @args, $nodeRange; push @args, 'clear'; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "nodeRange must be specified to clean remote event logs"); } } else{ #should it return the removed entries? if(defined $q->param('showRemoved')) { push @args, '-V'; } if(defined $q->param('count') || defined $q->param('percent') || defined $q->param('lastRecord')){ #remove some of the entries $request->{command} = 'tabprune'; #remove a certain number of records if(defined $q->param('count')){ push @args, ('-n', $q->param('count')); } #remove a percentage of the records if(defined $q->param('percent')){ push @args, ('-p', $q->param('percent')); } #remove all records before this record if(defined $q->param('lastRecord')){ push @args, ('-i', $q->param('lastRecord')); } } else{ $request->{command} = 'tabprune'; #-a removes all push @args, '-a'; } } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } #complete sub monitorsHandler{ my @responses; my @args; my $monitor; if(defined $path[1]){ $monitor = $path[1]; } #in the query string? elsif(defined $q->param('monitor')){ push @args, $q->param('monitor'); } if(defined $monitor) { push @args, $monitor; } if(isGet()){ $request->{command} = 'monls'; } elsif(isPost()){ $request->{command} = 'monadd'; if($q->param('nodeStatMon')){ push @args, '-n'; } #get the plug-in specific settings array foreach ($q->param('pluginSetting')){ push @args, '-s'; push @args, $_; } } elsif(isDelete()){ $request->{command} = 'monrm' } elsif(isPut() || isPatch()){ my $action = $q->param('action'); if($action eq "start"){ $request->{command} = 'monstart'; } elsif($action eq "stop"){ $request->{command} = 'monstop'; } elsif($action eq "config"){ $request->{command} = 'moncfg'; } elsif($action eq "deconfig"){ $request->{command} = 'mondeconfig'; } else{ unsupportedRequestType(); } if(!defined $q->param('nodeRange')){ #error } else{ push @args, $q->param('nodeRange'); } if(defined $q->param('remote')){ push @args, '-r'; } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } sub networksHandler{ my @responses; my @args; if(isGet()){ $request->{command} = 'tabdump'; push @args, 'networks'; } elsif(isPut() or isPatch()){ my $subResource; if(defined $path[1]){ $subResource = $path[1]; } if($subResource eq "hosts"){ $request->{command} = 'makehosts'; #is this needed? push @args, 'all'; } elsif($subResource eq "dhcp"){ #allow restarting of the dhcp service. scary? if($q->param('command') eq "restart"){ if(isAuthenticUser()){ system('service dhcp restart'); } else{ exit(0); } } else{ $request->{command} = 'makedhcp'; foreach($q->param('field')){ push @args, $_; } } } elsif($subResource eq "dns"){ #allow restarting of the named service. scary? if($q->param('command') eq "restart"){ if(isAuthenticUser()){ system('service named restart'); } } else{ $request->{command} = 'makedns'; foreach($q->param('field')){ push @args, $_; } } } } elsif(isPost()){ } elsif(isDelete()){ } else{ unsupportedRequestType(); exit(0); } return @responses; } sub nodesHandler{ my @responses; my @args; #does it specify nodes in the URI? if(defined $path[1]){ $request->{noderange} = $path[1]; } #in the query string? elsif(defined $q->param('nodeRange')){ $request->{noderange} = $q->param('nodeRange'); } if(isGet()){ my $subResource; if(defined $path[2]){ $subResource = $path[2]; } if($subResource =~ "power"){ $request->{command} = 'rpower'; push @args, 'stat'; } elsif($subResource =~ "bootState"){ $request->{command} = 'nodeset'; push @args,'stat'; } elsif($subResource =~ "energy"){ $request->{command} = 'renergy'; #no fields will default to 'all' if(defined $q->param('field')){ foreach ($q->param('field')){ push @args, $_; } } } elsif($subResource =~ "osImage"){ } elsif($subResource =~ "status"){ $request->{command} = 'nodestat'; } elsif($subResource =~ "inventory"){ $request->{command} = 'rinv'; if(defined $q->param('field')){ push @args, $q->param('field'); } else{ push @args, 'all'; } } elsif($subResource =~ "location"){ $request->{command} = 'nodels'; push @args, 'nodepos'; } elsif($subResource =~ "vitals"){ $request->{command} = 'rvitals'; if(defined $q->param('field')){ push @args, $q->param('field'); } else{ push @args, 'all'; } } else{ $request->{command} = 'nodels'; #if the table or field is specified in the URI if(defined $subResource){ push @args, $subResource; } #maybe it's specified in the parameters else{ push @args, $q->param('field'); } } } elsif(isPut()){ my $subResource; if(defined $path[2]){ $subResource = $path[2]; } if($subResource =~ "bootState"){ $request->{command} = 'nodeset'; if(defined $q->param('boot')){ push @args, 'boot'; } if(defined $q->param('install')){ if($q->param('install')){ push @args, "install=".$q->param('install'); } else{ push @args, 'install'; } } if(defined $q->param('netboot')){ if($q->param('netboot')){ push @args, "netboot=".$q->param('netboot'); } else{ push @args, 'netboot'; } } if(defined $q->param('statelite')){ if($q->param('statelite')){ push @args, "statelite=".$q->param('statelite'); } else{ push @args, 'statelite'; } } if(defined $q->param('bmcSetup')){ push @args, "runcmd=bmcsetup"; } #can't do this #if(defined $q->param('shell')){ #push @args, 'shell'; #} } else{ sendErrorMessage($STATUS_BAD_REQUEST, "The subResource \'$request->{subResource}\' does not exist"); } } elsif(isPost()){ $request->{command} = 'nodeadd'; if(defined $q->param('groups')){ $request->{groups} = $q->param('groups'); } #since we can't predict which table fields will be passed #we just pass everything else for my $arg ($q->param){ if($arg !~ "nodeRange" && $arg !~ "groups"){ push @args, $arg; } } } elsif(isPatch()){ $request->{command} = 'nodech'; } elsif(isDelete()){ #FYI: the nodeRange for delete has to be specified in the URI $request->{command} = 'noderm'; } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } my @notificationFields = ('filename', 'tables', 'tableops', 'comments', 'disable'); #complete, unless there is some way to alter existing notifications sub notificationsHandler{ my @responses; my @args; #does not support using the notification fileName in the URI if(isGet()){ if(defined $q->param('fileName')){ $request->{command} = 'gettab'; push @args, "filename".$q->param('fileName'); #if they specified the fields, just get those if(defined $q->param('field')){ foreach ($q->param('field')){ push @args, $_; } } #else show all of the fields else{ foreach (@notificationFields){ push @args, "notification.$_"; } } } else{ $request->{command} = 'tabdump'; push @args, "notification"; } } elsif(isPost()){ $request->{command} = 'regnotif'; if(!defined $q->param('fileName') || !defined $q->param('table') || !defined $q->param('operation')){ sendStatusMsg($STATUS_BAD_REQUEST, "fileName, table and operation must be specified for a POST on /notifications"); } else{ push @args, $q->param('fileName'); my $tables; foreach ($q->param('table')){ $tables .= "$_,"; } #get rid of the extra comma chop($tables); push @args, $tables; push @args, '-o'; my $operations; foreach ($q->param('operation')){ $operations .= "$_,"; } #get rid of the extra comma chop($operations); push @args, $q->param('operation'); } } elsif(isDelete()){ $request->{command} = 'unregnotif'; if(defined $q->param('fileName')){ push @args, $q->param('fileName'); } else{ sendStatusMsg($STATUS_BAD_REQUEST, "fileName must be specified for a DELETE on /notifications"); } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; print "request is ".Dumper($request); my $req = genRequest(); @responses = sendRequest($req); return @responses; } my @policyFields = ('priority','name','host','commands','noderange','parameters','time','rule','comments','disable'); #complete sub policiesHandler{ my @responses; my @args; my $priority; #does it specify the prioirty in the URI? if(defined $path[1]){ $priority = $path[1]; } #in the query string? elsif(defined $q->param('priority')){ $priority = $q->param('priority'); } if(isGet()){ if(defined $priority){ $request->{command} = 'gettab'; push @args, "priority=$priority"; my @fields = $q->param('field'); #if they specified fields to retrieve if(@fields){ push @args, @fields; } #give them everything if nothing is specified else{ foreach (@policyFields){ push @args, "policy.$_"; } } } else{ $request->{command} = 'tabdump'; push @args, 'policy'; } } elsif(isPost()){ if(defined $priority){ $request->{command} = 'tabch'; push @args, "priority=$priority"; for ($q->param){ if($_ ne /priority/){ push @args, "policy.$_=".$q->param($_); } } } #some response about the priority being required else{ sendStatusMsg($STATUS_BAD_REQUEST, "The priority must be specified when creating a policy"); exit(0); } } elsif(isDelete()){ #just allowing a delete by priority at the moment, could expand this to anything if(defined $priority){ $request->{command} = 'tabch'; push @args, '-d'; push @args, "priority=$priority"; push @args, "policy"; } } elsif(isPut() || isPatch()){ if(defined $priority){ $request->{command} = 'tabch'; push @args, "priority=$priority"; for ($q->param){ if($_ ne /priority/){ push @args, "policy.$_=".$q->param($_); } } } #some response about the priority being required else{ sendStatusMsg($STATUS_BAD_REQUEST, "The priority must be specified when updating a policy"); exit(0); } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; print "request is ".Dumper($request); my $req = genRequest(); @responses = sendRequest($req); return @responses; } #complete sub siteHandler{ my @data; my @responses; my @args; if(isGet()){ $request->{command} = 'tabdump'; push @{$request->{arg}}, 'site'; my $req = genRequest(); @responses = sendRequest($req); } elsif(isPut() || isPatch()){ $request->{command} = 'tabch'; if(defined $q->param('PUTDATA')){ my $entries = decode_json $q->param('PUTDATA');; foreach (values %$entries){ my %fields = %$_; foreach my $key (keys %fields){ if($key =~ /key/){ #the key needs to be first unshift @args, "key=$fields{$key}"; } else{ push @args, "site.$key=$fields{$key}"; } } push @{$request->{arg}}, @args; my $req = genRequest(); my @subResponses = sendRequest($req); #TODO: look at the reponses and see if there are errors push @responses, @subResponses; } } } else{ unsupportedRequestType(); exit(); } #change response formatting foreach my $response (@responses){ foreach my $item (@{$response->{data}}){ if($item !~ /^#/) { my @values = split(/,/, $item); my %item = ( entry => $values[0], value => $values[1], comments => $values[2], disable => $values[3]); push @data, \%item; } } } return @responses; } my $formatType; #provide direct table access #complete and tested on the site table #use of the actual DELETE doesn't seem to fit here, since a resource would not be deleted #using PUT or PATCH instead, though it doesn't feel all that correct either sub tablesHandler{ my @responses; my $table; my @args; #is the table name specified in the URI? if(defined $path[1]){ $table = $path[1]; } #handle all gets if(isGet()){ $request->{command} = 'tabdump'; if(defined $q->param('desc')){ push @args, '-d'; } #table was specified if (defined $table){ push @args, $table; if(!defined $q->param('desc')){ $formatType = 'splitCommas'; } } } elsif(isPut() || isPatch()){ my $condition = $q->param('condition'); if(!defined $table || !defined $condition){ sendStatusMsg($STATUS_BAD_REQUEST, "The table and condition must be specified when adding, changing or deleting an entry"); exit(0); } $request->{command} = 'tabch'; if(defined $q->param('delete')){ push @args, '-d'; push @args, $condition; push @args, $table; } else{ push @args, $condition; for($q->param('value')){ push @args, "$table.$_"; } } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } my @accountFields = ('key', 'username', 'password', 'cryptmethod', 'comments', 'disable'); #done aside from being able to change cluster users, which xcat can't do yet sub accountsHandler{ my @responses; my @args; my $key = $q->param('key'); if(isGet()){ #passwd table if(!defined $q->param('clusterUser')){ if(defined $key){ $request->{command} = 'tabget'; push @args, "key=$key"; if(defined $q->param('field')){ foreach ($q->param('field')){ push @args, "passwd.$_"; } } else{ foreach (@accountFields){ push @args, "passwd.$_"; } } } else{ $request->{command} = 'tabdump'; push @args, 'passwd'; } } #cluster user list else{ $request->{command} = 'xcatclientnnr'; push @args, 'clusteruserlist'; push @args, '-p'; } } elsif(isPost()){ if(!defined $q->param('clusterUser')){ if(defined $key){ $request->{command} = 'tabch'; push @args, "key=$key"; for ($q->param){ if($_ !~ /key/){ push @args, "passwd.$_=".$q->param($_); } } } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The key must be specified when creating a non-cluster user"); exit(0); } } #active directory user else{ if(defined $q->param('userName') && defined $q->param('userPass')){ $request->{command} = 'xcatclientnnr'; push @args, 'clusteruseradd'; push @args, $q->param('userName'); push @{$request->{arg}}, @args; $request->{environment} = {XCAT_USERPASS => $q->param('userPass')}; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The key must be specified when creating a cluster user"); exit(0); } } } elsif(isDelete()){ if(!defined $q->param('clusterUser')){ #just allowing a delete by key at the moment, could expand this to anything if(defined $key){ $request->{command} = 'tabch'; push @args, '-d'; push @args, "key=$key"; push @args, "passwd"; } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The key must be specified when deleting a non-cluster user"); exit(0); } } else{ if(defined $q->param('userName')){ $request->{command} = 'xcatclientnnr'; push @args, 'clusteruserdel'; push @args, $q->param('userName'); } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The userName must be specified when deleting a cluster user"); exit(0); } } } elsif(isPut() || isPatch()){ if(!defined $q->param('clusterUser')){ if(defined $key){ $request->{command} = 'tabch'; push @args, "key=$key"; for ($q->param){ if($_ !~ /key/){ push @args, "passwd.$_=".$q->param($_); } } } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The key must be specified when updating a non-cluster user"); exit(0); } } #TODO: there isn't currently a way to update cluster users else{ } } else{ unsupportedRequestType(); exit(0); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } sub objectsHandler{ my @responses; my @args; my @objectTypeList = ("auditlog","boottarget","eventlog","firmware","group","monitoring","network","node","notification","osimage","policy","route","site"); #my %objectTypes; #foreach my $item (@objectTypeList) { $objectTypes{$item} = 1 } my @objectTypes; my @objects; if(defined $path[1]){ $objectTypes[0] = $path[1]; if(defined $path[2]){ $objects[0] = $path[2]; } } if(defined $q->param('objectType')){ @objectTypes = $q->param('objectType'); } if(defined $q->param('object')){ @objects = $q->param('object'); } if($q->param('verbose')){ push @args, '-v'; } if(isGet()){ if(defined $objectTypes[0]){ $request->{command} = 'lsdef'; push @args, '-l'; push @args, '-t'; push @args, join(',', @objectTypes); if(defined $objects[0]){ push @args, '-o'; push @args, join(',', @objects); } if($q->param('info')){ push @args, '-h'; } } else{ if($q->param('info')){ push @args, '-h'; } else{ #couldn't find a way to do this through xcatd, so shortcutting the request my %resp = (data => \@objectTypeList); return (\%resp); } } } elsif(isPut()){ $request->{command} = 'chdef'; if($q->param('verbose')){ push @args, '-v'; } if(!defined $q->param('objectType')){ sendStatusMsg($STATUS_BAD_REQUEST, "The object must be specified."); } else{ push @args, '-t'; push @args, join(',', $q->param('objectType')); } if($q->param('objectName')){ push @args, join(',', $q->param('objectName')); } if($q->param('dynamic')){ push @args, '-d'; } if($q->param('minus')){ push @args, '-m'; } if($q->param('plus')){ push @args, '-p'; } if(defined $q->param('field')){ foreach ($q->param('field')){ #if it has ==, !=. =~ or !~ operators in the field, use the -w option if(/==|!=|=~|!~/){ push @args, '-w'; } push @args, $_; } } if($q->param('nodeRange')){ push @args, $q->param('nodeRange'); } } elsif(isPost()){ $request->{command} = 'mkdef'; if($q->param('verbose')){ push @args, '-v'; } if(!defined $q->param('objectType')){ sendStatusMsg($STATUS_BAD_REQUEST, "The object must be specified."); } else{ push @args, '-t'; push @args, join(',', $q->param('objectType')); } if($q->param('objectName')){ push @args, join(',', $q->param('objectName')); } if($q->param('dynamic')){ push @args, '-d'; } if($q->param('force')){ push @args, '-f'; } if(defined $q->param('field')){ foreach ($q->param('field')){ #if it has ==, !=. =~ or !~ operators in the field, use the -w option if(/==|!=|=~|!~/){ push @args, '-w'; } push @args, $_; } } if($q->param('nodeRange')){ push @args, $q->param('nodeRange'); } } elsif (isDelete()){ $request->{command} = 'rmdef'; if(defined $q->param('info')){ push @args, '-h'; } elsif(defined $q->param('all')){ push @args, '-a'; } elsif(defined $objectTypes[0]){ push @args, '-t'; push @args, join(',', @objectTypes); if(defined $objects[0]){ push @args, '-o'; push @args, join(',', @objects); } } else{ sendStatusMsg($STATUS_BAD_REQUEST, "Either the help info must be requested or the object must be specified or the flag that indicates everything should be removed."); exit(0); } if(defined $q->param('nodeRange')){ push @args, $q->param('nodeRange'); } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); @responses = sendRequest($req); return @responses; } #complete i think, tho chvm could handle args better sub vmsHandler{ my @args; if(defined $q->param('nodeRange')){ $request->{noderange} = $q->param('nodeRange'); } if(defined $q->param('verbose')){ push @args, '-V'; } if(isGet()){ $request->{command} = 'lsvm'; if(defined $q->param('all')){ push @args, '-a'; } } elsif(isPost()){ if(defined $q->param('clone')){ $request->{command} = 'clonevm'; if(defined $q->param('target')){ push @args, '-t'; push @args, $q->param('target'); } if(defined $q->param('source')){ push @args, '-b'; push @args, $q->param('source'); } if(defined $q->param('detached')){ push @args, '-d'; } if(defined $q->param('force')){ push @args, '-f'; } } else{ #man page for mkvm needs updating for options $request->{command} = 'mkvm'; if(defined $q->param('cec')){ push @args, '-c'; push @args, $q->param('cec'); } if(defined $q->param('startId')){ push @args, '-i'; push @args, $q->param('startId'); } if(defined $q->param('source')){ push @args, '-l'; push @args, $q->param('source'); } if(defined $q->param('profile')){ push @args, '-p'; push @args, $q->param('profile'); } if(defined $q->param('full')){ push @args, '--full'; } #if(defined $q->param('master')){ #push @args, '-m'; #push @args, $q->param('master'); #} #if(defined $q->param('size')){ #push @args, '-s'; #push @args, $q->param('size'); #} #if(defined $q->param('force')){ #push @args, '-f'; #} } } elsif(isPut() || isPatch()){ $request->{command} = 'chvm'; if(defined $q->param('field')){ foreach ($q->param('field')){ push @args, $_; } } } elsif(isDelete()){ $request->{command} = 'rmvm'; if(defined $request->{nodeRange}){ if(defined $q->param('retain')){ push @args, '-r'; } if(defined $q->param('service')){ push @args, '--service'; } } else{ sendStatusMsg($STATUS_BAD_REQUEST, "The node range must be specified when deleting vms"); exit(0); } } else{ unsupportedRequestType(); exit(); } push @{$request->{arg}}, @args; my $req = genRequest(); my @responses = sendRequest($req); return @responses; } #for operations that take a 'long' time to finish, this will provide the interface to check their status sub jobsHandler{ } #all data wrapping and writing is funneled through here sub wrapData{ my @data = shift; #trim the serverdone message off if(exists $data[0][$#{$data[0]}]->{serverdone}){ pop @{$data[0]}; } if(exists $formatters{$format}){ $formatters{$format}->(@data); } } sub wrapJson { my @data = shift; print header('application/json'); my $json; $json->{'data'} = \@data; print to_json($json); } sub wrapHtml { my $item; my @response = shift; my $baseUri = $url.$pathInfo; if($baseUri !~ /\/^/) { $baseUri .= "/"; } print $q->header('text/html'); #print $q->p("dumping in wrapHtml ".Dumper(@response)); foreach my $data (@response){ if(@$data[0]->{error}){ if(@$data[0]->{error}[0] =~ /Permission denied/ || @$data[0]->{error}[0] =~ /Authentication failure/){ sendStatusMsg($STATUS_UNAUTH, @$data[0]->{error}[0]); } else{ sendStatusMsg($STATUS_FORBIDDEN, @$data[0]->{error}[0]); } exit(0); } else{ if(isPost()){ sendStatusMsg($STATUS_CREATED); } else{ sendStatusMsg($STATUS_OK); } } foreach my $element (@$data){ #if($element->{error}){ if($element->{node}){ print ""; foreach $item (@{$element->{node}}){ #my $url = $baseUri.$item->{name}[0]; #print ""; print ""; if(exists $item->{data} && exists $item->{data}[0]){ if(ref($item->{data}[0]) eq 'HASH'){ if(exists $item->{data}[0]->{desc} && exists $item->{data}[0]->{desc}[0]){ print ""; } if(ref($item->{data}[0]) eq 'HASH' && exists $item->{data}[0]->{contents}[0]){ print ""; } } else{ print ""; } } elsif(exists $item->{error}){ print ""; } print ""; } print "
$item->{name}[0]
$item->{name}[0]$item->{data}[0]->{desc}[0]$item->{data}[0]->{contents}[0]$item->{data}[0]$item->{error}[0]
"; } elsif($element->{data}){ print ""; foreach $item (@{$element->{data}}){ my @values = split(/:/, $item, 2); #print ""; print ""; foreach (@values){ if($formatType =~ /splitCommas/){ my @fields = split(/,/, $_,-1); foreach (@fields){ print ""; } } else{ print ""; } } print "\n"; } print "
$key$value
$_$_
"; } elsif($element->{info}){ print ""; foreach $item (@{$element->{info}}){ my @values = split(/:/, $item, 2); #print ""; print ""; foreach (@values){ if($formatType =~ /splitCommas/){ my @fields = split(/,/, $_,-1); foreach (@fields){ print ""; } } else{ print ""; } } print "\n"; } print "
$key$value
$_$_
"; #foreach $item (@{$element->{info}}){ #print $item; #} } } } } sub wrapXml { my @data = shift; print header('text/xml'); foreach(@data){ foreach(@$_){ print XMLout($_, RootName=>'',NoAttr=>1,KeyAttr=>[]); } } } #general tests for valid requests and responses with HTTP codes here if(!doesResourceExist($resource)){ sendStatusMsg($STATUS_NOT_FOUND, "Resource '$resource' does not exist"); exit(0); } else{ if($DEBUGGING){ print $q->p("resource is $resource"); } handleRequest(); } #talk to the server use Socket; use IO::Socket::INET; use IO::Socket::SSL; use lib "/opt/xcat/lib/perl"; use xCAT::Table; # The database initialization may take some time in the system boot scenario # wait for a while for the database initialization #do we really need to do this for the web service? sub sendRequest{ my $request = shift; my $sitetab; my $retries = 0; if($DEBUGGING){ my $preXml = $request; #$preXml =~ s/< /g; #$preXml =~ s/>/>
/g; print $q->p("request XML
$preXml"); } #hardcoded port for now my $port = 3001; my $xcatHost = "localhost:$port"; #temporary, will be using username and password my $homedir = "/root"; my $keyfile = $homedir."/.xcat/client-cred.pem"; my $certfile = $homedir."/.xcat/client-cred.pem"; my $cafile = $homedir."/.xcat/ca.pem"; my $client; if (-r $keyfile and -r $certfile and -r $cafile) { $client = IO::Socket::SSL->new( PeerAddr => $xcatHost, SSL_key_file => $keyfile, SSL_cert_file => $certfile, SSL_ca_file => $cafile, SSL_use_cert => 1, Timeout => 15, ); } else { $client = IO::Socket::SSL->new( PeerAddr => $xcatHost, Timeout => 15, ); } unless ($client) { if ($@ =~ /SSL Timeout/) { sendStatusMsg($STATUS_TIMEOUT, "Connection failure: SSL Timeout or incorrect certificates in ~/.xcat"); exit(0); } else{ sendStatusMsg($STATUS_SERVICE_UNAVAILABLE, "Connection failure: $@"); exit(0); } } print $client $request; my $response; my $rsp; my @fullResponse; my $cleanexit=0; while (<$client>) { $response .= $_; if (m/<\/xcatresponse>/) { #replace ESC with xxxxESCxxx because XMLin cannot handle it $response =~ s/\e/xxxxESCxxxx/g; #print "responseXML is ".$response; $rsp = XMLin($response,SuppressEmpty=>undef,ForceArray=>1); #add ESC back foreach my $key (keys %$rsp) { if (ref($rsp->{$key}) eq 'ARRAY') { foreach my $text (@{$rsp->{$key}}) { next unless defined $text; $text =~ s/xxxxESCxxxx/\e/g; } } else { $rsp->{$key} =~ s/xxxxESCxxxx/\e/g; } } $response=''; push (@fullResponse, $rsp); if ($rsp->{serverdone}) { $cleanexit=1; last; } } } unless ($cleanexit) { sendStatusMsg($STATUS_SERVICE_UNAVAILABLE, "ERROR/WARNING: communication with the xCAT server seems to have been ended prematurely"); exit(0); } if($DEBUGGING){ print $q->p("response ".Dumper(@fullResponse)); } return @fullResponse; } sub isGet{ return uc($requestType) eq "GET"; } sub isPut{ return uc($requestType) eq "PUT"; } sub isPost{ return uc($requestType) eq "POST"; } sub isPatch{ return uc($requestType) eq "PATCH"; } sub isDelete{ return uc($requestType) eq "DELETE"; } #check to see if this is a valid user. userName and password are already set sub isAuthenticUser{ $request->{command} = 'authcheck'; my $req = genRequest(); my @responses = sendRequest($req); if($responses[0]->{data}[0] eq "Authenticated"){ #user is authenticated return 1; } #authentication failure sendStatusMsg($STATUS_UNAUTH, $responses[0]->{error}[0]); return 0; }