Ruby Wrapper for WS-I Analyzer tools

August 19, 2008

I’ve been developing some scripting to enable me to assess the integrity of integration artifacts created across a range of development teams in large scale SOA integration programmes. One of the most basic forms of assessment is the WS-Interoperability (WS-I) analysis of WSDL, to identify any specific non-compliances at grass-roots. (I can hear the RESTian hordes tooling up to brow-beat me into submission as I type this….and YES I understand, YOU don’t like WSDL, nor do I, but they DO exist so I’m gonna analyse em ! Right – now that’s out of the way on with the WSDL baiting…).

There’s a set of java/c# tools out there from the clever folk over at WS-I (http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools) which are already used informally within our organisation, and I’ve created a ruby-based wrapper for this. It’s not mature enough to be packaged as a Ruby Gem, and it does rely on an installation of the java tools in some folder on your machine – but the following ruby source does allow you to spin up objects in your Ruby code and kick it to analyse your WSDL artifacts without being exposed to java code. In line with my usual train-of-thought programming style and my few months of Ruby exposure, it’s not pretty but it works well enough to be parked for a while… Usage is pretty simple. The analyzer object can be initialised once and used thereafter to manage the analysis of many discrete WSDL resources. With each call to WSIAnalyser.analyze_wsdl() the first parameter is a file: or http: URI to a WSDL, and the second is a path/folder into which the outputs from this analysis will be placed. In any given WSDL the analyzer iterates through every <definitions/services> node, creating a report for each node. The outputs (in the output folder) are:

  1. A copy of the WSDL source
  2. A copy of the WS-I Tools configuration file generated by this script, one per <definitions/service> element in the source
  3. A WS-I analysis report generated by the underlying WS-I tool – one per <definitions/service> element in the source WSDL

a=WSITools::WSIAnalyzer.new
a.analyze("http://www.domain.com/someservice.wsdl", "c:/dev/wsdloutputs")
a.reports.each_pair do |r,s|
puts "WSDL [#{a.wsdl_uri}] Report [#{r}] Status [#{s}]"
end

The source:

WSI_ANALYZER_CONFIG_TEMPLAGE = %{<?xml version="1.0" encoding="UTF-8"?>
<wsi-analyzerConfig:configuration name="WS-I Basic Profile Analyzer Configuration" xmlns:wsi-analyzerConfig="http://www.ws-i.org/testing/2004/07/analyzerConfig/">
<wsi-analyzerConfig:description />
<wsi-analyzerConfig:verbose>
false
</wsi-analyzerConfig:verbose>
<wsi-analyzerConfig:assertionResults type="all" messageEntry="true" failureMessage="true"/>
<wsi-analyzerConfig:reportFile replace="true" location="xxxxxxxx">
<wsi-analyzerConfig:addStyleSheet href="" type="text/xsl"/>
</wsi-analyzerConfig:reportFile>
<wsi-analyzerConfig:testAssertionsFile />
<wsi-analyzerConfig:wsdlReference>
<wsi-analyzerConfig:wsdlElement type="port" parentElementName="" namespace="" />
<wsi-analyzerConfig:wsdlURI />
</wsi-analyzerConfig:wsdlReference>
</wsi-analyzerConfig:configuration>
}

#As we are modifying the path environment variable, we need to ensure that the correct delimiter is used
UNIX_PATH_DELIMITER=":"
WIN_PATH_DELIMITER=";"

#This must be changed to point to the physical root of the wsi-installation
WSI_HOME_TAG = "WSI_HOME"
WSI_HOME_VAL = "c:/dev/wsi-test-tools"
WSI_JAVA_HOME_TAG = "WSI_JAVA_HOME"
WSI_JAVA_HOME_VAL = "#{WSI_HOME_VAL}/java"
WSI_JAVA_OPTS_TAG = "WSI_JAVA_OPTS"
WSI_JAVA_OPTS_VAL = " -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser"
WSI_CLASSPATH_TAG = "WSI_CP"
WSI_TEST_ASSERTIONS_FILE = "#{WSI_HOME_VAL}/common/profiles/SSBP10_BP11_TAD.xml"
WSI_STYLESHEET_FILE = "#{WSI_HOME_VAL}/common/xsl/report.xsl"
WSI_EXECUTION_COMMAND = "#{WSI_JAVA_HOME_VAL}/bin/Analyzer.bat -config "

