From 4c6233bd09c398f3f85c09691c4343ace0ca97e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Wed, 20 Jun 2012 17:32:05 +0200 Subject: [PATCH 01/21] Bug #1308: Before a chown operation, check if the new user already has an object with the same name --- include/RequestManagerChown.h | 53 ++++++++++++++++++++++++++++++++++- src/rm/RequestManagerChown.cc | 47 +++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/include/RequestManagerChown.h b/include/RequestManagerChown.h index 50567a1da0..b1b9a15724 100644 --- a/include/RequestManagerChown.h +++ b/include/RequestManagerChown.h @@ -57,6 +57,20 @@ protected: int new_uid, int new_gid, RequestAttributes& att); + + /** + * Checks if the new owner cannot has other object with the same name (if + * the pool does not allow it) + * + * @param oid Object id + * @param noid New owner user id + * @param error_str Error reason, if any + * + * @return 0 if the operation is allowed, -1 otherwise + */ + virtual int check_name_unique(int oid, int noid, string& error_str); + + virtual PoolObjectSQL * get(const string& name, int uid, bool lock) = 0; }; /* ------------------------------------------------------------------------- */ @@ -75,6 +89,16 @@ public: }; ~VirtualMachineChown(){}; + + int check_name_unique(int oid, int noid, string& error_str) + { + return 0; + }; + + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return 0; + }; }; /* ------------------------------------------------------------------------- */ @@ -86,13 +110,18 @@ public: TemplateChown(): RequestManagerChown("TemplateChown", "Changes ownership of a virtual machine template") - { + { Nebula& nd = Nebula::instance(); pool = nd.get_tpool(); auth_object = PoolObjectSQL::TEMPLATE; }; ~TemplateChown(){}; + + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return static_cast(pool)->get(name, uid, lock); + }; }; /* ------------------------------------------------------------------------- */ @@ -113,6 +142,10 @@ public: ~VirtualNetworkChown(){}; + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return static_cast(pool)->get(name, uid, lock); + }; }; /* ------------------------------------------------------------------------- */ @@ -132,6 +165,10 @@ public: ~ImageChown(){}; + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return static_cast(pool)->get(name, uid, lock); + }; }; /* ------------------------------------------------------------------------- */ @@ -156,6 +193,11 @@ public: virtual void request_execute(xmlrpc_c::paramList const& _paramList, RequestAttributes& att); + + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return 0; + }; }; /* ------------------------------------------------------------------------- */ @@ -175,6 +217,10 @@ public: ~DatastoreChown(){}; + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return 0; + }; }; /* ------------------------------------------------------------------------- */ @@ -193,6 +239,11 @@ public: }; ~DocumentChown(){}; + + PoolObjectSQL * get(const string& name, int uid, bool lock) + { + return 0; + }; }; /* -------------------------------------------------------------------------- */ diff --git a/src/rm/RequestManagerChown.cc b/src/rm/RequestManagerChown.cc index 06bba1a303..95c610ff71 100644 --- a/src/rm/RequestManagerChown.cc +++ b/src/rm/RequestManagerChown.cc @@ -115,6 +115,41 @@ PoolObjectSQL * RequestManagerChown::get_and_quota( /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ +int RequestManagerChown::check_name_unique(int oid, int noid, string& error_str) +{ + PoolObjectSQL * object; + string name; + int obj_oid; + ostringstream oss; + + object = pool->get(oid, true); + + name = object->get_name(); + + object->unlock(); + + object = get(name, noid, true); + + if ( object != 0 ) + { + obj_oid = object->get_oid(); + object->unlock(); + + oss << PoolObjectSQL::type_to_str(PoolObjectSQL::USER) + << " [" << noid << "] already owns " + << PoolObjectSQL::type_to_str(auth_object) << " [" + << obj_oid << "] with NAME " << name; + + error_str = oss.str(); + return -1; + } + + return 0; +}; + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + void RequestManagerChown::request_execute(xmlrpc_c::paramList const& paramList, RequestAttributes& att) { @@ -123,6 +158,7 @@ void RequestManagerChown::request_execute(xmlrpc_c::paramList const& paramList, int ngid = xmlrpc_c::value_int(paramList.getInt(3)); int rc; + string error_str; string oname; string nuname; @@ -194,6 +230,17 @@ void RequestManagerChown::request_execute(xmlrpc_c::paramList const& paramList, } } + // --------------- Check name uniqueness ----------------------------------- + + if ( noid != -1 ) + { + if ( check_name_unique(oid, noid, error_str) != 0 ) + { + failure_response(INTERNAL, request_error(error_str, ""), att); + return; + } + } + // --------------- Update the object and check quotas ---------------------- if ( auth_object == PoolObjectSQL::VM || From fb398ff9b24b919b9b6f7ac63a3620cddb046186 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 21 Jun 2012 00:46:57 +0200 Subject: [PATCH 02/21] feature #1303: changed downloader for a sh vesion --- install.sh | 2 +- src/datastore_mad/remotes/downloader.rb | 286 ------------------------ 2 files changed, 1 insertion(+), 287 deletions(-) delete mode 100755 src/datastore_mad/remotes/downloader.rb diff --git a/install.sh b/install.sh index e0ebbcefc4..02743f1165 100755 --- a/install.sh +++ b/install.sh @@ -875,7 +875,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/downloader.sh \ 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 deleted file mode 100755 index 4153b58e26..0000000000 --- a/src/datastore_mad/remotes/downloader.rb +++ /dev/null @@ -1,286 +0,0 @@ -#!/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 - if @to=='-' - @output_file=STDOUT.dup - else - begin - @output_file=File.open(@to, "w") - rescue - STDERR.puts "Error opening output file '#{@to}" - exit(-1) - end - 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 - 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 - - @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]||"cat" - - compr="#{compr} >&#{@output_file.fileno}" - - @uncompress_proc=Open3.popen3(compr) - - @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 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 '-' - io=STDIN - 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) - - download_stderr="" - - process(header) - - while(!io.eof?) - if defined?(@popen) - @popen[2].read_nonblock(BLOCK_SIZE, download_stderr) - end - data=io.read(BLOCK_SIZE) - process(data) - end - - @finished=true - - @compr_in.close_write - - check_hashes - - postprocess if @to!='-' - 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 44af5b090754f5d1823354f7dfb9c6a8f2634b8b Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 21 Jun 2012 00:48:29 +0200 Subject: [PATCH 03/21] feature #1303: changed datastores to use sh downloader --- src/datastore_mad/remotes/fs/cp | 2 +- src/datastore_mad/remotes/iscsi/cp | 2 +- src/datastore_mad/remotes/lvm/cp | 2 +- src/datastore_mad/remotes/vmware/cp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datastore_mad/remotes/fs/cp b/src/datastore_mad/remotes/fs/cp index a3f92f7503..5e6fac36fa 100755 --- a/src/datastore_mad/remotes/fs/cp +++ b/src/datastore_mad/remotes/fs/cp @@ -78,7 +78,7 @@ if [ -n "$SHA1" ]; then HASHES="$HASHES --sha1 $SHA1" fi -COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC $DST" +COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC $DST" # ------------ Copy the image to the repository ------------- diff --git a/src/datastore_mad/remotes/iscsi/cp b/src/datastore_mad/remotes/iscsi/cp index 8a7f451512..44119c73b7 100755 --- a/src/datastore_mad/remotes/iscsi/cp +++ b/src/datastore_mad/remotes/iscsi/cp @@ -101,7 +101,7 @@ if [ -n "$SHA1" ]; then HASHES="$HASHES --sha1 $SHA1" fi -COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC -" +COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC -" case $SRC in http://*) diff --git a/src/datastore_mad/remotes/lvm/cp b/src/datastore_mad/remotes/lvm/cp index 28ac2cf9d5..e003b71b79 100755 --- a/src/datastore_mad/remotes/lvm/cp +++ b/src/datastore_mad/remotes/lvm/cp @@ -90,7 +90,7 @@ if [ -n "$SHA1" ]; then HASHES="$HASHES --sha1 $SHA1" fi -COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC -" +COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC -" case $SRC in http://*) diff --git a/src/datastore_mad/remotes/vmware/cp b/src/datastore_mad/remotes/vmware/cp index d5a20e88c0..6fb6f0e148 100755 --- a/src/datastore_mad/remotes/vmware/cp +++ b/src/datastore_mad/remotes/vmware/cp @@ -78,7 +78,7 @@ if [ -n "$SHA1" ]; then HASHES="$HASHES --sha1 $SHA1" fi -COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC $DST" +COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC $DST" # ------------ Copy the image to the repository ------------- From 39fa229ef08163212bebd430f97f34a26637c93f Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 21 Jun 2012 02:50:36 +0200 Subject: [PATCH 04/21] feature #1303: add downloader.sh for real --- src/datastore_mad/remotes/downloader.sh | 158 ++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100755 src/datastore_mad/remotes/downloader.sh diff --git a/src/datastore_mad/remotes/downloader.sh b/src/datastore_mad/remotes/downloader.sh new file mode 100755 index 0000000000..b190605a68 --- /dev/null +++ b/src/datastore_mad/remotes/downloader.sh @@ -0,0 +1,158 @@ +#!/bin/bash + +function get_type +{ + command=$1 + + ( $command | head -1024 | file -b --mime-type - ) 2>/dev/null +} + +function get_decompressor +{ + type=$1 + + case "$type" in + "application/x-gzip") + echo "gunzip -c -" + ;; + "application/x-bzip2") + echo "bunzip2 -c -" + ;; + *) + echo "cat" + ;; + esac +} + +function decompress +{ + command="$1" + to="$2" + + if [ "x$to" = "x-" ]; then + $command + else + $command > "$to" + fi +} + +function hasher +{ + algo=$1 + + if [ -n "$algo" ]; then + openssl dgst -$algo > $HASH_FILE + fi +} + +function unarchive +{ + TO="$1" + + file_type=$(get_type "cat $TO") + + tmp="$TO" + + if [ ${tmp:0:1} = "/" ]; then + $tmp="$PWD/$tmp" + fi + + IN="$tmp.tmp" + OUT="$tmp" + + case "$file_type" in + "application/x-tar") + command="tar -xf $IN -C $OUT" + ;; + "application/zip") + command="unzip -d $OUT $IN" + ;; + *) + command="" + ;; + esac + + if [ -n "$command" ]; then + mv "$OUT" "$IN" + mkdir "$OUT" + + $command + + if [ "$?" != "0" ]; then + echo "Error uncompressing archive" >&2 + exit -1 + fi + + rm "$IN" + fi +} + +TEMP=`getopt -o m:s: -l md5:,sha1: -- "$@"` + +if [ $? != 0 ] ; then + echo "Arguments error" + exit -1 +fi + +eval set -- "$TEMP" + +while true; do + case "$1" in + -m|--md5) + HASH_TYPE=md5 + HASH=$2 + shift 2 + ;; + -s|--sha1) + HASH_TYPE=sha1 + HASH=$2 + shift 2 + ;; + --) + shift + break + ;; + *) + shift + ;; + esac +done + +FROM=$1 +TO=$2 + +export HASH_FILE="/tmp/downloader.hash.$$" + +case "$FROM" in +http://*) + command="curl -L $FROM" + ;; +*) + command="cat $FROM" + ;; +esac + +file_type=$(get_type "$command") +decompressor=$(get_decompressor "$file_type") + +$command | tee >( decompress "$decompressor" "$TO" ) \ + >( hasher $HASH_TYPE ) >/dev/null + +if [ "$?" != "0" ]; then + echo "Error copying" >&2 + exit -1 +fi + +if [ -n "$HASH_TYPE" ]; then + HASH_RESULT=$( cat $HASH_FILE) + rm $HASH_FILE + if [ "$HASH_RESULT" != "$HASH" ]; then + echo "Hash does not match" >&2 + exit -1 + fi +fi + +if [ "$TO" != "-" ]; then + unarchive "$TO" +fi + From b6626dbee6e053ffbc5af73addb58c788c0577a3 Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Thu, 21 Jun 2012 11:58:41 +0200 Subject: [PATCH 05/21] bug: Make [] of XMLElement NOKOGIRI and REXML behave similarly when no text is in element --- src/oca/ruby/OpenNebula/XMLUtils.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/oca/ruby/OpenNebula/XMLUtils.rb b/src/oca/ruby/OpenNebula/XMLUtils.rb index bfe04fcc2d..c272773930 100644 --- a/src/oca/ruby/OpenNebula/XMLUtils.rb +++ b/src/oca/ruby/OpenNebula/XMLUtils.rb @@ -89,16 +89,14 @@ module OpenNebula if NOKOGIRI element=@xml.xpath(key.to_s) - if element.size == 0 - return nil - end + return nil if element.size == 0 else element=@xml.elements[key.to_s] + + return "" if element && !element.has_text? end - if element - element.text - end + element.text if element end # Delete an element from the xml From 53c699e24be3ccb6b1b53b4b57679caaf3dd7b9a Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Thu, 21 Jun 2012 12:50:38 +0200 Subject: [PATCH 06/21] feature #1303: add license to downloader.sh and minor changes --- src/datastore_mad/remotes/downloader.sh | 48 ++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/datastore_mad/remotes/downloader.sh b/src/datastore_mad/remotes/downloader.sh index b190605a68..354ed721b2 100755 --- a/src/datastore_mad/remotes/downloader.sh +++ b/src/datastore_mad/remotes/downloader.sh @@ -1,12 +1,31 @@ #!/bin/bash +# -------------------------------------------------------------------------- # +# 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. # +#--------------------------------------------------------------------------- # + +# Execute a command (first parameter) and use the first kb of stdout +# to determine the file type function get_type { command=$1 - ( $command | head -1024 | file -b --mime-type - ) 2>/dev/null + ( $command | head -n 1024 | file -b --mime-type - ) 2>/dev/null } +# Gets the command needed to decompress an stream. function get_decompressor { type=$1 @@ -24,27 +43,35 @@ function get_decompressor esac } +# Function called to decompress a stream. The first parameter is the command +# used to decompress the stream. Second parameter is the output file or +# - for stdout. function decompress { command="$1" to="$2" - if [ "x$to" = "x-" ]; then + if [ "$to" = "-" ]; then $command else $command > "$to" fi } +# Function called to hash a stream. First parameter is the algorithm name. function hasher { algo=$1 if [ -n "$algo" ]; then openssl dgst -$algo > $HASH_FILE + else + # Needs something consuming stdin or the pipe will break + cat >/dev/null fi } +# Unarchives a tar or a zip a file to a directpry with the same name. function unarchive { TO="$1" @@ -53,8 +80,9 @@ function unarchive tmp="$TO" - if [ ${tmp:0:1} = "/" ]; then - $tmp="$PWD/$tmp" + # Add full path if it is relative + if [ ${tmp:0:1} != "/" ]; then + tmp="$PWD/$tmp" fi IN="$tmp.tmp" @@ -118,14 +146,17 @@ while true; do esac done -FROM=$1 -TO=$2 +FROM="$1" +TO="$2" +# File used by the hasher function to store the resulting hash export HASH_FILE="/tmp/downloader.hash.$$" case "$FROM" in -http://*) - command="curl -L $FROM" +http://*|https://*) + # -k so it does not check the certificate + # -L to follow redirects + command="curl -k -L $FROM" ;; *) command="cat $FROM" @@ -152,6 +183,7 @@ if [ -n "$HASH_TYPE" ]; then fi fi +# Unarchive only if the destination is filesystem if [ "$TO" != "-" ]; then unarchive "$TO" fi From 9210a783ca3c365d61f6197bd2ef68b7049e6294 Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Thu, 21 Jun 2012 13:47:49 +0200 Subject: [PATCH 07/21] bug: Show the right error message in CLI name_to_id actions --- src/cli/one_helper.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cli/one_helper.rb b/src/cli/one_helper.rb index fcc5f8908d..9774436dfe 100644 --- a/src/cli/one_helper.rb +++ b/src/cli/one_helper.rb @@ -206,9 +206,8 @@ EOT else rc = OneHelper.name_to_id(name, pool, poolname) - if rc.first==-1 - return -1, "OpenNebula #{poolname} #{name} " << - "not found, use the ID instead" + if rc.first == -1 + return rc[0], rc[1] end rc[1] From 165a23d91e24e623407841d500bc4f5f9b94c3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Thu, 21 Jun 2012 15:15:56 +0200 Subject: [PATCH 08/21] Feature #1288: Oneadmin can't be moved outside of the oneadmin group --- src/onedb/3.4.1_to_3.5.80.rb | 81 +++++++++++++++++++++++++++++++++++ src/rm/RequestManagerChown.cc | 15 +++++++ 2 files changed, 96 insertions(+) diff --git a/src/onedb/3.4.1_to_3.5.80.rb b/src/onedb/3.4.1_to_3.5.80.rb index 842af7589a..eab4951d38 100644 --- a/src/onedb/3.4.1_to_3.5.80.rb +++ b/src/onedb/3.4.1_to_3.5.80.rb @@ -28,6 +28,87 @@ module Migrator def up + oneadmin_row = nil + @db.fetch("SELECT * FROM user_pool WHERE oid = 0") do |row| + oneadmin_row = row + end + + if oneadmin_row[:gid] != 0 + + puts " > Oneadmin user will be moved to the oneadmin group" + + # Change user group + + doc = Document.new(oneadmin_row[:body]) + + doc.root.each_element("GID") { |e| + e.text = "0" + } + + doc.root.each_element("GNAME") { |e| + e.text = "oneadmin" + } + + @db[:user_pool].filter(:oid=>0).delete + + @db[:user_pool].insert( + :oid => oneadmin_row[:oid], + :name => oneadmin_row[:name], + :body => doc.root.to_s, + :uid => oneadmin_row[:oid], + :gid => 0, + :owner_u => oneadmin_row[:owner_u], + :group_u => oneadmin_row[:group_u], + :other_u => oneadmin_row[:other_u]) + + # Remove oneadmin's id from previous group + + group_row = nil + + @db.fetch("SELECT * FROM group_pool WHERE oid = #{oneadmin_row[:gid]}") do |row| + group_row = row + end + + doc = Document.new(group_row[:body]) + + doc.root.delete_element("USERS/ID[.=0]") + + @db[:group_pool].filter(:oid=>group_row[:oid]).delete + + @db[:group_pool].insert( + :oid => group_row[:oid], + :name => group_row[:name], + :body => doc.root.to_s, + :uid => group_row[:oid], + :gid => group_row[:gid], + :owner_u => group_row[:owner_u], + :group_u => group_row[:group_u], + :other_u => group_row[:other_u]) + + # Add oneadmin's id to oneadmin group + + @db.fetch("SELECT * FROM group_pool WHERE oid = 0") do |row| + group_row = row + end + + doc = Document.new(group_row[:body]) + + doc.root.get_elements("USERS")[0].add_element("ID").text = "0" + + @db[:group_pool].filter(:oid=>group_row[:oid]).delete + + @db[:group_pool].insert( + :oid => group_row[:oid], + :name => group_row[:name], + :body => doc.root.to_s, + :uid => group_row[:oid], + :gid => group_row[:gid], + :owner_u => group_row[:owner_u], + :group_u => group_row[:group_u], + :other_u => group_row[:other_u]) + end + + @db.run "ALTER TABLE datastore_pool RENAME TO old_datastore_pool;" @db.run "CREATE TABLE datastore_pool (oid INTEGER PRIMARY KEY, name VARCHAR(128), body TEXT, uid INTEGER, gid INTEGER, owner_u INTEGER, group_u INTEGER, other_u INTEGER, UNIQUE(name));" diff --git a/src/rm/RequestManagerChown.cc b/src/rm/RequestManagerChown.cc index 95c610ff71..0714d820c3 100644 --- a/src/rm/RequestManagerChown.cc +++ b/src/rm/RequestManagerChown.cc @@ -333,6 +333,21 @@ void UserChown::request_execute(xmlrpc_c::paramList const& paramList, return; } + if ( oid == UserPool::ONEADMIN_ID ) + { + ostringstream oss; + + oss << PoolObjectSQL::type_to_str(PoolObjectSQL::USER) + << " [" << UserPool::ONEADMIN_ID << "] " << UserPool::oneadmin_name + << " cannot be moved outside of the " + << PoolObjectSQL::type_to_str(PoolObjectSQL::GROUP) + << " [" << GroupPool::ONEADMIN_ID << "] " + << GroupPool::ONEADMIN_NAME; + + failure_response(INTERNAL, request_error(oss.str(), ""), att); + return; + } + if ( att.uid != 0 ) { AuthRequest ar(att.uid, att.gid); From 4eb509722fbd5f32cbf29d0867d0099bc924c87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Thu, 21 Jun 2012 15:56:19 +0200 Subject: [PATCH 09/21] Feature #1288: Add missing constant in onequota_helper.rb file --- src/cli/one_helper/onequota_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli/one_helper/onequota_helper.rb b/src/cli/one_helper/onequota_helper.rb index b7576f785d..20d0a37cc3 100644 --- a/src/cli/one_helper/onequota_helper.rb +++ b/src/cli/one_helper/onequota_helper.rb @@ -18,6 +18,8 @@ require 'cli_helper' class OneQuotaHelper + EDITOR_PATH='/usr/bin/vi' + #--------------------------------------------------------------------------- # Tables to format user quotas #--------------------------------------------------------------------------- From 7122f5d5b01f998522c0477289724ecc6d22e7fb Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Thu, 21 Jun 2012 16:45:52 +0200 Subject: [PATCH 10/21] Allow DATABLOCKs to be defined with PATHs --- src/image/ImageManagerActions.cc | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/image/ImageManagerActions.cc b/src/image/ImageManagerActions.cc index 6ceee32f68..aafc10afe3 100644 --- a/src/image/ImageManagerActions.cc +++ b/src/image/ImageManagerActions.cc @@ -641,15 +641,24 @@ int ImageManager::stat_image(Template* img_tmpl, break; case Image::DATABLOCK: - img_tmpl->get("SIZE", res); + img_tmpl->get("PATH", res); - if (res.empty()) + if (res.empty())//no PATH { - res = "SIZE attribute is mandatory for DATABLOCK."; - return -1; + img_tmpl->get("SIZE", res); + + if (res.empty()) + { + res = "SIZE or PATH attributes are mandatory for DATABLOCK."; + return -1; + } + + return 0; + } + else + { + img_data << "" << res << ""; } - - return 0; } add_request(&sr); From 24db7e752755ce606fd0b34fc7aa04c50c9247c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Thu, 21 Jun 2012 16:32:01 +0200 Subject: [PATCH 11/21] Feature #1288: Clean VM quota when it reaches 0 usage and limi --- include/QuotaVirtualMachine.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/include/QuotaVirtualMachine.h b/include/QuotaVirtualMachine.h index 9070baf0dd..986f848e3c 100644 --- a/include/QuotaVirtualMachine.h +++ b/include/QuotaVirtualMachine.h @@ -83,16 +83,10 @@ public: VectorAttribute **va, map::iterator& it) { - it = attributes.end(); + it = attributes.begin(); return get_quota(id, va); } - /** - * Overrides base to not delete anything - * @param it The quota iterator, ignored - */ - void del(map::iterator& it){} - protected: static const char * VM_METRICS[]; From 37a816d4a4493d5980a3b6ce3f14c2f730906734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Thu, 21 Jun 2012 16:46:19 +0200 Subject: [PATCH 12/21] Feature #1291: Fix Image state transition from READY to USED/USED_PERS --- src/image/ImageManagerActions.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/image/ImageManagerActions.cc b/src/image/ImageManagerActions.cc index aafc10afe3..51ed4635db 100644 --- a/src/image/ImageManagerActions.cc +++ b/src/image/ImageManagerActions.cc @@ -92,7 +92,16 @@ int ImageManager::acquire_image(Image *img, string& error) { case Image::READY: img->inc_running(); - img->set_state(Image::USED); + + if ( img->isPersistent() ) + { + img->set_state(Image::USED_PERS); + } + else + { + img->set_state(Image::USED); + } + ipool->update(img); break; From 99529a168f54ce9fe7343b52762828e008dd2ab3 Mon Sep 17 00:00:00 2001 From: Hector Sanjuan Date: Thu, 21 Jun 2012 15:45:14 +0200 Subject: [PATCH 13/21] Feature #1288: Add user and group quotas support to Sunstone (cherry picked from commit 5b66d7e3d0799b974a125385feb2d6ee7a984fe1) --- .../models/OpenNebulaJSON/GroupJSON.rb | 9 +- .../models/OpenNebulaJSON/UserJSON.rb | 7 + src/sunstone/public/css/application.css | 8 + src/sunstone/public/js/opennebula.js | 12 + src/sunstone/public/js/plugins/groups-tab.js | 111 +++++++++- src/sunstone/public/js/plugins/users-tab.js | 135 +++++++++++- src/sunstone/public/js/sunstone-util.js | 206 +++++++++++++++++- src/sunstone/public/js/sunstone.js | 7 + 8 files changed, 490 insertions(+), 5 deletions(-) diff --git a/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb b/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb index d502973d0c..35e01f1403 100644 --- a/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb +++ b/src/sunstone/models/OpenNebulaJSON/GroupJSON.rb @@ -45,7 +45,8 @@ module OpenNebulaJSON end rc = case action_hash['perform'] - when "chown" then self.chown(action_hash['params']) + when "chown" then self.chown(action_hash['params']) + when "set_quota" then self.set_quota(action_hash['params']) else error_msg = "#{action_hash['perform']} action not " << " available for this resource" @@ -56,5 +57,11 @@ module OpenNebulaJSON def chown(params=Hash.new) super(params['owner_id'].to_i) end + + def set_quota(params=Hash.new) + quota_json = params['quotas'] + quota_template = template_to_str(quota_json) + super(quota_template) + end end end diff --git a/src/sunstone/models/OpenNebulaJSON/UserJSON.rb b/src/sunstone/models/OpenNebulaJSON/UserJSON.rb index c51c0bcfb2..ebf4338802 100644 --- a/src/sunstone/models/OpenNebulaJSON/UserJSON.rb +++ b/src/sunstone/models/OpenNebulaJSON/UserJSON.rb @@ -42,6 +42,7 @@ module OpenNebulaJSON when "chgrp" then self.chgrp(action_hash['params']) when "chauth" then self.chauth(action_hash['params']) when "update" then self.update(action_hash['params']) + when "set_quota" then self.set_quota(action_hash['params']) when "addgroup" then self.addgroup(action_hash['params']) when "delgroup" then self.delgroup(action_hash['params']) else @@ -67,6 +68,12 @@ module OpenNebulaJSON super(params['template_raw']) end + def set_quota(params=Hash.new) + quota_json = params['quotas'] + quota_template = template_to_str(quota_json) + super(quota_template) + end + def addgroup(params=Hash.new) super(params['group_id'].to_i) end diff --git a/src/sunstone/public/css/application.css b/src/sunstone/public/css/application.css index bed2567dca..36feb13fbd 100644 --- a/src/sunstone/public/css/application.css +++ b/src/sunstone/public/css/application.css @@ -663,4 +663,12 @@ ul.action_list li a:hover{ font-family: serif; text-align:center; vertical-align:middle; +} + +.quota_edit_icon:hover, .quota_remove_icon:hover { + cursor: pointer; +} + +div.current_quotas ul{ + list-style: none; } \ No newline at end of file diff --git a/src/sunstone/public/js/opennebula.js b/src/sunstone/public/js/opennebula.js index fcc7fac15b..716a1ebdda 100644 --- a/src/sunstone/public/js/opennebula.js +++ b/src/sunstone/public/js/opennebula.js @@ -693,6 +693,13 @@ var OpenNebula = { }, "list": function(params){ OpenNebula.Action.list(params,OpenNebula.Group.resource); + }, + "set_quota" : function(params){ + var action_obj = { quotas : params.data.extra_param }; + OpenNebula.Action.simple_action(params,OpenNebula.Group.resource,"set_quota",action_obj); + }, + "show" : function(params){ + OpenNebula.Action.show(params,OpenNebula.Group.resource); } }, @@ -736,6 +743,11 @@ var OpenNebula = { "fetch_template" : function(params){ OpenNebula.Action.show(params,OpenNebula.User.resource,"template"); }, + "set_quota" : function(params){ + var action_obj = { quotas : params.data.extra_param }; + OpenNebula.Action.simple_action(params,OpenNebula.User.resource,"set_quota",action_obj); + }, + // "addgroup" : function(params){ // var action_obj = {"group_id": params.data.extra_param }; // OpenNebula.Action.simple_action(params,OpenNebula.User.resource, diff --git a/src/sunstone/public/js/plugins/groups-tab.js b/src/sunstone/public/js/plugins/groups-tab.js index 03edb0f522..df5c7fe6ff 100644 --- a/src/sunstone/public/js/plugins/groups-tab.js +++ b/src/sunstone/public/js/plugins/groups-tab.js @@ -17,6 +17,7 @@ var groups_select=""; var dataTable_groups; var $create_group_dialog; +var $group_quotas_dialog; var groups_tab_content = '\

