From 5bab3873a2f03e3d0920f7e55ac5c37d15206d33 Mon Sep 17 00:00:00 2001 From: Chuck Brazie Date: Mon, 20 Feb 2017 15:33:44 -0500 Subject: [PATCH] Add in version 1 and version 2 REST-API into zVM. Default zVM to version 1 in the zvmxcatws.cgi Change-Id: I2ad620ded8f4f3bff997bf308881cbeabd61022b --- xCAT-server/xCAT-server.spec | 9 + xCAT-server/xCAT-wsapi/xcat-ws.conf.apache2 | 3 +- xCAT-server/xCAT-wsapi/xcat-ws.conf.apache22 | 3 +- xCAT-server/xCAT-wsapi/xcat-ws.conf.apache24 | 3 +- xCAT-server/xCAT-wsapi/zvmxcatws.cgi | 2521 ++++++++++++++++++ 5 files changed, 2536 insertions(+), 3 deletions(-) create mode 100644 xCAT-server/xCAT-wsapi/zvmxcatws.cgi diff --git a/xCAT-server/xCAT-server.spec b/xCAT-server/xCAT-server.spec index 6299f405f..2013dcf11 100644 --- a/xCAT-server/xCAT-server.spec +++ b/xCAT-server/xCAT-server.spec @@ -347,6 +347,7 @@ cp xCAT-wsapi/* $RPM_BUILD_ROOT/%{prefix}/ws # xcatws.cgi causes xCAT-server requires perl-JSON, which is not shipped with PCM %if %pcm rm -f $RPM_BUILD_ROOT/%{prefix}/ws/xcatws.cgi +rm -f $RPM_BUILD_ROOT/%{prefix}/ws/zvmxcatws.cgi %endif %if %fsm @@ -354,8 +355,16 @@ rm -f $RPM_BUILD_ROOT/%{prefix}/ws/xcatws.cgi echo "ScriptAlias /xcatrhevh %{prefix}/ws/xcatrhevh.cgi" > $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache22 echo "ScriptAlias /xcatrhevh %{prefix}/ws/xcatrhevh.cgi" > $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache24 %if %notpcm +%if %nots390x echo "ScriptAlias /xcatws %{prefix}/ws/xcatws.cgi" >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache22 echo "ScriptAlias /xcatws %{prefix}/ws/xcatws.cgi" >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache24 +%else +# Add in old version 1 REST-API and version 2 REST-API to z/VM, default to version 1 +echo "ScriptAlias /xcatwsv2 %{prefix}/ws/xcatws.cgi" >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache22 +echo "ScriptAlias /xcatwsv2 %{prefix}/ws/xcatws.cgi" >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache24 +echo "ScriptAlias /xcatws %{prefix}/ws/zvmxcatws.cgi" >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache22 +echo "ScriptAlias /xcatws %{prefix}/ws/zvmxcatws.cgi" >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache24 +%endif %endif cat $RPM_BUILD_ROOT/%{prefix}/ws/xcat-ws.conf.apache22 >> $RPM_BUILD_ROOT/etc/%httpconfigdir/conf.orig/xcat-ws.conf.apache22 diff --git a/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache2 b/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache2 index 7b245776d..21ce5b943 100644 --- a/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache2 +++ b/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache2 @@ -5,8 +5,9 @@ RewriteEngine On RewriteCond %{SERVER_PORT} 80 RewriteCond %{HTTPS} !=on RewriteRule ^/?xcatws/(.*) https://%{SERVER_NAME}/xcatws/$1 [R,L] +RewriteRule ^/?xcatwsv2/(.*) https://%{SERVER_NAME}/xcatwsv2/$1 [R,L] - + Order allow,deny Allow from all diff --git a/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache22 b/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache22 index 7b245776d..21ce5b943 100644 --- a/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache22 +++ b/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache22 @@ -5,8 +5,9 @@ RewriteEngine On RewriteCond %{SERVER_PORT} 80 RewriteCond %{HTTPS} !=on RewriteRule ^/?xcatws/(.*) https://%{SERVER_NAME}/xcatws/$1 [R,L] +RewriteRule ^/?xcatwsv2/(.*) https://%{SERVER_NAME}/xcatwsv2/$1 [R,L] - + Order allow,deny Allow from all diff --git a/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache24 b/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache24 index 7aa4f20f1..7fc48853c 100644 --- a/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache24 +++ b/xCAT-server/xCAT-wsapi/xcat-ws.conf.apache24 @@ -3,8 +3,9 @@ RewriteEngine On RewriteCond %{SERVER_PORT} 80 RewriteCond %{HTTPS} !=on RewriteRule ^/?xcatws/(.*) https://%{SERVER_NAME}/xcatws/$1 [R,L] +RewriteRule ^/?xcatwsv2/(.*) https://%{SERVER_NAME}/xcatwsv2/$1 [R,L] - + Require all granted diff --git a/xCAT-server/xCAT-wsapi/zvmxcatws.cgi b/xCAT-server/xCAT-wsapi/zvmxcatws.cgi new file mode 100644 index 000000000..84701929a --- /dev/null +++ b/xCAT-server/xCAT-wsapi/zvmxcatws.cgi @@ -0,0 +1,2521 @@ +#!/usr/bin/perl +use strict; +use CGI qw/:standard/; +#use JSON; # require this dynamically later on so that installations that do not use xcatws.cgi do not need perl-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 $VERSION = "2.8"; + +my $q = CGI->new; +my $url = $q->url; +my $pathInfo = $q->path_info; +my $requestType = $ENV{'REQUEST_METHOD'}; +my $queryString = $ENV{'QUERY_STRING'}; +my %queryhash; +my @path = split(/\//, $pathInfo); +shift(@path); +my $resource = $path[0]; +my $pageContent = ''; +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"; + +#default format +my $format = 'html'; + +sub addPageContent { + my $newcontent = shift; + $pageContent .= $newcontent; +} + +#send the response to client side +#the http only return once in each request, so all content shoudl save in a global variable, +#create the response header by status +sub sendResponseMsg { + my $code = shift; + my $tempFormat = ''; + if ('json' eq $format) { + $tempFormat = 'application/json'; + } + elsif ('xml' eq $format) { + $tempFormat = 'text/xml'; + } + else { + $tempFormat = 'text/html'; + } + print $q->header(-status => $code, -type => $tempFormat); + print $pageContent; + exit(0); +} + +sub unsupportedRequestType { + addPageContent("request method '$requestType' is not supported on resource '$resource'"); + sendResponseMsg($STATUS_NOT_ALLOWED); +} + +use XML::Simple; +$XML::Simple::PREFERRED_PARSER = 'XML::Parser'; + +sub genRequest { + if ($DEBUGGING) { + addPageContent($q->p("request " . Dumper($request))); + } + my $xml = XMLout($request, RootName => 'xcatrequest', NoAttr => 1, KeyAttr => []); +} + +#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,); + +fetchParameter($queryString); + +if ($queryhash{'format'}) { + $format = $queryhash{'format'}->[0]; + if (!exists $formatters{$format}) { + addPageContent("The format '$format' is not valid"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if ($format eq 'json') { + # require JSON dynamically and let them know if it is not installed + my $jsoninstalled = eval { require JSON; }; + unless ($jsoninstalled) { + addPageContent('{"data":"JSON perl module missing. Install perl-JSON before using the xCAT REST web services API."}'); + sendResponseMsg($STATUS_SERVICE_UNAVAILABLE); + } + } +} + +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, + debug => \&debugHandler, + hypervisor => \&hypervisorHandler, + version => \&versionHandler); + +#if no resource was specified +if ($pathInfo =~ /^\/$/ || $pathInfo =~ /^$/) { + addPageContent($q->p("This is the root page for the xCAT Rest Web Service. Available resources are:")); + foreach (sort keys %resources) { + addPageContent($q->p($_)); + } + sendResponseMsg($STATUS_OK); +} + +sub doesResourceExist { + my $res = shift; + return exists $resources{$res}; +} + +if ($DEBUGGING) { + if (defined $q->param('PUTDATA')) { + addPageContent("put data " . $q->p($q->param('PUTDATA') . "\n")); + } elsif (isPut()) { + my $entries = JSON::decode_json($q->param('PUTDATA')); + if (scalar(@$entries) >= 1) { + addPageContent("put data \n"); + foreach (@$entries) { + addPageContent("$_\n"); + } + } + } + + if (defined $q->param('POSTDATA')) { + addPageContent("post data " . $q->p($q->param('POSTDATA') . "\n")); + } elsif (isPost()) { + my $entries = JSON::decode_json($q->param('POSTDATA')); + if (scalar(@$entries) >= 1) { + addPageContent("post data \n"); + foreach (@$entries) { + addPageContent("$_\n"); + } + } + } + + addPageContent($q->p("Parameters ")); + my @params = $q->param; + foreach (@params) { + addPageContent("$_ = " . join(',', $q->param($_)) . "\n"); + } + addPageContent($q->p("Query String $queryString" . "\n")); + addPageContent($q->p("Query parameters from the Query String" . Dumper(\%queryhash) . "\n")); + addPageContent($q->p("HTTP Method $requestType" . "\n")); + addPageContent($q->p("URI $url" . "\n")); + addPageContent($q->p("path " . Dumper(@path) . "\n")); +} + +#when use put and post, can not fetch the url-parameter, so add this sub to support all kinks of method +sub fetchParameter { + my $parstr = shift; + unless ($parstr) { + return; + } + + my @pairs = split(/&/, $parstr); + foreach my $pair (@pairs) { + my ($key, $value) = split(/=/, $pair, 2); + $value =~ tr/+/ /; + $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/chr(hex($1))/eg; + push @{$queryhash{$key}}, $value; + } +} + +#extract the put data or post data into perl hash, easy for retrieve +sub extractData { + my $temphash = shift; + my $parArray = shift; + my $key; + my $value; + my $position; + + #traversal all element in the array + foreach (@$parArray) { + $position = index($_, '='); + if ($position < 0) { + $key = $_; + $value = 1; + } + else { + $key = substr $_, 0, $position; + $value = substr $_, $position + 1; + } + $temphash->{$key} = $value; + + if ($DEBUGGING) { + addPageContent($q->p("The parameter extract from put/post data:
" . Dumper($temphash))); + } + } +} + +my $userName=http('userName'); +my $password=http('password'); + +sub handleRequest { + if (defined $queryhash{'userName'}) { + $userName = $queryhash{'userName'}->[0]; + } + if (defined $queryhash{'password'}) { + $password = $queryhash{'password'}->[0]; + } + 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} = 'gettab'; + 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 { + addPageContent("A node range and group name must be specified for creating a group"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + 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 { + addPageContent("The path must be specified for creating directories for dsh"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + else { + if (defined $groupName && defined $q->param('fields')) { + $request->{command} = 'nodegrpch'; + push @args, $groupName; + push @args, $q->param('field'); + } + else { + addPageContent("The group and fields must be specified to update groups"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + } + elsif (isDelete()) { + if (defined $groupName) { + $request->{command} = 'rmdef'; + push @args, '-t'; + push @args, 'group'; + push @args, '-o'; + push @args, $groupName; + } + else { + addPageContent("The group must be specified to delete a group"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + 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; + my $subResource; + + if (defined($path[1])) { + $image = $path[1]; + } + + if (isGet()) { + $request->{command} = 'lsdef'; + push @args, '-t', 'osimage'; + if (defined $image) { + push @args, '-o', $image; + } + if (defined($q->param('field'))) { + push @args, '-i'; + push @args, join(',', $q->param('field')); + } + if (defined($q->param('criteria'))) { + foreach ($q->param('criteria')) { + push @args, '-w', "$_"; + } + } + } + elsif (isPost()) { + my $operationname = $image; + my $entries; + my %entryhash; + + #check the post data + unless (defined($q->param('POSTDATA'))) { + addPageContent("Invalid Parameters"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + $entries = JSON::decode_json($q->param('POSTDATA')); + if (scalar(@$entries) < 1) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + extractData(\%entryhash, $entries); + + #for image capture + if ($operationname eq 'capture') { + $request->{command} = 'imgcapture'; + if (defined($entryhash{'nodename'})) { + $request->{noderange} = $entryhash{'nodename'}; + } + else { + addPageContent('No node range.'); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if (defined($entryhash{'profile'})) { + push @args, '-p'; + push @args, $entryhash{'profile'}; + } + if (defined($entryhash{'osimage'})) { + push @args, '-o'; + push @args, $entryhash{'osimage'}; + } + if (defined($entryhash{'bootinterface'})) { + push @args, '-i'; + push @args, $entryhash{'bootinterface'}; + } + if (defined($entryhash{'netdriver'})) { + push @args, '-n'; + push @args, $entryhash{'netdriver'}; + } + if (defined($entryhash{'device'})) { + push @args, '-d'; + push @args, $entryhash{'device'}; + } + if (defined($entryhash{'compress'})) { + push @args, '-c'; + push @args, $entryhash{'compress'}; + } + } + elsif ($operationname eq 'export') { + $request->{command} = 'imgexport'; + if (defined($entryhash{'osimage'})) { + push @args, $entryhash{'osimage'}; + } + else { + addPageContent('No image specified'); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if (defined($entryhash{'destination'})) { + push @args, $entryhash{'destination'}; + } + if (defined($entryhash{'postscripts'})) { + push @args, '-p'; + push @args, $entryhash{'postscripts'}; + } + if (defined($entryhash{'extra'})) { + push @args, '-e'; + push @args, $entryhash{'extra'}; + } + if (defined($entryhash{'remotehost'})) { + push @args, '-R'; + push @args, $entryhash{'remotehost'}; + } + if (defined($entryhash{'verbose'})) { + push @args, '-v'; + } + } + elsif ($operationname eq 'import') { + $request->{command} = 'imgimport'; + if (defined($entryhash{'osimage'})) { + push @args, $entryhash{'osimage'}; + } + else { + addPageContent('No image specified'); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if (defined($entryhash{'profile'})) { + push @args, '-f'; + push @args, $entryhash{'profile'}; + } + if (defined($entryhash{'remotehost'})) { + push @args, '-R'; + push @args, $entryhash{'remotehost'}; + } + if (defined($entryhash{'postscripts'})) { + push @args, '-p'; + push @args, $entryhash{'postscripts'}; + } + if (defined($entryhash{'nozip'})) { + push @args, '-n'; + } + + if (defined($entryhash{'verbose'})) { + push @args, '-v'; + } + } + } + elsif (isPut()) { + + #check the operation type + unless (defined $path[2]) { + addPageContent("The subResource $subResource does not exist"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + $subResource = $path[2]; + + #check the image name + unless (defined $image) { + addPageContent("The image name is required to clean an os image"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if ($subResource eq 'check') { + $request->{command} = 'chkosimage'; + if (defined($q->param('PUTDATA'))) { + push @args, '-c'; + } + push @args, $image; + } + else { + addPageContent("The subResource $subResource does not exist"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + 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 { + addPageContent( + "Either the image name or the os, architecture and profile must be specified to remove an image"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + 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) { + addPageContent("Current logs available are auditlog, eventlog, and diagnostics"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + 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 { + addPageContent("nodeRange must be specified to GET remote event logs"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + elsif ($logType eq "diagnostics") { + addPageContent(uc($requestType) . " remote diagnostic logs is not supported"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + 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 { + addPageContent("nodeRange must be specified to clean remote event logs"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + elsif ($logType eq "diagnostics") { + addPageContent(uc($requestType) . " remote diagnostic logs is not supported"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + 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'; + } + } + } + # Currently, only diagnostic logs can be created + elsif (isPost()) { + if ($logType eq "diagnostics") { + + # Potential base bug on the reventlog and auditlog paths: + # $q->param('nodeRange') != $queryhash{'nodeRange'} + # The base code uses the first, which is undef even when a URL + # query parameter named nodeRange was passed by the caller (e.g. is + # found in the nova-compute.log traces). + # Re-assigning nodeRange on this new path since it does return + # a value. + + $nodeRange = $queryhash{'nodeRange'}; + if (defined $nodeRange) { + $request->{command} = 'diagnostics'; + $request->{noderange} = $nodeRange; + + my $parameter; + # Parse the optional upstream request ID, e.g. an OpenStack request UUID + $parameter = 'requestid'; + if (defined $queryhash{$parameter}) { + push @args, '--'.$parameter; + push @args, $queryhash{$parameter}->[0]; + } + + # Parse the optional upstream object ID, e.g. an OpenStack nova instance UUID + $parameter = 'objectid'; + if (defined $queryhash{$parameter}) { + push @args, '--'.$parameter; + push @args, $queryhash{$parameter}->[0]; + } + + unless ($q->param('POSTDATA')) { + addPageContent("A request body containing parameters is required"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + # Collect all parameters from the postdata + my %entryhash; + my $entries = JSON::decode_json($q->param('POSTDATA')); + if (scalar(@$entries) < 1) { + addPageContent("No request parameters were supplied in the message body."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + extractData(\%entryhash, $entries); + # TODO: add parameter parsing back in to pass through all body parameters. + # How to handle duplicate property names? Perhaps prefix them with upstream_ , and pass the prefix too ;-) + + # Parse the optional body parameters from the caller (NOT URL query parameters) + # TODO: this is early code that hard-codes a single key passed by the + # z/VM OpenStack nova plugin. It will be replaced later with more + # general code that behaves similarly. + $parameter = 'reason'; + if (defined $entryhash{$parameter}) { + push @args, '--upstream-'.$parameter; + push @args, $entryhash{$parameter}; + } + + } + else { + addPageContent("nodeRange must be specified to collect diagnostics"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + else { + addPageContent("POST is only valid for logType(s): diagnostics. It is not valid with the supplied logType: $logType"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + 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; + my $netname = ''; + + if (isGet()) { + $request->{command} = 'lsdef'; + push @{$request->{arg}}, '-t', 'network'; + if (defined($path[1])) { + push @{$request->{arg}}, '-o', $path[1]; + } + my @temparray = $q->param('field'); + + #add the field name to get + if (scalar(@temparray) > 0) { + push @{$request->{arg}}, '-i'; + push @{$request->{arg}}, join(',', @temparray),; + } + } + elsif (isPut() || isPost()) { + my $entries; + my $iscommand = 0; + if (isPut()) { + $request->{command} = 'chdef'; + if (defined($path[1])) { + if ($path[1] eq "makehosts" || $path[1] eq "makedns") { + # Issue makehost/makedns directly + $request->{command} = $path[1]; + $iscommand = 1; + } + } + } + else { + $request->{command} = 'mkdef'; + } + + if (!$iscommand) { + if (defined $path[1]) { + $netname = $path[1]; + } + + if ($netname eq '') { + addPageContent('A network name must be specified.'); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + push @{$request->{arg}}, '-t', 'network', '-o', $netname; + + if (defined($q->param('PUTDATA'))) { + $entries = JSON::decode_json($q->param('PUTDATA')); + } + elsif (defined($q->param('POSTDATA'))) { + $entries = JSON::decode_json($q->param('POSTDATA')); + } + else { + addPageContent("No Field and Value map was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if (scalar($entries) < 1) { + addPageContent("No Field and Value map was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + foreach (@$entries) { + push @{$request->{arg}}, $_; + } + } + } + elsif (isDelete()) { + $request->{command} = 'rmdef'; + + if (defined $path[1]) { + $netname = $path[1]; + } + if ($netname eq '') { + addPageContent('A network name must be specified.'); + sendResponseMsg($STATUS_BAD_REQUEST); + } + push @{$request->{arg}}, '-t', 'network', '-o', $netname; + } + else { + unsupportedRequestType(); + exit(0); + } + @responses = sendRequest(genRequest()); + + return @responses; +} + +sub nodesHandler { + my @responses; + my @args; + my $noderange; + my @envs; + + if (defined $path[1]) { + $noderange = $path[1]; + } + + if (isGet()) { + my $subResource; + if (defined $path[2]) { + $subResource = $path[2]; + unless (defined($noderange)) { + addPageContent("Invalid nodes and/or groups in noderange"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + $request->{noderange} = $noderange; + + #use the corresponding command by the subresource name + if ($subResource eq "power") { + $request->{command} = 'rpower'; + + #no fields will default to 'stat' + if (defined $q->param('field')) { + push @args, $q->param('field'); + } + else { + push @args, 'stat'; + } + } + elsif ($subResource eq "energy") { + $request->{command} = 'renergy'; + + #no fields will default to 'all' + if (defined $q->param('field')) { + push @args, $q->param('field'); + } + else { + push @args, 'all'; + } + } + elsif ($subResource eq "status") { + $request->{command} = 'nodestat'; + } + elsif ($subResource eq "inventory") { + $request->{command} = 'rinv'; + if (defined $q->param('field')) { + push @args, $q->param('field'); + } + else { + push @args, 'all'; + } + } + elsif ($subResource eq "vitals") { + $request->{command} = 'rvitals'; + if (defined $q->param('field')) { + push @args, $q->param('field'); + } + else { + push @args, 'all'; + } + } + elsif ($subResource eq "scan") { + $request->{command} = 'rscan'; + if (defined $q->param('field')) { + push @args, $q->param('field'); + } + } + else { + addPageContent("Unspported operation on nodes object."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + else { + $request->{command} = 'lsdef'; + push @args, "-t", "node"; + + #add the nodegroup into args + if (defined($noderange)) { + push @args, "-o", $noderange; + } + + #maybe it's specified in the parameters + my @temparray = $q->param('field'); + if (scalar(@temparray) > 0) { + push @args, "-i"; + push @args, join(',', @temparray); + } + } + } + elsif (isPut()) { + my $subResource; + my @entries; + my $entrydata; + + unless (defined($noderange)) { + addPageContent("Invalid nodes and/or groups in noderange"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + $request->{noderange} = $noderange; + + unless ($q->param('PUTDATA')) { + #temporary allowance for the put data to be contained in the queryString + unless ($queryhash{'putData'}) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + else { + foreach my $put (@{$queryhash{'putData'}}) { + my ($key, $value) = split(/=/, $put, 2); + if ($key eq 'field' && $value) { + push @entries, $value; + } + } + } + } + else { + @entries = JSON::decode_json($q->param('PUTDATA')); + if (scalar(@entries) < 1) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + + if (defined $path[2]) { + $subResource = $path[2]; + + if (($subResource ne "dsh") && ($subResource ne "dcp")) { + # For any function other than "dsh" or "dcp", + # move all operands to the argument list. + foreach (@entries) { + if (ref($_) eq 'ARRAY') { + foreach (@$_) { + push @args, $_; + } + } else { + push @args, $_; + } + } + } + if ($subResource eq "power") { + $request->{command} = "rpower"; + my %elements; + extractData(\%elements, @entries); + + unless (scalar(%elements)) { + addPageContent("No power operands were supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + elsif ($subResource eq "energy") { + $request->{command} = "renergy"; + } + elsif ($subResource eq "bootstat" or $subResource eq "bootstate") { + $request->{command} = "nodeset"; + } + elsif ($subResource eq "bootseq") { + $request->{command} = "rbootseq"; + } + elsif ($subResource eq "setboot") { + $request->{command} = "rsetboot"; + } + elsif ($subResource eq "migrate") { + $request->{command} = "rmigrate"; + } + elsif ($subResource eq "execcmdonvm") { + $request->{command} = "execcmdonvm"; + } + elsif ($subResource eq "dsh") { + $request->{command} = "xdsh"; + my %elements; + extractData(\%elements, @entries); + if (defined($elements{'devicetype'})) { + push @args, '--devicetype'; + push @args, $elements{'devicetype'}; + } + if (defined($elements{'execute'})) { + push @args, '-e'; + } + if (defined($elements{'environment'})) { + push @args, '-E'; + push @args, $elements{'environment'}; + } + if (defined($elements{'fanout'})) { + push @args, '-f'; + push @args, $elements{'fanout'}; + } + if (defined($elements{'nolocale'})) { + push @args, '-L'; + } + if (defined($elements{'userid'})) { + push @args, '-l'; + push @args, $elements{'userid'}; + } + if (defined($elements{'monitor'})) { + push @args, '-m'; + } + if (defined($elements{'options'})) { + push @args, '-o'; + push @args, $elements{'options'}; + } + if (defined($elements{'showconfig'})) { + push @args, '-q'; + } + if (defined($elements{'silent'})) { + push @args, '-Q'; + } + if (defined($elements{'remoteshell'})) { + push @args, '-r'; + push @args, $elements{'remoteshell'}; + } + if (defined($elements{'syntax'})) { + push @args, '-S'; + push @args, $elements{'syntax'}; + } + if (defined($elements{'timeout'})) { + push @args, '-t'; + push @args, $elements{'timeout'}; + } + if (defined($elements{'envlist'})) { + push @args, '-X'; + push @args, $elements{'envlist'}; + } + if (defined($elements{'sshsetup'})) { + push @args, '-K'; + push @args, $elements{'sshsetup'}; + } + if (defined($elements{'rootimg'})) { + push @args, '-i'; + push @args, $elements{'rootimg'}; + } + if (defined($elements{'command'})) { + push @args, $elements{'command'}; + } + if (defined($elements{'remotepasswd'})) { + push @envs, 'DSH_REMOTE_PASSWORD=' . $elements{'remotepasswd'}; + push @envs, 'DSH_FROM_USERID=root'; + push @envs, 'DSH_TO_USERID=root'; + } + } + elsif ($subResource eq "dcp") { + $request->{command} = "xdcp"; + my %elements; + extractData(\%elements, @entries); + if (defined($elements{'fanout'})) { + push @args, '-f'; + push @args, $elements{'fanout'}; + } + if (defined($elements{'rootimg'})) { + push @args, '-i'; + push @args, $elements{'rootimg'}; + } + if (defined($elements{'options'})) { + push @args, '-o'; + push @args, $elements{'options'}; + } + if (defined($elements{'rsyncfile'})) { + push @args, '-F'; + push @args, $elements{'rsyncfile'}; + } + if (defined($elements{'preserve'})) { + push @args, '-p'; + } + if (defined($elements{'pull'})) { + push @args, '-P'; + } + if (defined($elements{'showconfig'})) { + push @args, '-q'; + } + if (defined($elements{'remotecopy'})) { + push @args, '-r'; + push @args, $elements{'remotecopy'}; + } + if (defined($elements{'recursive'})) { + push @args, '-R'; + } + if (defined($elements{'timeout'})) { + push @args, '-t'; + push @args, $elements{'timeout'}; + } + if (defined($elements{'source'})) { + push @args, $elements{'source'}; + } + if (defined($elements{'target'})) { + push @args, $elements{'target'}; + } + } + } + else { + my %elements; + my $name; + my $val; + + $request->{command} = "tabch"; + push @args, "node=" . $request->{noderange}; + + extractData(\%elements, @entries); + while (($name, $val) = each (%elements)) { + push @args, $name . "=" . $val; + } + } + } + elsif (isPost()) { + $request->{command} = 'mkdef'; + push @args, "-t", "node"; + + unless (defined($noderange)) { + addPageContent("No nodename was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + push @args, "-o", $noderange; + + if ($q->param('POSTDATA')) { + my $entries = JSON::decode_json($q->param('POSTDATA')); + if (scalar($entries) < 1) { + addPageContent("No Field and Value map was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + foreach (@$entries) { + push @args, $_; + } + } + } + elsif (isDelete()) { + + #FYI: the nodeRange for delete has to be specified in the URI + $request->{command} = 'rmdef'; + push @args, "-t", "node"; + unless (defined($noderange)) { + addPageContent("No nodename was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + push @args, "-o", $noderange; + } + else { + unsupportedRequestType(); + exit(); + } + + push @{$request->{arg}}, @args; + if (@envs) { + push @{$request->{env}}, @envs; + } + 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')) { + addPageContent("fileName, table and operation must be specified for a POST on /notifications"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + 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 { + addPageContent("fileName must be specified for a DELETE on /notifications"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + else { + unsupportedRequestType(); + exit(); + } + + push @{$request->{arg}}, @args; + addPageContent("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 { + addPageContent("The priority must be specified when creating a policy"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + 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 { + addPageContent("The priority must be specified when updating a policy"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + else { + unsupportedRequestType(); + exit(); + } + + push @{$request->{arg}}, @args; + addPageContent("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} = 'lsdef'; + push @{$request->{arg}}, '-t', 'site', '-o', 'clustersite'; + my @temparray = $q->param('field'); + + #add the field name to get + if (scalar(@temparray) > 0) { + push @{$request->{arg}}, '-i'; + push @{$request->{arg}}, join(',', @temparray); + } + } + elsif (isPut()) { + $request->{command} = 'chdef'; + push @{$request->{arg}}, '-t', 'site', '-o', 'clustersite'; + unless ($q->param('PUTDATA')) { + #temporary allowance for the put data to be contained in the queryString + unless ($queryhash{'putData'}) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + else { + foreach my $put (@{$queryhash{'putData'}}) { + my ($key, $value) = split(/=/, $put, 2); + if ($key eq 'field' && $value) { + push @{$request->{arg}}, $value; + } + } + } + } else { + if ($q->param('PUTDATA')) { + my $entries = JSON::decode_json($q->param('PUTDATA')); + foreach (@$entries) { + push @{$request->{arg}}, $_; + } + } + else { + addPageContent("No Field and Value map was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + } + else { + unsupportedRequestType(); + } + + my $req = genRequest(); + @responses = sendRequest($req); + 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()) { + + #table was specified + if (defined $table) { + if (defined($q->param('col'))) { + $request->{command} = 'gettab'; + push @args, $q->param('col') . '=' . $q->param('value'); + my @temparray = $q->param('attribute'); + foreach (@temparray) { + push @args, $table . '.' . $_; + } + } + else { + $request->{command} = 'tabdump'; + push @args, $table; + if (!defined $q->param('desc')) { + $formatType = 'splitCommas'; + } + } + } + else { + $request->{command} = 'tabdump'; + } + } + elsif (isPut() || isPatch()) { + my $condition = $q->param('condition'); + my @vals; + my $entries; + if (!defined $condition) { + unless ($q->param('PUTDATA')) { + foreach my $put (@{$queryhash{'putData'}}) { + my ($key, $value) = split(/=/, $put, 2); + if ($key eq 'condition' && $value) { + $condition = $value; + } + } + foreach my $put (@{$queryhash{'putData'}}) { + my ($key, $value) = split(/=/, $put, 2); + if ($key eq 'value') { + push(@vals, $value); + } + } + } + else { + $entries = JSON::decode_json($q->param('PUTDATA')); + if (scalar(@$entries) < 1) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + } + + if (!defined $table || !defined $condition) { + if (scalar(@$entries) < 1) { + addPageContent("The table and condition must be specified when adding, changing or deleting an entry"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + $request->{command} = 'tabch'; + my $del; + if (!defined $q->param('delete')) { + foreach my $put (@{$queryhash{'putData'}}) { + my ($key, $value) = split(/=/, $put, 2); + if ($key eq 'delete') { + $del = 1; + } + } + } + + if (defined $q->param('delete') || defined $del) { + push @args, '-d'; + push @args, $condition; + push @args, $table; + } + elsif (defined $condition) { + push @args, $condition; + if ($q->param('value')) { + for ($q->param('value')) { + push @args, "$table.$_"; + } + } + else { + @args = (@args, @vals); + } + } + else { + foreach (@$entries) { + push @args, split(/ /,$_); + } + } + } + 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} = 'gettab'; + 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 { + addPageContent("The key must be specified when creating a non-cluster user"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + + #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 { + addPageContent("The key must be specified when creating a cluster user"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + } + 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 { + addPageContent("The key must be specified when deleting a non-cluster user"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + else { + if (defined $q->param('userName')) { + $request->{command} = 'xcatclientnnr'; + push @args, 'clusteruserdel'; + push @args, $q->param('userName'); + } + else { + addPageContent("The userName must be specified when deleting a cluster user"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + } + 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 { + addPageContent("The key must be specified when updating a non-cluster user"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + + #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')) { + addPageContent("The object must be specified."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + 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')) { + addPageContent("The object must be specified."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + 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 { + addPageContent( +"Either the help info must be requested or the object must be specified or the flag that indicates everything should be removed." + ); + sendResponseMsg($STATUS_BAD_REQUEST); + } + 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; + my $noderange; + my $subResource; + if (defined $path[1]) { + $noderange = $path[1]; + $request->{noderange} = $noderange; + } + else { + addPageContent("Invalid nodes and/or groups in noderange"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if (isGet()) { + $request->{command} = 'lsvm'; + if (defined $q->param('all')) { + push @args, '-a'; + } + + # for z/VM + if (defined $q->param('networknames')) { + push @args, '--getnetworknames'; + } + + if (defined $q->param('network')) { + push @args, '--getnetwork'; + push @args, $q->param('getnetwork'); + } + + if (defined $q->param('diskpoolnames')) { + push @args, '--diskpoolnames'; + } + + if (defined $q->param('diskpool')) { + push @args, '--diskpool'; + push @args, $q->param('diskpool'); + } + + if (defined $q->param('checknics')) { + push @args, '--checknics'; + push @args, $q->param('checknics'); + } + } + elsif (isPost()) { + my $entries; + my %entryhash; + my $position; + $request->{command} = 'mkvm'; + unless ($q->param('POSTDATA')) { + addPageContent("Invalid Parameters"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + #collect all parameters from the postdata + $entries = JSON::decode_json($q->param('POSTDATA')); + if (scalar(@$entries) < 1) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + extractData(\%entryhash, $entries); + + # For zVM; clonefrom must be first so that the mkvm call + # has the clone from node in correct spot in makeVM args + if (defined $entryhash{'clonefrom'}) { + push @args, $entryhash{'clonefrom'}; + } + + #for system p + if (defined $entryhash{'cec'}) { + push @args, '-c'; + push @args, $entryhash{'cec'}; + } + + if (defined $entryhash{'startId'}) { + push @args, '-i'; + push @args, $entryhash{'startId'}; + } + + if (defined $entryhash{'source'}) { + push @args, '-l'; + push @args, $entryhash{'source'}; + } + + if (defined $entryhash{'profile'}) { + push @args, '-p'; + push @args, $entryhash{'profile'}; + } + + if (defined $entryhash{'full'}) { + push @args, '--full'; + } + + #for KVM & Vmware + if (defined $entryhash{'master'}) { + push @args, '-m'; + push @args, $entryhash{'master'}; + } + + if (defined $entryhash{'disksize'}) { + push @args, '-s'; + push @args, $entryhash{'disksize'}; + } + + if (defined $entryhash{'memory'}) { + push @args, '--mem'; + push @args, $entryhash{'memory'}; + } + + if (defined $entryhash{'cpu'}) { + push @args, '--cpus'; + push @args, $entryhash{'cpu'}; + } + + if (defined $entryhash{'force'}) { + push @args, '-f'; + } + + # for z/VM + if (defined $entryhash{'userid'}) { + push @args, '--userid'; + push @args, $entryhash{'userid'}; + } + + if (defined $entryhash{'size'}) { + push @args, '--size'; + push @args, $entryhash{'size'}; + } + + if (defined $entryhash{'password'}) { + push @args, '--password'; + push @args, $entryhash{'password'}; + } + + if (defined $entryhash{'privilege'}) { + push @args, '--privilege'; + push @args, $entryhash{'privilege'}; + } + + if (defined $entryhash{'diskpool'}) { + push @args, '--diskpool'; + push @args, $entryhash{'diskpool'}; + } + + if (defined $entryhash{'diskvdev'}) { + push @args, '--diskVdev'; + push @args, $entryhash{'diskvdev'}; + } + if (defined $entryhash{'imagename'}) { + push @args, '--imagename'; + push @args, $entryhash{'imagename'}; + } + if (defined $entryhash{'osimage'}) { + push @args, '--osimage'; + push @args, $entryhash{'osimage'}; + } + if (defined $entryhash{'ipl'}) { + push @args, '--ipl'; + push @args, $entryhash{'ipl'}; + } + # For the mkvm call the zvm.pm code is looking for key=value + # for pool and pw; rather than a "--key value" + if ( defined $entryhash{'pool'} ) { + push @args, "pool=$entryhash{'pool'}"; + } + if ( defined $entryhash{'pw'} ) { + push @args, "pw=$entryhash{'pw'}"; + } + } + elsif (isPut()) { + $request->{command} = 'chvm'; + if ($q->param('PUTDATA')) { + my $entries = JSON::decode_json($q->param('PUTDATA')); + if (scalar(@$entries) < 1) { + addPageContent("No Field and Value map was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + foreach (@$entries) { + # Handle blank delimited parameters + push @args, split(/ /,$_); + } + } + else { + addPageContent("No Field and Value map was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + } + elsif (isDelete()) { + $request->{command} = 'rmvm'; + if (defined $q->param('retain')) { + push @args, '-r'; + } + if (defined $q->param('service')) { + push @args, '--service'; + } + } + else { + unsupportedRequestType(); + exit(); + } + + # Note: MUST parse these parameters after all others if we want to avoid + # duplicating this code on each if branch, since + # lsvm depends on its "subcommand" being the first parameter. + # TODO if we add these parameters to other paths, could we use a subroutine instead? only 2 inputs. + + # Parse the optional upstream object ID, e.g. an OpenStack nova instance UUID + if (defined $queryhash{'objectid'}) { + push @args, '--objectid'; + push @args, $queryhash{'objectid'}->[0]; + } + + # Parse the optional upstream request ID, e.g. an OpenStack request UUID + if (defined $queryhash{'requestid'}) { + push @args, '--requestid'; + push @args, $queryhash{'requestid'}->[0]; + } + + push @{$request->{arg}}, @args; + my $req = genRequest(); + my @responses = sendRequest($req); + return @responses; +} + +sub versionHandler { + $request->{command} = "lsxcatd"; + push @{$request->{arg}}, "-v"; + 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 { + +} + +sub hypervisorHandler { + my @responses; + my @args; + if (isPut()) { + my %entryhash; + if (defined $path[1]) { + $request->{noderange} = $path[1]; + } + else { + addPageContent("Invalid nodes and/or groups in node in noderange"); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + if (defined $path[2]) { + $request->{command} = $path[2]; + } + else { + $request->{command} = 'chhypervisor'; + } + my $entries = JSON::decode_json( $q->param('PUTDATA') ); + if (scalar(@$entries) < 1) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + foreach (@$entries) { + push @args, split(/ /,$_); + } + + push @{$request->{arg}}, @args; + my $req = genRequest(); + @responses = sendRequest($req); + return @responses; + } +} + +sub debugHandler { + my @responses; + my @args; + if (isPut()) { + my %entryhash; + $request->{command} = 'xcatclientnnr xcatdebug'; + + #push @args, 'xcatdebug'; + my $entries = JSON::decode_json( $q->param('PUTDATA') ); + if (scalar(@$entries) < 1) { + addPageContent("No set attribute was supplied."); + sendResponseMsg($STATUS_BAD_REQUEST); + } + + foreach (@$entries) { + push @{$request->{arg}}, $_; + } + + push @{$request->{arg}}, @args; + my $req = genRequest(); + @responses = sendRequest($req); + return @responses; + } +} + +#all data wrapping and writing is funneled through here +sub wrapData { + my $data = shift; + my $errorInformation = ''; + + #trim the serverdone message off + if (exists $data->[0]->{serverdone} && exists $data->[0]->{error}) { + $errorInformation = $data->[0]->{error}->[0]; + addPageContent($q->p($errorInformation)); + if (($errorInformation =~ /Permission denied/) || ($errorInformation =~ /Authentication failure/)) { + sendResponseMsg($STATUS_UNAUTH); + } + else { + sendResponseMsg($STATUS_FORBIDDEN); + } + exit 1; + } + else { + pop @{$data}; + } + if (exists $formatters{$format}) { + $formatters{$format}->($data); + } + + #all information were add into the global varibale, call the response funcion + if (exists $data->[0]->{info} && $data->[0]->{info}->[0] =~ /Could not find an object/) { + sendResponseMsg($STATUS_NOT_FOUND); + } + elsif (isPost()) { + sendResponseMsg($STATUS_CREATED); + } + else { + sendResponseMsg($STATUS_OK); + } +} + +sub wrapJson { + my $data = shift; + my $json; + $json->{'data'} = $data; + addPageContent(JSON::to_json($json)); +} + +sub wrapHtml { + my $item; + my $response = shift; + my $baseUri = $url . $pathInfo; + if ($baseUri !~ /\/^/) { + $baseUri .= "/"; + } + + foreach my $element (@$response) { + + #foreach my $element (@$data){ + #if($element->{error}){ + if ($element->{node}) { + addPageContent(""); + foreach $item (@{$element->{node}}) { + + #my $url = $baseUri.$item->{name}[0]; + addPageContent(""); + 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]) { + addPageContent(""); + } + if (ref($item->{data}[0]) eq 'HASH' && exists $item->{data}[0]->{contents}[0]) { + addPageContent(""); + } + } + else { + addPageContent(""); + } + } + elsif (exists $item->{error}) { + addPageContent(""); + } + addPageContent(""); + } + addPageContent("
$item->{name}[0]$item->{data}[0]->{desc}[0]$item->{data}[0]->{contents}[0]$item->{data}[0]$item->{error}[0]
"); + } + if ($element->{data}) { + addPageContent(""); + foreach $item (@{$element->{data}}) { + my @values = split(/:/, $item, 2); + addPageContent(""); + foreach (@values) { + if ($formatType =~ /splitCommas/) { + my @fields = split(/,/, $_, -1); + foreach (@fields) { + addPageContent(""); + } + } + else { + addPageContent(""); + } + } + addPageContent("\n"); + } + addPageContent("
$_$_
"); + } + if ($element->{info}) { + addPageContent(""); + foreach $item (@{$element->{info}}) { + addPageContent(""); + my $fieldname = ''; + my $fieldvalue = ''; + + #strip whitespace in the string + $item =~ s/^\s+//; + $item =~ s/\s+$//; + if ($item =~ /Object/) { + ($fieldname, $fieldvalue) = split(/:/, $item); + } + elsif ($item =~ /.*=.*/) { + my $position = index $item, '='; + $fieldname = substr $item, 0, $position; + $fieldvalue = substr $item, $position + 1; + } + else { + $fieldname = $item; + } + addPageContent(""); + if ($fieldvalue ne '') { + addPageContent(""); + } + addPageContent("\n"); + } + addPageContent("
" . $fieldname . "" . $fieldvalue . "
"); + } + if ($element->{error}) { + addPageContent(""); + foreach $item (@{$element->{error}}) { + addPageContent(""); + } + addPageContent("
" . $item . "
"); + } + } +} + +sub wrapXml { + my @data = shift; + foreach (@data) { + foreach (@$_) { + addPageContent(XMLout($_, RootName => '', NoAttr => 1, KeyAttr => [])); + } + } +} + +#general tests for valid requests and responses with HTTP codes here +if (!doesResourceExist($resource)) { + addPageContent("Resource '$resource' does not exist"); + sendResponseMsg($STATUS_NOT_FOUND); +} +else { + if ($DEBUGGING) { + addPageContent($q->p("resource is $resource")); + } + handleRequest(); +} + +#talk to the server +use Socket; +use IO::Socket::INET; +use IO::Socket::SSL; + +# 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; + addPageContent($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, + SSL_verify_mode => 'SSL_VERIFY_NONE', + Timeout => 15,); + } + unless ($client) { + if ($@ =~ /SSL Timeout/) { + addPageContent("Connection failure: SSL Timeout or incorrect certificates in ~/.xcat"); + sendResponseMsg($STATUS_TIMEOUT); + } + else { + addPageContent("Connection failurexx: $@"); + sendResponseMsg($STATUS_SERVICE_UNAVAILABLE); + } + } + + 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 + if ($DEBUGGING) { + addPageContent($response . "\n"); + } + $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) { + addPageContent("ERROR/WARNING: communication with the xCAT server seems to have been ended prematurely"); + sendResponseMsg($STATUS_SERVICE_UNAVAILABLE); + exit(0); + } + + if ($DEBUGGING) { + addPageContent($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 + addPageContent($responses[0]->{error}[0]); + sendResponseMsg($STATUS_UNAUTH); +}