#! /usr/bin/env perl # IBM(c) 2014 EPL license http://www.eclipse.org/legal/epl-v10.html # This package offers subroutines to access CIM server package xCAT::CIMUtils; use strict; use warnings; use HTTP::Headers; use HTTP::Request; use LWP::UserAgent; use XML::LibXML; use Data::Dumper; =head1 HTTP_PARAMS A hash which includes all the parameters for accessing HTTP server. The valid parameter: ip: The IP address of the HTTP server user: The user to access HTTP server. password: The password for the user. method: The http method. (GET, PUT, POST, DELETE). Default is GET protocol: The protocol which will be used to access HTTP server. (http/https). Default is https format: The format of payload. Default is xml payload: The payload of http Example: my %http_params = ( ip => '192.168.1.1', port => '5989', user => 'HMC', password => 'admin', method => 'POST', protocol => 'https'); =cut =head1 enum_instance () Description: Enumerate CIM instances. Arguments: http_params: A reference to HTTP_PARAMS cim_params: The CIM parameters classname - a mandatory param to specify the class that enumerate will target to. Return: 1 - A hash reference. The valid key includes: rc - The return code. 0 - success. > 0 - fail. cim_rc - The return code from CIM server. msg - Output message. 2 - Array of instances, each instance is a hash contains lots of properties. 3 - The name path of instance =cut sub enum_instance { my $http_params = shift; unless (ref($http_params)) { $http_params = shift; } my $cim_params = shift; # This is a mandatory parameter unless ($cim_params->{classname}) { return ({rc => 1, msg => "Missed the classname"}); } unless ($cim_params->{namespace}) { $cim_params->{namespace} = "ibmsd"; } # prepare the CIM payload my $tmpnode; # create a new doc my $doc = XML::LibXML->createDocument('1.0','UTF-8'); # create and add the root element my $root = $doc->createElement("CIM"); $root->setAttribute("CIMVERSION", "2.0"); $root->setAttribute("DTDVERSION", "2.0"); $doc->setDocumentElement($root); # create and add the MESSAGE element my $message = $doc->createElement("MESSAGE"); $message->setAttribute("ID", "1000"); $message->setAttribute("PROTOCOLVERSION", "1.0"); $root->addChild($message); # add a SIMPLE REQUEST my $simple_request = $doc->createElement("SIMPLEREQ"); $message->addChild($simple_request); # add an IMETHOD CALL my $imethod_call = $doc->createElement("IMETHODCALL"); $imethod_call->setAttribute("NAME", "EnumerateInstances"); $simple_request->addChild($imethod_call); # add the local name space path my $localnamespacepath = $doc->createElement("LOCALNAMESPACEPATH"); $tmpnode = $doc->createElement("NAMESPACE"); $tmpnode->setAttribute("NAME", "root"); $localnamespacepath->addChild($tmpnode); $tmpnode = $doc->createElement("NAMESPACE"); $tmpnode->setAttribute("NAME", $cim_params->{namespace}); $localnamespacepath->addChild($tmpnode); $imethod_call->addChild($localnamespacepath); # add the target class name my $param_classname = $doc->createElement("IPARAMVALUE"); $param_classname->setAttribute("NAME", "ClassName"); $imethod_call->addChild($param_classname); my $classname = $doc->createElement("CLASSNAME"); $classname->setAttribute("NAME", $cim_params->{classname}); $param_classname->addChild($classname); # add several common parameters $imethod_call->appendWellBalancedChunk('TRUEFALSEFALSETRUE'); my $payload = $doc->toString(); # generate http request my $ret = gen_http_request($http_params, $payload); if ($ret->{rc}) { return $ret; } # send request to http server $ret = send_http_request($http_params, $ret->{request}); if ($ret->{rc}) { return $ret; } # parse the http response my $ret_value; my $parser = XML::LibXML->new(); my $resp_doc = $parser->parse_string($ret->{payload}); # check the error message from CIM my $error_node = $resp_doc->getElementsByTagName("ERROR"); if ($error_node) { my $msg = $error_node->[0]->getAttribute("DESCRIPTION"); my $errorcode = $error_node->[0]->getAttribute("CODE"); return ({rc => 1, cim_rc => $errorcode, msg => $error_node->[0]->getAttribute("DESCRIPTION")." [cim return code: $errorcode]"}); } # get the name path of the instance, which is used to set property my @namepath = $resp_doc->getElementsByTagName("INSTANCENAME"); my $namepath_string; if (@namepath) { $namepath_string = $namepath[0]->toString(); } # get all the instance elements my @instances = $resp_doc->getElementsByTagName("VALUE.NAMEDINSTANCE"); foreach my $instance (@instances) { # get all the property element for each instance my @properties = $instance->getElementsByTagName("PROPERTY"); if (my @property_arrays = $instance->getElementsByTagName("PROPERTY.ARRAY")) { push @properties, @property_arrays; } my $ins_value; foreach my $property (@properties) { # get name, vlaue and type for each property. (only the one which has value) if (my $pname = $property->getAttribute("NAME")) { if (my $pvalue = $property->getAttribute("TYPE")) { $ins_value->{property}->{$pname}->{type} = $pvalue; } if ($property->getElementsByTagName("VALUE.ARRAY")) { my @nodelist = $property->getElementsByTagName("VALUE"); my @value_array = (); foreach my $n (@nodelist) { push @value_array, $n->textContent; } $ins_value->{property}->{$pname}->{value} = join(',',@value_array); } elsif (my $node = $property->getElementsByTagName("VALUE")) { $ins_value->{property}->{$pname}->{value} = $node->[0]->textContent; } } } push @{$ret_value}, $ins_value; } return ({rc =>0}, $ret_value, $namepath_string); } =head1 set_property () Description: Set the property for an instance. Arguments: http_params: A reference to HTTP_PARAMS cim_params: The CIM parameters namepath - a mandatory param to specify the path of the instance. It should be returned from 'enum_instance' subroutine. Return: 1 - A hash reference. The valid key includes: rc - The return code. 0 - success. > 0 - fail. cim_rc - The return code from CIM server. msg - Output message. =cut sub set_property { my $http_params = shift; unless (ref($http_params)) { $http_params = shift; } my $cim_params = shift; # This is a mandatory parameter unless ($cim_params->{namepath}) { return ({rc => 1, msg => "Missed the name path for the instance"}); } unless ($cim_params->{namespace}) { $cim_params->{namespace} = "ibmsd"; } # prepare the CIM payload my $tmpnode; # create a new doc my $doc = XML::LibXML->createDocument('1.0','UTF-8'); # create and add the root element my $root = $doc->createElement("CIM"); $root->setAttribute("CIMVERSION", "2.0"); $root->setAttribute("DTDVERSION", "2.0"); $doc->setDocumentElement($root); # create and add the MESSAGE element my $message = $doc->createElement("MESSAGE"); $message->setAttribute("ID", "1000"); $message->setAttribute("PROTOCOLVERSION", "1.0"); $root->addChild($message); # add a SIMPLE REQUEST my $simple_request = $doc->createElement("SIMPLEREQ"); $message->addChild($simple_request); # add an IMETHOD CALL my $imethod_call = $doc->createElement("IMETHODCALL"); $imethod_call->setAttribute("NAME", "SetProperty"); $simple_request->addChild($imethod_call); # add the local name space path my $localnamespacepath = $doc->createElement("LOCALNAMESPACEPATH"); $tmpnode = $doc->createElement("NAMESPACE"); $tmpnode->setAttribute("NAME", "root"); $localnamespacepath->addChild($tmpnode); $tmpnode = $doc->createElement("NAMESPACE"); $tmpnode->setAttribute("NAME", $cim_params->{namespace}); $localnamespacepath->addChild($tmpnode); $imethod_call->addChild($localnamespacepath); # add the target property name my $param_propertyname = $doc->createElement("IPARAMVALUE"); $param_propertyname->setAttribute("NAME", "PropertyName"); $imethod_call->addChild($param_propertyname); $tmpnode = $doc->createElement("VALUE"); $tmpnode->appendTextNode($cim_params->{propertyname}); $param_propertyname->addChild($tmpnode); # add the target property value my $param_newvaluename = $doc->createElement("IPARAMVALUE"); $param_newvaluename->setAttribute("NAME", "NewValue"); $imethod_call->addChild($param_newvaluename); $tmpnode = $doc->createElement("VALUE"); $tmpnode->appendTextNode($cim_params->{propertyvalue}); $param_newvaluename->addChild($tmpnode); # add parameters of instance name path my $param_namepath = $doc->createElement("IPARAMVALUE"); $param_namepath->setAttribute("NAME", "InstanceName"); $param_namepath->appendWellBalancedChunk($cim_params->{namepath}); $imethod_call->addChild($param_namepath); my $payload = $doc->toString(); # generate http request my $ret = gen_http_request($http_params, $payload); if ($ret->{rc}) { return $ret; } # send request to http server $ret = send_http_request($http_params, $ret->{request}); if ($ret->{rc}) { return $ret; } # parse the http response my $ret_value; my $parser = XML::LibXML->new(); my $resp_doc = $parser->parse_string($ret->{payload}); # check the error message from CIM my $error_node = $resp_doc->getElementsByTagName("ERROR"); if ($error_node) { my $msg = $error_node->[0]->getAttribute("DESCRIPTION"); my $errorcode = $error_node->[0]->getAttribute("CODE"); return ({rc => 1, cim_rc => $errorcode, msg => $error_node->[0]->getAttribute("DESCRIPTION")." [cim return code: $errorcode]"}); } # if no http and cim error, the setting was succeeded return ($ret); } =head1 gen_http_request () Description: Generate a http request. Arguments: http_params: A reference to HTTP_PARAMS payload: The payload for the http request. It can be null if the payload has been set in http_params. Return: A hash reference. The valid key includes: rc - The return code. 0 - success. > 0 - fail. msg - Output message request - The generated HTTP::Request object =cut sub gen_http_request { my $http_params = shift; my $http_payload = shift; # check the mandatory parameters unless (defined ($http_params->{ip}) && defined ($http_params->{port}) && defined($http_params->{user}) && defined($http_params->{password})) { return ({rc => 1, msg => "Missed the mandatory parameters: ip, port, user or password"}); } # set the default value for parameters unless (defined ($http_params->{protocol})) { $http_params->{protocol} = 'https'; } unless (defined ($http_params->{format})) { $http_params->{format} = 'xml'; } unless (defined ($http_params->{method})) { $http_params->{method} = 'GET'; } my $payload = ''; if (defined ($http_params->{payload})) { $payload = $http_params->{payload}; } if (defined ($http_payload)) { unless (ref($http_payload)) { #Todo: support payloasd to be a hash $payload = $http_payload; } } # create the http head my $header = HTTP::Headers->new('content-type' => "application/$http_params->{format}", 'Accept' => "application/$http_params->{format}", 'User-Agent' => "xCAT/2", 'Host' => "$http_params->{ip}:$http_params->{port}"); # set the user & password $header->authorization_basic($http_params->{user}, $http_params->{password}); # set the length of payload my $plen = length($payload); $header->push_header('Content-Length' => $plen); # create the URL my $url = "$http_params->{protocol}://$http_params->{ip}:$http_params->{port}"; my $request = HTTP::Request->new($http_params->{method}, $url, $header, $payload); # set the http version $request->protocol('HTTP/1.1'); return ({rc => 0, request => $request}); } =head1 send_http_request () Description: Send http request to http server and waiting for the response. Arguments: http_params: A reference to HTTP_PARAMS http_request: A HTTP::Request object Return: A hash reference. The valid key includes: rc - The return code. 0 - success. > 0 - fail. http_rc - The return code of http. 200, 400 ... msg - Output message payload - The http response from http server =cut sub send_http_request { my $http_params = shift; my $http_request = shift; # Load the library LWP::Protocol::https for https support if ($http_params->{protocol} eq 'https') { eval { require LWP::Protocol::https}; if ($@) { return ({rc => 1, msg => "Failed to load perl library LWP::Protocol::https"}); } } # create a new HTTP User Agent Object my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0}); if ($http_params->{timeout}) { $ua->timeout($http_params->{timeout}); } else { $ua->timeout(10); # the default timeout is 10s } if (defined($http_params->{verbose}) && defined ($http_params->{callback})) { $http_params->{callback}({data => ["\n========CIM Request Start========", $http_request->as_string(), "=======CIM Request End======="]}); } # send request and receive the response my $response = $ua->request($http_request); if (defined($http_params->{verbose}) && defined ($http_params->{callback}) && defined ($response->{_content})) { $http_params->{callback}({data => ["\n========CIM Response Start========", $response->{_content}, "=======CIM Response End======="]}); } # check the http response if (defined ($response) && defined ($response->{_rc}) && defined ($response->{_msg})) { if ($response->{_rc} eq "200" && $response->{_msg} eq "OK") { return ({rc => 0, http_rc => $response->{_rc}, msg => "$response->{_msg} [http return code: $response->{_rc}]", payload => $response->{_content}}); } } return ({rc => 1, http_rc => $response->{_rc}, msg => $response->{_msg}}); } 1;