#!/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("| $item->{name}[0]");
                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(" | $item->{data}[0]->{desc}[0]");
                        }
                        if (ref($item->{data}[0]) eq 'HASH' && exists $item->{data}[0]->{contents}[0]) {
                            addPageContent(" | $item->{data}[0]->{contents}[0]");
                        }
                    }
                    else {
                        addPageContent(" | $item->{data}[0]");
                    }
                }
                elsif (exists $item->{error}) {
                    addPageContent(" | $item->{error}[0]");
                }
                addPageContent(" | 
");
            }
            addPageContent("
");
        }
        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("| " . $fieldname . "");
                if ($fieldvalue ne '') {
                    addPageContent(" | " . $fieldvalue . "");
                }
                addPageContent(" | 
\n");
            }
            addPageContent("
");
        }
        if ($element->{error}) {
            addPageContent("");
            foreach $item (@{$element->{error}}) {
                addPageContent("| " . $item . " | 
");
            }
            addPageContent("
");
        }
    }
}
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);
}