WSIClasspath=[
"#{WSI_JAVA_HOME_VAL}/lib/wsi-test-tools.jar",
"#{WSI_JAVA_HOME_VAL}/lib",
"#{WSI_JAVA_HOME_VAL}/lib/xercesImpl.jar",
"#{WSI_JAVA_HOME_VAL}/lib/xmlParserAPIs.jar",
"#{WSI_JAVA_HOME_VAL}/lib/wsdl4j.jar",
"#{WSI_JAVA_HOME_VAL}/lib/uddi4j.jar",
"#{WSI_JAVA_HOME_VAL}/lib/axis.jar",
"#{WSI_JAVA_HOME_VAL}/lib/jaxrpc.jar",
"#{WSI_JAVA_HOME_VAL}/lib/saaj.jar",
"#{WSI_JAVA_HOME_VAL}/lib/commons-discovery.jar",
"#{WSI_JAVA_HOME_VAL}/lib/commons-logging.jar"
]

class WSIAnalyzer
VERSION = "1.0.0"

attr_reader :wsdl_uri
attr_reader :wsdl_name
attr_reader :wsdl_source
attr_reader :analyzer_config_uri
attr_reader :analyzer_config_source
attr_reader :wsdl_namespace
attr_reader :wsdl_service_name
attr_reader :wsdl_port_name
attr_reader :workspace
attr_reader :report_filename
attr_reader :wsi_approved
attr_reader :wsdl_service_declarations
attr_reader :reports
attr_reader :errors

