diff --git a/src/cloud/common/CloudAuth.rb b/src/cloud/common/CloudAuth.rb index 23331f79f8..b6203a11ea 100644 --- a/src/cloud/common/CloudAuth.rb +++ b/src/cloud/common/CloudAuth.rb @@ -39,11 +39,12 @@ class CloudAuth # Tokens will be generated if time > EXPIRE_TIME - EXPIRE_MARGIN EXPIRE_MARGIN = 300 - attr_reader :client, :token + attr_reader :client, :token, :logger # conf a hash with the configuration attributes as symbols - def initialize(conf) + def initialize(conf, logger=nil) @conf = conf + @logger = logger @token_expiration_time = Time.now.to_i + EXPIRE_DELTA diff --git a/src/cloud/common/CloudServer.rb b/src/cloud/common/CloudServer.rb index ec3b86134d..9e3883ad1a 100755 --- a/src/cloud/common/CloudServer.rb +++ b/src/cloud/common/CloudServer.rb @@ -37,7 +37,7 @@ class CloudServer ########################################################################## # Public attributes ########################################################################## - attr_reader :config + attr_reader :config, :logger # Initializes the Cloud server based on a config file # config_file:: _String_ for the server. MUST include the following @@ -45,9 +45,10 @@ class CloudServer # AUTH # VM_TYPE # XMLRPC - def initialize(config) + def initialize(config, logger=nil) # --- Load the Cloud Server configuration file --- @config = config + @logger = logger end # # Prints the configuration of the server @@ -82,3 +83,54 @@ class CloudServer return false end end + +module CloudLogger + require 'logger' + + DEBUG_LEVEL = [ + Logger::ERROR, # 0 + Logger::WARN, # 1 + Logger::INFO, # 2 + Logger::DEBUG # 3 + ] + + # Mon Feb 27 06:02:30 2012 [Clo] [E]: Error message example + MSG_FORMAT = %{%s [%s]: %s\n} + + # Mon Feb 27 06:02:30 2012 + DATE_FORMAT = "%a %b %d %H:%M:%S %Y" + + # Patch logger class to be compatible with Rack::CommonLogger + class ::Logger + def write(msg) + info msg.chop + end + end + + def enable_logging(path=nil, debug_level=3) + path ||= $stdout + logger = ::Logger.new(path) + logger.level = DEBUG_LEVEL[debug_level] + logger.formatter = proc do |severity, datetime, progname, msg| + MSG_FORMAT % [ + datetime.strftime(DATE_FORMAT), + severity[0..0], + msg ] + end + + # Add the logger instance to the Sinatra settings + set :logger, logger + + # The logging will be configured in Rack, not in Sinatra + disable :logging + + # Use the logger instance in the Rack methods + use Rack::CommonLogger, logger + + helpers do + def logger + settings.logger + end + end + end +end diff --git a/src/cloud/ec2/etc/econe.conf b/src/cloud/ec2/etc/econe.conf index 679d5fb6e9..f9ba61e670 100644 --- a/src/cloud/ec2/etc/econe.conf +++ b/src/cloud/ec2/etc/econe.conf @@ -34,6 +34,9 @@ # x509, for x509 certificate encryption of tokens :core_auth: cipher +# 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG +:debug_level: 3 + # VM types allowed and its template file (inside templates directory) :instance_types: :m1.small: diff --git a/src/cloud/ec2/lib/EC2QueryServer.rb b/src/cloud/ec2/lib/EC2QueryServer.rb index 4e2abd1072..66c14ad2a0 100644 --- a/src/cloud/ec2/lib/EC2QueryServer.rb +++ b/src/cloud/ec2/lib/EC2QueryServer.rb @@ -61,8 +61,8 @@ class EC2QueryServer < CloudServer ########################################################################### - def initialize(client, config) - super(config) + def initialize(client, config, logger) + super(config, logger) @client = client end diff --git a/src/cloud/ec2/lib/econe-server.rb b/src/cloud/ec2/lib/econe-server.rb index 0a8ccc1d98..0d20b59cc4 100644 --- a/src/cloud/ec2/lib/econe-server.rb +++ b/src/cloud/ec2/lib/econe-server.rb @@ -20,19 +20,23 @@ ONE_LOCATION=ENV["ONE_LOCATION"] if !ONE_LOCATION - RUBY_LIB_LOCATION = "/usr/lib/one/ruby" + LOG_LOCATION = "/var/log/one" VAR_LOCATION = "/var/lib/one" - CONFIGURATION_FILE = "/etc/one/econe.conf" - TEMPLATE_LOCATION = "/etc/one/ec2query_templates" + ETC_LOCATION = "/etc/one" + RUBY_LIB_LOCATION = "/usr/lib/one/ruby" else - RUBY_LIB_LOCATION = ONE_LOCATION+"/lib/ruby" - VAR_LOCATION = ONE_LOCATION+"/var" - CONFIGURATION_FILE = ONE_LOCATION+"/etc/econe.conf" - TEMPLATE_LOCATION = ONE_LOCATION+"/etc/ec2query_templates" + VAR_LOCATION = ONE_LOCATION + "/var" + LOG_LOCATION = ONE_LOCATION + "/var" + ETC_LOCATION = ONE_LOCATION + "/etc" + RUBY_LIB_LOCATION = ONE_LOCATION+"/lib/ruby" end -VIEWS_LOCATION = RUBY_LIB_LOCATION + "/cloud/econe/views" -EC2_AUTH = VAR_LOCATION + "/.one/ec2_auth" +EC2_AUTH = VAR_LOCATION + "/.one/ec2_auth" +EC2_LOG = LOG_LOCATION + "/econe-server.log" +CONFIGURATION_FILE = ETC_LOCATION + "/occi-server.conf" + +TEMPLATE_LOCATION = ETC_LOCATION + "/occi_templates" +VIEWS_LOCATION = RUBY_LIB_LOCATION + "/cloud/econe/views" $: << RUBY_LIB_LOCATION $: << RUBY_LIB_LOCATION+"/cloud" @@ -57,12 +61,13 @@ include OpenNebula begin conf = YAML.load_file(CONFIGURATION_FILE) rescue Exception => e - puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" + STDERR.puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" exit 1 end conf[:template_location] = TEMPLATE_LOCATION conf[:views] = VIEWS_LOCATION +conf[:debug_level] ||= 3 CloudServer.print_configuration(conf) @@ -70,21 +75,28 @@ CloudServer.print_configuration(conf) # Sinatra Configuration ############################################################################## set :config, conf -set :bind, settings.config[:server] -set :port, settings.config[:port] + +include CloudLogger +enable_logging EC2_LOG, settings.config[:debug_level].to_i if CloudServer.is_port_open?(settings.config[:server], settings.config[:port]) - puts "Port busy, please shutdown the service or move econe server port." - exit 1 + settings.logger.error { + "Port #{settings.config[:port]} busy, please shutdown " << + "the service or move occi server port." + } + exit -1 end +set :bind, settings.config[:server] +set :port, settings.config[:port] + begin ENV["ONE_CIPHER_AUTH"] = EC2_AUTH - cloud_auth = CloudAuth.new(settings.config) + cloud_auth = CloudAuth.new(settings.config, settings.logger) rescue => e - puts "Error initializing authentication system" - puts e.message + settings.logger.error {"Error initializing authentication system"} + settings.logger.error {e.message} exit -1 end @@ -116,6 +128,7 @@ before do params['econe_path'] = settings.econe_path username = settings.cloud_auth.auth(request.env, params) rescue Exception => e + logger.error {e.message} error 500, error_xml("AuthFailure", 0) end @@ -123,7 +136,7 @@ before do error 401, error_xml("AuthFailure", 0) else client = settings.cloud_auth.client(username) - @econe_server = EC2QueryServer.new(client, settings.config) + @econe_server = EC2QueryServer.new(client, settings.config, settings.logger) end end @@ -179,6 +192,7 @@ def do_http_request(params) end if OpenNebula::is_error?(result) + logger.error(result.message) error rc, error_xml(result.message, 0) end diff --git a/src/cloud/occi/etc/occi-server.conf b/src/cloud/occi/etc/occi-server.conf index 7d42afbf3d..5ab9849e09 100644 --- a/src/cloud/occi/etc/occi-server.conf +++ b/src/cloud/occi/etc/occi-server.conf @@ -34,8 +34,8 @@ # x509, for x509 certificate encryption of tokens :core_auth: cipher -# Life-time in seconds for token renewal (that used to handle OpenNebula auths) -:token_expiration_delta: 1800 +# 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG +:debug_level: 3 # VM types allowed and its template file (inside templates directory) :instance_types: diff --git a/src/cloud/occi/lib/OCCIServer.rb b/src/cloud/occi/lib/OCCIServer.rb index 193132c30b..a3102da56c 100755 --- a/src/cloud/occi/lib/OCCIServer.rb +++ b/src/cloud/occi/lib/OCCIServer.rb @@ -52,8 +52,8 @@ class OCCIServer < CloudServer # Server initializer # config_file:: _String_ path of the config file # template:: _String_ path to the location of the templates - def initialize(client, config) - super(config) + def initialize(client, config, logger) + super(config, logger) if config[:ssl_server] @base_url=config[:ssl_server] @@ -81,10 +81,10 @@ class OCCIServer < CloudServer ############################################################################ def get_collections(request) - xml_resp = "\n" + xml_resp = "" - COLLECTIONS.each { |c| - xml_resp << "\t<#{c.upcase}_COLLECTION href=\"#{@base_url}/#{c}\">\n" + COLLECTIONS.sort.each { |c| + xml_resp << "<#{c.upcase}_COLLECTION href=\"#{@base_url}/#{c}\"/>" } xml_resp << "" @@ -97,7 +97,7 @@ class OCCIServer < CloudServer <%= name.to_s %> <%= name.to_s %> - <% opts.each { |elem, value| + <% opts.sort{|k1,k2| k1[0].to_s<=>k2[0].to_s}.each { |elem, value| next if elem==:template str = elem.to_s.upcase %> <<%= str %>><%= value %>> @@ -106,9 +106,11 @@ class OCCIServer < CloudServer } def get_instance_types(request) - xml_resp = "\n" + xml_resp = "" - @config[:instance_types].each { |name, opts| + @config[:instance_types].sort { |k1,k2| + k1[0].to_s<=>k2[0].to_s + }.each { |name, opts| if request.params['verbose'] begin occi_it = ERB.new(INSTANCE_TYPE) @@ -120,7 +122,7 @@ class OCCIServer < CloudServer xml_resp << occi_it.gsub(/\n\s*/,'') else - xml_resp << "\t\n" + xml_resp << "" end } @@ -163,7 +165,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(vmpool, :status=>200, :verbose=>request.params['verbose']) + return to_occi_xml(vmpool, :code=>200, :verbose=>request.params['verbose']) end @@ -183,7 +185,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(network_pool, :status=>200, :verbose=>request.params['verbose']) + return to_occi_xml(network_pool, :code=>200, :verbose=>request.params['verbose']) end # Gets the pool representation of STORAGES @@ -202,7 +204,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(image_pool, :status=>200, :verbose=>request.params['verbose']) + return to_occi_xml(image_pool, :code=>200, :verbose=>request.params['verbose']) end # Gets the pool representation of USERs @@ -219,7 +221,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(user_pool, :status=>200, :verbose=>request.params['verbose']) + return to_occi_xml(user_pool, :code=>200, :verbose=>request.params['verbose']) end ############################################################################ @@ -255,7 +257,7 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- vm.info - return to_occi_xml(vm, :status=>201) + return to_occi_xml(vm, :code=>201) end # Get the representation of a COMPUTE resource @@ -274,7 +276,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(vm, :status=>200) + return to_occi_xml(vm, :code=>200) end # Deletes a COMPUTE resource @@ -317,7 +319,7 @@ class OCCIServer < CloudServer return result, code else vm.info - return to_occi_xml(vm, :status=>code) + return to_occi_xml(vm, :code=>code) end end @@ -347,7 +349,7 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- network.info - return to_occi_xml(network, :status=>201) + return to_occi_xml(network, :code=>201) end # Retrieves a NETWORK resource @@ -365,7 +367,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(network, :status=>200) + return to_occi_xml(network, :code=>200) end # Deletes a NETWORK resource @@ -411,7 +413,7 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- vnet.info - return to_occi_xml(vnet, :status=>202) + return to_occi_xml(vnet, :code=>202) end ############################################################################ @@ -457,7 +459,7 @@ class OCCIServer < CloudServer end # --- Prepare XML Response --- - return to_occi_xml(image, :status=>201) + return to_occi_xml(image, :code=>201) end # Get a STORAGE resource @@ -476,7 +478,7 @@ class OCCIServer < CloudServer end # --- Prepare XML Response --- - return to_occi_xml(image, :status=>200) + return to_occi_xml(image, :code=>200) end # Deletes a STORAGE resource (Not yet implemented) @@ -530,7 +532,7 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- image.info - return to_occi_xml(image, :status=>202) + return to_occi_xml(image, :code=>202) end # Get the representation of a USER @@ -549,7 +551,7 @@ class OCCIServer < CloudServer return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - return to_occi_xml(user, :status=>200) + return to_occi_xml(user, :code=>200) end ############################################################################ @@ -566,7 +568,7 @@ class OCCIServer < CloudServer return [404, error] end - vnc_proxy = OpenNebulaVNC.new(config,{:json_errors => false}) + vnc_proxy = OpenNebulaVNC.new(config, logger, {:json_errors => false}) return vnc_proxy.start(vm) end @@ -574,7 +576,8 @@ class OCCIServer < CloudServer begin OpenNebulaVNC.stop(pipe) rescue Exception => e - return [500, e.message] + logger.error {e.message} + return [500, "Error stopping VNC. Please check server logs."] end return [200,nil] diff --git a/src/cloud/occi/lib/occi-server.rb b/src/cloud/occi/lib/occi-server.rb index 10b814e5f2..851d58dc5c 100755 --- a/src/cloud/occi/lib/occi-server.rb +++ b/src/cloud/occi/lib/occi-server.rb @@ -25,18 +25,22 @@ ONE_LOCATION=ENV["ONE_LOCATION"] if !ONE_LOCATION - RUBY_LIB_LOCATION="/usr/lib/one/ruby" + LOG_LOCATION = "/var/log/one" VAR_LOCATION = "/var/lib/one" - TEMPLATE_LOCATION="/etc/one/occi_templates" - CONFIGURATION_FILE = "/etc/one/occi-server.conf" + ETC_LOCATION = "/etc/one" + RUBY_LIB_LOCATION = "/usr/lib/one/ruby" else - RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" - VAR_LOCATION = ONE_LOCATION+"/var" - TEMPLATE_LOCATION=ONE_LOCATION+"/etc/occi_templates" - CONFIGURATION_FILE = ONE_LOCATION+"/etc/occi-server.conf" + VAR_LOCATION = ONE_LOCATION + "/var" + LOG_LOCATION = ONE_LOCATION + "/var" + ETC_LOCATION = ONE_LOCATION + "/etc" + RUBY_LIB_LOCATION = ONE_LOCATION+"/lib/ruby" end -OCCI_AUTH = VAR_LOCATION + "/.one/occi_auth" +OCCI_AUTH = VAR_LOCATION + "/.one/occi_auth" +OCCI_LOG = LOG_LOCATION + "/occi-server.log" +CONFIGURATION_FILE = ETC_LOCATION + "/occi-server.conf" + +TEMPLATE_LOCATION = ETC_LOCATION + "/occi_templates" $: << RUBY_LIB_LOCATION $: << RUBY_LIB_LOCATION+"/cloud/occi" @@ -59,42 +63,55 @@ require 'CloudAuth' include OpenNebula ############################################################################## -# Parse Configuration file +# Configuration ############################################################################## +# Set Configuration settings begin conf = YAML.load_file(CONFIGURATION_FILE) rescue Exception => e - puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" + STDERR.puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" exit 1 end conf[:template_location] = TEMPLATE_LOCATION +conf[:debug_level] ||= 3 CloudServer.print_configuration(conf) -############################################################################## -# Sinatra Configuration -############################################################################## +set :config, conf + + +# Enable Logger +include CloudLogger +enable_logging OCCI_LOG, settings.config[:debug_level].to_i + + +# Set Sinatra configuration use Rack::Session::Pool, :key => 'occi' + set :public, Proc.new { File.join(root, "ui/public") } set :views, settings.root + '/ui/views' -set :config, conf if CloudServer.is_port_open?(settings.config[:server], settings.config[:port]) - puts "Port busy, please shutdown the service or move occi server port." - exit + settings.logger.error { + "Port #{settings.config[:port]} busy, please shutdown " << + "the service or move occi server port." + } + exit -1 end set :bind, settings.config[:server] set :port, settings.config[:port] + +# Create CloudAuth begin ENV["ONE_CIPHER_AUTH"] = OCCI_AUTH - cloud_auth = CloudAuth.new(settings.config) + cloud_auth = CloudAuth.new(settings.config, settings.logger) rescue => e - puts "Error initializing authentication system" - puts e.message + settings.logger.error {"Error initializing authentication system"} + settings.logger.error {e.message} exit -1 end @@ -110,17 +127,21 @@ before do begin username = settings.cloud_auth.auth(request.env, params) rescue Exception => e - error 500, e.message + logger.error {e.message} + error 500, "" end else username = session[:user] end if username.nil? #unable to authenticate + logger.error {"User not authorized"} error 401, "" else client = settings.cloud_auth.client(username) - @occi_server = OCCIServer.new(client, settings.config) + @occi_server = OCCIServer.new(client, + settings.config, + settings.logger) end end end @@ -147,20 +168,24 @@ helpers do begin username = settings.cloud_auth.auth(request.env, params) rescue Exception => e - error 500, e.message + logger.error {e.message} + error 500, "" end if username.nil? + logger.error {"User not authorized"} error 401, "" else client = settings.cloud_auth.client(username) - @occi_server = OCCIServer.new(client, settings.config) + @occi_server = OCCIServer.new(client, + settings.config, + settings.logger) user_id = OpenNebula::User::SELF user = OpenNebula::User.new_with_id(user_id, client) rc = user.info if OpenNebula.is_error?(rc) - # Add a log message + logger.error {rc.message} return [500, ""] end @@ -190,6 +215,7 @@ helpers do def treat_response(result,rc) if OpenNebula::is_error?(result) + logger.error {result.message} halt rc, result.message end diff --git a/src/cloud/occi/lib/ui/public/css/login.css b/src/cloud/occi/lib/ui/public/css/login.css index 2fdd890a84..b58bacd20f 100644 --- a/src/cloud/occi/lib/ui/public/css/login.css +++ b/src/cloud/occi/lib/ui/public/css/login.css @@ -132,12 +132,19 @@ div#login input#login_btn:hover { } .error_message { + width: 400px; + margin-left: auto; + margin-right: auto; display: none; position: relative; top: 80px; - font-family: Arial, Helvetica, sans-serif; - color:red; - font-size:1.6em; + font-size:1.0em; +} + +#login_spinner { + left: 44px; + position: relative; + top: 2px; } #label_remember { diff --git a/src/cloud/occi/lib/ui/public/js/login.js b/src/cloud/occi/lib/ui/public/js/login.js index 721200f945..363fb781fd 100644 --- a/src/cloud/occi/lib/ui/public/js/login.js +++ b/src/cloud/occi/lib/ui/public/js/login.js @@ -24,14 +24,20 @@ function auth_error(req, error){ switch (status){ case 401: - $("#one_error").hide(); - $("#auth_error").fadeIn("slow"); + $("#error_box").text("Invalid username or password"); break; case 500: - $("#auth_error").hide(); - $("#one_error").fadeIn("slow"); + $("#error_box").text("OpenNebula is not running or there was a server exception. Please check the server logs."); break; + case 0: + $("#error_box").text("No answer from server. Is it running?"); + break; + default: + $("#error_box").text("Unexpected error. Status "+status+". Check the server logs."); + }; + $("#error_box").fadeIn("slow"); + $("#login_spinner").hide(); } function authenticate(){ @@ -40,6 +46,9 @@ function authenticate(){ password = Crypto.SHA1(password); var remember = $("#check_remember").is(":checked"); + $("#error_box").fadeOut("slow"); + $("#login_spinner").show(); + var obj = { data: {username: username, password: password}, remember: remember, @@ -70,4 +79,5 @@ $(document).ready(function(){ }; $("input#username.box").focus(); + $("#login_spinner").hide(); }); diff --git a/src/cloud/occi/lib/ui/public/js/occi.js b/src/cloud/occi/lib/ui/public/js/occi.js index a818fe8008..a841b00ded 100644 --- a/src/cloud/occi/lib/ui/public/js/occi.js +++ b/src/cloud/occi/lib/ui/public/js/occi.js @@ -189,7 +189,7 @@ var OCCI = { $.ajax({ url: resource.toLowerCase(), type: "GET", - data: {timeout: timeout}, + data: {timeout: timeout, verbose: true}, dataType: "xml ONEjson", success: function(response){ var res = {}; diff --git a/src/cloud/occi/lib/ui/public/js/plugins/compute.js b/src/cloud/occi/lib/ui/public/js/plugins/compute.js index 5613a1b3a2..99402b25a0 100644 --- a/src/cloud/occi/lib/ui/public/js/plugins/compute.js +++ b/src/cloud/occi/lib/ui/public/js/plugins/compute.js @@ -153,13 +153,6 @@ var vm_actions = { error: onError }, - "VM.showstate" : { - type: "single", - call: OCCI.VM.show, - callback: updateVMStateCB, - error: onError - }, - "VM.refresh" : { type: "custom", call : function (){ @@ -178,7 +171,7 @@ var vm_actions = { "VM.suspend" : { type: "multiple", call: OCCI.VM.suspend, - callback: updateVMStateCB, + callback: updateVMachineElement, elements: vmElements, error: onError, notify: true @@ -187,7 +180,7 @@ var vm_actions = { "VM.resume" : { type: "multiple", call: OCCI.VM.resume, - callback: updateVMStateCB, + callback: updateVMachineElement, elements: vmElements, error: onError, notify: true @@ -196,7 +189,7 @@ var vm_actions = { "VM.stop" : { type: "multiple", call: OCCI.VM.stop, - callback: updateVMStateCB, + callback: updateVMachineElement, elements: vmElements, error: onError, notify: true @@ -214,7 +207,7 @@ var vm_actions = { "VM.shutdown" : { type: "multiple", call: OCCI.VM.shutdown, - callback: updateVMStateCB, + callback: updateVMachineElement, elements: vmElements, error: onError, notify: true @@ -223,7 +216,7 @@ var vm_actions = { "VM.cancel" : { type: "multiple", call: OCCI.VM.cancel, - callback: updateVMStateCB, + callback: updateVMachineElement, elements: vmElements, error: onError, notify: true @@ -240,7 +233,7 @@ var vm_actions = { "VM.saveas" : { type: "single", call: OCCI.VM.saveas, - callback: updateVMStateCB, + callback: updateVMachineElement, error:onError }, @@ -259,7 +252,7 @@ var vm_actions = { }; var options = ""; for (var i = 0; i'+type+''; }; $('#dialog select#instance_type').html(options); @@ -423,7 +416,7 @@ function vMachineElementArray(vm_json){ return [ '', id, - name + VMStateBulletStr(vm_json) + name ]; } @@ -473,16 +466,15 @@ function updateVMachinesView(request, vmachine_list){ $.each(vmachine_list,function(){ el_array = vMachineElementArray(this); vmachine_list_array.push(el_array); - Sunstone.runAction("VM.showstate",el_array[1]); }); updateView(vmachine_list_array,dataTable_vMachines); updateDashboard("vms",vmachine_list); }; -function updateVMStateCB(request,vm){ +function VMStateBulletStr(vm){ var vm_state = vm.COMPUTE.STATE; - var state_html = vm_state; + var state_html = ""; switch (vm_state) { case "INIT": case "PENDING": @@ -499,13 +491,8 @@ function updateVMStateCB(request,vm){ state_html = ''+vm_state+''; break; }; - - var tag = 'input#vm_'+vm.COMPUTE.ID; - var array = vMachineElementArray(vm); - array[2] = state_html + array[2]; - updateSingleElement(array,dataTable_vMachines,tag); -}; - + return state_html; +} // Refreshes the information panel for a VM function updateVMInfo(request,vm){ diff --git a/src/cloud/occi/lib/ui/templates/login.html b/src/cloud/occi/lib/ui/templates/login.html index 82b80c0785..8bf6d0c005 100644 --- a/src/cloud/occi/lib/ui/templates/login.html +++ b/src/cloud/occi/lib/ui/templates/login.html @@ -3,7 +3,7 @@ OpenNebula Self-Service Login - + @@ -28,11 +28,7 @@
-
- Invalid username or password -
-
- OpenNebula is not running +
@@ -46,7 +42,7 @@ - + retrieving
diff --git a/src/cloud/occi/test/fixtures/compute/empty.xml b/src/cloud/occi/test/fixtures/compute/empty.xml new file mode 100644 index 0000000000..17ef393d05 --- /dev/null +++ b/src/cloud/occi/test/fixtures/compute/empty.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/cloud/occi/test/fixtures/compute/first_compute.xml b/src/cloud/occi/test/fixtures/compute/first_compute.xml new file mode 100644 index 0000000000..4824a2b00e --- /dev/null +++ b/src/cloud/occi/test/fixtures/compute/first_compute.xml @@ -0,0 +1,20 @@ + + 0 + + oneadmin + 1 + 1024 + Compute + small + PENDING + + + DISK + hde + + + + 192.168.1.1 + 02:00:c0:a8:01:01 + + diff --git a/src/cloud/occi/test/fixtures/compute/first_compute_done.xml b/src/cloud/occi/test/fixtures/compute/first_compute_done.xml new file mode 100644 index 0000000000..46bd2c8352 --- /dev/null +++ b/src/cloud/occi/test/fixtures/compute/first_compute_done.xml @@ -0,0 +1,20 @@ + + 0 + + oneadmin + 1 + 1024 + Compute + small + DONE + + + DISK + hde + + + + 192.168.1.1 + 02:00:c0:a8:01:01 + + diff --git a/src/cloud/occi/test/fixtures/compute/second_compute.xml b/src/cloud/occi/test/fixtures/compute/second_compute.xml new file mode 100644 index 0000000000..ee057ed43e --- /dev/null +++ b/src/cloud/occi/test/fixtures/compute/second_compute.xml @@ -0,0 +1,20 @@ + + 1 + + users + 1 + 1024 + Compute2 + small + PENDING + + + DISK + hde + + + + 192.168.2.1 + 02:00:c0:a8:02:01 + + diff --git a/src/cloud/occi/test/fixtures/compute/second_compute_done.xml b/src/cloud/occi/test/fixtures/compute/second_compute_done.xml new file mode 100644 index 0000000000..e73c28ba3b --- /dev/null +++ b/src/cloud/occi/test/fixtures/compute/second_compute_done.xml @@ -0,0 +1,20 @@ + + 1 + + users + 1 + 1024 + Compute2 + small + DONE + + + DISK + hde + + + + 192.168.2.1 + 02:00:c0:a8:02:01 + + diff --git a/src/cloud/occi/test/fixtures/instance_type/extended.xml b/src/cloud/occi/test/fixtures/instance_type/extended.xml new file mode 100644 index 0000000000..f774763320 --- /dev/null +++ b/src/cloud/occi/test/fixtures/instance_type/extended.xml @@ -0,0 +1,20 @@ + + + large + large + 8 + 8192 + + + medium + medium + 4 + 4096 + + + small + small + 1 + 1024 + + diff --git a/src/cloud/occi/test/fixtures/instance_type/list.xml b/src/cloud/occi/test/fixtures/instance_type/list.xml new file mode 100644 index 0000000000..135ae70491 --- /dev/null +++ b/src/cloud/occi/test/fixtures/instance_type/list.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/cloud/occi/test/fixtures/network/empty.xml b/src/cloud/occi/test/fixtures/network/empty.xml new file mode 100644 index 0000000000..c96d82e6ed --- /dev/null +++ b/src/cloud/occi/test/fixtures/network/empty.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/cloud/occi/test/fixtures/network/first_net.xml b/src/cloud/occi/test/fixtures/network/first_net.xml new file mode 100644 index 0000000000..24b388783d --- /dev/null +++ b/src/cloud/occi/test/fixtures/network/first_net.xml @@ -0,0 +1,11 @@ + + 0 + Network + + oneadmin + Network of the user my_first_occi_user +
192.168.1.1
+ 125 + 0 + NO +
diff --git a/src/cloud/occi/test/fixtures/network/second_net.xml b/src/cloud/occi/test/fixtures/network/second_net.xml new file mode 100644 index 0000000000..9cad2d9594 --- /dev/null +++ b/src/cloud/occi/test/fixtures/network/second_net.xml @@ -0,0 +1,11 @@ + + 1 + Network2 + + users + Network of the user my_second_occi_user +
192.168.2.1
+ 125 + 0 + NO +
diff --git a/src/cloud/occi/test/fixtures/root.xml b/src/cloud/occi/test/fixtures/root.xml new file mode 100644 index 0000000000..38843f0418 --- /dev/null +++ b/src/cloud/occi/test/fixtures/root.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/cloud/occi/test/fixtures/storage/empty.xml b/src/cloud/occi/test/fixtures/storage/empty.xml new file mode 100644 index 0000000000..622d314777 --- /dev/null +++ b/src/cloud/occi/test/fixtures/storage/empty.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/cloud/occi/test/fixtures/storage/first_storage.xml b/src/cloud/occi/test/fixtures/storage/first_storage.xml new file mode 100644 index 0000000000..c24bea57ae --- /dev/null +++ b/src/cloud/occi/test/fixtures/storage/first_storage.xml @@ -0,0 +1,13 @@ + + 0 + Storage + + oneadmin + READY + DATABLOCK + Storage of the user my_first_occi_user + 100 + ext3 + NO + NO + diff --git a/src/cloud/occi/test/fixtures/storage/second_storage.xml b/src/cloud/occi/test/fixtures/storage/second_storage.xml new file mode 100644 index 0000000000..defd5b1a56 --- /dev/null +++ b/src/cloud/occi/test/fixtures/storage/second_storage.xml @@ -0,0 +1,13 @@ + + 1 + Storage2 + + users + READY + DATABLOCK + Storage of the user my_second_occi_user + 100 + ext3 + NO + NO + diff --git a/src/cloud/occi/test/spec/occi_spec.rb b/src/cloud/occi/test/spec/occi_spec.rb new file mode 100644 index 0000000000..ba35ae94dd --- /dev/null +++ b/src/cloud/occi/test/spec/occi_spec.rb @@ -0,0 +1,283 @@ +# -------------------------------------------------------------------------- # +# 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 File.expand_path(File.dirname(__FILE__) + '/spec_helper') + +# The following methods are helpers defined in spec_helper +# - compare_xml +# - get_fixture +# - network_template +# - storage_template +# - compute_template + +describe 'OCCI tests' do + before(:all) do + @user_oneadmin = "my_first_occi_user" + `oneuser create #{@user_oneadmin} my_pass`.scan(/^ID: (\d+)/) { |uid| + `oneuser show #{uid.first}`.scan(/PASSWORD\s*:\s*(\w*)/) { |password| + @user_pass = password.first.strip + } + + `oneuser chgrp #{uid.first} 0` + } + + @user_users = "my_second_occi_user" + `oneuser create #{@user_users} my_pass2`.scan(/^ID: (\d+)/) { |uid| + `oneuser show #{uid.first}`.scan(/PASSWORD\s*:\s*(\w*)/) { |password| + @user_pass2 = password.first.strip + } + } + + # Define BRIDGE attirbute in network.erb, otherwise the NETWORK creation will + `sed -i.bck "s%^#\\(BRIDGE = \\).*$%\\1 br0%" $ONE_LOCATION/etc/occi_templates/network.erb` + end + + describe "with a user of the oneadmin group" do + before(:each) do + basic_authorize(@user_oneadmin, @user_pass) + end + + it "should retrieve the list of collections" do + get '/' + compare_xml(last_response.body, get_fixture('/root.xml')) + last_response.status.should == 200 + end + + it "should retrieve the list of INSTANCE_TYPEs" do + get '/instance_type' + compare_xml(last_response.body, get_fixture('/instance_type/list.xml')) + last_response.status.should == 200 + end + + it "should retrieve the extended list of INSTANCE_TYPEs" do + get '/instance_type', {'verbose'=>true} + compare_xml(last_response.body, get_fixture('/instance_type/extended.xml')) + last_response.status.should == 200 + end + + context "for NETWORK" do + it "should retrieve the empty list" do + get '/network' + compare_xml(last_response.body, get_fixture('/network/empty.xml')) + last_response.status.should == 200 + end + + it "should create a new NETWORK" do + network = { + :name => "Network", + :description => "Network of the user #{@user_oneadmin}", + :address => "192.168.1.0", + :size => "100", + :pubic => "YES" + } + + post '/network', network_template(network) + compare_xml(last_response.body, get_fixture('/network/first_net.xml')) + last_response.status.should == 201 + end + + it "should retrieve the NETWORK with ID 0" do + get '/network/0' + compare_xml(last_response.body, get_fixture('/network/first_net.xml')) + last_response.status.should == 200 + end + end + + context "for STORAGE" do + it "should retrieve the empty list" do + get '/storage' + compare_xml(last_response.body, get_fixture('/storage/empty.xml')) + last_response.status.should == 200 + end + + it "should create a new STORAGE, type DATABLOCK. This request waits until the IMAGE is ready in OpenNebula" do + storage = { + :name => "Storage", + :description => "Storage of the user #{@user_oneadmin}", + :type => "DATABLOCK", + :size => "100", + :fstype => "ext3" + } + + post '/storage', {'occixml' => storage_template(storage)} + compare_xml(last_response.body, get_fixture('/storage/first_storage.xml')) + last_response.status.should == 201 + end + + it "should retrieve the STORAGE with ID 0" do + get '/storage/0' + compare_xml(last_response.body, get_fixture('/storage/first_storage.xml')) + last_response.status.should == 200 + end + end + + context "for COMPUTE" do + it "should retrieve the empty list" do + get '/compute' + compare_xml(last_response.body, get_fixture('/compute/empty.xml')) + last_response.status.should == 200 + end + + it "should create a new COMPUTE using the previous NETWORK (ID=0) and STORAGE(ID=0)" do + compute = { + :name => "Compute", + :instance_type => "small", + :disk => [ {:storage => '0'} ], + :nic => [ {:network => '0'} ] + } + + post '/compute', compute_template(compute) + compare_xml(last_response.body, get_fixture('/compute/first_compute.xml')) + last_response.status.should == 201 + end + + it "should retrieve the COMPUTE with ID 0" do + get '/compute/0' + compare_xml(last_response.body, get_fixture('/compute/first_compute.xml')) + last_response.status.should == 200 + end + + it "should terminate (DONE) the COMPUTE with ID 0" do + compute = { + :id => "0", + :state => "DONE" + } + + put '/compute/0', compute_action(compute) + compare_xml(last_response.body, get_fixture('/compute/first_compute_done.xml')) + last_response.status.should == 202 + end + end + end + + describe "with a user of the users group" do + before(:each) do + basic_authorize(@user_users, @user_pass2) + end + + it "should retrieve the list of collections" do + get '/' + compare_xml(last_response.body, get_fixture('/root.xml')) + last_response.status.should == 200 + end + + it "should retrieve the list of INSTANCE_TYPEs" do + get '/instance_type' + compare_xml(last_response.body, get_fixture('/instance_type/list.xml')) + last_response.status.should == 200 + end + + it "should retrieve the extended list of INSTANCE_TYPEs" do + get '/instance_type', {'verbose'=>true} + compare_xml(last_response.body, get_fixture('/instance_type/extended.xml')) + last_response.status.should == 200 + end + + context "for NETWORK" do + it "should retrieve the empty list" do + get '/network' + compare_xml(last_response.body, get_fixture('/network/empty.xml')) + last_response.status.should == 200 + end + + it "should create a new NETWORK" do + network = { + :name => "Network2", + :description => "Network of the user #{@user_users}", + :address => "192.168.2.0", + :size => "100", + :pubic => "YES" + } + + post '/network', network_template(network) + compare_xml(last_response.body, get_fixture('/network/second_net.xml')) + last_response.status.should == 201 + end + + it "should retrieve the NETWORK with ID 1" do + get '/network/1' + compare_xml(last_response.body, get_fixture('/network/second_net.xml')) + last_response.status.should == 200 + end + end + + context "for STORAGE" do + it "should retrieve the empty list" do + get '/storage' + compare_xml(last_response.body, get_fixture('/storage/empty.xml')) + last_response.status.should == 200 + end + + it "should create a new STORAGE, type DATABLOCK. This request waits until the IMAGE is ready in OpenNebula" do + storage = { + :name => "Storage2", + :description => "Storage of the user #{@user_users}", + :type => "DATABLOCK", + :size => "100", + :fstype => "ext3" + } + + post '/storage', {'occixml' => storage_template(storage)} + compare_xml(last_response.body, get_fixture('/storage/second_storage.xml')) + last_response.status.should == 201 + end + + it "should retrieve the STORAGE with ID 1" do + get '/storage/1' + compare_xml(last_response.body, get_fixture('/storage/second_storage.xml')) + last_response.status.should == 200 + end + end + + context "for COMPUTE" do + it "should retrieve the empty list" do + get '/compute' + compare_xml(last_response.body, get_fixture('/compute/empty.xml')) + last_response.status.should == 200 + end + + it "should create a new COMPUTE using the previous NETWORK (ID=1) and STORAGE(ID=1)" do + compute = { + :name => "Compute2", + :instance_type => "small", + :disk => [ {:storage => '1'} ], + :nic => [ {:network => '1'} ] + } + + post '/compute', compute_template(compute) + compare_xml(last_response.body, get_fixture('/compute/second_compute.xml')) + last_response.status.should == 201 + end + + it "should retrieve the COMPUTE with ID 1" do + get '/compute/1' + compare_xml(last_response.body, get_fixture('/compute/second_compute.xml')) + last_response.status.should == 200 + end + + it "should terminate (DONE) the COMPUTE with ID 1" do + compute = { + :id => "1", + :state => "DONE" + } + + put '/compute/1', compute_action(compute) + compare_xml(last_response.body, get_fixture('/compute/second_compute_done.xml')) + last_response.status.should == 202 + end + end + end +end \ No newline at end of file diff --git a/src/cloud/occi/test/spec/spec.opts b/src/cloud/occi/test/spec/spec.opts new file mode 100644 index 0000000000..ad561bd258 --- /dev/null +++ b/src/cloud/occi/test/spec/spec.opts @@ -0,0 +1,4 @@ +--colour +--format progress +--loadby mtime +--reverse \ No newline at end of file diff --git a/src/cloud/occi/test/spec/spec_helper.rb b/src/cloud/occi/test/spec/spec_helper.rb index b2a2917a97..0d7423a2fc 100644 --- a/src/cloud/occi/test/spec/spec_helper.rb +++ b/src/cloud/occi/test/spec/spec_helper.rb @@ -24,6 +24,8 @@ require 'rubygems' require 'rspec' require 'rack/test' +require 'rexml/document' + # Load the Sinatra app require 'occi-server' @@ -39,3 +41,153 @@ set :environment, :test def app Sinatra::Application end + + + +def get_fixture(path) + File.read(FIXTURES_PATH + path).strip +end + + +def compare_xml(a, b) + a = REXML::Document.new(a.to_s) + b = REXML::Document.new(b.to_s) + + normalized = Class.new(REXML::Formatters::Pretty) do + def write_text(node, output) + super(node.to_s.strip, output) + end + end + + normalized.new(indentation=0,ie_hack=false).write(node=a, a_normalized='') + normalized.new(indentation=0,ie_hack=false).write(node=b, b_normalized='') + + a_normalized.should == b_normalized +end + + +OCCI_NETWORK = %q{ + + <% if hash[:name] %> + <%= hash[:name] %> + <% end %> + + <% if hash[:description] %> + <%= hash[:description] %> + <% end %> + + <% if hash[:address] %> +
<%= hash[:address] %>
+ <% end %> + + <% if hash[:size] %> + <%= hash[:size] %> + <% end %> + + <% if hash[:public] %> + <%= hash[:public] %> + <% end %> +
+} + +def network_template(hash) + ERB.new(OCCI_NETWORK).result(binding) +end + +OCCI_IMAGE = %q{ + + <% if hash[:name] %> + <%= hash[:name] %> + <% end %> + + <% if hash[:type] %> + <%= hash[:type] %> + <% end %> + + <% if hash[:description] %> + <%= hash[:description] %> + <% end %> + + <% if hash[:size] %> + <%= hash[:size] %> + <% end %> + + <% if hash[:fstype] %> + <%= hash[:fstype] %> + <% end %> + + <% if hash[:public] %> + <%= hash[:public] %> + <% end %> + + <% if hash[:persistent] %> + <%= hash[:persistent] %> + <% end %> + +} + +def storage_template(hash) + ERB.new(OCCI_IMAGE).result(binding) +end + +OCCI_VM = %q{ + + <% if hash[:name] %> + <%= hash[:name] %> + <% end %> + + <% if hash[:instance_type] %> + + <% end %> + + <% if hash[:disk] %> + <% hash[:disk].each { |disk| %> + + <% if disk[:storage] %> + + <% end %> + + <% } %> + <% end %> + + <% if hash[:nic] %> + <% hash[:nic].each { |nic| %> + + <% if nic[:network] %> + + <% end %> + <% if nic[:ip] %> + <%= nic[:ip] %> + <% end %> + + <% } %> + <% end %> + + <% if hash[:context] %> + + <% hash[:context].each { |key, value| %> + <<%= key.to_s.upcase %>><%= value %>> + <% } %> + + <% end %> + +} + +OCCI_VM_ACTION = %q{ + + <% if hash[:id] %> + <%= hash[:id] %> + <% end %> + <% if hash[:state] %> + <%= hash[:state] %> + <% end %> + +} + +def compute_template(hash) + ERB.new(OCCI_VM).result(binding) +end + +def compute_action(hash) + ERB.new(OCCI_VM_ACTION).result(binding) +end diff --git a/src/im_mad/remotes/vmware.d/vmware.rb b/src/im_mad/remotes/vmware.d/vmware.rb index b4c061656c..ea6920571e 100755 --- a/src/im_mad/remotes/vmware.d/vmware.rb +++ b/src/im_mad/remotes/vmware.d/vmware.rb @@ -106,7 +106,7 @@ conf = YAML::load(File.read(CONF_FILE)) rc, data = do_action("virsh -c #{@uri} --readonly nodeinfo") if rc == false - exit info + exit data end data.split(/\n/).each{|line| diff --git a/src/ozones/Server/bin/ozones-server b/src/ozones/Server/bin/ozones-server index 7d71a28d8f..9a637cafbb 100755 --- a/src/ozones/Server/bin/ozones-server +++ b/src/ozones/Server/bin/ozones-server @@ -19,14 +19,14 @@ if [ -z "$ONE_LOCATION" ]; then OZONES_PID=/var/run/one/ozones.pid OZONES_LOCATION=/usr/lib/one/ozones - OZONES_SERVER=$OZONES_LOCATION/config.ru + OZONES_SERVER=$OZONES_LOCATION/ozones-server.rb OZONES_LOCK_FILE=/var/lock/one/.ozones.lock OZONES_LOG=/var/log/one/ozones-server.log OZONES_CONF=/etc/one/ozones-server.conf else OZONES_PID=$ONE_LOCATION/var/ozones.pid OZONES_LOCATION=$ONE_LOCATION/lib/ozones - OZONES_SERVER=$OZONES_LOCATION/config.ru + OZONES_SERVER=$OZONES_LOCATION/ozones-server.rb OZONES_LOCK_FILE=$ONE_LOCATION/var/.ozones.lock OZONES_LOG=$ONE_LOCATION/var/ozones-server.log OZONES_CONF=$ONE_LOCATION/etc/ozones-server.conf @@ -58,29 +58,26 @@ start() exit 1 fi - HOST=`cat $OZONES_CONF|grep ^\:host\:|cut -d' ' -f 2` - PORT=`cat $OZONES_CONF|grep ^\:port\:|cut -d' ' -f 2` - - lsof -i:$PORT &> /dev/null - if [ $? -eq 0 ]; then - echo "The port $PORT is being used. Please specify a different one." - exit 1 - fi - # Start the ozones daemon touch $OZONES_LOCK_FILE - rackup $OZONES_SERVER -s thin -p $PORT -o $HOST \ - -P $OZONES_PID &> $OZONES_LOG & + ruby $OZONES_SERVER > $OZONES_LOG 2>&1 & + LASTPID=$! + if [ $? -ne 0 ]; then + echo "Error executing $OZONES_SERVER, please check the log $OZONES_LOG" + exit 1 + else + echo $LASTPID > $OZONES_PID + fi sleep 2 - ps -p $(cat $OZONES_PID 2>/dev/null) > /dev/null 2>&1 + ps $LASTPID &> /dev/null if [ $? -ne 0 ]; then echo "Error executing $OZONES_SERVER, please check the log $OZONES_LOG" exit 1 fi - echo "ozones-server listening on $HOST:$PORT" + echo "ozones-server started" } # diff --git a/src/ozones/Server/config.ru b/src/ozones/Server/config.ru deleted file mode 100644 index b5e2604dee..0000000000 --- a/src/ozones/Server/config.ru +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/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. # -#--------------------------------------------------------------------------- # - -$: << File.dirname(__FILE__) - -require 'ozones-server.rb' - -run Sinatra::Application diff --git a/src/ozones/Server/etc/ozones-server.conf b/src/ozones/Server/etc/ozones-server.conf index 022158205e..1fa3f0695b 100644 --- a/src/ozones/Server/etc/ozones-server.conf +++ b/src/ozones/Server/etc/ozones-server.conf @@ -19,13 +19,17 @@ ############################################## ###################### -# DB Options +# DB Options ###################### :databasetype: sqlite +#:databaseserver: dbuser:dbpassword@localhost #:htaccess: /var/www/.htaccess :dbdebug: no +# 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG +:debug_level: 3 + ##################### # Server Configuration ##################### diff --git a/src/ozones/Server/lib/OZones/ApacheWritter.rb b/src/ozones/Server/lib/OZones/ApacheWritter.rb index 327874a2e4..ca3ae1fdd3 100644 --- a/src/ozones/Server/lib/OZones/ApacheWritter.rb +++ b/src/ozones/Server/lib/OZones/ApacheWritter.rb @@ -28,12 +28,20 @@ module OZones zone.vdcs.all.each{|vdc| htaccess << "RewriteRule ^#{vdc.NAME} " + "#{zone.ENDPOINT} [P]\n" + if zone.SUNSENDPOINT != nil htaccess << "RewriteRule ^sunstone_#{vdc.NAME}/(.+) " + "#{zone.SUNSENDPOINT}/$1 [P]\n" htaccess << "RewriteRule ^sunstone_#{vdc.NAME} " + "#{zone.SUNSENDPOINT}/ [P]\n" end + + if zone.SELFENDPOINT != nil + htaccess << "RewriteRule ^self_#{vdc.NAME}/(.+) " + + "#{zone.SELFENDPOINT}/$1 [P]\n" + htaccess << "RewriteRule ^self_#{vdc.NAME} " + + "#{zone.SELFENDPOINT}/ [P]\n" + end } } diff --git a/src/ozones/Server/lib/OZones/Zones.rb b/src/ozones/Server/lib/OZones/Zones.rb index 5c1230945f..6e9aae9db7 100644 --- a/src/ozones/Server/lib/OZones/Zones.rb +++ b/src/ozones/Server/lib/OZones/Zones.rb @@ -30,6 +30,7 @@ module OZones property :ONEPASS, String, :required => true property :ENDPOINT, String, :required => true property :SUNSENDPOINT, String + property :SELFENDPOINT, String has n, :vdcs diff --git a/src/ozones/Server/models/OzonesServer.rb b/src/ozones/Server/models/OzonesServer.rb index 835d78b8f9..1b43ba887b 100644 --- a/src/ozones/Server/models/OzonesServer.rb +++ b/src/ozones/Server/models/OzonesServer.rb @@ -14,13 +14,16 @@ # limitations under the License. # #--------------------------------------------------------------------------- # +require 'CloudServer' + require 'JSONUtils' -class OzonesServer +class OzonesServer < CloudServer include OpenNebulaJSON::JSONUtils - def initialize(cipher) + def initialize(cipher, config, logger) + super(config, logger) #Set cipher for Zone classes OZones::Zones.cipher = cipher end diff --git a/src/ozones/Server/ozones-server.rb b/src/ozones/Server/ozones-server.rb index 84dc77d9b7..70eb42323a 100755 --- a/src/ozones/Server/ozones-server.rb +++ b/src/ozones/Server/ozones-server.rb @@ -19,19 +19,25 @@ ONE_LOCATION=ENV["ONE_LOCATION"] if !ONE_LOCATION - ETC_LOCATION="/etc/one" - LIB_LOCATION="/usr/lib/one" - RUBY_LIB_LOCATION="/usr/lib/one/ruby" - VAR_LOCATION="/var/lib/one" + LOG_LOCATION = "/var/log/one" + VAR_LOCATION = "/var/lib/one" + ETC_LOCATION = "/etc/one" + LIB_LOCATION = "/usr/lib/one" + RUBY_LIB_LOCATION = "/usr/lib/one/ruby" else - ETC_LOCATION=ONE_LOCATION+"/etc" - LIB_LOCATION=ONE_LOCATION+"/lib" - RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" - VAR_LOCATION=ONE_LOCATION+"/var" + VAR_LOCATION = ONE_LOCATION + "/var" + LOG_LOCATION = ONE_LOCATION + "/var" + ETC_LOCATION = ONE_LOCATION + "/etc" + LIB_LOCATION = ONE_LOCATION+"/lib" + RUBY_LIB_LOCATION = ONE_LOCATION+"/lib/ruby" end +OZONES_LOG = LOG_LOCATION + "/ozones-server.log" +CONFIGURATION_FILE = ETC_LOCATION + "/ozones-server.conf" + $: << LIB_LOCATION + "/sunstone/models" $: << RUBY_LIB_LOCATION +$: << RUBY_LIB_LOCATION+'/cloud' $: << LIB_LOCATION+'/ozones/models' $: << LIB_LOCATION+'/ozones/lib' $: << RUBY_LIB_LOCATION+"/cli" @@ -52,12 +58,46 @@ require 'OzonesServer' ############################################################################## # Read configuration ############################################################################## -config_data=File.read(ETC_LOCATION+'/ozones-server.conf') -config=YAML::load(config_data) +begin + config=YAML::load_file(CONFIGURATION_FILE) +rescue Exception => e + warn "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" + exit 1 +end + +config[:debug_level] ||= 3 + +CloudServer.print_configuration(config) db_type = config[:databasetype] -db_url = db_type + "://" + VAR_LOCATION + "/ozones.db" +case db_type + when "sqlite" then + db_url = db_type + "://" + VAR_LOCATION + "/ozones.db" + when "mysql","postgres" then + if config[:databaseserver].nil? + warn "DB server needed for this type of DB backend" + exit -1 + end + + db_url = db_type + "://" + config[:databaseserver] + "/ozones" + else + warn "DB type #{db_type} not recognized" + exit -1 +end + +############################################################################## +# Sinatra Configuration +############################################################################## +set :config, config +set :bind, config[:host] +set :port, config[:port] + +use Rack::Session::Pool, :key => 'ozones' + +#Enable logger +include CloudLogger +enable_logging OZONES_LOG, settings.config[:debug_level].to_i ############################################################################## # DB bootstrapping @@ -79,7 +119,7 @@ if Auth.all.size == 0 credentials = IO.read(ENV['OZONES_AUTH']).strip.split(':') if credentials.length < 2 - warn "Authorization data malformed" + settings.logger.error {"Authorization data malformed"} exit -1 end credentials[1] = Digest::SHA1.hexdigest(credentials[1]) @@ -87,7 +127,8 @@ if Auth.all.size == 0 :password => credentials[1]}) @auth.save else - warn "oZones admin credentials not set, missing OZONES_AUTH file." + error_m = "oZones admin credentials not set, missing OZONES_AUTH file." + settings.logger.error { error_m } exit -1 end else @@ -100,19 +141,10 @@ ADMIN_PASS = @auth.password begin OZones::ProxyRules.new("apache",config[:htaccess]) rescue Exception => e - warn e.message + settings.logger {e.message} exit -1 end - -############################################################################## -# Sinatra Configuration -############################################################################## -use Rack::Session::Pool, :key => 'ozones' -set :bind, config[:host] -set :port, config[:port] -set :show_exceptions, false - ############################################################################## # Helpers ############################################################################## @@ -144,10 +176,11 @@ helpers do return [204, ""] else + logger.info {"User not authorized login attempt"} return [401, ""] end end - + logger.error {"Authentication settings wrong or not provided"} return [401, ""] end @@ -168,7 +201,9 @@ before do end end - @OzonesServer = OzonesServer.new(session[:key]) + @OzonesServer = OzonesServer.new(session[:key], + settings.config, + settings.logger) @pr = OZones::ProxyRules.new("apache",config[:htaccess]) end end diff --git a/src/ozones/Server/public/css/login.css b/src/ozones/Server/public/css/login.css index 1ea3b09872..217c8c12ca 100644 --- a/src/ozones/Server/public/css/login.css +++ b/src/ozones/Server/public/css/login.css @@ -133,12 +133,19 @@ div#login input#login_btn:hover { } .error_message { + width: 400px; + margin-left: auto; + margin-right: auto; display: none; position: relative; top: 80px; - font-family: Arial, Helvetica, sans-serif; - color:red; - font-size:1.6em; + font-size:1.0em; +} + +#login_spinner { + left: 44px; + position: relative; + top: 2px; } #label_remember { diff --git a/src/ozones/Server/public/js/login.js b/src/ozones/Server/public/js/login.js index 70b26a3011..1ddc1ecd61 100644 --- a/src/ozones/Server/public/js/login.js +++ b/src/ozones/Server/public/js/login.js @@ -23,15 +23,21 @@ function auth_error(req, error){ var status = error.error.http_status; switch (status){ - case 401: - $("#one_error").hide(); - $("#auth_error").fadeIn("slow"); - break; - case 500: - $("#auth_error").hide(); - $("#one_error").fadeIn("slow"); - break; - } + case 401: + $("#error_box").text("Invalid username or password"); + break; + case 500: + $("#error_box").text("OpenNebula is not running or there was a server exception. Please check the server logs."); + break; + case 0: + $("#error_box").text("No answer from server. Is it running?"); + break; + default: + $("#error_box").text("Unexpected error. Status "+status+". Check the server logs."); + + }; + $("#error_box").fadeIn("slow"); + $("#login_spinner").hide(); } function authenticate(){ @@ -39,6 +45,9 @@ function authenticate(){ var password = $("#password").val(); var remember = $("#check_remember").is(":checked"); + $("#error_box").fadeOut("slow"); + $("#login_spinner").show(); + oZones.Auth.login({ data: {username: username , password: password} , remember: remember @@ -61,4 +70,5 @@ $(document).ready(function(){ }; $("input#username.box").focus(); + $("#login_spinner").hide(); }); diff --git a/src/ozones/Server/public/js/plugins/vdcs-tab.js b/src/ozones/Server/public/js/plugins/vdcs-tab.js index 2077aa5e2e..d03b75c7f8 100644 --- a/src/ozones/Server/public/js/plugins/vdcs-tab.js +++ b/src/ozones/Server/public/js/plugins/vdcs-tab.js @@ -516,6 +516,10 @@ function setupCreateVDCDialog(){ function openCreateVDCDialog(){ var dialog = $('div#create_vdc_dialog') + if (!zones_select){ + notifyError(tr("No zones defined: You need to create at least 1 zone before creating an VDC")); + return false; + }; $('select#zoneid',dialog).html(zones_select); $('select#zoneid',dialog).trigger("change"); $('#vdc_available_hosts_list',dialog).empty(); diff --git a/src/ozones/Server/templates/login.html b/src/ozones/Server/templates/login.html index 6038b56e55..d1001aaaa4 100644 --- a/src/ozones/Server/templates/login.html +++ b/src/ozones/Server/templates/login.html @@ -3,7 +3,7 @@ OpenNebula oZones Login - + @@ -24,11 +24,7 @@
-
- Invalid username or password -
-
- OpenNebula is not running +
@@ -42,6 +38,7 @@ + retrieving
diff --git a/src/sunstone/OpenNebulaVNC.rb b/src/sunstone/OpenNebulaVNC.rb index 71c92b6fe2..8d0285c079 100644 --- a/src/sunstone/OpenNebulaVNC.rb +++ b/src/sunstone/OpenNebulaVNC.rb @@ -21,7 +21,7 @@ require 'OpenNebula' # This class provides support for launching and stopping a websockify proxy # class OpenNebulaVNC - def initialize(config, opt={:json_errors => true}) + def initialize(config, logger, opt={:json_errors => true}) @proxy_path = config[:vnc_proxy_path] @proxy_base_port = config[:vnc_proxy_base_port].to_i @@ -36,6 +36,7 @@ class OpenNebulaVNC end @options = opt + @logger = logger end # Start a VNC proxy @@ -71,7 +72,7 @@ class OpenNebulaVNC cmd ="#{@proxy_path} #{proxy_options} #{proxy_port} #{host}:#{vnc_port}" begin - $stderr.puts("Starting vnc proxy: #{cmd}") + @logger.info { "Starting vnc proxy: #{cmd}" } pipe = IO.popen(cmd) rescue Exception => e return [500, OpenNebula::Error.new(e.message).to_json] diff --git a/src/sunstone/config.ru b/src/sunstone/config.ru deleted file mode 100644 index aa4380a728..0000000000 --- a/src/sunstone/config.ru +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/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. # -#--------------------------------------------------------------------------- # - -$: << File.dirname(__FILE__) - -require 'sunstone-server.rb' - -run Sinatra::Application diff --git a/src/sunstone/etc/sunstone-server.conf b/src/sunstone/etc/sunstone-server.conf index bcc7e8225f..cddab640a1 100644 --- a/src/sunstone/etc/sunstone-server.conf +++ b/src/sunstone/etc/sunstone-server.conf @@ -1,6 +1,9 @@ # OpenNebula sever contact information :one_xmlrpc: http://localhost:2633/RPC2 +# 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG +:debug_level: 3 + # Server Configuration :host: 127.0.0.1 :port: 9869 diff --git a/src/sunstone/models/SunstoneServer.rb b/src/sunstone/models/SunstoneServer.rb index 165f758669..6696d7f436 100644 --- a/src/sunstone/models/SunstoneServer.rb +++ b/src/sunstone/models/SunstoneServer.rb @@ -14,6 +14,8 @@ # limitations under the License. # #--------------------------------------------------------------------------- # +require 'CloudServer' + require 'OpenNebulaJSON' include OpenNebulaJSON @@ -22,14 +24,15 @@ require 'OpenNebulaVNC' require 'OpenNebulaJSON/JSONUtils' include JSONUtils -class SunstoneServer +class SunstoneServer < CloudServer # FLAG that will filter the elements retrieved from the Pools POOL_FILTER = Pool::INFO_ALL # Secs to sleep between checks to see if image upload to repo is finished IMAGE_POLL_SLEEP_TIME = 5 - def initialize(client) + def initialize(client, config, logger) + super(config, logger) @client = client end @@ -194,7 +197,8 @@ class SunstoneServer begin log = File.read(vm_log_file) rescue Exception => e - return [200, "Log for VM #{id} not available"] + msg = "Log for VM #{id} not available" + return [200, {:vm_log => msg}.to_json] end return [200, {:vm_log => log}.to_json] @@ -210,7 +214,7 @@ class SunstoneServer return [404, resource.to_json] end - vnc_proxy = OpenNebulaVNC.new(config) + vnc_proxy = OpenNebulaVNC.new(config, logger) return vnc_proxy.start(resource) end @@ -221,7 +225,8 @@ class SunstoneServer begin OpenNebulaVNC.stop(pipe) rescue Exception => e - error = Error.new(e.message) + logger.error {e.message} + error = Error.new("Error stopping VNC. Please check server logs.") return [500, error.to_json] end diff --git a/src/sunstone/public/css/login.css b/src/sunstone/public/css/login.css index 9c92aa9279..d275f49a57 100644 --- a/src/sunstone/public/css/login.css +++ b/src/sunstone/public/css/login.css @@ -134,10 +134,17 @@ div#login input#login_btn:hover { .error_message { display: none; position: relative; + width:400px; + margin-left: auto; + margin-right: auto; top: 80px; - font-family: Arial, Helvetica, sans-serif; - color:red; - font-size:1.6em; + font-size:1.0em; +} + +#login_spinner { + left: 44px; + position: relative; + top: 2px; } #label_remember { diff --git a/src/sunstone/public/js/login.js b/src/sunstone/public/js/login.js index 953f46aa1f..6b460d35c0 100644 --- a/src/sunstone/public/js/login.js +++ b/src/sunstone/public/js/login.js @@ -24,14 +24,19 @@ function auth_error(req, error){ switch (status){ case 401: - $("#one_error").hide(); - $("#auth_error").fadeIn("slow"); + $("#error_box").text("Invalid username or password"); break; case 500: - $("#auth_error").hide(); - $("#one_error").fadeIn("slow"); + $("#error_box").text("OpenNebula is not running or there was a server exception. Please check the server logs."); break; + case 0: + $("#error_box").text("No answer from server. Is it running?"); + break; + default: + $("#error_box").text("Unexpected error. Status "+status+". Check the server logs."); }; + $("#error_box").fadeIn("slow"); + $("#login_spinner").hide(); } function authenticate(){ @@ -39,6 +44,9 @@ function authenticate(){ var password = $("#password").val(); var remember = $("#check_remember").is(":checked"); + $("#error_box").fadeOut("slow"); + $("#login_spinner").show(); + OpenNebula.Auth.login({ data: {username: username , password: password} , remember: remember @@ -61,4 +69,5 @@ $(document).ready(function(){ }; $("input#username.box").focus(); + $("#login_spinner").hide(); }); diff --git a/src/sunstone/public/js/plugins/users-tab.js b/src/sunstone/public/js/plugins/users-tab.js index a9d35c164f..19d48435e1 100644 --- a/src/sunstone/public/js/plugins/users-tab.js +++ b/src/sunstone/public/js/plugins/users-tab.js @@ -131,9 +131,11 @@ var user_actions = { "User.passwd" : { type: "multiple", call: OpenNebula.User.passwd, - //nocallback + callback: function(req,res){ + notifyMessage(tr("Change password successful")); + }, elements: userElements, - error: onError + error: onError, }, "User.chgrp" : { type: "multiple", @@ -492,6 +494,7 @@ function popUpCreateUserDialog(){ function popUpUpdatePasswordDialog(){ + $('#new_password',$update_pw_dialog).val(""); $update_pw_dialog.dialog('open'); } diff --git a/src/sunstone/sunstone-server.rb b/src/sunstone/sunstone-server.rb index 2e09422136..21230aa0f5 100755 --- a/src/sunstone/sunstone-server.rb +++ b/src/sunstone/sunstone-server.rb @@ -32,7 +32,9 @@ else end SUNSTONE_AUTH = VAR_LOCATION + "/.one/sunstone_auth" +SUNSTONE_LOG = LOG_LOCATION + "/sunstone.log" CONFIGURATION_FILE = ETC_LOCATION + "/sunstone-server.conf" + PLUGIN_CONFIGURATION_FILE = ETC_LOCATION + "/sunstone-plugins.yaml" SUNSTONE_ROOT_DIR = File.dirname(__FILE__) @@ -54,27 +56,42 @@ require 'CloudAuth' require 'SunstoneServer' require 'SunstonePlugins' + +############################################################################## +# Configuration +############################################################################## + begin conf = YAML.load_file(CONFIGURATION_FILE) rescue Exception => e - puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" + STDERR.puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}" exit 1 end -############################################################################## -# Sinatra Configuration -############################################################################## -use Rack::Session::Pool, :key => 'sunstone' +conf[:debug_level] ||= 3 + +CloudServer.print_configuration(conf) + +#Sinatra configuration + set :config, conf set :bind, settings.config[:host] set :port, settings.config[:port] +use Rack::Session::Pool, :key => 'sunstone' + +# Enable logger + +include CloudLogger +enable_logging SUNSTONE_LOG, settings.config[:debug_level].to_i + begin ENV["ONE_CIPHER_AUTH"] = SUNSTONE_AUTH cloud_auth = CloudAuth.new(settings.config) rescue => e - puts "Error initializing authentication system" - puts e.message + settings.logger.error { + "Error initializing authentication system" } + settings.logger.error { e.message } exit -1 end @@ -93,10 +110,12 @@ helpers do settings.cloud_auth.update_userpool_cache result = settings.cloud_auth.auth(request.env, params) rescue Exception => e - error 500, e.message + error 500, "" + logger.error { e.message } end if result.nil? + logger.info { "Unauthorized login attempt" } return [401, ""] else client = settings.cloud_auth.client(result) @@ -105,7 +124,7 @@ helpers do user = OpenNebula::User.new_with_id(user_id, client) rc = user.info if OpenNebula.is_error?(rc) - # Add a log message + logger.error { rc.message } return [500, ""] end @@ -157,7 +176,9 @@ before do halt 401 unless authorized? @SunstoneServer = SunstoneServer.new( - settings.cloud_auth.client(session[:user])) + settings.cloud_auth.client(session[:user]), + settings.config, + settings.logger) end end @@ -244,7 +265,10 @@ end post '/config' do begin body = JSON.parse(request.body.read) - rescue + rescue Exception => e + msg = "Error parsing configuration JSON" + logger.error { msg } + logger.error { e.message } [500, OpenNebula::Error.new(msg).to_json] end diff --git a/src/sunstone/templates/login.html b/src/sunstone/templates/login.html index c942f3184f..1dc5352d14 100644 --- a/src/sunstone/templates/login.html +++ b/src/sunstone/templates/login.html @@ -3,6 +3,7 @@ OpenNebula Sunstone Login + @@ -25,11 +26,7 @@
-
- Invalid username or password -
-
- OpenNebula is not running +
@@ -43,6 +40,7 @@ + retrieving