'+tr("Groups")+'

\ @@ -59,6 +60,69 @@ var create_group_tmpl = \ '; +var group_quotas_tmpl = '
\ +
\ +
'+tr("Please add/edit/remove quotas and click on the apply changes button. Note that if several items are selected, changes will be applied to each of them")+'.
\ +
'+tr("Add quota")+':
\ +
\ + \ + '+tr("Virtual Machine")+'\ + '+tr("Datastore")+'\ + '+tr("Image")+'\ + '+tr("Network")+'\ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ + \ +
\ +
\ +
'+tr("Current quotas")+':
\ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
\ + \ +
\ +
\ +
'; + var group_actions = { "Group.create" : { @@ -119,6 +183,34 @@ var group_actions = { // error : onError, // notify:true // }, + + "Group.fetch_quotas" : { + type: "single", + call: OpenNebula.Group.show, + callback: function (request,response) { + var parsed = parseQuotas(response.GROUP); + $('ul#quotas_ul_vm',$group_quotas_dialog).html(parsed.VM) + $('ul#quotas_ul_datastore',$group_quotas_dialog).html(parsed.DATASTORE) + $('ul#quotas_ul_image',$group_quotas_dialog).html(parsed.IMAGE) + $('ul#quotas_ul_network',$group_quotas_dialog).html(parsed.NETWORK) + }, + error: onError + }, + + "Group.quotas_dialog" : { + type: "custom", + call: popUpGroupQuotasDialog + }, + + "Group.set_quota" : { + type: "multiple", + call: OpenNebula.Group.set_quota, + elements: groupElements, + callback: function() { + notifyMessage(tr("Quotas updated correctly")); + }, + error: onError + }, "Group.help" : { type: "custom", call: function() { @@ -145,7 +237,10 @@ var group_buttons = { // tip: "Select the new group owner:", // condition : True // }, - + "Group.quotas_dialog" : { + type : "action", + text : tr("Update quotas") + }, "Group.delete" : { type: "confirm", text: tr("Delete") @@ -276,6 +371,19 @@ function popUpCreateGroupDialog(){ return false; } +function setupGroupQuotasDialog(){ + dialogs_context.append('
'); + $group_quotas_dialog = $('#group_quotas_dialog',dialogs_context); + var dialog = $group_quotas_dialog; + dialog.html(group_quotas_tmpl); + + setupQuotasDialog(dialog); +} + +function popUpGroupQuotasDialog(){ + popUpQuotasDialog($group_quotas_dialog, 'Group', groupElements()) +} + //Prepares the autorefresh function setGroupAutorefresh(){ setInterval(function(){ @@ -312,6 +420,7 @@ $(document).ready(function(){ Sunstone.runAction("Group.list"); setupCreateGroupDialog(); + setupGroupQuotasDialog(); setGroupAutorefresh(); initCheckAllBoxes(dataTable_groups); diff --git a/src/sunstone/public/js/plugins/users-tab.js b/src/sunstone/public/js/plugins/users-tab.js index c3ed6343fd..543da2c588 100644 --- a/src/sunstone/public/js/plugins/users-tab.js +++ b/src/sunstone/public/js/plugins/users-tab.js @@ -18,6 +18,7 @@ var dataTable_users; var users_select=""; var $create_user_dialog; +var $user_quotas_dialog; var $update_pw_dialog; var users_tab_content = '\ @@ -100,6 +101,69 @@ var update_pw_tmpl = '
\ \
'; +var user_quotas_tmpl = '
\ +
\ +
'+tr("Please add/edit/remove quotas and click on the apply changes button. Note that if several items are selected, changes will be applied to each of them")+'.
\ +
'+tr("Add quota")+':
\ +
\ + \ + '+tr("Virtual Machine")+'\ + '+tr("Datastore")+'\ + '+tr("Image")+'\ + '+tr("Network")+'\ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ +
\ + \ +
\ + \ + \ +
\ + \ +
\ +
\ +
'+tr("Current quotas")+':
\ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
    \ +