def initialize
begin
@log=Logger.new("analyzer.log")
@log.progname="WsiAnalyzer"
@log.level = Logger::DEBUG
@wsdl_uri=nil
@wsdl_name=nil
@wsdl_source=nil
@analyzer_config_uri=nil
@analyzer_config_source=nil
@wsdl_namespace=nil
@wsdl_service_name=nil
@wsdl_port_name=nil
@workspace=nil
@report_filename=nil
@wsdl_service_declarations={}
@reports={}
@errors=[]
#Check the installation location to ensure the wsi-test-tools are installed on the host
if not File.exists?(WSI_HOME_VAL)
@log.fatal("Unable to locate WSI-Test-Tools installation at [#{WSI_HOME_VAL}]")
return nil
end
#Ensure environment variables are in place
if(ENV[WSI_HOME_TAG].nil?)
@log.warn("No WSI-Test-Tools environment variables present [#{WSI_HOME_TAG}]")
configure_environment
end
rescue => ex
@log.error("Unable to initialise WSIAnalyzer: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
return nil
end
end

def analyze_wsdl(wsdl_uri, workspace)
begin
@wsdl_uri=wsdl_uri
@workspace=workspace
segments=@wsdl_uri.split('/')
@wsdl_name=segments.last
segments=@wsdl_name.split('.')
@wsdl_name=segments.first
@log.info("Analyzing WSDL[#{@wsdl_name}] from URI[#{@wsdl_uri}] in workspace [#{@workspace}]")
#obtain the wsdl source
acquire_wsdl
#obtain key wsdl attributes required to create ws-i configuration
extract_target_elements
#There may be more than one pass to make here so iterate
@wsdl_service_declarations.each_pair do |svc, port|
#create dynamic ws-i configuration
@log.info("Excuting WS-I analysis for WSDL [#{@wsdl_name}] Service[#{svc}] Port[#{port}]")
@report_filename="#{@workspace}/ws-i-report-for-wsdl-#{@wsdl_name}-svc-#{svc}-port-#{port}.xml"
create_dynamic_wsi_config(svc, port)
#execute the analysis
execute_wsi_analyzer
end
rescue => ex
@log.error("Unable to analyze WSDL [#{@wsdl_uri}]: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
return false
end
end

private

def execute_wsi_analyzer
begin
#Now kick the external WSI script to generate a report
commandline="#{WSI_EXECUTION_COMMAND} #{@analyzer_config_uri}"
@log.info("Execution WS-I Analyzer with shell [#{commandline}]")
@reports={}
@errors=[]
system(commandline)
#Verify if a report has been created
if(not File.exists?(@report_filename))
@log.warn("No report file [#{@report_filename}] produced by WS-I Analyzer")
else
@log.info("Scanning for WS-I summary status...")
begin
dom=REXML::Document.new File.new(@report_filename)
if dom.elements["report/summary"].nil?
if not dom.elements["report/analyzerFailure"].nil?
#A failure code has been identified in the report doccument
msg=dom.elements["report/analyzerFailure/failureDetail"]
@log.warn("WSDL [#{@wsdl_name}] WS-I Failure Report [#{msg}] ")
@errors << msg
status="failed"
end
else
#Summary status is passed (not sure if summary can be failed/error
status=dom.elements["report/summary"].attributes["result"].to_s
end
@log.info("WS-I Approval Status [#{status}] for WSDL [#{@wsdl_uri}]")
#Add a report summary to the list of reports
@reports[@report_filename]=status
end
end
rescue => ex
#Unable to acquire status - treat report as invalid/missing
@log.error("Unable to complete WS-I Analysis of [#{@wsdl_name}]: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def create_dynamic_wsi_config(svc, port)
begin
cdom=REXML::Document.new(WSI_ANALYZER_CONFIG_TEMPLAGE)
#cdom.elements.each do |e|
# puts e.inspect
#end
@log.info("WS-I configured to report to [#{@report_filename}]")
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:reportFile"].attributes["location"]=@report_filename
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:reportFile/wsi-analyzerConfig:addStyleSheet"].attributes["href"]=WSI_STYLESHEET_FILE
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:testAssertionsFile"].text=WSI_TEST_ASSERTIONS_FILE
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlElement"].attributes["namespace"]=@wsdl_namespace
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlElement"].attributes["parentElementName"]=svc
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlElement"].text=port
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlURI"].text=@wsdl_uri
#Now write the configuration file into the workspace of the artifact
#@log.debug(cdom.to_s)
@analyzer_config_source=cdom.to_s
@analyzer_config_uri="#{@workspace}/wsiAnalyzerConfig-svc-#{svc}-port-#{port}.xml"
open("#{@analyzer_config_uri}",'w'){ |f| f << cdom.to_s}
@log.info("Written dynamic WS-I config into [#{@analyzer_config_uri}]")
rescue => ex
@log.error("Unable to generate dynamic config for WS-I: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def extract_target_elements
#Need the servicename, portname and other stuff from the wsdl
#Get the DEFINITIONS.TARGETNAMESPACE attribute
#Get the DEFINITIONS/SERVICE.NAME attribute
#Get the DEFINITIONS/SERVICE/PORT.name attribute
dom=REXML::Document.new(@wsdl_source)
begin
#TODO: Need to ensure I can handle a WSDL with multiple SERVICE/PORT combinations
@wsdl_namespace=dom.elements["definitions"].attributes["targetNamespace"]
#@wsdl_service_name=dom.elements["definitions/service"].attributes["name"]
@wsdl_service_declarations={}
dom.elements.each("definitions/service") do |servicenode|
svc=servicenode.attributes["name"]
port=servicenode.elements[port].attributes["name"]
@wsdl_service_declarations[svc]=port
end
#@wsdl_port_name=dom.elements["definitions/service/port"].attributes["name"]
@log.info("Extracted Namespace[#{@wsdl_namespace}] Services[#{@wsdl_service_declarations.inspect}] from WSDL")
if @wsdl_service_declarations.size<1
raise "No Service elements [definitions/service] detected in WSDL [#{@wsdl_name}]"
end
rescue => ex
@log.error("Unable to extract WSDL markers for WS-I: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def acquire_wsdl
begin
@wsdl_source = open(@wsdl_uri).read
@log.info "Acquired WSDL [#{@wsdl_uri}] [#{@wsdl_source.length}] bytes of WSDL"
rescue => ex
@log.error("Unable to acquire WSDL [#{@wsdl_uri}]: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def configure_environment

#display WSI environment variables
path_delim=UNIX_PATH_DELIMITER
unless ENV["OS"].nil?
unless ENV["OS"].downcase.index("windows").nil?
path_delim=WIN_PATH_DELIMITER
end
end
ENV[WSI_HOME_TAG]=WSI_HOME_VAL
ENV[WSI_JAVA_HOME_TAG]=WSI_JAVA_HOME_VAL
ENV[WSI_JAVA_OPTS_TAG]=WSI_JAVA_OPTS_VAL
#Now add the path segments
WSIClasspath.each do |val|
if(ENV[WSI_CLASSPATH_TAG].nil?)
ENV[WSI_CLASSPATH_TAG]="#{val}"
else
ENV[WSI_CLASSPATH_TAG]="#{ENV[WSI_CLASSPATH_TAG]}#{path_delim}#{val}"
end
end
end
end
end

Powered by Qumana


VMWare Fusion and Intermittent XP VM Networking

August 19, 2008

This is a strange one that’s been perplexing me. VMWare Fusion 1.1.3, running WinXP SP2 solid as a rock for nearly 9 months. I use a NAT networking configuration on my XP VM, relying on my Macbook Pro host to establish a wired/wireless connection to the web, which is then shared by the VM. As I say – s-o-l-i-d as a rock!

Recently though I noticed an increasing trend of the XP VM networking manager informing me that the XP networking connection was partially configured after a failed initialisation, and therefore offered limited functionality – which meant NO connection in reality.

I trawled the web/forums for support but predictably stumbled across the same mix of affliction and useless assistance like “..er…reinstall everyting and that should do it” from “yours faithfully the new guy on the support desk just trying to make a living by reading page 1 of the firefighting guide” or the equally ridiculous and unhelpful help from the geek who believes packing as many three-letter-acronyms into every uttered statement as possible.

Ultimately I drew a blank on the support front which was very disappointing. I did however happen across an innocuous statement relating to DHCP and some kind of limitation when acquiring IP addresses over a wireless link. As such I began musing about whether ‘things’ or ‘services’ in my XP startup may be interfering with my XP IP stack obtaining full configuration. However, logic was screaming at me that all services using IP rely on the underlying IP stack to obtain it’s address which is then shared by port number specific socket users…but still I went with the flow…

What follows cannot be explained (by me anyhow) in scientific terms. I opened XP servivce manager, and stopped a range of services like Postgres, Mysql, Sqlserver, and some others I wasn’t clear on the use of. That was the only change I made.

To my surprise, my rock-solid XP VM stability returned with guaranteed networking every time ! I offer this post not as a technically enriching article, but as a last stop for those as desperate as I was when afflicted by this intermittent yet highly painful symptom !

Powered by Qumana


VMWare Fusion, OS-X and Large Files

August 19, 2008

I’ve been using VMWare Fusion running an XP Virtual Machine on my Macbook Pro for 9 months now, and it’s been rock solid. There are reasons (corporate) why I need to have a specific XP build for accessing certain business support systems hence why I’ve not made the OS-X conversion all the way. I’ve been trying to replicate my XP VM onto another external drive. The image is 40GB. Computers says NOOOOOO !

I was receiving an unhelpful Error 0 from the drag-n-drop copier in the OS-X Finder, and when I dropped to a terminal window it was copying around 4 gigabytes before complaining the file was too large….yeah…it made me wait before telling me that ! So how do I move a large file?

I checked out VMWare forums who recommended using vdisk-manager (and it’s many complicated parameters) to chunk the image into 2GB files. There was also chatter of using the VMWare Fusion advanced applications settings to set the flag to ‘break image into 2GB files’ but it turns out that option is only relevant to newly created image files.

To cut a long and painful story short, after trying many weird and wonderful options, I used the following command:

# split -b 2048m /Users/Name/Documents/Virtuals/source.vmdk /Volumes/USB Drive Name/dest

This successfully broke up my 40GB source file into a file-set, using a aa..nn suffix, in my destination like:

# ls /Volumes/USD Drive Name =>

destaa, destab, destac, destad .. destat

I was then able to move my VM onto another host and reconsitute the backup with a neat bit of ruby scripting (as an alternative to a long-winded command-line). Something along the lines of:

cmdline=’cat ‘

for suff in ‘aa’..’at’ do

cmdline << “dest#{suff} “

end

cmdline << “> source.vmdk”

system(cmdline)

This a long-running yet simple solution to something I’d been struggling with for a couple of hours, and there was no need for any external tools or complexities.

The copied VM works perfectly too…but I accept no resposibility for anyone who fluffs this approach !

Powered by Qumana