1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-25 02:50:08 +03:00

Feature #4217: Use Sunstone to download images and marketplaces

This commit changes entirely the way images are downloaded.
Instead of downloading them by running 'downloader.sh' in the
local machine, it will do it on the Sunstone server, and it
will in turn stream the response to the client.

This commit implements the server and the CLI.
This commit is contained in:
Jaime Melis 2016-03-09 18:33:03 +01:00
parent 8207c38e2a
commit 0f3dbf58db
7 changed files with 234 additions and 94 deletions

View File

@ -332,6 +332,12 @@ EOT
}
]
FORCE={
:name => 'force',
:large => '--force',
:description => 'Overwrite the file'
}
TEMPLATE_OPTIONS_VM=[TEMPLATE_NAME_VM]+TEMPLATE_OPTIONS+[DRY]
CAPACITY_OPTIONS_VM=[TEMPLATE_OPTIONS[0],TEMPLATE_OPTIONS[1],TEMPLATE_OPTIONS[3]]
@ -1046,4 +1052,105 @@ EOT
# in options hash
(template_options-options.keys)!=template_options
end
def self.sunstone_url
if (one_sunstone = ENV['ONE_SUNSTONE'])
one_sunstone
elsif (one_xmlrpc = ENV['ONE_XMLRPC'])
uri = URI(one_xmlrpc)
"#{uri.scheme}://#{uri.host}:9869"
else
"http://localhost:9869"
end
end
def self.download_resource_sunstone(kind, id, path, force)
client = OneHelper.client
user, password = client.one_auth.split(":", 2)
# Step 1: Build Session to get Cookie
uri = URI(File.join(sunstone_url,"login"))
req = Net::HTTP::Post.new(uri)
req.basic_auth user, password
begin
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
rescue
return OpenNebula::Error.new("Error connecting to '#{uri}'.")
end
cookie = res.response['set-cookie'].split('; ')[0]
if cookie.nil?
return OpenNebula::Error.new("Unable to get Cookie. Is OpenNebula running?")
end
# Step 2: Open '/' to get the csrftoken
uri = URI(sunstone_url)
req = Net::HTTP::Get.new(uri)
req['Cookie'] = cookie
begin
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
rescue
return OpenNebula::Error.new("Error connecting to '#{uri}'.")
end
m = res.body.match(/var csrftoken = '(.*)';/)
csrftoken = m[1] rescue nil
if csrftoken.nil?
return OpenNebula::Error.new("Unable to get csrftoken.")
end
# Step 3: Download resource
uri = URI(File.join(sunstone_url,
kind.to_s,
id.to_s,
"download?csrftoken=#{csrftoken}"))
req = Net::HTTP::Get.new(uri)
req['Cookie'] = cookie
req['User-Agent'] = "OpenNebula CLI"
begin
File.open(path, 'wb') do |f|
Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req) do |res|
res.read_body do |chunk|
f.write(chunk)
end
end
end
end
rescue Errno::EACCES
return OpenNebula::Error.new("Target file not writable.")
end
error_message = nil
File.open(path, 'rb') do |f|
begin
f.seek(-1024, IO::SEEK_END)
rescue Errno::EINVAL
end
tail = f.read
m = tail.match(/@\^_\^@ (.*) @\^_\^@/m)
error_message = m[1] if m
end
if error_message
File.unlink(path)
return OpenNebula::Error.new("Remote server error: #{error_message}")
end
end
end

View File

@ -180,9 +180,12 @@ cmd=CommandParser::CmdParser.new(ARGV) do
Downloads an image to a file
EOT
command :download, download_desc, :imageid, :path do
command :download, download_desc, :imageid, :path,
:options => [OpenNebulaHelper::FORCE] do
helper.perform_action(args[0],options,"downloaded") do |image|
image.download(args[1], helper.client)
download_args = [:image, args[0], args[1], options[:force]]
OpenNebulaHelper.download_resource_sunstone(*download_args)
end
end

