Trac #56 - Implemented and usable but still not a complete and dynamically generated WSDL file. Unit tests are in place (through client/server SOAP protocol, or directly onto the internal API)

SVN:trunk[209]
This commit is contained in:
Romain Quetiez
2009-12-28 11:22:54 +00:00
parent 002c1bf869
commit d8269cc11b
5 changed files with 552 additions and 17 deletions

View File

@@ -16,6 +16,7 @@ require_once('dbobjectset.class.php');
require_once('userrights.class.inc.php');
require_once('../webservices/webservices.class.inc.php');
// Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?)
@@ -201,15 +202,16 @@ abstract class TestWebServices extends TestHandler
{
// simply overload DoExecute (temporary)
static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = '', $sOptionnalHeaders = null)
static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null)
{
$aDataAndAuth = $aData;
$aDataAndAuth['operation'] = 'login';
$aDataAndAuth['auth_user'] = $sLogin;
$aDataAndAuth['auth_pwd'] = $sPassword;
$sHost = $GLOBALS['_SERVER']['HTTP_HOST'];
$sUrl = "http://$sHost/$sRelativeUrl";
$sHost = $_SERVER['HTTP_HOST'];
$sRawPath = $_SERVER['SCRIPT_NAME'];
$sPath = dirname($sRawPath);
$sUrl = "http://$sHost/$sPath/$sRelativeUrl";
return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders);
}
@@ -248,6 +250,26 @@ abstract class TestWebServices extends TestHandler
}
}
/**
* Test to execute a piece of code (checks if an error occurs)
*
* @package iTopORM
* @author Romain Quetiez <romainquetiez@yahoo.fr>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.itop.com
* @since 1.0
* @version $itopversion$
*/
abstract class TestSoapWebService extends TestHandler
{
// simply overload DoExecute (temporary)
function __construct()
{
parent::__construct();
}
}
/**
* Test to check that a function outputs some values depending on its input
*

View File

@@ -591,18 +591,8 @@ class TestMyBizModel extends TestBizModel
// Test a complex biz model on the fly
///////////////////////////////////////////////////////////////////////////
class TestQueriesOnFarm extends TestBizModel
abstract class MyFarm extends TestBizModel
{
static public function GetName()
{
return 'Farm test';
}
static public function GetDescription()
{
return 'A series of tests on the farm business model (SQL generation)';
}
static public function GetConfigFile() {return '../config-test-farm.php';}
protected function DoPrepare()
@@ -673,6 +663,20 @@ class TestQueriesOnFarm extends TestBizModel
$iId = $oNew->DBInsertNoReload();
return $iId;
}
}
class TestQueriesOnFarm extends MyFarm
{
static public function GetName()
{
return 'Farm test';
}
static public function GetDescription()
{
return 'A series of tests on the farm business model (SQL generation)';
}
protected function CheckQuery($sQuery, $bIsCorrectQuery)
{
@@ -940,6 +944,45 @@ class TestBulkChangeOnFarm extends TestBizModel
}
///////////////////////////////////////////////////////////////////////////
// Test data load
///////////////////////////////////////////////////////////////////////////
class TestFullTextSearchOnFarm extends MyFarm
{
static public function GetName()
{
return 'Farm test - full text search';
}
static public function GetDescription()
{
return 'Focus on the full text search feature';
}
protected function DoExecute()
{
echo "<h3>Create protagonists...</h3>";
$iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19');
$iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23');
$this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23');
$this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01');
$this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11');
$this->InsertBird('rooster', 'male', 12, 0, 0);
$this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35);
echo "<h3>Search...</h3>";
$oSearch = new DBObjectSearch('Mammal');
$oSearch->AddCondition_FullText('manof');
//$oResultSet = new DBObjectSet($oSearch);
$this->search_and_show_list($oSearch);
return true;
}
}
///////////////////////////////////////////////////////////////////////////
// Benchmark queries
///////////////////////////////////////////////////////////////////////////
@@ -1087,7 +1130,7 @@ class TestItopWebServices extends TestWebServices
$sCsvData = $aLoadSpec['csvdata'];
$aPostData = array('class' => $sClass, 'csvdata' => $sCsvData);
$sRes = self::DoPostRequestAuth('webservices/import.php', $aPostData);
$sRes = self::DoPostRequestAuth('../webservices/import.php', $aPostData);
echo "<div><h3>$sTitle</h3><pre>$sCsvData</pre><div>$sRes</div></div>";
}
@@ -1110,7 +1153,7 @@ class TestItopWebServices extends TestWebServices
),
array(
'class' => 'bizTeam',
'csvdata' => "name;org_id;location_id\nSquadra Azzura;1;1"
'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris"
),
array(
'class' => 'bizWorkgroup',
@@ -1130,4 +1173,91 @@ class TestItopWebServices extends TestWebServices
return true;
}
}
$aWebServices = array(
array(
'verb' => 'CreateIncidentTicket',
'args' => array(
'desc of ticket', /* sDescription */
'initial situation blah blah blah', /* sInitialSituation */
array('id' => 1), /* aCallerDesc */
array('id' => 2), /* aCustomerDesc */
array('id' => 1), /* aWorkgroupDesc */
array(
array(
'class' => 'logInfra',
'search' => array('id' => 108),
'link_values' => array('impactoche' => 'plus que critique'),
),
array(
'class' => 'bizDevice',
'search' => array('name' => 'Router03'),
'link_values' => array('impact' => 'ouais bof'),
),
), /* aImpact */
'low' /* sSeverity */
),
),
);
class TestSoap extends TestSoapWebService
{
static public function GetName() {return 'Test SOAP';}
static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';}
protected function DoExecute()
{
$this->m_SoapClient = new SoapClient(
// null,
"http://localhost:81/trunk/webservices/Itop.wsdl",
array(
//'location' => 'http://localhost:81/trunk/webservices/soapserver.php',
//'uri' => 'http://test-itop/',
'login' => 'admin',
'password' => 'admin',
// note: using the classmap functionality lead to APACHE fault on the server side
//'classmap' => array('stdClass' => 'ItopError'),
'trace' => 1,
)
);
global $aWebServices;
$aWebService = $aWebServices[0];
// $oRes = $this->m_SoapClient->CreateIncidentTicket();
$oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']);
echo "<pre>\n";
print_r($oRes);
echo "</pre>\n";
print "<pre>\n";
print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n";
print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n";
print "</pre>";
return true;
}
}
class TestWebServicesDirect extends TestBizModel
{
static public function GetName() {return 'Test web services locally';}
static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';}
static public function GetConfigFile() {return '../config-itop.php';}
protected function DoExecute()
{
global $aWebServices;
$aWebService = $aWebServices[0];
$oWebServices = new WebServices();
$oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']);
echo "<pre>\n";
print_r($oRes);
echo "</pre>\n";
return true;
}
}
?>