\ +
\ +
\ + \ +
\ +
\ +
'; + var user_actions = { "User.create" : { @@ -254,6 +318,34 @@ var user_actions = { error: onError }, + "User.fetch_quotas" : { + type: "single", + call: OpenNebula.User.show, + callback: function (request,response) { + var parsed = parseQuotas(response.USER); + $('ul#quotas_ul_vm',$user_quotas_dialog).html(parsed.VM) + $('ul#quotas_ul_datastore',$user_quotas_dialog).html(parsed.DATASTORE) + $('ul#quotas_ul_image',$user_quotas_dialog).html(parsed.IMAGE) + $('ul#quotas_ul_network',$user_quotas_dialog).html(parsed.NETWORK) + }, + error: onError + }, + + "User.quotas_dialog" : { + type: "custom", + call: popUpUserQuotasDialog + }, + + "User.set_quota" : { + type: "multiple", + call: OpenNebula.User.set_quota, + elements: userElements, + callback: function() { + notifyMessage(tr("Quotas updated correctly")); + }, + error: onError + }, + "User.help" : { type: "custom", call: function() { @@ -283,6 +375,10 @@ var user_buttons = { type : "action", text : tr("Change password"), }, + "User.quotas_dialog" : { + type : "action", + text : tr("Update quotas") + }, "User.chgrp" : { type: "confirm_with_select", text: tr("Change group"), @@ -331,6 +427,10 @@ var user_info_panel = { title: tr("User information"), content:"" }, + "user_quotas_tab" : { + title: tr("User quotas"), + content:"" + }, }; var users_tab = { @@ -473,7 +573,25 @@ function updateUserInfo(request,user){ '' }; + var quotas_tab = { + title : tr("User quotas"), + content : '\ + \ + '+prettyPrintJSON(user_info.DATASTORE_QUOTA)+'\ +
\ + \ + '+prettyPrintJSON(user_info.VM_QUOTA)+'\ +
\ + \ + '+prettyPrintJSON(user_info.IMAGE_QUOTA)+'\ +
\ + \ + '+prettyPrintJSON(user_info.NETWORK_QUOTA)+'\ +
' + }; + Sunstone.updateInfoPanelTab("user_info_panel","user_info_tab",info_tab); + Sunstone.updateInfoPanelTab("user_info_panel","user_quotas_tab",quotas_tab); Sunstone.popUpInfoPanel("user_info_panel"); }; @@ -554,8 +672,22 @@ function setupUpdatePasswordDialog(){ }); }; +function setupUserQuotasDialog(){ + dialogs_context.append('
'); + $user_quotas_dialog = $('#user_quotas_dialog',dialogs_context); + var dialog = $user_quotas_dialog; + dialog.html(user_quotas_tmpl); + + setupQuotasDialog(dialog); +} + +function popUpUserQuotasDialog(){ + popUpQuotasDialog($user_quotas_dialog, 'User', userElements()) +} + function popUpCreateUserDialog(){ $create_user_dialog.dialog('open'); + } @@ -564,8 +696,6 @@ function popUpUpdatePasswordDialog(){ $update_pw_dialog.dialog('open'); } - - // Prepare the autorefresh of the list function setUserAutorefresh(){ setInterval(function(){ @@ -610,6 +740,7 @@ $(document).ready(function(){ setupCreateUserDialog(); setupUpdatePasswordDialog(); + setupUserQuotasDialog(); setUserAutorefresh(); initCheckAllBoxes(dataTable_users); diff --git a/src/sunstone/public/js/sunstone-util.js b/src/sunstone/public/js/sunstone-util.js index f887985a43..dbdb401bf7 100644 --- a/src/sunstone/public/js/sunstone-util.js +++ b/src/sunstone/public/js/sunstone-util.js @@ -953,4 +953,208 @@ function buildOctet(permTable){ other+=1; return ""+owner+group+other; -}; \ No newline at end of file +}; + +function setupQuotasDialog(dialog){ + + var height = Math.floor($(window).height()*0.8); //set height to a percentage of the window + + //Prepare jquery dialog + dialog.dialog({ + autoOpen: false, + modal:true, + width: 740, + height: height + }); + + $('button',dialog).button(); + $('#vm_quota,#datastore_quota,#image_quota,#network_quota',dialog).hide(); + + $('#quota_types input',dialog).click(function(){ + $('#vm_quota,#datastore_quota,#image_quota,#network_quota',dialog).hide(); + $('#'+$(this).val()+'_quota',dialog).show(); + $('#add_quota_button',dialog).show(); + }) + + $('#add_quota_button',dialog).hide(); + + $('#add_quota_button',dialog).click(function(){ + var sel = $('#quota_types input:checked',dialog).val(); + var fields = $('div#'+sel+'_quota input,div#'+sel+'_quota select',dialog); + var json = {}; + + for (var i = 0; i < fields.length; i++){ + var field = $(fields[i]); + var name = field.attr('name'); + var value = field.val(); + if (name == 'ID' && !value.length){ + notifyError(tr("Please select an element")); + return false; + }; + if (!value) value = 0; + json[name] = value; + }; + + json['TYPE'] = sel.toUpperCase(); + + var li = quotaListItem(json) + $('ul#quotas_ul_'+sel,dialog).append($(li).hide().fadeIn()); + return false; + }); + + $('form', dialog).submit(function(){ + var obj = {}; + $('ul li',this).each(function(){ + var json = JSON.parse($(this).attr('quota')); + var type = json['TYPE']; + delete json['TYPE']; + obj[type.toUpperCase()] = json; + }); + + var action = $('div.form_buttons button',this).val(); + var sel_elems = SunstoneCfg["actions"][action].elements(); + Sunstone.runAction(action,sel_elems,obj); + dialog.dialog('close'); + return false; + }); +} + +function popUpQuotasDialog(dialog, resource, sel_elems){ + var im_sel = makeSelectOptions(dataTable_images,1,4,[],[]); + var vn_sel = makeSelectOptions(dataTable_vNetworks,1,4,[],[]); + $('#datastore_quota select',dialog).html(datastores_sel()); + $('#image_quota select',dialog).html(im_sel); + $('#network_quota select',dialog).html(vn_sel); + + + //If only one user is selected we fecth the user's quotas, otherwise we do nothing. + if (sel_elems.length == 1){ + var id = sel_elems[0]; + Sunstone.runAction(resource + '.fetch_quotas',id); + } else { + $('ul',dialog).empty(); + }; + + dialog.dialog('open'); +} + +function setupQuotaIcons(){ + $('.quota_edit_icon').live('click',function(){ + var dialog = $(this).parents('form'); + var li = $(this).parents('li'); + var quota = JSON.parse(li.attr('quota')); + switch (quota.TYPE){ + case "VM": + $('div#vm_quota input[name="VMS"]',dialog).val(quota.VMS); + $('div#vm_quota input[name="MEMORY"]',dialog).val(quota.MEMORY); + $('div#vm_quota input[name="CPU"]',dialog).val(quota.CPU); + break; + case "DATASTORE": + $('div#datastore_quota select[name="ID"]',dialog).val(quota.ID); + $('div#datastore_quota input[name="SIZE"]',dialog).val(quota.SIZE); + $('div#datastore_quota input[name="IMAGES"]').val(quota.IMAGES); + break; + case "IMAGE": + $('div#image_quota select[name="ID"]',dialog).val(quota.ID); + $('div#image_quota input[name="RVMS"]',dialog).val(quota.RVMS); + break; + case "NETWORK": + $('div#network_quota select[name="ID"]',dialog).val(quota.ID); + $('div#network_quota input[name="LEASES"]',dialog).val(quota.LEASES); + break; + } + $('div#quota_types input[value="'+quota.TYPE.toLowerCase()+'"]',dialog).trigger('click'); + $(this).parents('li').fadeOut(function(){$(this).remove()}); + return false; + }); + + $('.quota_remove_icon').live('click',function(){ + $(this).parents('li').fadeOut(function(){$(this).remove()}); + return false; + }); +} + +function parseQuotas(elem){ + var quotas = []; + var results = { + VM : "", + DATASTORE : "", + IMAGE : "", + NETWORK : "" + } + //max 1 vm quota + if (!$.isEmptyObject(elem.VM_QUOTA)){ + elem.VM_QUOTA.VM.TYPE = 'VM' + quotas.push(elem.VM_QUOTA.VM) + } + + var ds_arr = [] + if ($.isArray(elem.DATASTORE_QUOTA.DATASTORE)){ + ds_arr = elem.DATASTORE_QUOTA.DATASTORE + } else if (!$.isEmptyObject(elem.DATASTORE_QUOTA)){ + ds_arr = [elem.DATASTORE_QUOTA.DATASTORE] + } + + for (var i = 0; i < ds_arr.length; i++){ + ds_arr[i].TYPE = 'DATASTORE'; + quotas.push(ds_arr[i]); + } + + var im_arr = [] + if ($.isArray(elem.IMAGE_QUOTA.IMAGE)){ + im_arr = elem.IMAGE_QUOTA.IMAGE + } else if (!$.isEmptyObject(elem.IMAGE_QUOTA)){ + im_arr = [elem.IMAGE_QUOTA.IMAGE] + } + + for (var i = 0; i < im_arr.length; i++){ + im_arr[i].TYPE = 'IMAGE'; + quotas.push(im_arr[i]); + } + + var vn_arr = [] + if ($.isArray(elem.NETWORK_QUOTA)){ + vn_arr = elem.NETWORK_QUOTA.NETWORK + } else if (!$.isEmptyObject(elem.NETWORK_QUOTA)){ + vn_arr = [elem.NETWORK_QUOTA.NETWORK] + } + + for (var i = 0; i < vn_arr.length; i++){ + vn_arr[i].TYPE = 'NETWORK'; + quotas.push(vn_arr[i]); + } + + for (var i = 0; i < quotas.length; i++){ + var li = quotaListItem(quotas[i]); + results[quotas[i].TYPE] += li; + } + return results; +} + +//Receives a quota json object. Returns a nice string out of it. +function quotaListItem(quota_json){ + var value = JSON.stringify(quota_json) + var str = '
  • ';
    +    switch(quota_json.TYPE){
    +    case "VM":
    +        str +=  'VMs: ' + quota_json.VMS + (quota_json.VMS_USED ? ' (' + quota_json.VMS_USED + '). ' : ". ") +
    +               'Memory: ' + quota_json.MEMORY + (quota_json.MEMORY_USED ? ' (' + quota_json.MEMORY_USED + '). ' : ". ") +
    +               'CPU: ' + quota_json.CPU +  (quota_json.CPU_USED ? ' (' + quota_json.CPU_USED + '). ' : ". ");
    +        break;
    +    case "DATASTORE":
    +        str +=  'ID: ' + getDatastoreName(quota_json.ID) + '. ' +
    +               'Size: ' + quota_json.SIZE +  (quota_json.SIZE_USED ? ' (' + quota_json.SIZE_USED + '). ' : ". ") +
    +               'Images: ' + quota_json.IMAGES +  (quota_json.IMAGES_USED ? ' (' + quota_json.IMAGES_USED + '). ' : ".");
    +        break;
    +    case "IMAGE":
    +        str +=  'ID: ' + getImageName(quota_json.ID) + '. ' +
    +               'RVMs: ' + quota_json.RVMS +  (quota_json.RVMS_USED ? ' (' + quota_json.RVMS_USED + '). ' : ". ");
    +        break;
    +    case "NETWORK":
    +        str +=  'ID: ' + getVNetName(quota_json.ID) + '. ' +
    +               'Leases: ' + quota_json.LEASES +  (quota_json.LEASES_USED ? ' (' + quota_json.LEASES_USED + '). ': ". ");
    +        break;
    +    }
    +    str += ' 
  • '; + return str; +} \ No newline at end of file diff --git a/src/sunstone/public/js/sunstone.js b/src/sunstone/public/js/sunstone.js index 1cc349f8de..ae10d40f8d 100644 --- a/src/sunstone/public/js/sunstone.js +++ b/src/sunstone/public/js/sunstone.js @@ -309,6 +309,11 @@ $(document).ready(function(){ //This dialog is shared to update templates setupTemplateUpdateDialog(); + //Setup quota icons + //Live listeners not working when being added in specific + //context of users/groups dialog. Adding them globally then. + setupQuotaIcons(); + //Listen for .action_buttons //An action buttons runs a predefined action. If it has type //"multiple" it runs that action on the elements of a datatable. @@ -373,6 +378,8 @@ $(document).ready(function(){ return false; }); + + //Start with the dashboard (supposing we have one). showTab('dashboard_tab'); From 6d18f1209e75105f8a0c2f8c869b4152f3d8fe92 Mon Sep 17 00:00:00 2001 From: Hector Sanjuan Date: Thu, 21 Jun 2012 16:22:18 +0200 Subject: [PATCH 14/21] Feature #1299: Added image clone support in Sunstone (cherry picked from commit 9c9d76f5c2c393f685d4d20d10b4a68c3d253e67) --- .../models/OpenNebulaJSON/ImageJSON.rb | 5 ++ src/sunstone/public/js/opennebula.js | 5 ++ src/sunstone/public/js/plugins/images-tab.js | 87 +++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/src/sunstone/models/OpenNebulaJSON/ImageJSON.rb b/src/sunstone/models/OpenNebulaJSON/ImageJSON.rb index 99a0bdf04b..6224ef063c 100644 --- a/src/sunstone/models/OpenNebulaJSON/ImageJSON.rb +++ b/src/sunstone/models/OpenNebulaJSON/ImageJSON.rb @@ -58,6 +58,7 @@ module OpenNebulaJSON when "chown" then self.chown(action_hash['params']) when "chmod" then self.chmod_octet(action_hash['params']) when "chtype" then self.chtype(action_hash['params']) + when "clone" then self.clone(action_hash['params']) else error_msg = "#{action_hash['perform']} action not " << " available for this resource" @@ -84,5 +85,9 @@ module OpenNebulaJSON def chtype(params=Hash.new) super(params['type']) end + + def clone(params=Hash.new) + super(params['name']) + end end end diff --git a/src/sunstone/public/js/opennebula.js b/src/sunstone/public/js/opennebula.js index 716a1ebdda..893772121b 100644 --- a/src/sunstone/public/js/opennebula.js +++ b/src/sunstone/public/js/opennebula.js @@ -816,6 +816,11 @@ var OpenNebula = { OpenNebula.Image.resource, "chtype", action_obj); + }, + "clone" : function(params) { + var name = params.data.extra_param ? params.data.extra_param : ""; + var action_obj = { "name" : name }; + OpenNebula.Action.simple_action(params,OpenNebula.Image.resource, "clone", action_obj); } }, diff --git a/src/sunstone/public/js/plugins/images-tab.js b/src/sunstone/public/js/plugins/images-tab.js index 0aea6334dd..dc55758419 100644 --- a/src/sunstone/public/js/plugins/images-tab.js +++ b/src/sunstone/public/js/plugins/images-tab.js @@ -436,6 +436,16 @@ var image_actions = { error: onError, notify: true }, + "Image.clone_dialog" : { + type: "custom", + call: popUpImageCloneDialog + }, + "Image.clone" : { + type: "single", + call: OpenNebula.Image.clone, + error: onError, + notify: true + }, "Image.help" : { type: "custom", call: function() { @@ -496,6 +506,10 @@ var image_buttons = { } } }, + "Image.clone_dialog" : { + type: "action", + text: tr("Clone"), + }, "Image.delete" : { type: "confirm", text: tr("Delete") @@ -1136,6 +1150,78 @@ function setupImageActionCheckboxes(){ } +function setupImageCloneDialog(){ + //Append to DOM + dialogs_context.append('
    '); + var dialog = $('#image_clone_dialog',dialogs_context); + + //Put HTML in place + + var html = '
    \ +
    '+tr("Choose a new name for the image")+':
    \ +
    '+tr("Several image are selected, please choose prefix to name the new copies")+':
    \ +
    \ +\ +\ +\ +
    \ + \ +
    \ +'; + + dialog.html(html); + + //Convert into jQuery + dialog.dialog({ + autoOpen:false, + width:375, + modal:true, + resizable:false, + }); + + $('button',dialog).button(); + + $('form',dialog).submit(function(){ + var name = $('input', this).val(); + var sel_elems = imageElements(); + if (!name || !sel_elems.length) + notifyError('A name or prefix is needed!'); + if (sel_elems.length > 1){ + for (var i=0; i< sel_elems.length; i++) + Sunstone.runAction('Image.clone', + sel_elems[i], + name+getImageName(sel_elems[i])); + } else { + Sunstone.runAction('Image.clone',sel_elems[0],name) + }; + dialog.dialog('close'); + setTimeout(function(){ + Sunstone.runAction('Image.refresh'); + }, 1500); + return false; + }); +} + +function popUpImageCloneDialog(){ + var dialog = $('#image_clone_dialog'); + var sel_elems = imageElements(); + //show different text depending on how many elements are selected + if (sel_elems.length > 1){ + $('.clone_one',dialog).hide(); + $('.clone_several',dialog).show(); + $('input',dialog).val('Copy of '); + } + else { + $('.clone_one',dialog).show(); + $('.clone_several',dialog).hide(); + $('input',dialog).val('Copy of '+getImageName(sel_elems[0])); + }; + + $(dialog).dialog('open'); +} + //The DOM is ready at this point $(document).ready(function(){ @@ -1172,6 +1258,7 @@ $(document).ready(function(){ setupImageTemplateUpdateDialog(); setupTips($create_image_dialog); setupImageActionCheckboxes(); + setupImageCloneDialog(); setImageAutorefresh(); initCheckAllBoxes(dataTable_images); From 2a21c25f45e21f53cadef1acb5be0225ff641591 Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Thu, 21 Jun 2012 16:52:04 +0200 Subject: [PATCH 15/21] Fix typo --- src/image/ImageManagerActions.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image/ImageManagerActions.cc b/src/image/ImageManagerActions.cc index aafc10afe3..4bffeab789 100644 --- a/src/image/ImageManagerActions.cc +++ b/src/image/ImageManagerActions.cc @@ -649,7 +649,7 @@ int ImageManager::stat_image(Template* img_tmpl, if (res.empty()) { - res = "SIZE or PATH attributes are mandatory for DATABLOCK."; + res = "Either SIZE or PATH are mandatory for DATABLOCK."; return -1; } From 06581af467305c69ac2399dff66363435e010934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Thu, 21 Jun 2012 18:12:50 +0200 Subject: [PATCH 16/21] Feature #1288: Re-do commit:e2f59f0c . Previous code didn't check that all quota limits are 0, just the ones requested. This version also cleans quotas that have 0 limit and usage after a 'oneuser quota' operation --- include/Quota.h | 7 +++++++ src/um/Quota.cc | 46 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/include/Quota.h b/include/Quota.h index 6cb8137e0a..c5a2ec5f40 100644 --- a/include/Quota.h +++ b/include/Quota.h @@ -159,6 +159,13 @@ protected: attributes.erase(it); } + /** + * Checks if a quota has 0 limit and usage, and deletes it + * + * @param qid id of the quota + */ + void cleanup_quota(const string& qid); + private: /** * Creates an empty quota based on the given attribute. The attribute va diff --git a/src/um/Quota.cc b/src/um/Quota.cc index 7db910fed5..6b54658af9 100644 --- a/src/um/Quota.cc +++ b/src/um/Quota.cc @@ -122,6 +122,8 @@ int Quota::set(vector * new_quotas, string& error) goto error_limits; } } + + cleanup_quota(id); } return 0; @@ -271,12 +273,8 @@ void Quota::del_quota(const string& qid, map& usage_req) { VectorAttribute * q; map::iterator it; - map::iterator q_it; - int limit, limit_tmp; - int usage, usage_tmp; - - if ( get_quota(qid, &q, q_it) == -1) + if ( get_quota(qid, &q) == -1) { return; } @@ -286,9 +284,6 @@ void Quota::del_quota(const string& qid, map& usage_req) return; } - limit = 0; - usage = 0; - for (int i=0; i < num_metrics; i++) { string metrics_used = metrics[i]; @@ -303,6 +298,41 @@ void Quota::del_quota(const string& qid, map& usage_req) } add_to_quota(q, metrics_used, -it->second); + } + + cleanup_quota(qid); +} + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + +void Quota::cleanup_quota(const string& qid) +{ + VectorAttribute * q; + map::iterator it; + map::iterator q_it; + + int limit, limit_tmp; + int usage, usage_tmp; + + if ( get_quota(qid, &q, q_it) == -1) + { + return; + } + + if ( q == 0 ) + { + return; + } + + limit = 0; + usage = 0; + + for (int i=0; i < num_metrics; i++) + { + string metrics_used = metrics[i]; + + metrics_used += "_USED"; q->vector_value(metrics[i], limit_tmp); q->vector_value(metrics_used.c_str(), usage_tmp); From 73baa84fbd48403ac9735190feedb05902be32db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn?= Date: Thu, 21 Jun 2012 18:30:13 +0200 Subject: [PATCH 17/21] Feature #1288: Code cleanup --- include/Quota.h | 12 ------------ src/um/Quota.cc | 4 +++- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/include/Quota.h b/include/Quota.h index c5a2ec5f40..4363cf3e44 100644 --- a/include/Quota.h +++ b/include/Quota.h @@ -147,18 +147,6 @@ protected: VectorAttribute **va, map::iterator& it); - /** - * Deletes a quota from the index. The quota pointer is freed. - * @param it The quota iterator, as returned by Quota::get_quota - */ - virtual void del(map::iterator& it) - { - Attribute * attr = it->second; - delete attr; - - attributes.erase(it); - } - /** * Checks if a quota has 0 limit and usage, and deletes it * diff --git a/src/um/Quota.cc b/src/um/Quota.cc index 6b54658af9..03fc1435b2 100644 --- a/src/um/Quota.cc +++ b/src/um/Quota.cc @@ -343,7 +343,9 @@ void Quota::cleanup_quota(const string& qid) if ( limit == 0 && usage == 0 ) { - del(q_it); + delete static_cast(q_it->second); + + attributes.erase(q_it); } } From 71ab59f84fee7fbec06a52af3efdd3afcab483e0 Mon Sep 17 00:00:00 2001 From: Jaime Melis Date: Thu, 21 Jun 2012 18:34:11 +0200 Subject: [PATCH 18/21] feature #1303: Make downloader.sh compatible with openssl 0.9 and 1.0 --- src/datastore_mad/remotes/downloader.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datastore_mad/remotes/downloader.sh b/src/datastore_mad/remotes/downloader.sh index 354ed721b2..f0b86672d6 100755 --- a/src/datastore_mad/remotes/downloader.sh +++ b/src/datastore_mad/remotes/downloader.sh @@ -64,7 +64,7 @@ function hasher algo=$1 if [ -n "$algo" ]; then - openssl dgst -$algo > $HASH_FILE + openssl dgst -$algo | awk '{print $NF}' > $HASH_FILE else # Needs something consuming stdin or the pipe will break cat >/dev/null @@ -121,7 +121,7 @@ if [ $? != 0 ] ; then echo "Arguments error" exit -1 fi - + eval set -- "$TEMP" while true; do From 6a6485416632feb971abed25d52b6172eb17e6c9 Mon Sep 17 00:00:00 2001 From: Jaime Melis Date: Thu, 21 Jun 2012 18:34:44 +0200 Subject: [PATCH 19/21] feature #1303: downloader.sh doesn't send garbage to the oned.log file --- src/datastore_mad/remotes/downloader.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/datastore_mad/remotes/downloader.sh b/src/datastore_mad/remotes/downloader.sh index f0b86672d6..3b095c01b6 100755 --- a/src/datastore_mad/remotes/downloader.sh +++ b/src/datastore_mad/remotes/downloader.sh @@ -154,9 +154,10 @@ export HASH_FILE="/tmp/downloader.hash.$$" case "$FROM" in http://*|https://*) - # -k so it does not check the certificate - # -L to follow redirects - command="curl -k -L $FROM" + # -k so it does not check the certificate + # -L to follow redirects + # -sS to hide output except on failure + command="curl -sS -k -L $FROM" ;; *) command="cat $FROM" From ee4201eee00b6baed4fe9f7ebb4be82288f382fb Mon Sep 17 00:00:00 2001 From: Hector Sanjuan Date: Thu, 21 Jun 2012 16:45:32 +0200 Subject: [PATCH 20/21] Feature #1303: Small improvements to marketplace plugin Limit context of some selectors, clearup custom variables before adding new ones... (cherry picked from commit 6c66b1d9fda668b7f63580c2be7122e53c30e1f6) --- .../public/js/plugins/marketplace-tab.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/sunstone/public/js/plugins/marketplace-tab.js b/src/sunstone/public/js/plugins/marketplace-tab.js index f039cac26d..689d2a0b4b 100644 --- a/src/sunstone/public/js/plugins/marketplace-tab.js +++ b/src/sunstone/public/js/plugins/marketplace-tab.js @@ -91,16 +91,18 @@ var market_actions = { document.getElementById("img_name").value = response['name']; document.getElementById("img_path").value = response['links']['download']['href']; + $("#custom_var_image_box",$create_image_dialog).empty(); + var md5 = response['files'][0]['checksum']['md5'] if ( md5 ) { option = ''; - $("#custom_var_image_box").append(option); + $("#custom_var_image_box",$create_image_dialog).append(option); } var sha1 = response['files'][0]['checksum']['sha1'] if ( sha1 ) { option = ''; - $("#custom_var_image_box").append(option); + $("#custom_var_image_box",$create_image_dialog).append(option); } popUpCreateImageDialog(); @@ -237,6 +239,13 @@ function infoListenerMarket(dataTable){ var id = aData["_id"]["$oid"]; if (!id) return true; + var count = $('tbody .check_item:checked', dataTable).length; + + if (e.ctrlKey || count >= 1){ + $('.check_item',this).trigger('click'); + return false; + } + popDialogLoading(); $.ajax({ @@ -263,11 +272,9 @@ function infoListenerMarket(dataTable){ function onlyOneCheckboxListener(dataTable) { $('tbody input.check_item', dataTable).live("change", function(){ - var checked = $('input.check_item:checked', $('tr', dataTable)); - var self = this; - checked.each(function(){ - if(this!=self) this.checked = '' - }) + var checked = $(this).is(':checked'); + $('input.check_item:checked', dataTable).removeAttr('checked'); + $(this).attr('checked', checked); }); } From 98c8cc85f414586a9c3bb3895884c5b25fc7a21c Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Fri, 22 Jun 2012 01:33:48 +0200 Subject: [PATCH 21/21] feature #1291: Update QuotaVirtualMAchine interface --- include/QuotaVirtualMachine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/QuotaVirtualMachine.h b/include/QuotaVirtualMachine.h index 986f848e3c..d600fde363 100644 --- a/include/QuotaVirtualMachine.h +++ b/include/QuotaVirtualMachine.h @@ -60,6 +60,7 @@ public: */ void del(Template* tmpl); +protected: /** * Gets a quota, overrides base to not to use ID. * @param id of the quota, ignored @@ -87,7 +88,6 @@ public: return get_quota(id, va); } -protected: static const char * VM_METRICS[]; static const int NUM_VM_METRICS;