View File

@ -131,9 +131,11 @@ CommandParser::CmdParser.new(ARGV) do
Downloads a MarketApp to a file
EOT
command :download, download_desc, :appid, :path do
command :download, download_desc, :appid, :path,
:options => [OpenNebulaHelper::FORCE] do
helper.perform_action(args[0],options,"downloaded") do |app|
app.download(args[1], helper.client)
download_args = [:marketplaceapp, args[0], args[1], options[:force]]
OpenNebulaHelper.download_resource_sunstone(*download_args)
end
end

View File

@ -253,56 +253,6 @@ module OpenNebula
return call(IMAGE_METHODS[:snapshotflatten], @pe_id, snap_id)
end
#######################################################################
# OCA specific methods
#######################################################################
# Invokes 'download.sh' to copy the image to the specified path
# It calls the <ds_mad>/export action and downloader.sh
#
# @param path The destination of the downloader.sh action
# @param client The request client
#
# @return [nil, OpenNebula::Error] nil in case of success or Error
def download(path, client)
rc = info
return rc if OpenNebula.is_error?(rc)
ds_id = self['DATASTORE_ID']
ds = Datastore.new(Datastore.build_xml(ds_id), client)
rc = ds.info
return rc if OpenNebula.is_error?(rc)
drv_message = "<DS_DRIVER_ACTION_DATA>" <<
"#{to_xml}#{ds.to_xml}" <<
"</DS_DRIVER_ACTION_DATA>"
drv_message_64 = Base64::strict_encode64(drv_message)
export = "#{VAR_LOCATION}/remotes/datastore/#{ds['DS_MAD']}/export"
export_stdout = `#{export} #{drv_message_64} #{id}`
doc = REXML::Document.new(export_stdout).root
import_source = doc.elements['IMPORT_SOURCE'].text rescue nil
if import_source.nil? || import_source.empty?
return OpenNebula::Error.new("Cannot find image source.")
end
download_cmd = "#{VAR_LOCATION}/remotes/datastore/downloader.sh " <<
"#{import_source} #{path}"
system(download_cmd)
if (status = $?.exitstatus) != 0
error_msg = "Error executing '#{download_cmd}'. " <<
"Exit status: #{status}"
return OpenNebula::Error.new(error_msg)
end
end
#######################################################################
# Helpers to get Image information
#######################################################################

View File

@ -214,45 +214,6 @@ module OpenNebula
end
end
# Invokes 'download.sh' to download the app to the specified path
#
# @param path The destination of the downloader.sh action
# @param client The request client
#
# @return [nil, OpenNebula::Error] nil in case of success or Error
def download(path, client)
rc = info
return rc if OpenNebula.is_error?(rc)
market_id = self['MARKETPLACE_ID']
market = MarketPlace.new(MarketPlace.build_xml(market_id), client)
rc = market.info
return rc if OpenNebula.is_error?(rc)
# This 'drv_message' is missing some elements, compared to the one
# sent by the core. However, 'downloader.sh' only requires the
# MarketPlace information.
drv_message = "<DS_DRIVER_ACTION_DATA>" <<
"#{market.to_xml}" <<
"</DS_DRIVER_ACTION_DATA>"
drv_message_64 = Base64::strict_encode64(drv_message)
ENV['DRV_ACTION'] = drv_message_64
download_cmd = "#{VAR_LOCATION}/remotes/datastore/downloader.sh " <<
"#{self['SOURCE']} #{path}"
system(download_cmd)
if (status = $?.exitstatus) != 0
error_msg = "Error executing '#{download_cmd}'. " <<
"Exit status: #{status}"
return OpenNebula::Error.new(error_msg)
end
end
# Enables this app
def enable
return call(MARKETPLACEAPP_METHODS[:enable], @pe_id, true)

View File

