diff --git a/build-ubunturepo b/build-ubunturepo index b2b4e0e31..f8ae7a10d 100755 --- a/build-ubunturepo +++ b/build-ubunturepo @@ -194,7 +194,7 @@ then if [ ! -d ../../$package_dir_name ];then mkdir -p "../../$package_dir_name" fi - packages="xCAT-client xCAT-genesis-scripts perl-xCAT xCAT-server xCAT-UI xCAT xCATsn xCAT-test xCAT-OpenStack" + packages="xCAT-client xCAT-genesis-scripts perl-xCAT xCAT-server xCAT-UI xCAT xCATsn xCAT-test xCAT-OpenStack xCAT-OpenStack-baremetal" for file in `echo $packages` do diff --git a/builddep.sh b/builddep.sh index 534f1b76e..62c13467d 100755 --- a/builddep.sh +++ b/builddep.sh @@ -138,6 +138,12 @@ if [ "$PERLVER" == "v5.8.2" ]; then OSVER='5.3' elif [ "$PERLVER" == "v5.8.8" ]; then OSVER='6.1' + aixver=`lslpp -lc|grep 'bos.rte:'|head -1|cut -d: -f3` + if [[ $aixver < '6.1.9.0' ]]; then + AIX61Y=0 + else + AIX61Y=1 + fi elif [ "$PERLVER" == "v5.10.1" ]; then OSVER='7.1' aixver=`lslpp -lc|grep 'bos.rte:'|head -1|cut -d: -f3` @@ -173,8 +179,8 @@ for i in `ls *.rpm|grep -v -E '^tcl-|^tk-|^expect-|^unixODBC-|^xCAT-UI-deps|^per opts="" fi - # On 7.1L we need a newer version of perl-Net_SSLeay.pm - if [[ $AIX71L -eq 1 ]]; then + # On 7.1L and 6.1Y we need a newer version of perl-Net_SSLeay.pm + if [[ $AIX71L -eq 1 || $AIX61Y -eq 1 ]]; then if [[ $i == perl-Net_SSLeay.pm-1.30-* ]]; then continue; fi # skip the old rpm else if [[ $i == perl-Net_SSLeay.pm-1.55-* ]]; then continue; fi # skip the new rpm diff --git a/makerpm b/makerpm index 2d9d89943..5fa58918a 100755 --- a/makerpm +++ b/makerpm @@ -53,6 +53,7 @@ function makexcat { tar -X /tmp/xcat-excludes -cf $RPMROOT/SOURCES/templates.tar templates gzip -f $RPMROOT/SOURCES/templates.tar cp xcat.conf $RPMROOT/SOURCES + cp xcat.conf.apach24 $RPMROOT/SOURCES cp xCATMN $RPMROOT/SOURCES else # xCATsn tar -X /tmp/xcat-excludes -cf $RPMROOT/SOURCES/license.tar LICENSE.html diff --git a/perl-xCAT/xCAT/DBobjUtils.pm b/perl-xCAT/xCAT/DBobjUtils.pm index 5bf27c69e..19428d0e0 100755 --- a/perl-xCAT/xCAT/DBobjUtils.pm +++ b/perl-xCAT/xCAT/DBobjUtils.pm @@ -2747,7 +2747,7 @@ sub collapsenicsattr() # e.g nicips.eth0 # do not need to handle nic attributes without the postfix .ethx, # it will be overwritten by the attributes with the postfix .ethx, - if ($nodeattr =~ /^(nic\w+)\.(\w+)$/) + if ($nodeattr =~ /^(nic\w+)\.(.*)$/) { if ($1 && $2) { diff --git a/perl-xCAT/xCAT/Schema.pm b/perl-xCAT/xCAT/Schema.pm index dc8f3ab6c..2e8398f3f 100755 --- a/perl-xCAT/xCAT/Schema.pm +++ b/perl-xCAT/xCAT/Schema.pm @@ -1562,8 +1562,8 @@ hwinv => { node => 'The node name or group name.', cputype => 'The cpu model name for the node.', cpucount => 'The number of cpus for the node.', - memory => 'The size of the memory for the node.', - disksize => 'The size of the disks for the node.', + memory => 'The size of the memory for the node in MB.', + disksize => 'The size of the disks for the node in GB.', comments => 'Any user-provided notes.', disable => "Set to 'yes' or '1' to comment out this row.", }, diff --git a/perl-xCAT/xCAT/TableUtils.pm b/perl-xCAT/xCAT/TableUtils.pm index 519f63a98..daacc6e28 100644 --- a/perl-xCAT/xCAT/TableUtils.pm +++ b/perl-xCAT/xCAT/TableUtils.pm @@ -376,6 +376,7 @@ fi mkdir -p \$dest_dir cat /tmp/$to_userid/.ssh/authorized_keys >> \$home/.ssh/authorized_keys 2>&1 cat /tmp/$to_userid/.ssh/id_rsa.pub >> \$home/.ssh/authorized_keys 2>&1 +rm -f \$home/.ssh/id_rsa 2>&1 cp /tmp/$to_userid/.ssh/id_rsa \$home/.ssh/id_rsa 2>&1 cp /tmp/$to_userid/.ssh/id_rsa.pub \$home/.ssh/id_rsa.pub 2>&1 chmod 0600 \$home/.ssh/id_* 2>&1 @@ -1999,7 +2000,7 @@ sub updatenodegroups { } } my ($ent) = $tabhd->getNodeAttribs($node, ['groups']); - my @list = qw(all); + my @list = (); if (defined($ent) and $ent->{groups}) { push @list, split(/,/,$ent->{groups}); } diff --git a/xCAT-OpenStack-baremetal/debian/README.Debian b/xCAT-OpenStack-baremetal/debian/README.Debian new file mode 100644 index 000000000..0d88c5231 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/README.Debian @@ -0,0 +1,6 @@ +xcat-openstack-baremetal for Debian +----------------------------------- + + + + -- root Wed, 12 Mar 2014 01:47:54 -0700 diff --git a/xCAT-OpenStack-baremetal/debian/README.source b/xCAT-OpenStack-baremetal/debian/README.source new file mode 100644 index 000000000..4ea5d6ff6 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/README.source @@ -0,0 +1,9 @@ +xcat-openstack-baremetal for Debian +----------------------------------- + + + + + + diff --git a/xCAT-OpenStack-baremetal/debian/changelog b/xCAT-OpenStack-baremetal/debian/changelog new file mode 100644 index 000000000..5502ea420 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/changelog @@ -0,0 +1,5 @@ +xcat-openstack-baremetal (2.8.4-1) unstable; urgency=low + + * Initial release (Closes: #nnnn) + + -- root Wed, 12 Mar 2014 01:47:54 -0700 diff --git a/xCAT-OpenStack-baremetal/debian/compat b/xCAT-OpenStack-baremetal/debian/compat new file mode 100644 index 000000000..45a4fb75d --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/compat @@ -0,0 +1 @@ +8 diff --git a/xCAT-OpenStack-baremetal/debian/control b/xCAT-OpenStack-baremetal/debian/control new file mode 100644 index 000000000..e29fd97c5 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/control @@ -0,0 +1,14 @@ +Source: xcat-openstack-baremetal +Section: admin +Priority: extra +Maintainer: xCAT +Build-Depends: debhelper (>= 8.0.0) +Standards-Version: 3.9.4 +Homepage: http://xcat.sourceforge.net/ +#Vcs-Git: git://git.debian.org/collab-maint/xcat-openstack-baremetal.git +#Vcs-Browser: http://git.debian.org/?p=collab-maint/xcat-openstack-baremetal.git;a=summary + +Package: xcat-openstack-baremetal +Architecture: all +Depends: xCAT-client +Description: Executables and data of the xCAT baremetal driver for OpenStack diff --git a/xCAT-OpenStack-baremetal/debian/copyright b/xCAT-OpenStack-baremetal/debian/copyright new file mode 100644 index 000000000..152491524 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/copyright @@ -0,0 +1,38 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: xcat-openstack-baremetal +Source: + +Files: * +Copyright: + +License: + + + . + + +# If you want to use GPL v2 or later for the /debian/* files use +# the following clauses, or change it to suit. Delete these two lines +Files: debian/* +Copyright: 2014 root +License: GPL-2+ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +# Please avoid to pick license terms that are more restrictive than the +# packaged work, as it may make Debian's contributions unacceptable upstream. diff --git a/xCAT-OpenStack-baremetal/debian/dirs b/xCAT-OpenStack-baremetal/debian/dirs new file mode 100644 index 000000000..371ba01dc --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/dirs @@ -0,0 +1,7 @@ +opt/xcat/bin +opt/xcat/sbin +opt/xcat/lib/perl/xCAT_plugin +opt/xcat/lib/python/xcat/openstack/baremetal +opt/xcat/share/xcat/openstack/postscripts +opt/xcat/share/man/man1 +opt/xcat/share/doc/man1 diff --git a/xCAT-OpenStack-baremetal/debian/docs b/xCAT-OpenStack-baremetal/debian/docs new file mode 100644 index 000000000..e69de29bb diff --git a/xCAT-OpenStack-baremetal/debian/files b/xCAT-OpenStack-baremetal/debian/files new file mode 100644 index 000000000..0bf4353c0 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/files @@ -0,0 +1,2 @@ +xcat-openstack-baremetal_2.8.4-1_all.deb admin extra +xcat-openstack-baremetal_2.8.4-1_all.deb admin extra diff --git a/xCAT-OpenStack-baremetal/debian/install b/xCAT-OpenStack-baremetal/debian/install new file mode 100644 index 000000000..883d1f178 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/install @@ -0,0 +1,6 @@ +lib/* opt/xcat/lib/ +share/xcat/openstack/postscripts/* opt/xcat/share/xcat/openstack/postscripts/ +share/man/man1/* opt/xcat/share/man/man1/ +share/doc/man1/* opt/xcat/share/doc/man1/ + + diff --git a/xCAT-OpenStack-baremetal/debian/postinst b/xCAT-OpenStack-baremetal/debian/postinst new file mode 100644 index 000000000..f8997ea58 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/postinst @@ -0,0 +1,43 @@ +#!/bin/sh +# postinst script for xcat-openstack-baremetal +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + configure) +#copy the postscripts under /installl/postscripts directory on MN only +if [ -f "/etc/xCATMN" ]; then + cp /opt/xcat/share/xcat/openstack/postscripts/* /install/postscripts/ +fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/xCAT-OpenStack-baremetal/debian/prerm b/xCAT-OpenStack-baremetal/debian/prerm new file mode 100644 index 000000000..5057be146 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/prerm @@ -0,0 +1,46 @@ +#!/bin/sh +# prerm script for xcat-openstack-baremetal +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|deconfigure) + #remove postscripts under /installl/postscripts directory on MN only + if [ -f "/etc/xCATMN" ]; then + for fn in /opt/xcat/share/xcat/openstack/postscripts/* + do + bn=`basename $fn` + rm /install/postscripts/$bn + done + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/xCAT-OpenStack-baremetal/debian/rules b/xCAT-OpenStack-baremetal/debian/rules new file mode 100755 index 000000000..c57f3dbf2 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/rules @@ -0,0 +1,44 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +build: + pwd + `pwd`/xpod2man + +clean: + dh_testdir + dh_testroot + dh_clean -d + +install: + pwd + dh_testdir + dh_testroot + dh_installdirs + dh_install -X".svn" + chmod 444 `pwd`/debian/xcat-openstack-baremetal/opt/xcat/share/man/man1/* + chmod 644 `pwd`/debian/xcat-openstack-baremetal/opt/xcat/share/doc/man1/* + dh_link + +binary-indep: build install + pwd + export + dh_installman + dh_compress + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +binary-arch: + pwd +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/xCAT-OpenStack-baremetal/debian/source/format b/xCAT-OpenStack-baremetal/debian/source/format new file mode 100644 index 000000000..d3827e75a --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.debhelper.log b/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.debhelper.log new file mode 100644 index 000000000..a151b91a0 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.debhelper.log @@ -0,0 +1,201 @@ +dh_installdirs +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb +dh_installdirs +dh_install +dh_link +dh_installman +dh_compress +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb diff --git a/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.links b/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.links new file mode 100644 index 000000000..293b8cc77 --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.links @@ -0,0 +1,4 @@ +opt/xcat/bin/xcatclient opt/xcat/sbin/deploy_ops_bm_node +opt/xcat/bin/xcatclient opt/xcat/sbin/cleanup_ops_bm_node +opt/xcat/bin/xcatclient opt/xcat/bin/opsaddbmnode +opt/xcat/bin/xcatclientnnr opt/xcat/bin/opsaddimage diff --git a/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.substvars b/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.substvars new file mode 100644 index 000000000..abd3ebebc --- /dev/null +++ b/xCAT-OpenStack-baremetal/debian/xcat-openstack-baremetal.substvars @@ -0,0 +1 @@ +misc:Depends= diff --git a/xCAT-OpenStack-baremetal/lib/perl/xCAT_plugin/openstack.pm b/xCAT-OpenStack-baremetal/lib/perl/xCAT_plugin/openstack.pm index 6e591945a..2fcfaae6c 100644 --- a/xCAT-OpenStack-baremetal/lib/perl/xCAT_plugin/openstack.pm +++ b/xCAT-OpenStack-baremetal/lib/perl/xCAT_plugin/openstack.pm @@ -103,6 +103,13 @@ sub opsaddbmnode { my $nodes = $request->{node}; + #get node mgt + my $nodehmhash; + my $nodehmtab = xCAT::Table->new("nodehm"); + if ($nodehmtab) { + $nodehmhash = $nodehmtab->getNodesAttribs($nodes,['power', 'mgt']); + } + #get bmc info for the nodes my $ipmitab = xCAT::Table->new("ipmi", -create => 0); my $tmp_ipmi; @@ -113,6 +120,7 @@ sub opsaddbmnode { $callback->({error=>["Cannot open the ipmi table."],errorcode=>[1]}); return 1; } + #get mac for the nodes my $mactab = xCAT::Table->new("mac", -create => 0); my $tmp_mac; @@ -135,24 +143,55 @@ sub opsaddbmnode { return 1; } + #get default username and password for bmc + my $d_bmcuser; + my $d_bmcpasswd; + my $passtab = xCAT::Table->new('passwd'); + if ($passtab) { + ($tmp_passwd)=$passtab->getAttribs({'key'=>'ipmi'},'username','password'); + if (defined($tmp_passwd)) { + $d_bmcuser = $tmp_passwd->{username}; + $d_bmcpasswd = $tmp_passwd->{password}; + } + } + + #print "d_bmcuser=$d_bmcuser, d_bmcpasswd=$d_bmcpasswd \n"; foreach my $node (@$nodes) { #collect the node infomation needed for each node, some info - #may not be defined in the xCAT db - my ($bmc, $bmc_user, $bmc_password, $mac, $cpu, $memory, $disk); - my $ref_ipmi = $tmp_ipmi->{$node}->[0]; - if ($ref_ipmi) { - if (exists($ref_ipmi->{bmc})) { - $bmc = $ref_ipmi->{bmc}; - } - if (exists($ref_ipmi->{username})) { - $bmc_user = $ref_ipmi->{username}; - } - if (exists($ref_ipmi->{password})) { - $bmc_password = $ref_ipmi->{password}; + my $mgt; + my $ref_nodehm = $nodehmhash->{$node}->[0]; + if ($ref_nodehm) { + if ($ref_nodehm->{'power'}) { + $mgt = $ref_nodehm->{'power'}; + } elsif ($ref_nodehm->{'mgt'}) { + $mgt = $ref_nodehm->{'mgt'}; } } - $ref_mac = $tmp_mac->{$node}->[0]; + my ($bmc, $bmc_user, $bmc_password, $mac, $cpu, $memory, $disk); + if (($mgt) && ($mgt eq 'ipmi')) { + my $ref_ipmi = $tmp_ipmi->{$node}->[0]; + if ($ref_ipmi) { + if (exists($ref_ipmi->{bmc})) { + $bmc = $ref_ipmi->{bmc}; + } + if (exists($ref_ipmi->{username})) { + $bmc_user = $ref_ipmi->{username}; + if (exists($ref_ipmi->{password})) { + $bmc_password = $ref_ipmi->{password}; + } + } else { #take the default if they cannot be found on ipmi table + if ($d_bmcuser) { $bmc_user = $d_bmcuser; } + if ($d_bmcpasswd) { $bmc_password = $d_bmcpasswd; } + } + } + } # else { # for hardware control point other than ipmi, just fake it in OpenStack. + #$bmc = "0.0.0.0"; + #$bmc_user = "xCAT"; + #$bmc_password = "xCAT"; + #} + + my $ref_mac = $tmp_mac->{$node}->[0]; if ($ref_mac) { if (exists($ref_mac->{mac})) { $mac = $ref_mac->{mac}; @@ -180,7 +219,7 @@ sub opsaddbmnode { } else { $disk = $b[0]; } - print "a=@a, b=@b\n"; + #print "a=@a, b=@b\n"; #TODO: what if the unit is not in GB? We need to convert it to MB $disk =~ s/GB|gb//g; } @@ -418,7 +457,7 @@ sub deploy_ops_bm_node { $callback->($rsp); return 0; } - print "node=$node, image=$img_name, host=$hostname, ip=$fixed_ip, mask=$netmask\n"; + #print "node=$node, image=$img_name, host=$hostname, ip=$fixed_ip, mask=$netmask\n"; #validate the image name my $osimagetab = xCAT::Table->new('osimage', -create=>1); @@ -604,7 +643,7 @@ sub add_postscript { my $callback=shift; my $node=shift; my $script=shift; - print "script=$script\n"; + #print "script=$script\n"; my $posttab=xCAT::Table->new("postscripts", -create =>1); my %setup_hash; diff --git a/xCAT-OpenStack-baremetal/lib/python/xcat/openstack/baremetal/driver.py b/xCAT-OpenStack-baremetal/lib/python/xcat/openstack/baremetal/driver.py index dc3308f61..e42348be2 100644 --- a/xCAT-OpenStack-baremetal/lib/python/xcat/openstack/baremetal/driver.py +++ b/xCAT-OpenStack-baremetal/lib/python/xcat/openstack/baremetal/driver.py @@ -168,7 +168,7 @@ class xCATBareMetalDriver(bm_driver.BareMetalDriver): """ #import pdb #pdb.set_trace() - context = nova_context.get_admin_context() + context = nova_context.get_admin_context() try: node = bm_driver._get_baremetal_node_by_instance_uuid(instance['uuid']) diff --git a/xCAT-OpenStack-baremetal/xpod2man b/xCAT-OpenStack-baremetal/xpod2man index 79c4df9c2..5401b0b26 100755 --- a/xCAT-OpenStack-baremetal/xpod2man +++ b/xCAT-OpenStack-baremetal/xpod2man @@ -21,7 +21,7 @@ my @pods = getPodList($poddir); #foreach (@pods) { print "$_\n"; } exit; # Build the cmd overview page. -writesummarypage("$poddir/man1/xcat.1.pod", @pods); +#writesummarypage("$poddir/man1/xcat.1.pod", @pods); # Build the man page for each pod. #mkdir($mandir) or die "Error: could not create $mandir.\n"; @@ -126,6 +126,7 @@ sub getPodList { # Create the xcat man page that gives a summary description of each xcat cmd. +# Not used sub writesummarypage { my $file = shift; # relative path file name of the man page # the rest of @_ contains the pod files that describe each cmd diff --git a/xCAT-SoftLayer/xCAT-SoftLayer.spec b/xCAT-SoftLayer/xCAT-SoftLayer.spec index 5e2eb386c..df514ddfb 100644 --- a/xCAT-SoftLayer/xCAT-SoftLayer.spec +++ b/xCAT-SoftLayer/xCAT-SoftLayer.spec @@ -11,8 +11,9 @@ Vendor: IBM Corp. Distribution: %{?_distribution:%{_distribution}}%{!?_distribution:%{_vendor}} Prefix: /opt/xcat BuildRoot: /var/tmp/%{name}-%{version}-%{release}-root - +%ifos linux BuildArch: noarch +%endif Requires: xCAT-server #Requires: xCAT-server >= %{epoch}:%(cat Version|cut -d. -f 1,2) @@ -47,7 +48,7 @@ mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/doc/packages/xCAT-SoftLayer mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/man/man1 mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/doc/man1 -cp -a share/xcat/install/* $RPM_BUILD_ROOT/%{prefix}/share/xcat/install/ +cp -p -R share/xcat/install/* $RPM_BUILD_ROOT/%{prefix}/share/xcat/install/ cp -d bin/* $RPM_BUILD_ROOT/%{prefix}/bin chmod 755 $RPM_BUILD_ROOT/%{prefix}/bin/* diff --git a/xCAT-client/bin/genimage b/xCAT-client/bin/genimage index 2f210d811..0fb485fea 100755 --- a/xCAT-client/bin/genimage +++ b/xCAT-client/bin/genimage @@ -117,7 +117,7 @@ if ((!$imagename) && (!$profile) && (!$os) && (!$arch)) { if ($? == 0) { if (($tmpimgs) && ($tmpimgs !~ /^Could/)) { #Could is returned when the osimage table is empty my @images=split('\n', $tmpimgs); - print "Do you want to re-genarate an existing image from the osimage table? "; + print "Do you want to re-generate an existing image from the osimage table? "; print "[y/n] "; my $conf = ; chomp($conf); @@ -128,7 +128,7 @@ if ((!$imagename) && (!$profile) && (!$os) && (!$arch)) { foreach(sort @images){ print " $_\n"; } - # default is the first image cause in many cases + # default is the first image print "Which image do you want to re-generate? ["; print $images[0]; print "] "; @@ -294,13 +294,10 @@ if ($profile) { print " Profile: $profile\n"; } # get the interface if ((!$imagename) && (!$interface)){ while(1){ - print "Which network interface do you want the image to boot from? ["; - print "eth0"; - print "] "; + print "OPTIONAL: Which specific network interface will the image boot from? []"; $interface = ; chomp($interface); if($interface eq ""){ - $interface = "eth0"; last; }else{ print "You want your stateless machines to boot off of "; @@ -317,8 +314,9 @@ if ((!$imagename) && (!$interface)){ } } } + if ($interface) { print " Interface: $interface\n"; } + else { print " No interface specified. The interface will be determined at network boot time.\n"; } } -if ($interface) { print " Interface: $interface\n"; } diff --git a/xCAT-client/podchecker b/xCAT-client/podchecker new file mode 100755 index 000000000..9bd3cd2da --- /dev/null +++ b/xCAT-client/podchecker @@ -0,0 +1,145 @@ +#!/usr/bin/perl + eval 'exec perl -S $0 "$@"' + if 0; +############################################################################# +# podchecker -- command to invoke the podchecker function in Pod::Checker +# +# Copyright (c) 1998-2000 by Bradford Appleton. All rights reserved. +# This file is part of "PodParser". PodParser is free software; +# you can redistribute it and/or modify it under the same terms +# as Perl itself. +############################################################################# + +use strict; +#use diagnostics; + +=head1 NAME + +podchecker - check the syntax of POD format documentation files + +=head1 SYNOPSIS + +B [B<-help>] [B<-man>] [B<-(no)warnings>] [IS< >...] + +=head1 OPTIONS AND ARGUMENTS + +=over 8 + +=item B<-help> + +Print a brief help message and exit. + +=item B<-man> + +Print the manual page and exit. + +=item B<-warnings> B<-nowarnings> + +Turn on/off printing of warnings. Repeating B<-warnings> increases the +warning level, i.e. more warnings are printed. Currently increasing to +level two causes flagging of unescaped "E,E" characters. + +=item I + +The pathname of a POD file to syntax-check (defaults to standard input). + +=back + +=head1 DESCRIPTION + +B will read the given input files looking for POD +syntax errors in the POD documentation and will print any errors +it find to STDERR. At the end, it will print a status message +indicating the number of errors found. + +Directories are ignored, an appropriate warning message is printed. + +B invokes the B function exported by B +Please see L for more details. + +=head1 RETURN VALUE + +B returns a 0 (zero) exit status if all specified +POD files are ok. + +=head1 ERRORS + +B returns the exit status 1 if at least one of +the given POD files has syntax errors. + +The status 2 indicates that at least one of the specified +files does not contain I POD commands. + +Status 1 overrides status 2. If you want unambiguous +results, call B with one single argument only. + +=head1 SEE ALSO + +L and L + +=head1 AUTHORS + +Please report bugs using L. + +Brad Appleton Ebradapp@enteract.comE, +Marek Rouchal Emarekr@cpan.orgE + +Based on code for B written by +Tom Christiansen Etchrist@mox.perl.comE + +=cut + + +use Pod::Checker; +use Pod::Usage; +use Getopt::Long; + +## Define options +my %options; + +## Parse options +GetOptions(\%options, qw(help man warnings+ nowarnings)) || pod2usage(2); +pod2usage(1) if ($options{help}); +pod2usage(-verbose => 2) if ($options{man}); + +if($options{nowarnings}) { + $options{warnings} = 0; +} +elsif(!defined $options{warnings}) { + $options{warnings} = 1; # default is warnings on +} + +## Dont default to STDIN if connected to a terminal +pod2usage(2) if ((@ARGV == 0) && (-t STDIN)); + +## Invoke podchecker() +my $status = 0; +@ARGV = qw(-) unless(@ARGV); +for my $podfile (@ARGV) { + if($podfile eq '-') { + $podfile = '<&STDIN'; + } + elsif(-d $podfile) { + warn "podchecker: Warning: Ignoring directory '$podfile'\n"; + next; + } + my $errors = + podchecker($podfile, undef, '-warnings' => $options{warnings}); + if($errors > 0) { + # errors occurred + $status = 1; + printf STDERR ("%s has %d pod syntax %s.\n", + $podfile, $errors, + ($errors == 1) ? 'error' : 'errors'); + } + elsif($errors < 0) { + # no pod found + $status = 2 unless($status); + print STDERR "$podfile does not contain any pod commands.\n"; + } + else { + print STDERR "$podfile pod syntax OK.\n"; + } +} +exit $status; + diff --git a/xCAT-client/pods/man1/chzone.1.pod b/xCAT-client/pods/man1/chzone.1.pod new file mode 100644 index 000000000..2a91fad5a --- /dev/null +++ b/xCAT-client/pods/man1/chzone.1.pod @@ -0,0 +1,138 @@ +=head1 B + +B - Changes a zone defined in the cluster. + +=head1 B + +B [B<--defaultzone>] [-K] [B<-k> I] [B<-a> I | B<-r> I] [B<-g>] [B<-f>] [B<-s> I] [-V] + +B [B<-h> | B<-v>] + + +=head1 B + +The B command is designed to change the definition of a zone previous defined in the cluster. +The chzone command is only supported on Linux ( No AIX support). +The nodes are not updated with the new root ssh keys by chzone. You must run updatenode -k or xdsh -K to the nodes to update the root ssh keys to the new generated zone keys. This will also sync any service nodes with the zone keys, if you have a hierarchical cluster. +Note: if any zones in the zone table, there must be one and only one defaultzone. Otherwise, errors will occur. + +=head1 B + +=over 5 + +=item B<-h>|B<--help> + +Displays usage information. + +=item B<-v>|B<--version> + +Displays command version and build date. + +=item B<-k | --sshkeypath> I + +This is the path to the id_rsa key that will be used to build new root's ssh keys for the zone. If -k is used, it will generate the ssh public key from the input ssh RSA private key, and store both in /etc/xcat/sshkeys//.ssh directory. + +=item B<-K | --genkeys> + +Using this flag, will generate new ssh RSA private and public keys for the zone into the /etc/xcat/sshkeys//.ssh directory. +The nodes are not automatically updated with the new root ssh keys by chzone. You must run updatenode -k or xdsh -K to the nodes to update the root ssh keys to the new generated zone keys. This will also sync any service nodes with the zone keys, if you have a hierarchical cluster. + +=item B<--default> + +if --defaultzone is input, then it will set the zone defaultzone attribute to yes. +if --defaultzone is input and another zone is currently the default, +then the -f flag must be used to force a change to the new defaultzone. +If -f flag is not use an error will be returned and no change made. +Note: if any zones in the zone table, there must be one and only one defaultzone. Otherwise, errors will occur. + +=item B<-a | --addnoderange> I + +For each node in the noderange, it will set the zonename attribute for that node to the input zonename. +If the -g flag is also on the command, then +it will add the group name "zonename" to each node in the noderange. + +=item B<-r | --rmnoderange> I + +For each node in the noderange, if the node is a member of the input zone, it will remove the zonename attribute for that node. +If any of the nodes in the noderange is not a member of the zone, you will get an error and nothing will be changed. +If the -g flag is also on the command, then +it will remove the group name "zonename" from each node in the noderange. + + +=item B<-s| --sshbetweennodes> B + +If -s entered, the zone sshbetweennodes attribute will be set to yes or no based on the input. When this is set to yes, then ssh will be setup to allow passwordless root access between nodes. If no, then root will be prompted for a password when running ssh between the nodes in the zone. + +=item B<-f | --force> + +Used with the (--defaultzone) flag to override the current default zone. + +=item B<-g | --assigngroup> + +Used with the (-a or -r ) flag to add or remove the group zonename for all nodes in the input noderange. + +=item B<-V>|B<--Verbose> + +Verbose mode. + + +=back + + +=head1 B + +=over 3 + +=item * + +To chzone zone1 to the default zone, enter: + +B I --default -f + +=item * + +To generate new root ssh keys for zone2A using the ssh id_rsa private key in /root/.ssh: + +B I -k /root/.ssh + +Note: you must use xdsh -K or updatenode -k to update the nodes with the new keys + +=item * + +To generate new root ssh keys for zone2A, enter : + +B I -K + +Note: you must use xdsh -K or updatenode -k to update the nodes with the new keys + +=item * + +To add a new group of nodes (compute3) to zone3 and add zone3 group to the nodes, enter: + +B I -a compute3 -g + +=item * + +To remove a group of nodes (compute4) from zone4 and remove zone4 group from the nodes, enter: + +B I -r compute4 -g + +=item * + +To change the sshbetweennodes setting on the zone to not allow passwordless ssh between nodes, enter: + +B I -s no + +Note: you must use xdsh -K or updatenode -k to update the nodes with this new setting. + +=back + +B + +B + +Location of the chzone command. + +=head1 B + +L ,L ,L , L diff --git a/xCAT-client/pods/man1/genimage.1.pod b/xCAT-client/pods/man1/genimage.1.pod index 11a46e2c8..fdd3f5f88 100644 --- a/xCAT-client/pods/man1/genimage.1.pod +++ b/xCAT-client/pods/man1/genimage.1.pod @@ -6,10 +6,10 @@ B - Generates a stateless image to be used for a diskless install. B -B B<-o> I [B<-a> I] B<-p> I B<-i> I B<-n> I [B<--onlyinitrd>] [B<-r> I] [B<-k> I] [B<-g> I] [B<-m> I] [B<-l> I] [B<--permission> I] [B<--interactive>] [B<--dryrun>] - B [B<-o> I] [B<-a> I] [B<-p> I] [B<-i> I] [B<-n> I] [B<--onlyinitrd>] [B<-r> I] [B<-k> I] [B<-g> I] [B<-m> I] [B<-l> I] [B<--permission> I] [B<--interactive>] [B<--dryrun>] [B<--ignorekernelchk>] I +B B<-o> I [B<-a> I] B<-p> I B<-i> I B<-n> I [B<--onlyinitrd>] [B<-r> I] [B<-k> I] [B<-g> I] [B<-m> I] [B<-l> I] [B<--permission> I] [B<--interactive>] [B<--dryrun>] + B [B<-h> | B<--help> | B<-v> | B<--version>] @@ -17,43 +17,43 @@ B [B<-h> | B<--help> | B<-v> | B<--version>] =head1 DESCRIPTION Generates a stateless and a statelite image that can be used to boot xCAT nodes in a diskless mode. -If I is not specified, the default packages included -(and excluded) in the image are specified by +The I format of the command is recommended. When specified, genimage will use the osimage definition for information to generate this image. Additional options specified on the command line will override any corresponding previous osimage settings, and will be written back to the osimage definition. + +If I is not specified (old method): + - the default packages included (and excluded) in the image are specified by /opt/xcat/share/xcat/netboot//[.][.].pkglist and /opt/xcat/share/xcat/netboot//[.][.].exlist. -Additional packages that are not from the os distro can be specified in a + - Additional packages that are not from the os distro can be specified in a /opt/xcat/share/xcat/netboot//[.][.].otherpkgs.pkglist file. -Customized package list files can be specified under /install/custom/netboot/ directory. The generated image will be put in /install/netboot/// directory. + - Customized package list files will override these files and can be specified under /install/custom/netboot/ directory. + - The generated image will be put in /install/netboot/// directory. -The newly generated image names will have the following format: + - osimage definitions will be created in the I and I tables. The newly generated image names will have the following format: - for stateless: --netboot- + for stateless: --netboot- - for statelite: --statelite- + for statelite: --statelite- -B command will create them into I and I tables. - -If I is specified, the package list file names are read from the I table and I tables. If B runs on the management node, both the I table and I table will be updated with the given values from the options. The B command will generate two initial ramdisks for B and B, one is B, the other one is B. -After your image is created, you can chroot to the +After your image is generated, you can chroot to the image, install any additional software you would like, or make modifications to files, and then run the following command to prepare the image for deployment. for stateless: B for statelite: B -Becides prompting for the input for some paramters, the B command takes default quesses for the parameters not specified or not defined in the I and I tables. It also makes default answers for questions from yum/zypper command when installing rpms into the image. Please use --interactive flag if you want yum/zypper command to prompt you for the answers. +Besides prompting for some paramter values, the B command takes default guesses for the parameters not specified or not defined in the I and I tables. It also assumes default answers for questions from the yum/zypper command when installing rpms into the image. Please use --interactive flag if you want the yum/zypper command to prompt you for the answers. -If B<--onlyinitrd> is specified, only regenerates the initrd for a stateless image to be used for a diskless install. +If B<--onlyinitrd> is specified, genimage only regenerates the initrd for a stateless image to be used for a diskless install. The B command must be run on a system that is the same architecture and same distro with same major release version as the nodes it will be used on. If the management node is not the same architecture or same distro level, copy the contents of @@ -61,10 +61,9 @@ used on. If the management node is not the same architecture or same distro lev the management node to that system. Then change directory to /opt/xcat/share/xcat/netboot/ and run ./genimage. - =head1 Parameters -I specifies the name of a os image definition to be used. The specification for the image is storted in the I table and I table. +I specifies the name of an os image definition to be used. The specification for the image is stored in the I table and I table. =head1 OPTIONS @@ -88,7 +87,7 @@ the nodes' nodetype.profile attribute must be set to this same value. =item B<-i> I -The network interface the diskless node will boot over (e.g. eth0). +This argument is now optional, and allows you to specify the network boot interface to be configured in the image (e.g. eth0). If not specified, the interface will be determined and configured during the network boot process. =item B<-n> I @@ -183,46 +182,53 @@ To prompt the user for inputs: genimage =item 2 +To generate an image using information from an osimage definition: -To generate a fedora8 image for a compute node architecture + genimage myimagename + +=item 3 +To run genimage in test mode without actually generating an image: + + genimage --dryrun myimagename + +=item 4 +To generate an image and have yum/zypper prompt for responses: + + genimage myimagename --interactive + +=item 5 +To generate an image, replacing some values in the osimage definition: + + genimage -i eth0 -n tg3 myimagename + +=item 6 +(old method) To generate a fedora8 image for a compute node architecture x86_64 and place it in the /install/netboot/fedora8/x86_64/compute/rootimg directory: genimage -i eth0 -o fedora8 -p compute -=item 3 +=item 7 +(old method) genimage -i eth0 -r eth1,eth2 -n tg3,bnx2 -o centos5.1 -p compute -=item 4 +=item 8 +(old method) genimage -i eth0 -n tg3,bnx2 -o sles11 -p compute --interactive -=item 5 +=item 9 +(old method) genimage -i eth0 -n igb,e1000e,e1000,bnx2,tg3 -o centos5.4 -p nfsroot --permission 777 -=item 6 - - genimage -i eth0 -n tg3 myimagename - -=item 7 - - genimage myimagename - -=item 8 - - genimage myimagename --interactive - -=item 9 - -To regenerate the initrd for a fedora8 image for a compute node architecture x86_64 and place it in the /install/netboot/fedora8/x86_64/compute/rootimg directory: change directory to /opt/xcat/share/xcat/netboot/fedora and run: - - genimage --onlyinitrd -i eth0 -n tg3,bnx2 -o fedora8 -p compute - =item 10 +(old method) +To regenerate the initrd for a fedora8 image for a compute node architecture x86_64 and place it in the /install/netboot/fedora8/x86_64/compute/rootimg directory: - genimage --dryrun myimagename + cd /opt/xcat/share/xcat/netboot/fedora + ./genimage --onlyinitrd -i eth0 -n tg3,bnx2 -o fedora8 -p compute =back diff --git a/xCAT-client/pods/man1/mkzone.1.pod b/xCAT-client/pods/man1/mkzone.1.pod new file mode 100644 index 000000000..e5a6310f7 --- /dev/null +++ b/xCAT-client/pods/man1/mkzone.1.pod @@ -0,0 +1,121 @@ +=head1 B + +B - Defines a new zone in the cluster. + +=head1 B + +B [B<--defaultzone>] [B<-k> I] [B<-a> I] [B<-g>] [B<-f>] [B<-s> I] [-V] + +B [B<-h> | B<-v>] + +=head1 B + +The B command is designed to divide the xCAT cluster into multiple zones. The nodes in each zone will share common root ssh keys. This allows the nodes in a zone to be able to as root ssh to each other without password, but cannot do the same to any node in another zone. All zones share a common xCAT Management Node and database including the site table, which defines the attributes of the entire cluster. +The mkzone command is only supported on Linux ( No AIX support). +The nodes are not updated with the new root ssh keys by mkzone. You must run updatenode -k or xdsh -K to the nodes to update the root ssh keys to the new generated zone keys. This will also sync any service nodes with the zone keys, if you have a hierarchical cluster. +Note: if any zones in the zone table, there must be one and only one defaultzone. Otherwise, errors will occur. + +=head1 B + +=over 5 + +=item B<-h>|B<--help> + +Displays usage information. + +=item B<-v>|B<--version> + +Displays command version and build date. + +=item B<-k | --sshkeypath> I + +This is the path to the id_rsa key that will be used to build root's ssh keys for the zone. If -k is used, it will generate the ssh public key from the input ssh RSA private key and store both in /etc/xcat/sshkeys//.ssh directory. +If -f is not used, then it will generate a set of root ssh keys for the zone and store them in /etc/xcat/sshkeys//.ssh. + +=item B<--default> + +if --defaultzone is input, then it will set the zone defaultzone attribute to yes; otherwise it will set to no. +if --defaultzone is input and another zone is currently the default, +then the -f flag must be used to force a change to the new defaultzone. +If -f flag is not use an error will be returned and no change made. +Note: if any zones in the zone table, there must be one and only one defaultzone. Otherwise, errors will occur. + +=item B<-a | --addnoderange> I + +For each node in the noderange, it will set the zonename attribute for that node to the input zonename. +If the -g flag is also on the command, then +it will add the group name "zonename" to each node in the noderange. + +=item B<-s| --sshbetweennodes> B + +If -s entered, the zone sshbetweennodes attribute will be set to yes or no. It defaults to yes. When this is set to yes, then ssh will be setup +to allow passwordless root access between nodes. If no, then root will be prompted for a password when running ssh between the nodes in the zone. + +=item B<-f | --force> + +Used with the (--defaultzone) flag to override the current default zone. + +=item B<-g | --assigngroup> + +Used with the (-a) flag to create the group zonename for all nodes in the input noderange. + +=item B<-V>|B<--Verbose> + +Verbose mode. + +=back + +=head1 B + +=over 3 + +=item * + +To make a new zone1 using defaults , enter: + +B I + +Note: with the first mkzone, you will automatically get the xcatdefault zone created as the default zone. This zone uses ssh keys from + /.ssh directory. + +=item * + +To make a new zone2 using defaults and make it the default zone enter: + +B I --defaultzone -f + +=item * + +To make a new zone2A using the ssh id_rsa private key in /root/.ssh: + +B I -k /root/.ssh + +=item * + +To make a new zone3 and assign the noderange compute3 to the zone enter: + +B I -a compute3 + +=item * + +To make a new zone4 and assign the noderange compute4 to the zone and add zone4 as a group to each node enter: + +B I -a compute4 -g + +=item * + +To make a new zone5 and assign the noderange compute5 to the zone and add zone5 as a group to each node but not allow passwordless ssh between the nodes enter: + +B I -a compute5 -g -s no + +=back + +B + +B + +Location of the mkzone command. + +=head1 B + +L, L, L, L diff --git a/xCAT-client/pods/man1/rmzone.1.pod b/xCAT-client/pods/man1/rmzone.1.pod new file mode 100644 index 000000000..18cfeed06 --- /dev/null +++ b/xCAT-client/pods/man1/rmzone.1.pod @@ -0,0 +1,84 @@ +=head1 B + +B - Removes a zone from the cluster. + +=head1 B + +B [B<-g>] [B<-f>] + +B [B<-h> | B<-v>] + + +=head1 B + +The B command is designed to remove a previously defined zone from the cluster. +It will remove the zone entry in the zone table. It will remove the zone from the zonename attributes on the nodes that were assigned to the zone. Optionally, it will remove the zonename group from the nodes that were assigned to the zone. +It will also remove the root ssh keys that were created for that zone on the Management Node. +The rmzone command is only supported on Linux ( No AIX support). +The nodes are not automatically updated with new root ssh keys by rmzone. You must run updatenode -k or xdsh -K to the nodes to update the root ssh keys. The nodes new ssh key will be assigned from the defaultzone in the zone table, or if no entries in the zone table, the keys will come from /root/.ssh. +Note: if any zones in the zone table, there must be one and only one defaultzone. Otherwise, errors will occur. + + +=head1 B + +=over 5 + +=item B<-h>|B<--help> + +Displays usage information. + +=item B<-v>|B<--version> + +Displays command version and build date. + +=item B<-f | --force> + +Used to remove a zone that is defined as current default zone. This should only be done if you are removing all zones, or you will +adding a new zone or changing an existing zone to be the default zone. + +=item B<-g | --assigngroup> + +Remove the assigned group named B from all nodes assigned to the zone being removed. + +=item B<-V>|B<--Verbose> + +Verbose mode. + + +=back + + +=head1 B + +=over 3 + +=item * + +To remove zone1 from the zone table and the zonename attribute on all it's assigned nodes , enter: + +B I + + +=item * + +To remove zone2 from the zone table, the zone2 zonename attribute, and the zone2 group assigned to all nodes that were in zone2, enter: + +B I -g + +=item * + +To remove zone3 from the zone table, all the node zone attributes and override the fact it is the defaultzone, enter: + +B I -g -f + +=back + +B + +B + +Location of the rmzone command. + +=head1 B + +L ,L ,L , L diff --git a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm index 0c4b2a451..16f11296e 100755 --- a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm +++ b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm @@ -622,7 +622,8 @@ sub processArgs # if it has an "=" sign its an attr=val - we hope # - this will handle "attr= " - my ($attr, $value) = $a =~ /^\s*(\S+?)\s*=\s*(\S*.*)$/; + # The attribute itself might contain "space", like "nics.Local Connection Adapter 1" on windows + my ($attr, $value) = $a =~ /^\s*(.*?)\s*=\s*(\S*.*)$/; if (!defined($attr) || !defined($value)) { my $rsp; @@ -644,7 +645,7 @@ sub processArgs my $nicattrs = 0; foreach my $kattr (keys %::ATTRS) { - if ($kattr =~ /^nic\w+\.\w+$/) + if ($kattr =~ /^nic\w+\..*$/) { $nicattrs = 1; } @@ -850,7 +851,7 @@ sub processArgs foreach my $dattr (@dispattrs) { # lsdef -t node -h -i nicips.eth0 - if($dattr =~ /^(nic\w+)\.\w+$/) + if($dattr =~ /^(nic\w+)\..*$/) { $dattr = $1; } @@ -1148,7 +1149,7 @@ sub processArgs my $i = 0; for ($i=0; $i < (scalar @::AttrList) ; $i++ ) { - if($::AttrList[$i] =~ /^(nic\w+)\.(\w+)$/) + if($::AttrList[$i] =~ /^(nic\w+)\.(.*)$/) { $::AttrList[$i] = $1; push @{$::NicsAttrHash{$::AttrList[$i]}}, $2; @@ -1304,7 +1305,7 @@ sub defmk { my $attrorig = $attr; # nicips.eth0 => nicips - if ($attr =~ /^(nic\w+)\.\w+$/) + if ($attr =~ /^(nic\w+)\..*$/) { $attr = $1; } @@ -1948,7 +1949,7 @@ sub defch { my $attrorig = $attr; # nicips.eth0 => nicips - if ($attr =~ /^(nic\w+)\.\w+$/) + if ($attr =~ /^(nic\w+)\..*$/) { $attr = $1; } @@ -2635,7 +2636,7 @@ sub setFINALattrs my %tmphash = (); foreach my $nodeattr (keys %{$::CLIATTRS{$objname}}) { - if ($nodeattr =~ /^(nic\w+)\.\w+$/) + if ($nodeattr =~ /^(nic\w+)\..*$/) { my $tmpnicattr = $1; if (!defined($tmphash{$tmpnicattr})) @@ -3408,7 +3409,7 @@ sub defls my $rsp; $rsp->{data}->[0] = "Could not find an object named \'$obj\' of type \'$type\'."; - xCAT::MsgUtils->message("I", $rsp, $::callback); + xCAT::MsgUtils->message("E", $rsp, $::callback); next; } } diff --git a/xCAT-server/lib/xcat/plugins/blade.pm b/xCAT-server/lib/xcat/plugins/blade.pm index 0a6e91568..a9fcc666a 100644 --- a/xCAT-server/lib/xcat/plugins/blade.pm +++ b/xCAT-server/lib/xcat/plugins/blade.pm @@ -4423,7 +4423,7 @@ sub process_request { if ($request->{mtm} and $request->{mtm} =~ /^(\w{4})/) { my $group = xCAT::data::ibmhwtypes::parse_group($request->{mtm}); if (defined($group)) { - xCAT::TableUtils->updatenodegroups($node, $group); + xCAT::TableUtils->updatenodegroups($node, $group.",all"); } } if ($mac) { diff --git a/xCAT-server/lib/xcat/plugins/lsslp.pm b/xCAT-server/lib/xcat/plugins/lsslp.pm old mode 100644 new mode 100755 index af9a1893b..0218da5e6 --- a/xCAT-server/lib/xcat/plugins/lsslp.pm +++ b/xCAT-server/lib/xcat/plugins/lsslp.pm @@ -368,7 +368,7 @@ sub parse_args { ################################### my (@octets) = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; if ( scalar(@octets) != 4 ) { - return( [1,"Invalid IP address: $ip"] ); + return( usage("Invalid IP address: $ip") ); } foreach my $octet ( @octets ) { if (( $octet < 0 ) or ( $octet > 255 )) { diff --git a/xCAT-server/lib/xcat/plugins/xdsh.pm b/xCAT-server/lib/xcat/plugins/xdsh.pm index 4b09f211e..e08d985a7 100644 --- a/xCAT-server/lib/xcat/plugins/xdsh.pm +++ b/xCAT-server/lib/xcat/plugins/xdsh.pm @@ -1061,7 +1061,7 @@ sub syncSNZoneKeys my $file="/tmp/xcatzonesynclist"; # Run xdcp -F /tmp/xcatzonesynclist # can leave it , never changes and is built each time - my $content= "\"/etc/xcat/sshkeys/* -> /etc/xcat/sshkeys/\""; + my $content= "\"/etc/xcat/sshkeys/ -> /etc/xcat/sshkeys/\""; `echo $content > $file`; # xdcp rsync the file @@ -1081,7 +1081,7 @@ sub syncSNZoneKeys @::good_SN = @sn; # initialize all good # run the command to the servicenodes - # xdcp -F + # xdcp -o "--delete" -F my $addreq; $addreq->{'_xcatdest'} = $::mnname; $addreq->{node} = \@sn; @@ -1092,6 +1092,8 @@ sub syncSNZoneKeys push (@{$addreq->{arg}},"--nodestatus"); # return nodestatus } push (@{$addreq->{arg}},"-v"); + push (@{$addreq->{arg}},"-o"); + push (@{$addreq->{arg}},"--delete"); # will cleanup the directory if zones are removed push (@{$addreq->{arg}},"-F"); push (@{$addreq->{arg}},$file); $addreq->{command}->[0] = "xdcp"; # input command is xdsh, but we need to run xdcp -F diff --git a/xCAT-server/lib/xcat/plugins/zone.pm b/xCAT-server/lib/xcat/plugins/zone.pm index fcbb6809a..0a658be74 100644 --- a/xCAT-server/lib/xcat/plugins/zone.pm +++ b/xCAT-server/lib/xcat/plugins/zone.pm @@ -95,13 +95,26 @@ sub process_request my $args = $request->{arg}; @ARGV = @{$args}; # get arguments - my %options = (); - $Getopt::Long::ignorecase = 0; + # Get the zonename if it is in the input + my @SaveARGV = @ARGV; + my $zonename; + my $arg= @SaveARGV[0]; + if (!($arg =~ /-h/) && (!($arg =~ /-v/))) { # if not -h -v,then it must be a zone name + $zonename = @SaveARGV[0]; # here is the zonename, if there is one + if ($zonename) { # take zonename off the argument list so it will parse correctly + my $tmp = shift(@SaveARGV); + @ARGV = @SaveARGV; + } + } + Getopt::Long::Configure("posix_default"); + Getopt::Long::Configure("no_gnu_compat"); Getopt::Long::Configure("bundling"); + my %options = (); if ( !GetOptions( - 'a|noderange=s' => \$options{'noderange'}, + 'a|noderange=s' => \$options{'addnoderange'}, + 'r|noderange=s' => \$options{'rmnoderange'}, 'defaultzone|defaultzone' => \$options{'defaultzone'}, 'g|assigngrp' => \$options{'assigngroup'}, 'f|force' => \$options{'force'}, @@ -132,14 +145,14 @@ sub process_request exit 0; } # test to see if the zonename was input - if (scalar(@ARGV) == 0) { + if (!$zonename) { my $rsp = {}; $rsp->{error}->[0] = - "zonename not specified, see man page for syntax."; + "zonename not specified, it is required for this command."; xCAT::MsgUtils->message("E", $rsp, $callback); exit 1; } else { - $request->{zonename} = $ARGV[0]; + $request->{zonename} = $zonename; } # if -s entered must be yes/1 or no/0 if ($options{'sshbetweennodes'}) { @@ -166,12 +179,40 @@ sub process_request $rsp->{info}->[0] = "The site table sshbetweennodes attribute is set to $entries[0]. It is not used when zones are defined. To get rid of this warning, remove the site table sshbetweennodes attribute."; xCAT::MsgUtils->message("I", $rsp, $callback); - } - # save input noderange - if ($options{'noderange'}) { + } + # -a and -r flags cannot be used together + if (($options{'addnoderange'}) && ($options{'rmnoderange'})) { + my $rsp = {}; + $rsp->{error}->[0] = + "You may not use the -a flag to add nodes and the -r flag to remove nodes on one command."; + xCAT::MsgUtils->message("E", $rsp, $callback); + exit 1; + + } + # save input noderange to add nodes + if ($options{'addnoderange'}) { # check to see if Management Node is in the noderange, if so error - $request->{noderange}->[0] = $options{'noderange'}; + $request->{noderange}->[0] = $options{'addnoderange'}; + my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]); + my @mname = xCAT::Utils->noderangecontainsMn(@nodes); + if (@mname) + { # MN in the nodelist + my $nodes=join(',', @mname); + my $rsp = {}; + $rsp->{error}->[0] = + "You must not run $command and include the management node: $nodes."; + xCAT::MsgUtils->message("E", $rsp, $callback, 1); + exit 1; + } + + + } + # save input noderange to remove nodes + if ($options{'rmnoderange'}) { + + # check to see if Management Node is in the noderange, if so error + $request->{noderange}->[0] = $options{'rmnoderange'}; my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]); my @mname = xCAT::Utils->noderangecontainsMn(@nodes); if (@mname) @@ -243,13 +284,21 @@ sub mkzone return 1; } # test for -g, if no noderange this is an error - if (( ! defined($$options{'noderange'})) && ($$options{'assigngroup'})) { + if (( ! defined($$options{'addnoderange'})) && ($$options{'assigngroup'})) { my $rsp = {}; $rsp->{error}->[0] = " The -g flag requires a noderange ( -a)."; xCAT::MsgUtils->message("E", $rsp, $callback); return 1; } + # test for -r, not valid + if ($$options{'rmnoderange'}) { + my $rsp = {}; + $rsp->{error}->[0] = + " The -r flag Is not valid for mkzone. Use chzone."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } # check to see if the input zone already exists if (xCAT::Zone->iszonedefined($request->{zonename})) { my $rsp = {}; @@ -265,10 +314,13 @@ sub mkzone $keydir .= "/.ssh"; - # update the zone table - $rc=updatezonetable($request, $callback,$options,$keydir); + # add new zones to the zone table + $rc=addtozonetable($request, $callback,$options,$keydir); if ($rc == 0) { # zone table setup is ok - $rc=addnodestozone($request, $callback,$options,$keydir); + # test for a noderange, if(-a) not supplied nothing to do + if (defined($$options{'addnoderange'})) { + $rc=addnodestozone($request, $callback,$options,$keydir); + } if ($rc == 0) { # zone table setup is ok # generate root ssh keys $rc=gensshkeys($request, $callback,$options,$keydir); @@ -287,6 +339,10 @@ sub mkzone =head3 Parses and runs chzone + Input + request + callback + Input arguments from the GetOpts =cut @@ -295,14 +351,100 @@ sub mkzone sub chzone { my ($request, $callback,$options,$keydir) = @_; + my $rc=0; + # Create default path to generated ssh keys + # keydir comes in set to /etc/xcat/sshkeys + $keydir .= $request->{zonename}; + $keydir .= "/.ssh"; + my $zonename=$request->{zonename}; + # already checked but lets do it again, need a zonename + if (!($request->{zonename})) { + + my $rsp = {}; + $rsp->{error}->[0] = + "zonename not specified The zonename is required."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + # see if they asked to do anything + if ((!($$options{'sshkeypath'})) && (!($$options{'gensshkeys'})) && + (!( $$options{'addnoderange'})) && (!( $$options{'rmnoderange'})) && + (!( $$options{'defaultzone'})) && + (!($$options{'assigngroup'} )) && (!($$options{'sshbetweennodes'}))) { + my $rsp = {}; + $rsp->{info}->[0] = + "chzone was run but nothing to do."; + xCAT::MsgUtils->message("I", $rsp, $callback); + return 0; + } + # test for -g, if no noderange (-r or -a) this is an error + if ((( ! defined($$options{'addnoderange'}))&& ( ! defined($$options{'rmnoderange'}))) && ($$options{'assigngroup'})) { + my $rsp = {}; + $rsp->{error}->[0] = + " The -g flag requires a noderange using the -a or -r option."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + + # if -r remove nodes from zone, check to see that they are a member of the zone + # if not a member of the zone, error out and do nothing + if ($$options{'rmnoderange'}){ + my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]); + + foreach my $node (@nodes) { + my $nodezonename=xCAT::Zone->getmyzonename($node); + if ($nodezonename ne $zonename) { + my $rsp = {}; + $rsp->{error}->[0] = + " $node does not belong to the zone:$zonename. Rerun the chzone -r command with only nodes in the noderange that are currently assigned to the zone."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + + } + } + # get the zone ssh key directory. We don't have a good zone without it. + my $sshrootkeydir = xCAT::Zone->getzonekeydir($zonename); + if ($sshrootkeydir == 1) { # error return + #if we have been requested to regenerated the ssh keys continue + if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) { + my $rsp = {}; + $rsp->{info}->[0] = + " sshkeydir attribute not defined for $zonename. The zone sshkeydir will be regenerated."; + xCAT::MsgUtils->message("I", $rsp, $callback); + } else { # sshkeydir is missing and they did not request to regenerate, that is an error + my $rsp = {}; + $rsp->{error}->[0] = + " sshkeydir attribute not defined for $zonename. The zone sshkeydir must be regenerated. Rerun this command with -k or -K options"; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + } else { # we got a sshkeydir from the database, use it + $keydir=$sshrootkeydir; + } + # do we regenerate keys (-k or -K) + if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) { + $rc=gensshkeys($request, $callback,$options,$keydir); + if ($rc != 0) { + return 1; + } + } + + # update the zone table + $rc=updatezonetable($request, $callback,$options,$keydir); + if ($rc == 0) { # zone table setup is ok + # update the nodelist table + if (defined($$options{'addnoderange'})) { + $rc=addnodestozone($request, $callback,$options,$keydir); + } else { # note -a and -r are not allowed on one chzone + if (defined($$options{'rmnoderange'})) { + $rc=rmnodesfromzone($request, $callback,$options,$keydir); + } + } + } - # my $rsp = {}; - - #xCAT::MsgUtils->message("I", $rsp, $callback); - - return 0; - + return $rc; } #------------------------------------------------------- @@ -411,7 +553,7 @@ sub rmzone $tab->delEntries({zonename=>$zonename}); # remove zonename and possibly group name (-g flag) from any nodes defined in this zone - my $rc=rmnodesfromzone($request, $callback,$options); + my $rc=rmnodesfromzone($request, $callback,$options,"ALL"); return $rc; @@ -518,7 +660,7 @@ sub gensshkeys #------------------------------------------------------- =head3 - updatezonetable + addtozonetable Add the new zone to the zone table, check if already there and error - use either chzone or -f to override default @@ -527,7 +669,7 @@ sub gensshkeys =cut #------------------------------------------------------- -sub updatezonetable +sub addtozonetable { my ($request, $callback,$options,$keydir) = @_; my $rc=0; @@ -601,6 +743,81 @@ sub updatezonetable } #------------------------------------------------------- +=head3 + updatezonetable + change either the sshbetweennodes or defaultzone attribute + or generate new keys ( -k -K) + + +=cut + +#------------------------------------------------------- +sub updatezonetable +{ + my ($request, $callback,$options,$keydir) = @_; + my $zoneentry; + my $zonename=$request->{zonename}; + # check for changes + if (($$options{'sshbetweennodes'}) || ( $$options{'defaultzone'}) || + ($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) { + + my $tab = xCAT::Table->new("zone"); + if($tab) { + + # now add the users changes + my %tb_cols; + # generated keys ( -k or -K) + if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) { + $tb_cols{sshkeydir} = $keydir; # key directory + } + # set sshbetweennodes attribute from -s flag + if ( $$options{'sshbetweennodes'}) { + $tb_cols{sshbetweennodes} = $$options{'sshbetweennodes'}; + } + # if --defaultzone + if ( $$options{'defaultzone'}) { # set the default + # check to see if a default already defined + my $curdefaultzone = xCAT::Zone->getdefaultzone($callback); + if (!(defined ($curdefaultzone))) { # no default defined + $tb_cols{defaultzone} ="yes"; + } else { # already a default + if ($$options{'force'}) { # force the default + $tb_cols{defaultzone} ="yes"; + $tab->setAttribs({zonename => $zonename}, \%tb_cols); + # now change the old default zone to not be the default + my %tb1_cols; + $tb1_cols{defaultzone} ="no"; + $tab->setAttribs({zonename => $curdefaultzone}, \%tb1_cols); + $tab->commit(); + $tab->close(); + } else { # no force this is an error + my $rsp = {}; + $rsp->{error}->[0] = + " Failure setting default zone. The defaultzone $curdefaultzone already exists. Use the -f flag if you want to override the current default zone."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + } + } else { # not a default zone change, just commit the other changes + $tab->setAttribs({zonename => $zonename}, \%tb_cols); + $tab->commit(); + $tab->close(); + } + } else { + my $rsp = {}; + $rsp->{error}->[0] = + " Failure opening the zone table."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + } + + + return 0; + +} +#------------------------------------------------------- + =head3 addnodestozone Add the new zonename attribute to any nodes in the noderange ( if a noderange specified) @@ -615,15 +832,17 @@ sub addnodestozone { my ($request, $callback,$options,$keydir) = @_; my $rc=0; - # test for a noderange, if not supplied nothing to do - if ( ! defined($$options{'noderange'})) { - return 0; - } my $zonename=$request->{zonename}; - # there is a node range. update the nodelist table # if -g add zonename group also - my $group=$$options{'noderange'}; my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]); + # check to see if noderange expanded + if (!(scalar @nodes)) { + my $rsp = {}; + $rsp->{error}->[0] = + " The noderange $request->{noderange}->[0] is not valid. The nodes are not defined."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } my $tab = xCAT::Table->new("nodelist"); if ($tab) { @@ -651,9 +870,11 @@ sub addnodestozone =head3 rmnodesfromzone - removes the zonename from all nodes with their zonename the input zone + removes the zonename from all nodes with their zonename the input zone or + the noderange supplied on the -r flag if -g, removes zonename group from all nodes defined with their zonename the input zone. - + Note if $ALL is input it removes all nodes from the zone, + otherwise $request->{noderange} points to the noderange =cut @@ -661,13 +882,26 @@ sub addnodestozone #------------------------------------------------------- sub rmnodesfromzone { - my ($request, $callback,$options) = @_; + my ($request, $callback,$options,$ALL) = @_; my $zonename=$request->{zonename}; my $tab = xCAT::Table->new("nodelist"); if ($tab) { # read all the nodes with zonename - my @nodes = xCAT::Zone->getnodesinzone($callback,$zonename); + my @nodes; + if ($ALL) { # do all nodes + @nodes = xCAT::Zone->getnodesinzone($callback,$zonename); + } else { # the nodes in the noderange ( -r ) + @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]); + # check to see if noderange expanded + if (!(scalar @nodes)) { + my $rsp = {}; + $rsp->{error}->[0] = + " The noderange $request->{noderange}->[0] is not valid. The nodes are not defined."; + xCAT::MsgUtils->message("E", $rsp, $callback); + return 1; + } + } # if -g then remove the zonename group attribute on each node if ($$options{'assigngroup'}){ foreach my $node (@nodes) { diff --git a/xCAT-server/xCAT-wsapi/genrestapidoc.pm b/xCAT-server/xCAT-wsapi/genrestapidoc.pm new file mode 100755 index 000000000..fde1f379e --- /dev/null +++ b/xCAT-server/xCAT-wsapi/genrestapidoc.pm @@ -0,0 +1,128 @@ +#! /usr/bin/perl + +package genrestapidoc; + +my @apigroups = ( + { + groupname => 'node', + resources => ['allnode', 'nodeallattr'] + }, + + { + groupname => 'network', + resources => ['network', 'network_allattr', 'network_attr'] + }, +); + +my %formathdl = ( + text => \&outtext, +); + +sub outtext { + my $def = shift; + my $opt = shift; + my $head = shift; + + if ($head) { + print "\n$head\n"; + } + + my $postfix = "?userName=xxx&password=xxx&pretty=1"; + + if (defined ($def->{desc})) { + print " $opt - $def->{desc}\n"; + } + + if (defined ($def->{usage})) { + my @parts = split ('\|', $def->{usage}); + if ($parts[1]) { + print " Parameters: $parts[2]\n"; + } + if ($parts[2]) { + print " Returns: $parts[2]\n"; + } + } + + if (defined ($def->{example})) { + my @parts = split ('\|', $def->{example}); + print " Example:\n"; + + if ($parts[1]) { + print " $parts[1]\n"; + } + + if ($parts[2] && $parts[3] && $parts[4]) { + my ($uri, $data); + if ($part[3] =~ /\s+/) { + ($uri, $data) = split(/ /, $part[3]); + print " #curl $parts[2] -k \'https://myserver/xcatws$uri$postfix\' -H Content-Type:application/json --data \'$data\'\n"; + } else { + print " #curl $parts[2] -k \'https://myserver/xcatws$parts[3]$postfix\'\n"; + } + $parts[4] =~ s/\n/\n /g; + print " $parts[4]\n"; + } + + } +} + +sub gendoc { + my $URIdef = shift; + my $format = shift; + + unless ($format) { + $format = "text"; + } + + my @errmsg; + +foreach my $group (@apigroups) { + my $groupname = $group->{'groupname'}; + if (defined ($URIdef->{$groupname})) { + foreach my $res (@{$group->{'resources'}}) { + if (defined ($URIdef->{$groupname}->{$res})) { + if (defined ($URIdef->{$groupname}->{$res}->{GET})) { + $formathdl{$format}->($URIdef->{$groupname}->{$res}->{GET}, "GET", $URIdef->{$groupname}->{$res}->{desc}); + } + if (defined ($URIdef->{$groupname}->{$res}->{PUT})) { + $formathdl{$format}->($URIdef->{$groupname}->{$res}->{PUT}, "PUT"); + } + if (defined ($URIdef->{$groupname}->{$res}->{POST})) { + $formathdl{$format}->($URIdef->{$groupname}->{$res}->{POST}, "POST"); + } + if (defined ($URIdef->{$groupname}->{$res}->{DELETE})) { + $formathdl{$format}->($URIdef->{$groupname}->{$res}->{DELETE}, "DELETE"); + } + } else { + push @errmsg, "Cannot find the definition for resource [$res]\n"; + } + } + } else { + push @errmsg, "Cannot find the definition for resource group [$groupname]\n"; + } +} + + print @errmsg; +} +sub displayUsage { + foreach my $group (keys %URIdef) { + print "Resource Group: $group\n"; + foreach my $res (keys %{$URIdef{$group}}) { + print " Resource: $res\n"; + print " $URIdef{$group}->{$res}->{desc}\n"; + if (defined ($URIdef{$group}->{$res}->{GET})) { + print " GET: $URIdef{$group}->{$res}->{GET}->{desc}\n"; + } + if (defined ($URIdef{$group}->{$res}->{PUT})) { + print " PUT: $URIdef{$group}->{$res}->{PUT}->{desc}\n"; + } + if (defined ($URIdef{$group}->{$res}->{POST})) { + print " POST: $URIdef{$group}->{$res}->{POST}->{desc}\n"; + } + if (defined ($URIdef{$group}->{$res}->{DELETE})) { + print " DELETE: $URIdef{$group}->{$res}->{DELETE}->{desc}\n"; + } + } + } +} + diff --git a/xCAT-server/xCAT-wsapi/restapi.pl b/xCAT-server/xCAT-wsapi/restapi.pl new file mode 100755 index 000000000..c53bcb4b9 --- /dev/null +++ b/xCAT-server/xCAT-wsapi/restapi.pl @@ -0,0 +1,1734 @@ +#!/usr/bin/perl +# IBM(c) 2014 EPL license http://www.eclipse.org/legal/epl-v10.html +use strict; +use CGI qw/:standard/; #todo: remove :standard when the code only uses object oriented interface +#use JSON; #todo: require this dynamically later on so that installations that do not use xcatws.cgi do not need perl-JSON +use Data::Dumper; + +#talk to the server +use Socket; +use IO::Socket::INET; +use IO::Socket::SSL; +use lib "/opt/xcat/lib/perl"; +use xCAT::Table; + +#bmp: you need more comments in most of the file, especially in the handler routines + +#URIdef{node|network}->{allnode|nodeattr} + +my %URIdef = ( + #### definition for node resources + node => { + allnode => { + desc => "[URI:/node] - The node list resource.", + matcher => '^\/node$', + GET => { + desc => "Get all the nodes in xCAT.", + usage => "||An array of node names.|", + example => "|Get all the node names from xCAT database.|GET|/node|[\n all,\n node1,\n node2,\n node3,\n]|", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout_remove_appended_type, + } + }, + nodeallattr => { + desc => "[URI:/node/{nodename}] - The node resource", + matcher => '^\/node\/[^\/]*$', + GET => { + desc => "Get all the attibutes for the node {nodename}.", + usage => "||An array of node objects.|", + example => "|Get all the attibutes for node \'node1\'.|GET|/node/node1|[\n {\n netboot:xnba,\n mgt:1,\n groups:22,\n name:node1,\n postbootscripts:otherpkgs,\n postscripts:syslog,remoteshell,syncfiles\n }\n]|", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout, + }, + PUT => { + desc => "Change the attibutes for the node {nodename}.", + usage => "||An array of node objects.|", + example => "|Change the attributes mgt=dfm and netboot=yaboot.|PUT|/node/node1 {mgt:dfm,netboot:yaboot}|{\n \"info\":[\n \"1 object definitions have been created or modified.\"\n ]\n}|", + cmd => "chdef", + fhandler => \&defhdl, + outhdler => \&infoout, + }, + POST => { + desc => "Create the node {nodename}. DataBody: {attr1:v1,att2:v2...}.", + usage => "||An array of node objects.|", + example => "|Create a node with attributes groups=all, mgt=dfm and netboot=yaboot|POST|/node/node1 {groups:all,mgt:dfm,netboot:yaboot}|{\n \"info\":[\n \"1 object definitions have been created or modified.\"\n ]\n}|", + cmd => "mkdef", + fhandler => \&defhdl, + outhdler => \&infoout, + }, + DELETE => { + desc => "Remove the node {nodename}.", + usage => "||An array of node objects.|", + example => "|Delete the node node1|DELETE|/node/node1|{\n \"info\":[\n \"1 object definitions have been removed.\"\n ]\n}|", + cmd => "rmdef", + fhandler => \&defhdl, + outhdler => \&infoout, + }, + }, + nodeattr => { + desc => "[URI:/node/{nodename}/attr/attr1;attr2;attr3 ...] - The attributes resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/attr/\S+$', + GET => { + desc => "Get the specific attributes for the node {nodename}.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout, + }, + PUT => { + desc => "Change attributes for the node {nodename}. DataBody: {attr1:v1,att2:v2,att3:v3 ...}.", + cmd => "chdef", + fhandler => \&defhdl, + } + }, + power => { + desc => "[URI:/node/{nodename}/power] - The power resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/power$', + GET => { + desc => "Get the power status for the node {nodename}.", + cmd => "rpower", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + PUT => { + desc => "Change power status for the node {nodename}. DataBody: {action:on|off|reset ...}.", + cmd => "rpower", + fhandler => \&actionhdl, + outhdler => \&actionout, + } + }, + energy => { + desc => "[URI:/node/{nodename}/energy] - The energy resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/energy$', + GET => { + desc => "Get the energy status for the node {nodename}.", + cmd => "renergy", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + PUT => { + desc => "Change energy attributes for the node {nodename}. DataBody: {cappingstatus:on ...}.", + cmd => "renergy", + fhandler => \&actionhdl, + outhdler => \&actionout, + } + }, + energyattr => { + disable => 1, + desc => "[URI:/node/{nodename}/energy/cappingmaxmin;cappingstatus;cappingvalue ...] - The specific energy attributes for the node {nodename}", + matcher => '^\/node\/[^\/]*/energy/\S+$', + GET => { + desc => "Get the specific energy attributes cappingmaxmin,cappingstatus,cappingvalue for the node {nodename}.", + cmd => "renergy", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + PUT => { + desc => "Change energy attributes for the node {nodename}. DataBody: {cappingstatus:on ...}.", + cmd => "renergy", + fhandler => \&actionhdl, + outhdler => \&actionout, + } + }, + serviceprocessor => { + disable => 1, + desc => "[URI:/node/{nodename}/sp/{ip;netmask...}] - The specific attributes of service processor for the node {nodename}", + matcher => '^\/node\/[^\/]*/sp/\S+$', + GET => { + desc => "Get the specific attributes for service processor resource.", + cmd => "rspconfig", + fhandler => \&sphdl, + }, + PUT => { + desc => "Change the specific attributes for the service processor resource. DataBody: {ip:xx.xx.xx.xx.xx.xx,netmask:xx.xx.xx.xx ...}.", + cmd => "rspconfig", + fhandler => \&sphdl, + } + }, + macaddress => { + disable => 1, + desc => "[URI:/node/{nodename}/mac] - The mac address resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/mac$', + GET => { + desc => "Get the mac address for the node {nodename}. Generally, it also updates the mac attribute of the node.", + cmd => "getmacs", + fhandler => \&common, + }, + }, + nextboot => { + desc => "[URI:/node/{nodename}/nextboot] - The boot order in next boot for the node {nodename}", + matcher => '^\/node\/[^\/]*/nextboot$', + GET => { + desc => "Get the next boot order.", + cmd => "rsetboot", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + PUT => { + desc => "Change the next boot order. DataBody: {order:net}.", + cmd => "rsetboot", + fhandler => \&actionhdl, + outhdler => \&actionout, + } + }, + bootorder => { + desc => "[URI:/node/{nodename}/bootorder] - The boot order for the node {nodename}", + matcher => '^\/node\/[^\/]*/bootorder$', + GET => { + desc => "Get the boot order.", + cmd => "rbootseq", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + PUT => { + desc => "Change the boot order. DataBody: {\"order\":\"net,hd\"}.", + cmd => "rbootseq", + fhandler => \&actionhdl, + outhdler => \&actionout, + } + }, + vitals => { + desc => "[URI:/node/{nodename}/vitals] - The vitals attributes for the node {nodename}", + matcher => '^\/node\/[^\/]*/vitals$', + GET => { + desc => "Get all the vitals attibutes.", + cmd => "rvitals", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + }, + vitalsattr => { + disable => 1, + desc => "[URI:/node/{nodename}/vitals/{temp|voltage|wattage|fanspeed|power|leds...}] - The specific vital attributes for the node {nodename}", + matcher => '^\/node\/[^\/]*/vitals/\S+$', + GET => { + desc => "Get the specific vital attibutes.", + cmd => "rvitals", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + }, + inventory => { + desc => "[URI:/node/{nodename}/inventory] - The inventory attributes for the node {nodename}", + matcher => '^\/node\/[^\/]*/inventory$', + GET => { + desc => "Get all the inventory attibutes.", + cmd => "rinv", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + }, + inventoryattr => { + desc => "[URI:/node/{nodename}/inventory/{pci;model...}] - The specific inventory attributes for the node {nodename}", + matcher => '^\/node\/[^\/]*/inventory/\S+$', + GET => { + desc => "Get the specific inventory attibutes.", + cmd => "rinv", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + }, + eventlog => { + desc => "[URI:/node/{nodename}/eventlog] - The eventlog resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/eventlog$', + GET => { + desc => "Get all the eventlog for the node {nodename}.", + cmd => "reventlog", + fhandler => \&common, + }, + DELETE => { + desc => "Clean up the event log for the node {nodename}.", + cmd => "reventlog", + fhandler => \&common, + }, + }, + beacon => { + desc => "[URI:/node/{nodename}/beacon] - The beacon resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/beacon$', + GET => { + desc => "Get the beacon status for the node {nodename}.", + cmd => "rbeacon", + fhandler => \&common, + }, + PUT => { + desc => "Change the beacon status for the node {nodename}. DataBody: {on|off|blink}.", + cmd => "rbeacon", + fhandler => \&common, + }, + }, + virtualization => { + desc => "[URI:/node/{nodename}/virtualization] - The virtualization resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/virtualization$', + GET => { + desc => "Get the vm status for the node {nodename}.", + cmd => "lsvm", + fhandler => \&common, + }, + PUT => { + desc => "Change the vm status for the node {nodename}. DataBody: {new:1|clone:1|migrate:1 ...}. new=1 means to run mkvm; clone=1 means to run rclone; migrate=1 means to run rmigrate.", + cmd => "", + fhandler => \&common, + }, + DELETE => { + desc => "Remove the vm node {nodename}.", + cmd => "rmvm", + fhandler => \&common, + }, + }, + updating => { + desc => "[URI:/node/{nodename}/updating] - The updating resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/updating$', + PUT => { + desc => "Update the node with file syncing, software maintenance and rerun postscripts.", + cmd => "updatenode", + fhandler => \&common, + }, + }, + filesyncing => { + desc => "[URI:/node/{nodename}/filesyncing] - The filesyncing resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/filesyncing$', + PUT => { + desc => "Sync files for the node {nodename}. DataBody: {location of syncfile}", + cmd => "updatenode", + fhandler => \&common, + }, + }, + software_maintenance => { + desc => "[URI:/node/{nodename}/sw] - The software maintenance for the node {nodename}", + matcher => '^\/node\/[^\/]*/sw$', + PUT => { + desc => "Perform the software maintenance process for the node {nodename}.", + cmd => "updatenode", + fhandler => \&common, + }, + }, + postscript => { + desc => "[URI:/node/{nodename}/postscript] - The postscript resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/postscript$', + PUT => { + desc => "Run the postscripts for the node {nodename}. DataBody: {p1,p2,p3...}", + cmd => "updatenode", + fhandler => \&common, + }, + }, + nodeshell => { + desc => "[URI:/node/{nodename}/nodeshell] - The nodeshell resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/nodeshell$', + PUT => { + desc => "Run the command in the nodeshell of the node {nodename}. DataBody: { ... }", + cmd => "xdsh", + fhandler => \&common, + }, + }, + nodecopy => { + desc => "[URI:/node/{nodename}/nodecopy] - The nodecopy resource for the node {nodename}", + matcher => '^\/node\/[^\/]*/nodecopy$', + PUT => { + desc => "Copy files to the node {nodename}. DataBody: { ... }", + cmd => "xcp", + fhandler => \&common, + }, + }, + subnode => { + desc => "[URI:/node/{nodename}/subnode] - The sub nodes for the node {nodename}", + matcher => '^\/node\/[^\/]*/subnode$', + GET => { + desc => "Return the Children node for the node {nodename}.", + cmd => "rscan", + fhandler => \&common, + }, + PUT => { + desc => "Update the Children node for the node {nodename}.", + cmd => "rscan", + fhandler => \&common, + }, + }, + # for slpnode, we need use the query attribute to specify the network parameter for lsslp command + slpnode => { + desc => "[URI:/slpnode?network=xx] - The slp nodes in the xCAT cluster", + matcher => '^\/slpnode\?.*$', + GET => { + desc => "Get all the nodes which support slp protocol in the network.", + cmd => "lsslp", + fhandler => \&common, + }, + PUT => { + desc => "Update the discovered nodes to database.", + cmd => "lsslp", + fhandler => \&common, + }, + }, + specific_slpnode => { + desc => "[URI:/slpnode/{IMM;CMM;CEC;FSP...}?network=xx] - The slp nodes with specific service type in the xCAT cluster", + matcher => '^\/slpnode/[^\/]*/\?.*$', + GET => { + desc => "Get all the nodes with specific slp service type in the network.", + cmd => "lsslp", + fhandler => \&common, + }, + PUT => { + desc => "Update the discovered nodes to database.", + cmd => "lsslp", + fhandler => \&common, + }, + }, + bootstat => { + desc => "[URI:/node/{nodename}/bootstat] - The boot state resource for node {nodename}.", + matcher => '^\/node\/[^\/]*/bootstat$', + GET => { + desc => "Get boot state.", + cmd => "nodeset", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + PUT => { + desc => "Set the boot state. DataBody: {osimage:xxx}", + cmd => "nodeset", + fhandler => \&actionhdl, + outhdler => \&actionout, + }, + }, + + + # TODO: rflash + }, + + #### definition for group resources + group => { + }, + + #### definition for network resources + network => { + network => { + desc => "[URI:/network] - The network resource.", + matcher => '^\/network$', + GET => { + desc => "Get all the networks in xCAT.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout_remove_appended_type, + }, + POST => { + desc => "Create the network resources base on the network configuration on xCAT MN.", + cmd => "makenetworks", + fhandler => \&defhdl, + }, + }, + network_allattr => { + desc => "[URI:/network/{netname}] - The network resource", + matcher => '^\/network\/[^\/]*$', + GET => { + desc => "Get all the attibutes for the network {netname}.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout, + }, + PUT => { + desc => "Change the attibutes for the network {netname}.", + cmd => "chdef", + fhandler => \&defhdl, + }, + POST => { + desc => "Create the network {netname}. DataBody: {attr1:v1,att2:v2...}.", + cmd => "mkdef", + fhandler => \&defhdl, + }, + DELETE => { + desc => "Remove the network {netname}.", + cmd => "rmdef", + fhandler => \&defhdl, + }, + }, + network_attr => { + desc => "[URI:/network/{netname}/attr/attr1;attr2;attr3 ...] - The attributes resource for the network {netname}", + matcher => '^\/network\/[^\/]*/attr/\S+$', + GET => { + desc => "Get the specific attributes for the network {netname}.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout, + }, + PUT => { + desc => "Change attributes for the network {netname}. DataBody: {attr1:v1,att2:v2,att3:v3 ...}.", + cmd => "chdef", + fhandler => \&defhdl, + } + }, + + }, + + #### definition for osimage resources + osimage => { + osimage => { + desc => "[URI:/osimage] - The osimage resource.", + matcher => '^\/osimage$', + GET => { + desc => "Get all the osimage in xCAT.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout_remove_appended_type, + }, + POST => { + desc => "Create the osimage resources base on the iso specified in the Data body. DataBody: {isopath: path}", + cmd => "copycds", + fhandler => \&defhdl, + }, + }, + osimage_allattr => { + desc => "[URI:/osimage/{osname}] - The osimage resource", + matcher => '^\/osimage\/[^\/]*$', + GET => { + desc => "Get all the attibutes for the osimage {osname}.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout, + }, + PUT => { + desc => "Change the attibutes for the osimage {osname}.", + cmd => "chdef", + fhandler => \&defhdl, + }, + POST => { + desc => "Create the osimage {osname}. DataBody: {attr1:v1,att2:v2...}.", + cmd => "mkdef", + fhandler => \&defhdl, + }, + DELETE => { + desc => "Remove the osimage {osname}.", + cmd => "rmdef", + fhandler => \&defhdl, + }, + }, + osimage_attr => { + desc => "[URI:/osimage/{osname}/attr/attr1;attr2;attr3 ...] - The attributes resource for the osimage {osname}", + matcher => '^\/osimage\/[^\/]*/attr/\S+$', + GET => { + desc => "Get the specific attributes for the osimage {osname}.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout, + }, + PUT => { + desc => "Change attributes for the osimage {osname}. DataBody: {attr1:v1,att2:v2,att3:v3 ...}.", + cmd => "chdef", + fhandler => \&defhdl, + } + }, + + # todo: genimage, packimage, imagecapture, imgexport, imgimport + }, + + #### definition for global setting resources + site => { + site => { + desc => "[URI:/globalconf] - The global configuration resource.", + matcher => '^\/globalconf$', + GET => { + desc => "Get all the configuration in global.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout_remove_appended_type, + }, + }, + site => { + desc => "[URI:/globalconf/attr/{attr1;attr2 ...}] - The specific global configuration resource.", + matcher => '^\/globalconf/attr/\S+$', + GET => { + desc => "Get the specific configuration in global.", + cmd => "lsdef", + fhandler => \&defhdl, + outhdler => \&defout_remove_appended_type, + }, + PUT => { + }, + }, + }, + + #### definition for database/table resources + + #### definition for database/table resources + table => { + table_nodes => { + desc => "[URI:/table/{tablelist}/node/{noderange}/{attrlist}] - The node table resource", + matcher => '^/table/[^/]+/node(/[^/]+){0,2}$', + GET => { + desc => "Get attibutes for noderange {noderange} of the table {table}.", + cmd => "getTablesNodesAttribs", # not used + fhandler => \&tablenodehdl, + outhdler => \&tableout, + }, + PUT => { + desc => "Change the attibutes for the table {table}.", + cmd => "chdef", + fhandler => \&defhdl, + #outhdler => \&defout, + }, + POST => { + desc => "Create the table {table}. DataBody: {attr1:v1,att2:v2...}.", + cmd => "mkdef", + fhandler => \&defhdl, + }, + DELETE => { + desc => "Remove the table {table}.", + cmd => "rmdef", + fhandler => \&defhdl, + }, + }, + table_rows => { + desc => "[URI:/table/{tablelist}/row/{keys}/{attrlist}] - The non-node table resource", + matcher => '^/table/[^/]+/row(/[^/]+){0,2}$', + GET => { + desc => "Get attibutes for rows of the table {table}.", + cmd => "getTablesAllRowAttribs", # not used + fhandler => \&tablerowhdl, + outhdler => \&tableout, + }, + PUT => { + desc => "Change the attibutes for the table {table}.", + cmd => "chdef", + fhandler => \&defhdl, + #outhdler => \&defout, + }, + POST => { + desc => "Create the table {table}. DataBody: {attr1:v1,att2:v2...}.", + cmd => "mkdef", + fhandler => \&defhdl, + }, + DELETE => { + desc => "Remove the table {table}.", + cmd => "rmdef", + fhandler => \&defhdl, + }, + }, + }, + +); + +# supported formats +my %formatters = ( + 'json' => \&wrapJson, + #'html' => \&wrapHtml, + #'xml' => \&wrapXml +); + +#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"; + +my $XCAT_PATH = '/opt/xcat/bin'; +my $VERSION = "2.8"; + + +# Development notes: +# - added this line to /etc/httpd/conf/httpd.conf to hide the cgi-bin and .cgi extension in the uri: +# ScriptAlias /xcatws /var/www/cgi-bin/xcatws.cgi +# - also upgraded CGI to 3.52 +# - If "Internal Server Error" is returned, look at /var/log/httpd/ssl_error_log +# - can run your cgi script from the cli: http://perldoc.perl.org/CGI.html#DEBUGGING + +# This is how the parameters come in: +# GET: url parameters come $q->url_param. There is no put/post data. +# PUT: url parameters come $q->url_param. Put data comes in q->param(PUTDATA). +# POST: url parameters come $q->url_param. Post data comes in q->param(POSTDATA). +# DELETE: ?? + +# Notes from http://perldoc.perl.org/CGI.html: +# %params = $q->Vars; # same as $q->param() except put it in a hash +# @foo = split("\0",$params{'foo'}); +# my $error = $q->cgi_error; #todo: check for errors that occurred while processing user input +# print $q->end_html; #todo: add the tags +# $q->url_param() # gets url options, even when there is put/post data (unlike q->param) + +#### Main procedure to handle the REST request + +my $q = CGI->new; +#my $url = $q->url; # the 1st part of the url, https, hostname, port num, and /xcatws +my $pathInfo = $q->path_info; # the resource specification, i.e. everything in the url after xcatws +#my $requestType = $ENV{'REQUEST_METHOD'}; +my $requestType = $q->request_method(); # GET, PUT, POST, PATCH, DELETE +#my $queryString = $ENV{'QUERY_STRING'}; #todo: remove this when not used any more +#my $userAgent = $ENV{'HTTP_USER_AGENT'}; # curl, etc. +my $userAgent = $q->user_agent(); # the client program: curl, etc. +#my %queryhash; # the queryString will get put into this +my @path = split(/\//, $pathInfo); +#shift(@path); # get rid of the initial / +#my $resource = $path[0]; +my $pageContent = ''; # global var containing the ouptut back to the rest client +my $request = {clienttype => 'ws'}; # global var that holds the request to send to xcatd +my $format = 'json'; +my $pretty; +my $xmlinstalled; + +# Handle the command parameter for debugging and generating doc +my $dbgdata; +sub dbgusage { print "Usage:\n $0 -h\n $0 -d\n $0 {GET|PUT|POST|DELETE} URI user:password data\n"; } + +if ($ARGV[0] eq "-h") { + dbgusage(); + exit 0; +} elsif ($ARGV[0] eq "-g") { + require genrestapidoc; + genrestapidoc::gendoc(\%URIdef); + exit 0; +} elsif ($ARGV[0] eq "-d") { + displayUsage(); + exit 0; +} elsif ($ARGV[0] =~ /(GET|PUT|POST|DELETE)/) { + $requestType = $ARGV[0]; + $pathInfo= $ARGV[1]; + + unless ($pathInfo) { dbgusage(); exit 1; } + + if ($ARGV[2] =~ /(.*):(.*)/) { + $ENV{userName} = $1; + $ENV{password} = $2; + } else { + dbgusage(); + exit 0; + } + $dbgdata = $ARGV[3] if defined ($ARGV[3]); +} elsif (defined ($ARGV[0])) { + dbgusage(); + exit 1; +} + +my $JSON; # global ptr to the json object. Its set by loadJSON() +#if (isPut() || isPost()) { + loadJSON(); # need to do this early, so we can fetch the params +#} + +# the input parameters from both the url and put/post data will combined and then +# separated into the general params (not specific to the api call) and params specific to the call +# Note: some of the values of the params in the hash can be arrays +my ($generalparams, $paramhash) = fetchParameters(); + +my $DEBUGGING = $generalparams->{debug}; # turn on or off the debugging output by setting debug=1 (or 2) in the url string +if ($DEBUGGING) { + displaydebugmsg(); +} + +# Process the format requested +$format = $generalparams->{format} if (defined ($generalparams->{format})); + +# Remove the last '/' in the pathInfo +$pathInfo =~ s/\/$//; + +# Get the payload format from the end of URI +#if ($pathInfo =~ /\.json$/) { +# $format = "json"; +# $pathInfo =~ s/\.json$//; +#} elsif ($pathInfo =~ /\.json.pretty$/) { +# $format = "json"; +# $pretty = 1; +# $pathInfo =~ s/\.json.pretty$//; +#} elsif ($pathInfo =~ /\.xml$/) { +# $format = "xml"; +# $pathInfo =~ s/\.xml$//; +#} elsif ($pathInfo =~ /\.html$/) { +# $format = "html"; +# $pathInfo =~ s/\.html$//; +#} + +#if (!exists $formatters{$format}) { +# error("The format '$format' is not supported",$STATUS_BAD_REQUEST); +#} + +if ($format eq 'json') { +# loadJSON(); # in case it was not loaded before + if ($generalparams->{pretty}) { $JSON->indent(1); } +} + +# require XML dynamically and let them know if it is not installed +# we need XML all the time to send request to xcat, even if thats not the return format requested by the user +loadXML(); + +# Match the first layer of resource URI +my $uriLayer1; + +#bmp: why can't you just split on "/" like xcatws.cgi did? +# Get all the layers in the URI +my @layers; +my $portion = index($pathInfo, '/'); +while (1) { + my $endportion = index($pathInfo, '/', $portion+1); + if ($endportion >= 0) { + my $layer = substr($pathInfo, $portion+1, ($endportion - $portion - 1)); + push @layers, $layer if ($layer); + $portion = $endportion; + } else { # the last layer + my $layer = substr($pathInfo, $portion+1); + push @layers, $layer if ($layer); + last; + } +} + +if ($#layers < 0) { + # If no resource was specified + addPageContent($q->p("This is the root page for the xCAT Rest Web Service. Available resources are:")); + foreach (sort keys %URIdef) { + addPageContent($q->p($_)); + } + sendResponseMsg($STATUS_OK); # this will also exit +} else { + $uriLayer1 = $layers[0]; +} + +# set the user and password to access xcatd +#todo: replace with using certificates or an api key +$request->{becomeuser}->[0]->{username}->[0] = $ENV{userName} if (defined($ENV{userName})); +$request->{becomeuser}->[0]->{username}->[0] = $generalparams->{userName} if (defined($generalparams->{userName})); +$request->{becomeuser}->[0]->{password}->[0] = $ENV{password} if (defined($ENV{password})); +$request->{becomeuser}->[0]->{password}->[0] = $generalparams->{password} if (defined($generalparams->{password})); + +# find and invoke the correct handler and output handler functions +my $outputdata; +my $handled; +if (defined ($URIdef{$uriLayer1})) { + # Make sure the resource has been defined + foreach my $res (keys %{$URIdef{$uriLayer1}}) { + my $matcher = $URIdef{$uriLayer1}->{$res}->{matcher}; + #bmp: if you use m|$matcher| here instead then you won't need to escape all of the /'s ? + if ($pathInfo =~ m|$matcher|) { + # matched to a resource + if (defined ($URIdef{$uriLayer1}->{$res}->{$requestType}->{fhandler})) { #bmp: if there isn't a handler, shouldn't we error out? + my $params; + unless (defined ($URIdef{$uriLayer1}->{$res}->{$requestType})) { #bmp: how can this not be defined if we got here? + error("request method '$requestType' is not supported on resource '$pathInfo'",$STATUS_NOT_ALLOWED); + } + $params->{'cmd'} = $URIdef{$uriLayer1}->{$res}->{$requestType}->{cmd} if (defined ($URIdef{$uriLayer1}->{$res}->{$requestType}->{cmd})); + $params->{'outputhdler'} = $URIdef{$uriLayer1}->{$res}->{$requestType}->{outhdler} if (defined ($URIdef{$uriLayer1}->{$res}->{$requestType}->{outhdler})); + $params->{'layers'} = \@layers; + $params->{'resourcegroup'} = $uriLayer1; + $params->{'resourcename'} = $res; + # Call the hanldle subroutine to send request to xcatd and format the output + #@outputdata = $URIdef{$uriLayer1}->{$res}->{$requestType}->{fhandler}->($params); + # get the response from xcatd + $outputdata = $URIdef{$uriLayer1}->{$res}->{$requestType}->{fhandler}->($params); + # Filter the output data from the response + $outputdata = filterData ($outputdata); + # Restructure the output data + if (defined ($URIdef{$uriLayer1}->{$res}->{$requestType}->{outhdler})) { + $outputdata = $URIdef{$uriLayer1}->{$res}->{$requestType}->{outhdler}->($outputdata, $params); + } else { + # Call the appropriate formatting function stored in the formatters hash + if (exists $formatters{$format}) { + $formatters{$format}->($outputdata); + } + } + + $handled = 1; + last; + } + } + } +} else { + error("Unspported resource.",$STATUS_NOT_FOUND); +} + +unless ($handled) { + error("Unspported resource.",$STATUS_NOT_FOUND); +} + + +# all output has been added into the global varibale pageContent, call the response funcion +#if (exists $data->[0]->{info} && $data->[0]->{info}->[0] =~ /Could not find an object/) { +# sendResponseMsg($STATUS_NOT_FOUND); +#} +if (isPost()) { + sendResponseMsg($STATUS_CREATED); +} +else { + sendResponseMsg($STATUS_OK); +} + +#### End of the Main Program + +#=========================================================== +# Subrutines +sub isGET { return uc($requestType) eq "GET"; } +sub isPost { return uc($requestType) eq "POST"; } +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"; } + + +# handle the input like +# Object name: +# attr=value +# --- +# TO +# --- +# nodename : value +# attr : value +sub defout { + my $data = shift; + + my $json; + foreach my $d (@$data) { + my $jsonnode; + my $lines = $d->{info}; + foreach my $l (@$lines) { + if ($l =~ /^Object name: /) { # start new node + if (defined($jsonnode)) { push @$json, $jsonnode; } # push previous object onto array + my ($nodename) = $l =~ /^Object name:\s+(\S+)/; + $jsonnode = { name => $nodename }; + } + else { # just an attribute of the current node + if (!defined($jsonnode)) { error('improperly formatted lsdef output from xcatd', $STATUS_TEAPOT); } + my ($attr, $val) = $l =~ /^\s*(\S+)=(.*)$/; + if (!defined($attr)) { error('improperly formatted lsdef output from xcatd', $STATUS_TEAPOT); } + $jsonnode->{$attr} = $val; + } + } + if (defined($jsonnode)) { push @$json, $jsonnode; $jsonnode=undef; } # push last object onto array + } + addPageContent($JSON->encode($json)); +} +# handle the input like +# all (node) +# node1 (node) +# node2 (node) +# --- +# TO +# --- +# all +# node1 +# node2 + +sub defout_remove_appended_type { + my $data = shift; + + my $json; + foreach my $d (@$data) { + my $jsonnode; + my $lines = $d->{info}; + foreach my $l (@$lines) { + if ($l =~ /^(\S*)\s+\(.*\)$/) { # start new node + push @{$json}, $1; + } + } + #if (defined($jsonnode)) { push @$json, $jsonnode; $jsonnode=undef; } # push last object onto array + } + if ($json) { + addPageContent($JSON->encode($json)); + } +} + +sub infoout { + my $data = shift; + + my $json; + foreach my $d (@$data) { + if (defined ($d->{info})) { + push @{$json->{info}}, @{$d->{info}}; + } + } + if ($json) { + addPageContent($JSON->encode($json)); + } +} + +sub actionout { + my $data = shift; + my $param =shift; + + my $json; + my $jsonnode; + foreach my $d (@$data) { + if (defined ($d->{node}->[0]->{name})) { + $jsonnode->{$d->{node}->[0]->{name}->[0]}->{'name'} = $d->{node}->[0]->{name}->[0]; + } else { + next; + } + if (defined ($d->{node}->[0]->{data}) && ! defined($d->{node}->[0]->{data}->[0]->{contents})) { + $jsonnode->{$d->{node}->[0]->{name}->[0]}->{$param->{'resourcename'}} = $d->{node}->[0]->{data}->[0]; + } elsif (defined ($d->{node}->[0]->{data}->[0]->{contents})) { + if (defined($d->{node}->[0]->{data}->[0]->{desc})) { + $jsonnode->{$d->{node}->[0]->{name}->[0]}->{$d->{node}->[0]->{data}->[0]->{desc}->[0]} = $d->{node}->[0]->{data}->[0]->{contents}->[0]; + } else { + $jsonnode->{$d->{node}->[0]->{name}->[0]}->{$param->{'resourcename'}} = $d->{node}->[0]->{data}->[0]->{contents}->[0]; + } + } + } + + foreach (keys %{$jsonnode}) { + push @$json, $jsonnode->{$_}; + } + + addPageContent($JSON->encode($json)) if ($json); +} + +sub defout_1 { + my $msg = shift; + + my @output; + my $hn; + my $node; + foreach (@{$msg}) { + if (defined ($_->{info})) { + foreach my $line (@{$_->{info}}) { + if ($line =~ /Object name: (.*)/) { + #if ($node) { + # push @output, $hn; + #} + $node = $1; + } elsif ($line =~ /(.*)=(.*)/) { + my $n = $1; + my $v = $2; + $n =~ s/^\s*//; + $n =~ s/\s*$//; + $v =~ s/^\s*//; + $v =~ s/\s*$//; + $hn->{$node}->{$n} = $v; + } + } + push @output, $hn; + } else { + push @output, $_; + } + } + return \@output; +} + + +# invoke one of the def cmds +sub defhdl { + my $params = shift; + + my @args; + my @urilayers = @{$params->{'layers'}}; + + # set the command name + $request->{command} = $params->{'cmd'}; + + # push the -t args + push @args, ('-t', $params->{'resourcegroup'}); + + # push the object name - node/noderange + if (defined ($urilayers[1])) { + push @args, ('-o', $urilayers[1]); + } + + foreach my $k (keys(%$paramhash)) { + push @args, "$k=$paramhash->{$k}" if ($k); + } + + if ($params->{'resourcename'} eq "allnode") { + push @args, '-s'; + } elsif ($params->{'resourcename'} eq "nodeattr") { + my $attrs = $urilayers[3]; + $attrs =~ s/;/,/g; + + if (isGET()) { + push @args, ('-i', $attrs); + } + } + + push @{$request->{arg}}, @args; + my $req = genRequest(); + my $responses = sendRequest($req); + + return $responses; +} + +sub actionhdl { + my $params = shift; + + my @args; + my @urilayers = @{$params->{'layers'}}; + + # set the command name + $request->{command} = $params->{'cmd'}; + + # push the object name - node/noderange + if (defined ($urilayers[1])) { + $request->{noderange} = $urilayers[1]; + } + + if ($params->{'resourcename'} eq "power") { + if (isGET()) { + push @args, 'stat'; + } elsif ($paramhash->{'action'}) { + #my @v = keys(%$paramhash); + push @args, $paramhash->{'action'}; + } else { + error("Missed Action.",$STATUS_NOT_FOUND); + } + } elsif ($params->{'resourcename'} =~ /(energy|energyattr)/) { + if (isGET()) { + if ($params->{'resourcename'} eq "energy") { + push @args, 'all'; + } elsif ($params->{'resourcename'} eq "energyattr") { + my @attrs = split(';', $urilayers[3]); + push @args, @attrs; + } + } elsif ($paramhash) { + my @params = keys(%$paramhash); + push @args, "$params[0]=$paramhash->{$params[0]}"; + } else { + error("Missed Action.",$STATUS_NOT_FOUND); + } + } elsif ($params->{'resourcename'}eq "bootstat") { + if (isGET()) { + push @args, 'stat'; + } elsif ($paramhash->{'action'}) { + push @args, $paramhash->{'action'}; + } elsif ($paramhash) { + my @params = keys(%$paramhash); + push @args, "$params[0]=$paramhash->{$params[0]}"; + } else { + error("Missed Action.",$STATUS_NOT_FOUND); + } + } elsif ($params->{'resourcename'} eq "nextboot") { + if (isGET()) { + push @args, 'stat'; + } elsif ($paramhash->{'order'}) { + push @args, $paramhash->{'order'}; + } else { + error("Missed Action.",$STATUS_NOT_FOUND); + } + } elsif ($params->{'resourcename'} =~ /(vitals|vitalsattr|inventory|inventoryattr)/) { + if (defined($urilayers[3])) { + my @attrs = split(';', $urilayers[3]); + push @args, @attrs; + } + } + + push @{$request->{arg}}, @args; + my $req = genRequest(); + my $responses = sendRequest($req); + + return $responses; +} + + +# get attrs of tables for a noderange +sub tablenodehdl { + my $params = shift; + + my @args; + my @urilayers = @{$params->{'layers'}}; + # the array elements for @urilayers are: + # 0 - 'table' + # 1 - + # 2 - 'node' + # 3 - (optional) + # 4 - (optional) + + # set the command name + my @tables = split(/,/, $urilayers[1]); + + if (!defined($urilayers[3]) || $urilayers[3] eq 'ALLNODES') { + $request->{command} = 'getTablesAllNodeAttribs'; + } else { + $request->{command} = 'getTablesNodesAttribs'; + $request->{noderange} = $urilayers[3]; + } + + # if they specified attrs, sort/group them by table + my $attrlist = $urilayers[4]; + if (!defined($attrlist)) { $attrlist = 'ALL'; } # attr=ALL means get all non-blank attributes + my @attrs = split(/,/, $attrlist); + my %attrhash; + foreach my $a (@attrs) { + if ($a =~ /\./) { + my ($table, $attr) = split(/\./, $a); + push @{$attrhash{$table}}, $attr; + } + else { # the attr doesn't have a table qualifier so apply to all tables + foreach my $t (@tables) { push @{$attrhash{$t}}, $a; } + } + } + + # deal with all of the tables and the attrs for each table + foreach my $tname (@tables) { + my $table = { tablename => $tname }; + if (defined($attrhash{$tname})) { $table->{attr} = $attrhash{$tname}; } + else { $table->{attr} = 'ALL'; } + push @{$request->{table}}, $table; + } + + + my $req = genRequest(); + # disabling the KeyAttr option is important in this case, so xmlin doesn't pull the name attribute + # out of the node hash and make it the key + my $responses = sendRequest($req, {SuppressEmpty => undef, ForceArray => 0, KeyAttr => []}); + + return $responses; +} + + +# get attrs of tables for keys +sub tablerowhdl { + my $params = shift; + + my @args; + my @urilayers = @{$params->{'layers'}}; + # the array elements for @urilayers are: + # 0 - 'table' + # 1 - + # 2 - 'row' + # 3 - (optional) + # 4 - (optional) + + # do stuff that is common between getAttribs and getTablesAllRowAttribs + my @tables = split(/,/, $urilayers[1]); + my $attrlist = $urilayers[4]; + if (!defined($attrlist)) { $attrlist = 'ALL'; } # attr=ALL means get all non-blank attributes + my @attrs = split(/,/, $attrlist); + + # get all rows for potentially multiple tables + if (!defined($urilayers[3]) || $urilayers[3] eq 'ALLROWS') { + $request->{command} = 'getTablesAllRowAttribs'; + + # if they specified attrs, sort/group them by table + my %attrhash; + foreach my $a (@attrs) { + if ($a =~ /\./) { + my ($table, $attr) = split(/\./, $a); + push @{$attrhash{$table}}, $attr; + } + else { # the attr doesn't have a table qualifier so apply to all tables + foreach my $t (@tables) { push @{$attrhash{$t}}, $a; } + } + } + + # deal with all of the tables and the attrs for each table + foreach my $tname (@tables) { + my $table = { tablename => $tname }; + if (defined($attrhash{$tname})) { $table->{attr} = $attrhash{$tname}; } + else { $table->{attr} = 'ALL'; } + push @{$request->{table}}, $table; + } + } + + # for 1 table, get just one row based on the keys given + else { + if (scalar(@tables) > 1) { error('currently you can only specify keys for a single table.', $STATUS_BAD_REQUEST); } + $request->{command} = 'getAttribs'; + $request->{table} = $tables[0]; + if (defined($urilayers[3])) { + my @keyvals = split(/,/, $urilayers[3]); + foreach my $kv (@keyvals) { + my ($key, $value) = split(/\s*=\s*/, $kv, 2); + $request->{keys}->{$key} = $value; + } + } + foreach my $a (@attrs) { push @{$request->{attr}}, $a; } + } + + my $req = genRequest(); + # disabling the KeyAttr option is important in this case, so xmlin doesn't pull the name attribute + # out of the node hash and make it the key + my $responses = sendRequest($req, {SuppressEmpty => undef, ForceArray => 0, KeyAttr => []}); + + return $responses; +} + +# parse the output of all attrs of tables. This is used for both node-oriented tables +# and non-node-oriented tables. +#todo: investigate a converter straight from xml to json +sub tableout { + my $data = shift; + my $json = {}; + # For the table calls, we turned off ForceArray and KeyAttr for XMLin(), so the output is a little + # different than usual. Each element is a hash with key "table" that is either a hash or array of hashes. + # Each element of that is a hash with 2 keys called "tablename" and "node". The latter has either: an array of node hashes, + # or (if there is only 1 node returned) the node hash directly. + # We are producing json that is a hash of table name keys that each have an array of node objects. + foreach my $d (@$data) { + my $table = $d->{table}; + if (!defined($table)) { # special case for the getAttribs cmd + $json = $d; + last; + } + #debug(Dumper($d)); debug (Dumper($jsonnode)); + if (ref($table) eq 'HASH') { $table = [$table]; } # if a single table, make it a 1 element array of tables + foreach my $t (@$table) { + my $jsonnodes = []; # start an array of node objects for this table + my $tabname = $t->{tablename}; + if (!defined($tabname)) { $tabname = 'unknown' . $::i++; } #todo: have lissa fix this bug + $json->{$tabname} = $jsonnodes; # add it into the top level hash + my $node = $t->{node}; + if (!defined($node)) { $node = $t->{row}; } + #debug(Dumper($d)); debug (Dumper($jsonnode)); + if (ref($node) eq 'HASH') { $node = [$node]; } # if a single node, make it a 1 element array of nodes + foreach my $n (@$node) { push @$jsonnodes, $n; } + } + } + addPageContent($JSON->encode($json)); +} + +sub displayUsage { + foreach my $group (keys %URIdef) { + print "Resource Group: $group\n"; + foreach my $res (keys %{$URIdef{$group}}) { + print " Resource: $res\n"; + print " $URIdef{$group}->{$res}->{desc}\n"; + if (defined ($URIdef{$group}->{$res}->{GET})) { + print " GET: $URIdef{$group}->{$res}->{GET}->{desc}\n"; + } + if (defined ($URIdef{$group}->{$res}->{PUT})) { + print " PUT: $URIdef{$group}->{$res}->{PUT}->{desc}\n"; + } + if (defined ($URIdef{$group}->{$res}->{POST})) { + print " POST: $URIdef{$group}->{$res}->{POST}->{desc}\n"; + } + if (defined ($URIdef{$group}->{$res}->{DELETE})) { + print " DELETE: $URIdef{$group}->{$res}->{DELETE}->{desc}\n"; + } + } + } +} + + +# This handles and removes serverdone and error tags in the perl data structure that is from the xml that xcatd returned +#bmp: is there a way to avoid make a copy of the whole response? For big output that could be time consuming. +# For the error tag, you don't have to bother copying the response, because you are going to exit anyway. +# Maybe this function could just verify there is a serverdone and handle any error, and then +# let each specific output handler ignore the serverdone tag? +sub filterData { + my $data = shift; + my $errorInformation = ''; + #debugandexit(Dumper($data)); + + my $outputdata; + #trim the serverdone message off + foreach (@{$data}) { + if (exists($_->{serverdone}) || defined($_->{error})) { + if (exists($_->{serverdone}) && defined($_->{error})) { + if (ref($_->{error}) eq 'ARRAY') { $errorInformation = $_->{error}->[0]; } + else { $errorInformation = $_->{error}; } + addPageContent(qq({"error":"$errorInformation"})); + if (($errorInformation =~ /Permission denied/) || ($errorInformation =~ /Authentication failure/)) { + sendResponseMsg($STATUS_UNAUTH); + } + else { + sendResponseMsg($STATUS_FORBIDDEN); + } + exit 1; + } + next; + } else { + push @{$outputdata}, $_; + } + + } + + return $outputdata; +} + +# Structure the response perl data structure into well-formed json. Since the structure of the +# xml output that comes from xcatd is inconsistent and not very structured, we have a lot of work to do. +sub wrapJson { + # this is an array of responses from xcatd. Often all the output comes back in 1 response, but not always. + my $data = shift; + + addPageContent($JSON->encode($data)); + return; + + + # put, delete, and patch usually just give a short msg, if anything + if (isPut() || isDelete() || isPatch()) { + addPageContent($JSON->encode($data)); + return; + } +} + + +#bmp: this isn't used anymore, just here for reference, right? +# structure the json output for node resource api calls +sub wrapJsonNodes { + # this is an array of responses from xcatd. Often all the output comes back in 1 response, but not always. + my $data = shift; + + # Divide the processing into several groups of requests, according to how they return the output + # At this point, these are all gets and posts. The others were taken care of wrapJson() + my $json; + if (isGet()) { + if (!defined $path[2] && !defined($paramhash->{field})) { # querying node list + # The data structure is: array of hashes that have a single key 'node'. The value for that key + # is an array of hashes with a single key 'name'. The value for that key + # is a 1-element array that contains the node name. + # Create a json array of node name strings. + $json = []; + foreach my $d (@$data) { + my $ar = $d->{node}; + foreach my $a (@$ar) { + my $nodename = $a->{name}->[0]; + if (!defined($nodename)) { error('improperly formatted lsdef output from xcatd', $STATUS_TEAPOT); } + push @$json, $nodename; + } + } + addPageContent($JSON->encode($json)); + } + elsif (!defined $path[2] && defined($paramhash->{field})) { # querying node attributes + # The data structure is: array of hashes that have a single key 'info'. The value for that key + # is an array of lines of lsdef output (all nodes in the same array). + # Create a json array of node objects. Each node object contains the attributes/values (including + # the nodename) of that object. + $json = []; + foreach my $d (@$data) { + my $jsonnode; + my $lines = $d->{info}; + foreach my $l (@$lines) { + if ($l =~ /^Object name: /) { # start new node + if (defined($jsonnode)) { push @$json, $jsonnode; } # push previous object onto array + my ($nodename) = $l =~ /^Object name:\s+(\S+)/; + $jsonnode = { nodename => $nodename }; + } + else { # just an attribute of the current node + if (!defined($jsonnode)) { error('improperly formatted lsdef output from xcatd', $STATUS_TEAPOT); } + my ($attr, $val) = $l =~ /^\s*(\S+)=(.*)$/; + if (!defined($attr)) { error('improperly formatted lsdef output from xcatd', $STATUS_TEAPOT); } + $jsonnode->{$attr} = $val; + } + } + if (defined($jsonnode)) { push @$json, $jsonnode; $jsonnode=undef; } # push last object onto array + } + addPageContent($JSON->encode($json)); + } + elsif (grep(/^$path[2]$/, qw(power inventory vitals energy status))) { # querying other node info + # The data structure is: array of hashes that have a single key 'node'. The value for that key + # is a 1-element array that has a hash with keys 'name' and 'data'. The 'name' value is a 1-element + # array that has the nodename. The 'data' value is a 1-element array of a hash that has keys 'desc' + # and 'content' (sometimes desc is ommited), or in the case of status it has the status directly in the array. + # Create a json array of node objects. Each node object contains the attributes/values (including + # the nodename) of that object. + $json = {}; # its keys are nodenames + foreach my $d (@$data) { + # each element is a complex structure that contains 1 attr and value for a node + my $node = $d->{node}->[0]; + my $nodename = $node->{name}->[0]; + my $nodedata = $node->{data}->[0]; + if ($path[2] eq 'status') { + $json->{$nodename} = $nodedata; + } + else { + my $contents = $nodedata->{contents}->[0]; + my $desc = 'power'; # rpower doesn't output a desc tag + if (defined($nodedata->{desc})) { $desc = $nodedata->{desc}->[0]; } + # add this desc and content into this node's hash + $json->{$nodename}->{$desc} = $contents; + } + } + if ($path[2] eq 'status') { addPageContent($JSON->encode($json)); } + else { + # convert this hash of hashes into an array of hashes + my @jsonarray; + foreach my $n (sort(keys(%$json))) { + $json->{$n}->{nodename} = $n; # add the key (nodename) inside of the node's hash + push @jsonarray, $json->{$n}; + } + addPageContent($JSON->encode(\@jsonarray)); + } + } + else { # querying a node subresource (rpower, rvitals, rinv, etc.) + addPageContent($JSON->encode($data)); + } # end else path[2] defined + } + elsif (isPost()) { # dsh or dcp + if ($path[2] eq 'dsh') { + # The data structure is: array of hashes with a single key, either 'data' or 'errorcode'. The value + # of 'errorcode' is a 1-element array containing the error code. The value of 'data' is an array of + # output lines prefixed by the node name. Some of the lines can be null. + # Create a hash with 2 keys: 'errorcode' and 'nodes'. The 'nodes' value is a hash of nodenames, each + # value is an array of the output for that node. + $json = {}; # its keys are nodenames + foreach my $d (@$data) { + # this is either an errorcode hash or data hash + if (defined($d->{errorcode})) { + $json->{errorcode} = $d->{errorcode}->[0]; + } + elsif (defined($d->{data})) { + foreach my $line (@{$d->{data}}) { + my ($nodename, $output) = $line =~ m/^(\S+): (.*)$/; + if (defined($nodename)) { push @{$json->{$nodename}}, $output; } + } + } + else { error('improperly formatted xdsh output from xcatd', $STATUS_TEAPOT); } + } + addPageContent($JSON->encode($json)); + } + elsif ($path[2] eq 'dcp') { + # The data structure is a 1-element array of a hash with 1 key 'errorcode'. That has a 1-element + # array with the code in it. Let's simplify it. + $json->{errorcode} = $data->[0]->{errorcode}->[0]; + addPageContent($JSON->encode($json)); + } + else { + addPageContent($JSON->encode($data)); + } + } # end if isPost +} + +# Append content to the global var holding the output to go back to the rest client +sub addPageContent { + my $newcontent = shift; + $pageContent .= $newcontent; +} + +# send the response to client side, then exit +# with http there is only one return for each request, so all content should be in pageContent global variable when you call this +# create the response header by status code and format +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); + if ($pageContent) { $pageContent .= "\n"; } # if there is any content, append a newline + print $pageContent; + exit(0); +} + +# Convert xcat request to xml for sending to xcatd +sub genRequest { + my $xml = XML::Simple::XMLout($request, RootName => 'xcatrequest', NoAttr => 1, KeyAttr => []); +} + +# Send the request to xcatd and read the response. The request passed in has already been converted to xml. +# The response returned to the caller of this function has already been converted from xml to perl structure. +sub sendRequest { + my $request = shift; + my $xmlinoptions = shift; # optional arg to not set ForceArray on the XMLin() call + my $sitetab; + my $retries = 0; + + if ($DEBUGGING == 2) { + my $preXml = $request; + $preXml =~ s/< /g; + $preXml =~ s/>/>
/g; + addPageContent($q->p("DEBUG: request XML: " . $request . "\n")); + } + + #hardcoded port for now + my $port = 3001; + my $xcatHost = "localhost:$port"; + + #temporary, will be using username and password + my $homedir = "/root"; + my $keyfile = $homedir . "/.xcat/client-cred.pem"; + my $certfile = $homedir . "/.xcat/client-cred.pem"; + my $cafile = $homedir . "/.xcat/ca.pem"; + + my $client; + if (-r $keyfile and -r $certfile and -r $cafile) { + $client = IO::Socket::SSL->new( + PeerAddr => $xcatHost, + SSL_key_file => $keyfile, + SSL_cert_file => $certfile, + SSL_ca_file => $cafile, + SSL_use_cert => 1, + Timeout => 15,); + } + else { + $client = IO::Socket::SSL->new( + PeerAddr => $xcatHost, + Timeout => 15,); + } + unless ($client) { + if ($@ =~ /SSL Timeout/) { + error("Connection failure: SSL Timeout or incorrect certificates in ~/.xcat",$STATUS_TIMEOUT); + } + else { + error("Connection failurexx: $@",$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("DEBUG: response from xcatd: " . $response . "\n"); + } + $response =~ s/\e/xxxxESCxxxx/g; + debug("response xml=$response"); + + #bmp: i added the $xmlinoptions var because for the table output it saved me processing if everything + # wasn't forced into arrays. Consider if that could save you processing on other api calls too. + if (!$xmlinoptions) { $xmlinoptions = {SuppressEmpty => undef, ForceArray => 1}; } + $rsp = XML::Simple::XMLin($response, %$xmlinoptions); + #debug(Dumper($rsp)); + + #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 (exists($rsp->{serverdone})) { + $cleanexit = 1; + last; + } + } + } + unless ($cleanexit) { + error("communication with the xCAT server seems to have been ended prematurely",$STATUS_SERVICE_UNAVAILABLE); + } + + if ($DEBUGGING == 2) { + addPageContent($q->p("DEBUG: full response from xcatd: " . Dumper($fullResponse))); + } + return $fullResponse; +} + +# Put input parameters from both $q->url_param and put/post data (if it exists) into generalparams and paramhash for all to use +sub fetchParameters { + my @generalparamlist = qw(userName password pretty debug); + # 1st check for put/post data and put that in the hash + my $pdata; + if (isPut()) { $pdata = $q->param('PUTDATA'); } + elsif (isPost()) { $pdata = $q->param('POSTDATA'); } + if ($dbgdata) { + $pdata = $dbgdata; + } + my $genparms = {}; + my $phash; + if ($pdata) { + $phash = eval { $JSON->decode($pdata); }; + if ($@) { error("$@",$STATUS_BAD_REQUEST); } + #debug("phash=" . Dumper($phash)); + if (ref($phash) ne 'HASH') { error("put or post data must be a json object (hash/dict).", $STATUS_BAD_REQUEST); } + + # if any general parms are in the put/post data, move them to genparms + foreach my $k (keys %$phash) { + if (grep(/^$k$/, @generalparamlist)) { + $genparms->{$k} = $phash->{$k}; + delete($phash->{$k}); + } + } + } + else { $phash = {}; } + + # now get params from the url (if any of the keys overlap, the url value will overwrite the put/post value) + foreach my $p ($q->url_param) { + my @a = $q->url_param($p); # this could be a single value or an array, have to figure it out + my $value; + if (scalar(@a) > 1) { $value = [@a]; } # convert it to a reference to an array + else { $value = $a[0]; } + if (grep(/^$p$/, @generalparamlist)) { $genparms->{$p} = $value; } + else { $phash->{$p} = $value; } + } + + return ($genparms, $phash); +} + +# Load the XML::Simple module +sub loadXML { + if ($xmlinstalled) { return; } + + $xmlinstalled = eval { require XML::Simple; }; + unless ($xmlinstalled) { + error('The XML::Simple perl module is missing. Install perl-XML-Simple before using the xCAT REST web services API with this format."}',$STATUS_SERVICE_UNAVAILABLE); + } + $XML::Simple::PREFERRED_PARSER = 'XML::Parser'; +} + +# Load the JSON perl module, if not already loaded. Sets the $JSON global var. +sub loadJSON { + if ($JSON) { return; } # already loaded + # require JSON dynamically and let them know if it is not installed + my $jsoninstalled = eval { require JSON; }; + unless ($jsoninstalled) { + error("JSON perl module missing. Install perl-JSON before using the xCAT REST web services API.", $STATUS_SERVICE_UNAVAILABLE); + } + $JSON = JSON->new(); +} + +# add a error msg to the output in the correct format and end this request +sub error { + my ($msg, $errorcode) = @_; + my $severity = 'error'; + my $m; + #if ($format eq 'xml') { $m = "<$severity>$msg\n"; } + #elsif ($format eq 'json') { + $m = qq({"$severity":"$msg"}); + #} + #else { $m = "

$severity: $msg

\n"; } + addPageContent($m); + sendResponseMsg($errorcode); +} + + +# if debugging, output the given string +sub debug { + if (!$DEBUGGING) { return; } + addPageContent($q->p("DEBUG: $_[0]\n")); +} + +# when having bugs that cause this cgi to not produce any output, output something and then exit. +sub debugandexit { + debug("$_[0]\n"); + sendResponseMsg($STATUS_OK); +} + +sub displaydebugmsg { + addPageContent($q->p("DEBUG: generalparams:". Dumper($generalparams))); + addPageContent($q->p("DEBUG: paramhash:". Dumper($paramhash))); + addPageContent($q->p("DEBUG: q->request_method: $requestType\n")); + #addPageContent($q->p("DEBUG: q->user_agent: $userAgent\n")); + addPageContent($q->p("DEBUG: pathInfo: $pathInfo\n")); + #addPageContent($q->p("DEBUG: path " . Dumper(@path) . "\n")); + #foreach (keys(%ENV)) { addPageContent($q->p("DEBUG: ENV{$_}: $ENV{$_}\n")); } + #addPageContent($q->p("DEBUG: userName=".$paramhash->{userName}.", password=".$paramhash->{password}."\n")); + #addPageContent($q->p("DEBUG: http() values:\n" . http() . "\n")); + #if ($pdata) { addPageContent($q->p("DEBUG: pdata: $pdata\n")); } + addPageContent("\n"); + if ($DEBUGGING == 3) { + sendResponseMsg($STATUS_OK); # this will also exit + } +} + + +# push flags (options) onto the xcatd request. Arguments: request args array, flags array. +# Format of flags array: +# Use this function for cmds with a lot of flags like xdcp and xdsh +sub pushFlags { + my ($args, $flags) = @_; + foreach my $f (@$flags) { + my ($key, $flag, $arg) = @$f; + if (defined($paramhash->{$key})) { + push @$args, $flag; + if ($arg) { push @$args, $paramhash->{$key}; } + } + } +} diff --git a/xCAT-server/xCAT-wsapi/xcatws-test.sh b/xCAT-server/xCAT-wsapi/xcatws-test.sh index 9bfbd18ab..a7bca7c6c 100755 --- a/xCAT-server/xCAT-wsapi/xcatws-test.sh +++ b/xCAT-server/xCAT-wsapi/xcatws-test.sh @@ -42,80 +42,83 @@ function chkrc #todo: add a test case for every api call that is documented set -x # clean up from last time -curl -# -X DELETE -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw" >/dev/null; echo '' +curl -# -X DELETE -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw" >/dev/null; echo '' # create test nodes -curl -# -X POST -k "https://127.0.0.1/xcatws/nodes/wstest1-wstest2?userName=$user&password=$pw&$format" $ctype --data '{"groups":"wstest","netboot":"xnba"}' \ +curl -# -X POST -k "https://127.0.0.1/xcatws/node/wstest1-wstest2?userName=$user&password=$pw&$format" $ctype --data '{"groups":"wstest","netboot":"xnba"}' \ | grep -q '2 object definitions have been created'; chkrc # list all nodes and make sure they are in the list -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node?userName=$user&password=$pw&$format" \ | pcregrep -qM '"wstest1",\n\s*"wstest2"'; chkrc # list all node's group and netboot attributes -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes?userName=$user&password=$pw&field=groups&field=netboot" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node?userName=$user&password=$pw&field=groups&field=netboot" \ | grep -qE '"nodename":"wstest1".*"groups":"wstest"'; chkrc # list all attributes of all nodes -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes?userName=$user&password=$pw&field=ALL" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node?userName=$user&password=$pw&field=ALL" \ | grep -qE '"nodename":"wstest1".*"groups":"wstest"'; chkrc # list the noderange and make sure they are in the list -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw&$format" \ | pcregrep -qM '"wstest1",\n\s*"wstest2"'; chkrc # list all node's group and netboot attributes -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw&field=groups&field=netboot" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw&field=groups&field=netboot" \ | grep -qE '"nodename":"wstest1".*"groups":"wstest"'; chkrc # list all attributes of all nodes -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw&field=ALL" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw&field=ALL" \ | grep -qE '"nodename":"wstest1".*"groups":"wstest"'; chkrc # change some attributes -curl -# -X PUT -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw&$format" $ctype --data '{"room":"222","netboot":"pxe"}' \ +curl -# -X PUT -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw&$format" $ctype --data '{"room":"222","netboot":"pxe"}' \ | grep -q '2 object definitions have been created or modified'; chkrc # verify they got the new values -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw&field=room&field=netboot" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw&field=room&field=netboot" \ | grep -qE '"nodename":"wstest1".*"room":"222"'; chkrc # delete the nodes -curl -# -X DELETE -k "https://127.0.0.1/xcatws/nodes/wstest?userName=$user&password=$pw&$format" \ +curl -# -X DELETE -k "https://127.0.0.1/xcatws/node/wstest?userName=$user&password=$pw&$format" \ | grep -q '2 object definitions have been removed'; chkrc # list all nodes and make sure they are not in the list -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node?userName=$user&password=$pw&$format" \ | pcregrep -qM '"wstest1",\n\s*"wstest2"'; chkrc not # list the power state of the noderange -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/$nr/power?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/$nr/power?userName=$user&password=$pw&$format" \ | grep -q '"power":"on"'; chkrc # list the nodestat state of the noderange -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/$nr/status?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/$nr/status?userName=$user&password=$pw&$format" \ | grep -q '":"sshd"'; chkrc # list the node inventory of the noderange -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/$nr/inventory?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/$nr/inventory?userName=$user&password=$pw&$format" \ | grep -q '"Board manufacturer":"IBM"'; chkrc # list the node vitals of the noderange -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/$nr/vitals?userName=$user&password=$pw&$format" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/$nr/vitals?userName=$user&password=$pw&$format" \ | grep -q '"Cooling Fault":"false"'; chkrc # list the node energy settings of the noderange -curl -# -X GET -k "https://127.0.0.1/xcatws/nodes/$nr/energy?userName=$user&password=$pw&$format&field=cappingstatus&field=cappingmaxmin" \ +curl -# -X GET -k "https://127.0.0.1/xcatws/node/$nr/energy?userName=$user&password=$pw&$format&field=cappingstatus&field=cappingmaxmin" \ | grep -q '"cappingstatus":"off"'; chkrc # run a cmd on the noderange -curl -# -X POST -k "https://127.0.0.1/xcatws/nodes/$nr/dsh?userName=$user&password=$pw&$format" $ctype --data '{"command":"pwd"}' \ +curl -# -X POST -k "https://127.0.0.1/xcatws/node/$nr/dsh?userName=$user&password=$pw&$format" $ctype --data '{"command":"pwd"}' \ | grep -q '"/root"'; chkrc # copy a file to the noderange -curl -# -X POST -k "https://127.0.0.1/xcatws/nodes/$nr/dcp?userName=$user&password=$pw&$format" $ctype --data '{"source":"/root/.bashrc","target":"/tmp/"}' \ +curl -# -X POST -k "https://127.0.0.1/xcatws/node/$nr/dcp?userName=$user&password=$pw&$format" $ctype --data '{"source":"/root/.bashrc","target":"/tmp/"}' \ | grep -q '"errorcode":"0"'; chkrc +# test the table calls +curl -# -X GET -k "https://127.0.0.1/xcatws/table/nodelist/wstest?userName=$user&password=$pw&$format" + exit @@ -128,5 +131,5 @@ exit #curl -X GET -k "https://127.0.0.1/xcatws/images/bp-netboot?userName=$user&password=$pw&$format&field=osvers" #todo: remove when these test cases are in xcatws-test.pl -#./xcatws-test.pl -u "https://127.0.0.1/xcatws/nodes/test001?userName=$user&password=$pw" -m GET -#./xcatws-test.pl -u "https://127.0.0.1/xcatws/nodes/test001?userName=$user&password=$pw" -m PUT "nodepos.room=foo" +#./xcatws-test.pl -u "https://127.0.0.1/xcatws/node/test001?userName=$user&password=$pw" -m GET +#./xcatws-test.pl -u "https://127.0.0.1/xcatws/node/test001?userName=$user&password=$pw" -m PUT "nodepos.room=foo"