4
webservices/itop.wsdl Normal file
View File

@@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- WSDL file generated by PHP WSDLCreator (http://www.protung.ro) -->
<definitions name="WSDLItop" targetNamespace="urn:WSDLItop" xmlns:typens="urn:WSDLItop" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:typens0="http://localhost/php2swdl"><message name="CreateIncidentTicket"><part name="sDescription" type="xsd:anyType"></part><part name="sInitialSituation" type="xsd:anyType"></part><part name="aCallerDesc" type="xsd:anyType"></part><part name="aCustomerDesc" type="xsd:anyType"></part><part name="aWorkgroupDesc" type="xsd:anyType"></part><part name="aImpactedCIs" type="xsd:anyType"></part><part name="sSeverity" type="xsd:anyType"></part></message><message name="CreateIncidentTicketResponse"><part name="CreateIncidentTicketReturn" type="typens0:WebServiceResult"></part></message><portType name="WebServicesPortType"><operation name="CreateIncidentTicket"><documentation>Create an incident ticket from a monitoring system
Some CIs might be specified (by their name/IP)</documentation><input message="typens:CreateIncidentTicket"></input><output message="typens:CreateIncidentTicketResponse"></output></operation></portType><binding name="WebServicesBinding" type="typens:WebServicesPortType"><soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding><operation name="CreateIncidentTicket"><soap:operation soapAction="urn:WebServicesAction"></soap:operation><input><soap:body namespace="urn:WSDLItop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"></soap:body></input><output><soap:body namespace="urn:WSDLItop" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"></soap:body></output></operation></binding><service name="WSDLItopService"><port name="WebServicesPort" binding="typens:WebServicesBinding"><soap:address location="http://localhost:81/trunk/webservices/soapserver.php"></soap:address></port></service></definitions>

View File

@@ -0,0 +1,56 @@
<?php
/**
* SOAP-based web service
*
* @package iTopORM
* @author Romain Quetiez <romainquetiez@yahoo.fr>
* @author Denis Flaven <denisflave@free.fr>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.itop.com
* @since 1.0
* @version 1.1.1.1 $
*/
// Important note: if some required includes are missing, this might result
// in the error "looks like we got no XML document"...
require_once('../application/application.inc.php');
require_once('../application/startup.inc.php');
require('./webservices.class.inc.php');
// pb ? - login_web_page::DoLogin(); // Check user rights and prompt if needed
// Main program
$oSoapServer = new SoapServer(
null,
//"http://localhost:81/trunk/webservices/Itop.wsdl", // to be a file generated dynamically with location = here
array(
'uri' => 'http://test-itop/',
// note: using the classmap and no WSDL spec causes a fault in APACHE (looks like an infinite loop)
//'classmap' => array('ItopErrorSOAP' => 'ItopError')
)
);
// $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION);
$oSoapServer->setClass('WebServices', null);
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
$oSoapServer->handle();
}
else
{
echo "This SOAP server can handle the following functions: ";
$aFunctions = $oSoapServer->getFunctions();
echo "<ul>\n";
foreach($aFunctions as $sFunc)
{
echo "<li>$sFunc</li>\n";
}
echo "</ul>\n";
echo "";
}
?>

