From 250027c7bafe463429979bc19074ce14ecd13f55 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Wed, 13 Jun 2012 18:45:41 +0200 Subject: [PATCH 01/11] feature #1303: Fix onemarket show --- src/cloud/marketplace/bin/onemarket | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/cloud/marketplace/bin/onemarket b/src/cloud/marketplace/bin/onemarket index 78b8043f1a..5f8ece131a 100755 --- a/src/cloud/marketplace/bin/onemarket +++ b/src/cloud/marketplace/bin/onemarket @@ -172,13 +172,7 @@ cmd=CommandParser::CmdParser.new(ARGV) do if CloudClient::is_error?(response) [response.code.to_i, response.to_s] else - if options[:json] - [0,response.body] - else - array_list = JSON.parse(response.body) - TABLE.show(array_list['appliances']) - 0 - end + [0,response.body] end end end From b79ee004000dda5e096cdec066830d785a3bf3c3 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 14 Jun 2012 12:09:55 +0200 Subject: [PATCH 02/11] feature #1303: added downloader script supports gzip and bz2 compressors and also tar and zip archivers. copies files and downloads http/https paths --- install.sh | 1 + src/datastore_mad/remotes/downloader.rb | 286 ++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100755 src/datastore_mad/remotes/downloader.rb diff --git a/install.sh b/install.sh index ff3b53401d..96e52fdb78 100755 --- a/install.sh +++ b/install.sh @@ -873,6 +873,7 @@ TM_LVM_FILES="src/tm_mad/lvm/clone \ #------------------------------------------------------------------------------- DATASTORE_DRIVER_COMMON_SCRIPTS="src/datastore_mad/remotes/xpath.rb \ + src/datastore_mad/remotes/downloader.rb \ src/datastore_mad/remotes/libfs.sh" DATASTORE_DRIVER_DUMMY_SCRIPTS="src/datastore_mad/remotes/dummy/cp \ diff --git a/src/datastore_mad/remotes/downloader.rb b/src/datastore_mad/remotes/downloader.rb new file mode 100755 index 0000000000..79f3e41653 --- /dev/null +++ b/src/datastore_mad/remotes/downloader.rb @@ -0,0 +1,286 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +require 'open3' +require 'net/http' +require 'uri' +require 'optparse' +require 'digest' +require 'fileutils' + +require 'pp' + + +class Stream + BLOCK_SIZE=1024*64 + + TYPES={ + "application/x-gzip" => "gunzip -c -", + "application/x-bzip2" => "bunzip2 -c -", + } + + POSTPROCESS_TYPE={ + "application/x-tar" => "tar -xf #IN# -C #OUT#", + "application/zip" => "unzip -d #OUT# #IN#" + } + + DIGEST={ + "md5" => lambda { Digest::MD5.new }, + "sha1" => lambda { Digest::SHA1.new }, + "sha256" => lambda { Digest::SHA2.new(256) }, + "sha384" => lambda { Digest::SHA2.new(384) }, + "sha512" => lambda { Digest::SHA2.new(512) }, + } + + def initialize(from, to, options={}) + @from=from + @to=to + @options=options + + @digests={} + + @uncompress_proc=nil + @compr_in=nil + @compr_out=nil + + @writer_thread=nil + + prepare_digests + end + + def open_output_file + begin + @output_file=File.open(@to, "w") + rescue + STDERR.puts "Error opening output file '#{@to}" + exit(-1) + end + end + + def prepare_digests + @options.each do |key, value| + if DIGEST.has_key?(key) + @digests[key]={ + :hash => value, + :digest => DIGEST[key].call + } + end + end + end + + def process(data) + begin + @compr_in.write(data) + @compr_in.flush + rescue Errno::EPIPE + STDERR.puts "Error uncompressing image." + exit(-1) + end + + @digests.each do |name, algo| + algo[:digest] << data + end + end + + def type(header) + io=Open3.popen3("file -b --mime-type -") + io[0].write(header) + io[0].close + out=io[1].read.strip + io[1].close + out + end + + def set_compress(header) + t=type(header) + + compr=TYPES[t] + + if compr + @uncompress_proc=Open3.popen3(compr) + else + @uncompress_proc=Open3.popen3("cat") + end + + @compr_in=@uncompress_proc[0] + @compr_out=@uncompress_proc[1] + end + + def start_file_writer + @writer_thread=Thread.new do + while(!@compr_out.eof?) + data=@compr_out.read(BLOCK_SIZE) + if data + #STDERR.puts "Compr reader #{data.length}" + @output_file.write(data) + #STDERR.puts "File writer #{data.length}" + else + #STDERR.puts "Data is empty!" + end + end + end + end + + def http_downloader(url) + uri=URI(url) + + Net::HTTP.start(uri.host, uri.port) do |http| + request=Net::HTTP::Get.new(uri.request_uri) + + http.request(request) do |response| + response.read_body(&process) + end + end + end + + def wget_downloader(url) + @popen=Open3.popen3("wget -O - '#{url}'") + @popen[0].close + @popen[1] + end + + def check_hashes + fail=false + + @digests.each do |name, d| + given=d[:hash].downcase + computed=d[:digest].hexdigest.downcase + if given!=computed + fail=true + STDERR.puts "Digest #{name} does not match. "<< + "#{given}!=#{computed}" + end + end + + exit(-1) if fail + end + + def download + io=nil + + begin + case @from + when /^https?:\/\// + io=wget_downloader(@from) + when /^file:\/\/(.*)$/ + name=$1 + io=open(name, 'r') + else + io=open(@from, 'r') + end + rescue # Errno::ENOENT + STDERR.puts "File not found" + exit(-1) + end + + header=io.read(BLOCK_SIZE) + + if !header + if @popen + STDERR.puts @popen[2].read.strip.split("\n").last + exit(-1) + end + end + + open_output_file + + set_compress(header) + start_file_writer + + download_stderr="" + + process(header) + + while(!io.eof?) + @popen[2].read_nonblock(BLOCK_SIZE, download_stderr) + data=io.read(BLOCK_SIZE) + process(data) + end + + @finished=true + + @compr_in.close_write + + @writer_thread.join + + check_hashes + + postprocess + end + + def postprocess + f=open(@to) + header=f.read(BLOCK_SIZE) + f.close + + t=type(header) + + if POSTPROCESS_TYPE.has_key?(t) + if @to[0,1]=='/' + to=@to + else + to=ENV['PWD']+'/'+@to + end + + tmp_file="#{to}.tmp" + FileUtils.mv(to, tmp_file) + + FileUtils.mkdir(to) + + cmd=POSTPROCESS_TYPE[t] + cmd.gsub!('#IN#', tmp_file) + cmd.gsub!('#OUT#', @to) + + system(cmd) + status=$? + + if !status.success? + STDERR.puts "Error uncompressing archive" + exit(-1) + end + + FileUtils.rm(tmp_file) + end + end +end + + +options={} + +OptionParser.new do |opts| + opts.banner="Usage: download_helper " + + Stream::DIGEST.each do |name, value| + opts.on("--#{name} HASH", "Check #{name} hash") do |v| + options[name]=v + end + end +end.parse! + +if ARGV.length<2 + STDERR.puts "You need to specify source and destination" + exit(-1) +end + +input=ARGV[0] +output=ARGV[1] + + +s=Stream.new(input, output, options) +s.download + From e3ac27df78fb7e90bd89e7454b30868516168f2b Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Thu, 14 Jun 2012 12:27:46 +0200 Subject: [PATCH 03/11] feature #1303: Add checksum support to marketplace tab --- src/sunstone/public/js/plugins/marketplace-tab.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/sunstone/public/js/plugins/marketplace-tab.js b/src/sunstone/public/js/plugins/marketplace-tab.js index 4061f45cfd..f039cac26d 100644 --- a/src/sunstone/public/js/plugins/marketplace-tab.js +++ b/src/sunstone/public/js/plugins/marketplace-tab.js @@ -90,6 +90,19 @@ var market_actions = { success: function(response){ document.getElementById("img_name").value = response['name']; document.getElementById("img_path").value = response['links']['download']['href']; + + var md5 = response['files'][0]['checksum']['md5'] + if ( md5 ) { + option = ''; + $("#custom_var_image_box").append(option); + } + + var sha1 = response['files'][0]['checksum']['sha1'] + if ( sha1 ) { + option = ''; + $("#custom_var_image_box").append(option); + } + popUpCreateImageDialog(); }, error: function(response) From 7ad904bd413f371c27e9fea8d8d6ecab5b207e6f Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 14 Jun 2012 12:29:50 +0200 Subject: [PATCH 04/11] feature #1303: added downloader to datastore fs/cp --- src/datastore_mad/remotes/fs/cp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/datastore_mad/remotes/fs/cp b/src/datastore_mad/remotes/fs/cp index 81a6b07679..ecffd9ec83 100755 --- a/src/datastore_mad/remotes/fs/cp +++ b/src/datastore_mad/remotes/fs/cp @@ -39,7 +39,9 @@ source ${DRIVER_PATH}/../libfs.sh DRV_ACTION=$1 ID=$2 -XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" +UTILS_PATH="${DRIVER_PATH}/.." + +XPATH="$UTILS_PATH/xpath.rb -b $DRV_ACTION" unset i XPATH_ELEMENTS @@ -49,26 +51,42 @@ done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/RESTRICTED_DIRS \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SAFE_DIRS \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/UMASK \ - /DS_DRIVER_ACTION_DATA/IMAGE/PATH) + /DS_DRIVER_ACTION_DATA/IMAGE/PATH \ + /DS_DRIVER_ACTION_DATA/IMAGE/MD5 \ + /DS_DRIVER_ACTION_DATA/IMAGE/SHA1) BASE_PATH="${XPATH_ELEMENTS[0]}" RESTRICTED_DIRS="${XPATH_ELEMENTS[1]}" SAFE_DIRS="${XPATH_ELEMENTS[2]}" UMASK="${XPATH_ELEMENTS[3]}" SRC="${XPATH_ELEMENTS[4]}" +MD5="${XPATH_ELEMENTS[5]}" +SHA1="${XPATH_ELEMENTS[6]}" mkdir -p "$BASE_PATH" set_up_datastore "$BASE_PATH" "$RESTRICTED_DIRS" "$SAFE_DIRS" "$UMASK" DST=`generate_image_path` +HASHES="" + +if [ -n "$MD5"]; then + HASHES="$HASHES --md5 $MD5" +fi + +if [ -n "$SHA1"]; then + HASHES="$HASHES --sha1 $SHA1" +fi + +COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC $DST" + # ------------ Copy the image to the repository ------------- case $SRC in http://*) log "Downloading $SRC to the image repository" - exec_and_log "$WGET -O $DST $SRC" "Error downloading $SRC" + exec_and_log "$COPY_COMMAND" "Error downloading $SRC" ;; *) @@ -80,7 +98,7 @@ http://*) log "Copying local image $SRC to the image repository" - exec_and_log "cp -f $SRC $DST" "Error copying $SRC to $DST" + exec_and_log "$COPY_COMMAND" "Error copying $SRC to $DST" ;; esac From dd22bbb362a9f36d45d3fb0eb22cdee68beebe7e Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 14 Jun 2012 16:17:41 +0200 Subject: [PATCH 05/11] feature #1303: support for STDIN and STDOUT in downloader.rb Now the file "-" means standard input/output --- src/datastore_mad/remotes/downloader.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/datastore_mad/remotes/downloader.rb b/src/datastore_mad/remotes/downloader.rb index 79f3e41653..814d3e8a0a 100755 --- a/src/datastore_mad/remotes/downloader.rb +++ b/src/datastore_mad/remotes/downloader.rb @@ -64,11 +64,15 @@ class Stream end def open_output_file - begin - @output_file=File.open(@to, "w") - rescue - STDERR.puts "Error opening output file '#{@to}" - exit(-1) + if @to=='-' + @output_file=STDOUT + else + begin + @output_file=File.open(@to, "w") + rescue + STDERR.puts "Error opening output file '#{@to}" + exit(-1) + end end end @@ -175,6 +179,8 @@ class Stream begin case @from + when '-' + io=STDIN when /^https?:\/\// io=wget_downloader(@from) when /^file:\/\/(.*)$/ @@ -220,7 +226,7 @@ class Stream check_hashes - postprocess + postprocess if @to!='-' end def postprocess From 38b8769331a5cc34c4f9aaf01d8a55bdb4431efa Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 14 Jun 2012 16:22:17 +0200 Subject: [PATCH 06/11] feature #1303: changed datastores cp to use downloader.rb --- src/datastore_mad/remotes/fs/cp | 8 ++--- src/datastore_mad/remotes/iscsi/cp | 23 +++++++++++--- src/datastore_mad/remotes/lvm/cp | 22 ++++++++++++-- src/datastore_mad/remotes/vmware/cp | 47 +++++++++++++++++++++-------- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/datastore_mad/remotes/fs/cp b/src/datastore_mad/remotes/fs/cp index ecffd9ec83..4230f728b9 100755 --- a/src/datastore_mad/remotes/fs/cp +++ b/src/datastore_mad/remotes/fs/cp @@ -52,8 +52,8 @@ done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SAFE_DIRS \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/UMASK \ /DS_DRIVER_ACTION_DATA/IMAGE/PATH \ - /DS_DRIVER_ACTION_DATA/IMAGE/MD5 \ - /DS_DRIVER_ACTION_DATA/IMAGE/SHA1) + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/MD5 \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/SHA1) BASE_PATH="${XPATH_ELEMENTS[0]}" RESTRICTED_DIRS="${XPATH_ELEMENTS[1]}" @@ -70,11 +70,11 @@ DST=`generate_image_path` HASHES="" -if [ -n "$MD5"]; then +if [ -n "$MD5" ]; then HASHES="$HASHES --md5 $MD5" fi -if [ -n "$SHA1"]; then +if [ -n "$SHA1" ]; then HASHES="$HASHES --sha1 $SHA1" fi diff --git a/src/datastore_mad/remotes/iscsi/cp b/src/datastore_mad/remotes/iscsi/cp index 7eb8c4bd83..eadf39b6f2 100755 --- a/src/datastore_mad/remotes/iscsi/cp +++ b/src/datastore_mad/remotes/iscsi/cp @@ -54,8 +54,9 @@ done < <($XPATH /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VG_NAME \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BASE_IQN \ /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BASE_TID \ - /DS_DRIVER_ACTION_DATA/IMAGE/PATH) - + /DS_DRIVER_ACTION_DATA/IMAGE/PATH \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/MD5 \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/SHA1) BASE_PATH="${XPATH_ELEMENTS[0]}" RESTRICTED_DIRS="${XPATH_ELEMENTS[1]}" @@ -66,6 +67,8 @@ VG_NAME="${XPATH_ELEMENTS[5]:-$VG_NAME}" BASE_IQN="${XPATH_ELEMENTS[6]:-$BASE_IQN}" BASE_TID="${XPATH_ELEMENTS[7]:-$BASE_TID}" SRC="${XPATH_ELEMENTS[8]}" +MD5="${XPATH_ELEMENTS[9]}" +SHA1="${XPATH_ELEMENTS[10]}" set_up_datastore "$BASE_PATH" "$RESTRICTED_DIRS" "$SAFE_DIRS" "$UMASK" @@ -85,11 +88,23 @@ REGISTER_CMD=$(cat < Date: Thu, 14 Jun 2012 18:28:46 +0200 Subject: [PATCH 07/11] feature #1303 copy vmware directory images with cp --- src/datastore_mad/remotes/vmware/cp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/datastore_mad/remotes/vmware/cp b/src/datastore_mad/remotes/vmware/cp index c748c0b129..2361963db3 100755 --- a/src/datastore_mad/remotes/vmware/cp +++ b/src/datastore_mad/remotes/vmware/cp @@ -98,7 +98,11 @@ http://*) log "Copying local disk folder $SRC to the image repository" - exec_and_log "$COPY_COMMAND" "Error copying $SRC to $DST" + if [ -d $SRC ]; then + exec_and_log "cp -rf $SRC $DST" "Error copying $SRC to $DST" + else + exec_and_log "$COPY_COMMAND" "Error copying $SRC to $DST" + fi ;; esac From fe0ed3c06cedf048a6327876223ce3ba30392562 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Fri, 15 Jun 2012 15:58:35 +0200 Subject: [PATCH 08/11] feature #1303: bug in downloader.rb (reading stder for local files) --- src/datastore_mad/remotes/downloader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datastore_mad/remotes/downloader.rb b/src/datastore_mad/remotes/downloader.rb index 814d3e8a0a..df2fa57140 100755 --- a/src/datastore_mad/remotes/downloader.rb +++ b/src/datastore_mad/remotes/downloader.rb @@ -213,7 +213,7 @@ class Stream process(header) while(!io.eof?) - @popen[2].read_nonblock(BLOCK_SIZE, download_stderr) + @popen[2].read_nonblock(BLOCK_SIZE, download_stderr) if @popen data=io.read(BLOCK_SIZE) process(data) end From df71bfa3abb0f628822dc0ec4e3a0a657cf0bbf6 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Fri, 15 Jun 2012 18:12:43 +0200 Subject: [PATCH 09/11] feature #1303: downloader retries writing when the uncompress command is busy --- src/datastore_mad/remotes/downloader.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/datastore_mad/remotes/downloader.rb b/src/datastore_mad/remotes/downloader.rb index df2fa57140..96203176d1 100755 --- a/src/datastore_mad/remotes/downloader.rb +++ b/src/datastore_mad/remotes/downloader.rb @@ -89,9 +89,18 @@ class Stream def process(data) begin - @compr_in.write(data) - @compr_in.flush - rescue Errno::EPIPE + try=5 + begin + @compr_in.write(data) + @compr_in.flush + rescue + if try>0 + try-=1 + sleep 0.1 + retry + end + end + rescue STDERR.puts "Error uncompressing image." exit(-1) end From a9f8634a29c57f1c4d873bb6f9f417afb67ca2cb Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Mon, 18 Jun 2012 16:09:09 +0200 Subject: [PATCH 10/11] feature #1303: make the uncompressor write to the fd ruby 1.8.7 has a bug writting files (or pipes) to overcome it the file (or stdout) is opened in the downloader and then the uncompress command is started redirecting stdout to the descriptor. Pseudocode: if write_to_stdout? fd=dup(stdout) else fd=open(output_file) end exec("$UNCOMPRESSOR >&$fd") --- src/datastore_mad/remotes/downloader.rb | 33 +++++++------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/datastore_mad/remotes/downloader.rb b/src/datastore_mad/remotes/downloader.rb index 96203176d1..4153b58e26 100755 --- a/src/datastore_mad/remotes/downloader.rb +++ b/src/datastore_mad/remotes/downloader.rb @@ -65,7 +65,7 @@ class Stream def open_output_file if @to=='-' - @output_file=STDOUT + @output_file=STDOUT.dup else begin @output_file=File.open(@to, "w") @@ -122,13 +122,11 @@ class Stream def set_compress(header) t=type(header) - compr=TYPES[t] + compr=TYPES[t]||"cat" - if compr - @uncompress_proc=Open3.popen3(compr) - else - @uncompress_proc=Open3.popen3("cat") - end + compr="#{compr} >&#{@output_file.fileno}" + + @uncompress_proc=Open3.popen3(compr) @compr_in=@uncompress_proc[0] @compr_out=@uncompress_proc[1] @@ -149,20 +147,8 @@ class Stream end end - def http_downloader(url) - uri=URI(url) - - Net::HTTP.start(uri.host, uri.port) do |http| - request=Net::HTTP::Get.new(uri.request_uri) - - http.request(request) do |response| - response.read_body(&process) - end - end - end - def wget_downloader(url) - @popen=Open3.popen3("wget -O - '#{url}'") + @popen=Open3.popen3("wget -O - '#{url}'") @popen[0].close @popen[1] end @@ -215,14 +201,15 @@ class Stream open_output_file set_compress(header) - start_file_writer download_stderr="" process(header) while(!io.eof?) - @popen[2].read_nonblock(BLOCK_SIZE, download_stderr) if @popen + if defined?(@popen) + @popen[2].read_nonblock(BLOCK_SIZE, download_stderr) + end data=io.read(BLOCK_SIZE) process(data) end @@ -231,8 +218,6 @@ class Stream @compr_in.close_write - @writer_thread.join - check_hashes postprocess if @to!='-' From 8dbb1dc283778ac9d0bab6078e91a0e7780a6db3 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Mon, 18 Jun 2012 18:22:08 +0200 Subject: [PATCH 11/11] feature #1303: bug in iscsi cp datastore remote --- src/datastore_mad/remotes/iscsi/cp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/datastore_mad/remotes/iscsi/cp b/src/datastore_mad/remotes/iscsi/cp index eadf39b6f2..bab4bdcd57 100755 --- a/src/datastore_mad/remotes/iscsi/cp +++ b/src/datastore_mad/remotes/iscsi/cp @@ -40,7 +40,9 @@ source ${DRIVER_PATH}/iscsi.conf DRV_ACTION=$1 ID=$2 -XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" +UTILS_PATH="${DRIVER_PATH}/.." + +XPATH="$UTILS_PATH/xpath.rb -b $DRV_ACTION" unset i XPATH_ELEMENTS