@ -214,6 +214,82 @@ class SunstoneServer < CloudServer
end
end
############################################################################
#
############################################################################
def download_resource(kind, id)
case kind
when "image"
# Get Image
image = Image.new(Image.build_xml(id.to_i), @client)
rc = image.info
return [500, rc.message] if OpenNebula.is_error?(rc)
# Get Datastore
ds_id = image['DATASTORE_ID']
ds = Datastore.new(Datastore.build_xml(ds_id), @client)
rc = ds.info
return [500, rc.message] if OpenNebula.is_error?(rc)
# Build Driver message
drv_message = "<DS_DRIVER_ACTION_DATA>" <<
"#{image.to_xml}#{ds.to_xml}" <<
"</DS_DRIVER_ACTION_DATA>"
drv_message_64 = Base64::strict_encode64(drv_message)
begin
export = "#{VAR_LOCATION}/remotes/datastore/#{ds['DS_MAD']}/export"
export_stdout = `#{export} #{drv_message_64} #{id}`
doc = REXML::Document.new(export_stdout).root
source = doc.elements['IMPORT_SOURCE'].text
rescue Exception => e
return [500, "#{e.message}\n#{e.backtrace}"]
end
when "marketplaceapp"
# Get MarketPlaceApp
marketapp = MarketPlaceApp.new(MarketPlaceApp.build_xml(id.to_i), @client)
rc = marketapp.info
return [500, rc.message] if OpenNebula.is_error?(rc)
# Get Datastore
market_id = marketapp['MARKETPLACE_ID']
market = MarketPlace.new(MarketPlace.build_xml(market_id), @client)
rc = market.info
return [500, rc.message] if OpenNebula.is_error?(rc)
# Build Driver message
drv_message = "<DS_DRIVER_ACTION_DATA>" <<
"#{market.to_xml}" <<
"</DS_DRIVER_ACTION_DATA>"
drv_message_64 = Base64::strict_encode64(drv_message)
source = marketapp['SOURCE']
else
return [404, "Unknown resource."]
end
download_cmd = "DRV_ACTION=#{drv_message_64} "<<
"#{VAR_LOCATION}/remotes/datastore/downloader.sh " <<
"#{source} -"
filename = "one-#{kind}-#{id}"
return [download_cmd, filename]
end
############################################################################
# Unused
############################################################################

View File

@ -92,6 +92,8 @@ require 'yaml'
require 'securerandom'
require 'tmpdir'
require 'fileutils'
require 'base64'
require 'rexml/document'
require 'CloudAuth'
require 'SunstoneServer'
@ -324,7 +326,7 @@ before do
request.body.rewind
unless %w(/ /login /vnc /spice).include?(request.path)
halt 401 unless authorized? && valid_csrftoken?
halt [401, "csrftoken"] unless authorized? && valid_csrftoken?
end
if env['HTTP_ZONE_NAME']
@ -717,6 +719,45 @@ post '/upload_chunk' do
""
end
##############################################################################
# Download image or marketapp
##############################################################################
READ_LENGTH = 10*1024*1024
get '/:resource/:id/download' do
dl_resource = @SunstoneServer.download_resource(params[:resource], params[:id])
# If the first element of dl_resource is a number, it is the exit_code after
# an error happend, so return it.
return dl_resource if dl_resource[0].kind_of?(Fixnum)
download_cmd, filename = dl_resource
# Send headers
headers['Cache-Control'] = "no-transform" # Do not use Rack::Deflater
headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
content_type :'application/octet-stream'
# Start stream
stream do |out|
Open3.popen3(download_cmd) do |_,o,e,w|
until o.eof?
out << o.read(READ_LENGTH)
end
if !w.value.success?
error_message = "downloader.sh: " << e.read
logger.error { error_message }
if request.user_agent == "OpenNebula CLI"
out << "@^_^@ #{error_message} @^_^@"
end
end
end
end
end
##############################################################################
# Create a new Resource
##############################################################################