View File

@@ -0,0 +1,323 @@
<?php
/**
* Create Ticket web service
* Web Service API wrapper
*
* @package iTopORM
* @author Romain Quetiez <romainquetiez@yahoo.fr>
* @author Denis Flaven <denisflave@free.fr>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.itop.com
* @since 1.0
* @version 1.1.1.1 $
*/
class WebServiceResult
{
/**
* Overall status
*
* @var m_bStatus
*/
public $m_bStatus;
/**
* Error log
*
* @var m_aErrors
*/
public $m_aErrors;
/**
* Warning log
*
* @var m_aWarnings
*/
public $m_aWarnings;
/**
* Information log
*
* @var m_aInfos
*/
public $m_aInfos;
/**
* Constructor
*
* @param status $bStatus
*/
public function __construct()
{
$this->m_bStatus = true;
$this->m_aErrors = array();
$this->m_aWarnings = array();
$this->m_aInfos = array();
}
/**
* Did the current processing encounter a stopper issue ?
*
* @return bool
*/
public function IsOk()
{
return $this->m_bStatus;
}
/**
* Log an error
*
* @param description $sDescription
*/
public function LogError($sDescription)
{
$this->m_aErrors[] = $sDescription;
$this->m_bStatus = false;
}
/**
* Log a warning
*
* @param description $sDescription
*/
public function LogWarning($sDescription)
{
$this->m_aWarnings[] = $sDescription;
}
/**
* Log an error or a warning
*
* @param string $sDescription
* @param boolean $bIsStopper
*/
public function LogIssue($sDescription, $bIsStopper = true)
{
if ($bIsStopper) $this->LogError($sDescription);
else $this->LogWarning($sDescription);
}
/**
* Log operation details
*
* @param description $sDescription
*/
public function LogInfo($sDescription)
{
$this->m_aInfos[] = $sDescription;
}
}
class WebServices
{
/**
* Helper to set an external key
*
* @param string sAttCode
* @param array aCallerDesc
* @param DBObject oTargetObj
* @param WebServiceResult oRes
*
*/
protected function SetExternalKey($sAttCode, $aExtKeyDesc, &$oTargetObj, &$oRes)
{
$oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode);
$bIsMandatory = !$oExtKey->IsNullAllowed();
if (count($aExtKeyDesc) == 0)
{
$oRes->LogIssue("Ext key $sAttCode: no data was given to give a value to the key", $bIsMandatory);
return;
}
$sKeyClass = $oExtKey->GetTargetClass();
$oReconFilter = new CMDBSearchFilter($sKeyClass);
foreach ($aExtKeyDesc as $sForeignAttCode => $value)
{
if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode))
{
$sMsg = "Ext key $sAttCode: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass'";
$oRes->LogIssue($sMsg, $bIsMandatory);
}
// The foreign attribute is one of our reconciliation key
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
}
$oExtObjects = new CMDBObjectSet($oReconFilter);
switch($oExtObjects->Count())
{
case 0:
$sMsg = "External key $sAttCode could not be found (searched: '".$oReconFilter->ToOQL()."')";
$oRes->LogIssue($sMsg, $bIsMandatory);
break;
case 1:
// Do change the external key attribute
$oForeignObj = $oExtObjects->Fetch();
$oTargetObj->Set($sAttCode, $oForeignObj->GetKey());
// Report it (no need to report if the object already had this value
if (array_key_exists($sAttCode, $oTargetObj->ListChanges()))
{
$oRes->LogInfo("$sAttCode has been set to ".$oForeignObj->GetKey());
}
break;
default:
$sMsg = "Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')";
$oRes->LogIssue($sMsg, $bIsMandatory);
}
}
/**
* Helper to link objects
*
* @param string sLinkAttCode
* @param string sLinkedClass
* @param array $aLinkList
* @param DBObject oTargetObj
* @param WebServiceResult oRes
*
* @return array List of objects that could not be found
*/
protected function AddLinkedObjects($sLinkAttCode, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes)
{
$oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode);
$sLinkClass = $oLinkAtt->GetLinkedClass();
$sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote();
$aItemsFound = array();
$aItemsNotFound = array();
foreach ($aLinkList as $aItemData)
{
$sTargetClass = $aItemData['class'];
if (!MetaModel::IsValidClass($sTargetClass))
{
$oRes->LogError("Invalid class $sTargetClass for impacted item");
continue; // skip
}
if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass))
{
$oRes->LogError("$sTargetClass is not a child class of $sLinkedClass");
continue; // skip
}
$oReconFilter = new CMDBSearchFilter($sTargetClass);
$aCIStringDesc = array();
foreach ($aItemData['search'] as $sAttCode => $value)
{
if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode))
{
$oRes->LogError("Invalid filter code $sAttCode for class $sTargetClass");
continue; // skip
}
$aCIStringDesc[] = "$sAttCode: $value";
// The attribute is one of our reconciliation key
$oReconFilter->AddCondition($sAttCode, $value, '=');
}
if (count($aCIStringDesc) == 1)
{
// take the last and unique value to describe the object
$sItemDesc = $value;
}
else
{
// describe the object by the given keys
$sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')';
}
$oExtObjects = new CMDBObjectSet($oReconFilter);
switch($oExtObjects->Count())
{
case 0:
$oRes->LogWarning("Object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL()."')");
$aItemsNotFound[] = $sItemDesc;
break;
case 1:
$aItemsFound[] = array (
'object' => $oExtObjects->Fetch(),
'link_values' => @$aItemData['link_values'],
'desc' => $sItemDesc,
);
break;
default:
$oRes->LogWarning("Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')");
$aItemsNotFound[] = $sItemDesc;
}
}
if (count($aItemsFound) > 0)
{
$aLinks = array();
foreach($aItemsFound as $aItemData)
{
$oLink = MetaModel::NewObject($sLinkClass);
$oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey());
foreach($aItemData['link_values'] as $sKey => $value)
{
if(!MetaModel::IsValidAttCode($sLinkClass, $sKey))
{
$oRes->LogWarning("Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'");
}
else
{
$oLink->Set($sKey, $value);
}
}
$aLinks[] = $oLink;
}
$oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks);
$oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet);
}
return $aItemsNotFound;
}
/**
* Create an incident ticket from a monitoring system
* Some CIs might be specified (by their name/IP)
*
* @param string sDecription
* @param string sInitialSituation
* @param array aCallerDesc
* @param array aCustomerDesc
* @param array aWorkgroupDesc
* @param array aImpactedCIs
* @param string sSeverity
*
* @return WebServiceResult
*/
function CreateIncidentTicket($sDescription, $sInitialSituation, $aCallerDesc, $aCustomerDesc, $aWorkgroupDesc, $aImpactedCIs, $sSeverity)
{
$oRes = new WebServiceResult();
new CMDBChange();
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", "Administrator");
$iChangeId = $oMyChange->DBInsertNoReload();
$oNewTicket = MetaModel::NewObject('bizIncidentTicket');
$oNewTicket->Set('title', $sDescription);
$oNewTicket->Set('initial_situation', $sInitialSituation);
$oNewTicket->Set('severity', $sSeverity);
$this->SetExternalKey('org_id', $aCustomerDesc, $oNewTicket, $oRes);
$this->SetExternalKey('caller_id', $aCallerDesc, $oNewTicket, $oRes);
$this->SetExternalKey('workgroup_id', $aWorkgroupDesc, $oNewTicket, $oRes);
$aDevicesNotFound = $this->AddLinkedObjects('impacted_infra_manual', 'logInfra', $aImpactedCIs, $oNewTicket, $oRes);
if (count($aDevicesNotFound) > 0)
{
$oTargetObj->Set('impact', implode(', ', $aDevicesNotFound));
}
if ($oRes->IsOk())
{
$iId = $oNewTicket->DBInsertTrackedNoReload($oMyChange);
$oRes->LogInfo("Created ticket #$iId");
}
return $oRes;
}
}
?>