diff --git a/src/sunstone/bin/sunstone-server b/src/sunstone/bin/sunstone-server index e7b505ac42..6ecf6bb9c4 100755 --- a/src/sunstone/bin/sunstone-server +++ b/src/sunstone/bin/sunstone-server @@ -23,12 +23,18 @@ if [ -z "$ONE_LOCATION" ]; then SUNSTONE_LOCK_FILE=/var/lock/one/.sunstone.lock SUNSTONE_LOG=/var/log/one/sunstone.log SUNSTONE_CONF=/etc/one/sunstone-server.conf + ONEMONITOR_CMD=/usr/lib/one/sunstone/share/OneMonitor/runOneMonitor.sh + HOST_LOG_FOLDER=/var/log/one/OneMonitor/host + VM_LOG_FOLDER=/var/log/one/OneMonitor/vm else SUNSTONE_PID=$ONE_LOCATION/var/sunstone.pid SUNSTONE_SERVER=$ONE_LOCATION/lib/sunstone/config.ru SUNSTONE_LOCK_FILE=$ONE_LOCATION/var/.sunstone.lock SUNSTONE_LOG=$ONE_LOCATION/var/sunstone.log SUNSTONE_CONF=$ONE_LOCATION/etc/sunstone-server.conf + ONEMONITOR_CMD=$ONE_LOCATION/lib/sunstone/share/OneMonitor/runOneMonitor.sh + HOST_LOG_FOLDER=$ONE_LOCATION/var/OneMonitor/host + VM_LOG_FOLDER=$ONE_LOCATION/var/OneMonitor/vm fi setup() @@ -84,6 +90,24 @@ start() fi echo "sunstone-server listening on $HOST:$PORT" + + # Start the monitoring app + if [ ! -d $HOST_LOG_FOLDER ] + then + mkdir -p $HOST_LOG_FOLDER + [[ $? -ne 0 ]] && ( echo "Error creating host log directory"; exit 1 ) + fi + + if [ ! -d $VM_LOG_FOLDER ] + then + mkdir -p $VM_LOG_FOLDER + [[ $? -ne 0 ]] && ( echo "Error creating vm log directory"; exit 1 ) + fi + + $ONEMONITOR_CMD $MONITORING_INTERVAL $HOST_LOG_FOLDER $VM_LOG_FOLDER &>/dev/null & + + [[ $? -ne 0 ]] && ( echo "Error launching monitoring daemon"; exit 1 ) + } # @@ -102,6 +126,8 @@ stop() # Remove pid files rm -f $SUNSTONE_LOCK_FILE &> /dev/null + killall $ONEMONITOR_CMD + echo "sunstone-server stopped" } diff --git a/src/sunstone/models/SunstoneServer.rb b/src/sunstone/models/SunstoneServer.rb index 509d053c86..9c1198fe21 100644 --- a/src/sunstone/models/SunstoneServer.rb +++ b/src/sunstone/models/SunstoneServer.rb @@ -17,6 +17,8 @@ require 'OpenNebulaJSON' include OpenNebulaJSON +require 'OneMonitorClient' + class SunstoneServer def initialize(username, password) # TBD one_client_user(name) from CloudServer @@ -265,60 +267,22 @@ class SunstoneServer # ############################################################################ - def get_log(resource,id,config,monitor_resources,history_length) - log_file_prefix = case resource - when "vm","VM" - config[:host_log_file] - when "host","HOST" - config[:vm_log_file] - end + def get_log(params) + resource = params[:resource] + id = params[:id] + id = "global" unless id + columns = params['monitor_resources'].split(',') + history_length = params['history_length'] - if !log_file_prefix or log_file_prefix.empty? - log_file_prefix = "/srv/cloud/one-dummy/logs/"+resource - end + log_file_folder = case resource + when "vm","VM" + VM_LOG_FOLDER + when "host","HOST" + HOST_LOG_FOLDER + end - log_file = "#{log_file_prefix}_#{id}.csv" - - first_line = `head -1 #{log_file}`.chomp - - if $?.exitstatus != 0 - error = Error.new("Cannot open log file") - return [500, error.to_json] - end - - n_lines = `wc -l #{log_file} | cut -d' ' -f 1`.to_i - if n_lines <= history_length.to_i - history_length = n_lines-1 - end - - fields = first_line.split(',') - poll_time_pos = fields.index("time") - id_pos = fields.index("id") - - if !id_pos or !poll_time_pos - error = Error.new("It seems poll_time or id information cannot be read from log file") - return [500, error.to_json] - end - - series = [] #will hold several graphs - tail = `tail -#{history_length} #{log_file}` - - monitor_resources.split(',').each do | resource | - - graph = [] - resource_pos = fields.index(resource) - - tail.each_line do | line | - line_arr = line.delete('"').split(',') - if (line_arr[id_pos].to_i == id.to_i) - graph << [ line_arr[poll_time_pos].to_i*1000, line_arr[resource_pos].to_i ] - end - end - - series << graph - end - - return series.to_json + monitor_client = OneMonitorClient.new(id,log_file_folder) + return monitor_client.get_data_for_id(id,columns,history_length).to_json end ############################################################################ diff --git a/src/sunstone/public/css/application.css b/src/sunstone/public/css/application.css index d068cbef4a..6840aefa7c 100644 --- a/src/sunstone/public/css/application.css +++ b/src/sunstone/public/css/application.css @@ -55,17 +55,16 @@ table#dashboard_table tr { vertical-align: top; } -table#dashboard_table td{ +table#dashboard_table > tbody > tr > td{ width:50%; } div.panel { background-color: #ffffff; padding:0; - width:80%; margin: 10px; border: 1px #ddd solid; - min-height: 110px; + min-height: 50px; -webkit-border-radius: 3px; -moz-border-radius: 3px; -moz-box-shadow: 5px 5px 5px #888; @@ -77,6 +76,8 @@ div.panel h3 { border: 0; padding:5px 10px 5px 10px; margin: 0; + color: white; + font-weight: bold; background-color: #353735; -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; @@ -105,7 +106,7 @@ div.panel_info table.info_table tr { border: 0; border-bottom: 1px dotted #ccc; } -div.panel_info table.info_table td { +div.panel_info table.info_table > tbody > tr > td { border: 0; width: 100%!important; } @@ -114,11 +115,11 @@ div.panel_info table.info_table td.value_td { text-align: right; } -.key_td_green { +.green { color: green!important; } -.key_td_red { +.red { color: #B81515!important; } @@ -387,6 +388,12 @@ tr.even:hover{ font-weight:bold; } +.info_table td.graph_td{ + padding-top:0px!important; + padding-bottom:0px!important; + vertical-align:middle!important; +} + .info_table td.value_td{ text-align:left; } diff --git a/src/sunstone/public/js/opennebula.js b/src/sunstone/public/js/opennebula.js index cc74ec6087..88391d8756 100644 --- a/src/sunstone/public/js/opennebula.js +++ b/src/sunstone/public/js/opennebula.js @@ -504,6 +504,37 @@ var OpenNebula = { } } }); + }, + "monitor_all" : function(params){ + var callback = params.success; + var callback_error = params.error; + var resource = OpenNebula.Host.resource; + var data = params.data; + + var method = "monitor"; + var action = OpenNebula.Helper.action(method); + var request = OpenNebula.Helper.request(resource,method, data); + + $.ajax({ + url: "host/monitor", + type: "GET", + data: data['monitor'], + dataType: "json", + success: function(response) + { + if (callback) + { + callback(request,response); + } + }, + error: function(response) + { + if (callback_error) + { + callback_error(request, OpenNebula.Error(response)); + } + } + }); } }, @@ -1369,6 +1400,37 @@ var OpenNebula = { } } }); + }, + "monitor_all" : function(params){ + var callback = params.success; + var callback_error = params.error; + var resource = OpenNebula.VM.resource; + var data = params.data; + + var method = "monitor"; + var action = OpenNebula.Helper.action(method); + var request = OpenNebula.Helper.request(resource,method, data); + + $.ajax({ + url: "vm/monitor", + type: "GET", + data: data['monitor'], + dataType: "json", + success: function(response) + { + if (callback) + { + callback(request,response); + } + }, + error: function(response) + { + if (callback_error) + { + callback_error(request, OpenNebula.Error(response)); + } + } + }); } }, diff --git a/src/sunstone/public/js/plugins/dashboard-tab.js b/src/sunstone/public/js/plugins/dashboard-tab.js index b6bc6fb2ac..1550a5e092 100644 --- a/src/sunstone/public/js/plugins/dashboard-tab.js +++ b/src/sunstone/public/js/plugins/dashboard-tab.js @@ -14,44 +14,81 @@ /* limitations under the License. */ /* -------------------------------------------------------------------------- */ +var HISTORY_LENGTH=40; +var GRAPH_AUTOREFRESH_INTERVAL=10000; //10 secs + +var graph1 = { + title : "graph1", + monitor_resources : "total,active,error", + history_length : HISTORY_LENGTH +}; + +var graph2 = { + title : "graph2", + monitor_resources : "cpu_usage,used_cpu,max_cpu", + history_length : HISTORY_LENGTH +}; + +var graph3 = { + title : "graph3", + monitor_resources : "mem_usage,used_mem,max_mem", + history_length : HISTORY_LENGTH +}; + +var graph4 = { + title : "graph4", + monitor_resources : "total,active,error", + history_length : HISTORY_LENGTH +}; + +var graph5 = { + title : "graph5", + monitor_resources : "net_tx,net_rx", + history_length : HISTORY_LENGTH +}; + var dashboard_tab_content = '\ +\ +\ +
\ +\ \ - \ \ @@ -59,95 +96,67 @@ var dashboard_tab_content = \ \ - \ \ \ \ - \ +
\ -
\ -

Hosts\ -
\ - +\ -
\ -

\ -
\ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
Total
Active
\ -
\ -
\ -
\
\ -

Clusters\ -
\ - +\ -
\ -

\ +

Summary of resources

\
\ - \ - \ - \ - \ - \ -
Total
\ +\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
Hosts (total/active)
Clusters
VM Templates (total/public)
VM Instances (total/running/failed)
Virtual Networks (total/public)
Images (total/public)
Users
\ +\
\
\
\
\ -

Virtual Machines\ -
\ - +\ -
\ -

\ -
\ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
Total
Running
Failed
\ -
\ -
\ -
\ -
\ -

Virtual Networks\ -
\ - +\ -
\ -

\ -
\ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
Total
Public
\ -
\ -
\ +

Quickstart

\ +
\ +
\ + \ + Host
\ + Cluster
\ + VM Template
\ + VM Instance
\ + Image
\ + User
\ +
\ + \ +
\ \
\
\ -

\ - Images\ -
\ - +\ -
\ -

\ -
\ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
Total
Public
\ -
\ +

Sunstone documentation

\ +
    \ +
  • Sunstone installation and setup
  • \ +
  • Sunstone plugin guide
  • \ +
  • Sunstone plugin reference
  • \ +
\
\
\ +
\ +
\ +\ + \ + \ \ -
\
\ -

Users\ -
\ - +\ -
\ -

\ +

Historical monitoring information

\
\ - \ - \ - \ - \ - \ -
Total
\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
Total host count
'+spinner+'
Hosts CPU
'+spinner+'
Hosts memory
'+spinner+'
Total VM count
'+spinner+'
VM Network stats
'+spinner+'
\
\
\
'; - +
\ +\ +'; var dashboard_tab = { title: 'Dashboard', @@ -157,6 +166,74 @@ var dashboard_tab = { Sunstone.addMainTab('dashboard_tab',dashboard_tab); +function plot_global_graph(data,info){ + var id = info.title; + var labels_arr = info.monitor_resources.split(','); + var serie; + var series = []; + var width = ($(window).width()-129)*45/100; + + $('#'+id).html('
'); + + for (var i = 0; i< labels_arr.length; i++) { + serie = { + label: labels_arr[i], + data: data[i] + }; + series.push(serie); + }; + + var options = { + legend : { show : true, + noColumns: labels_arr.length, + container: $('#'+id+'_legend')}, + xaxis : { mode: "time", + timeformat: "%h:%M" + }, + yaxis : { labelWidth: 40 } + } + + switch (id){ + case "graph3": + case "graph5": + options["yaxis"]["tickFormatter"] = function(val,axis) { return humanize_size(val); } + } + + + + $.plot($('#'+id+'_graph'),series,options); +} + +function quickstart_setup(){ + + $('#quickstart').button("disable"); + + $('#quickstart_form input').click(function(){ + $('#quickstart').val($(this).val()); + $('#quickstart').button("enable"); + }); + + $('#quickstart').click(function(){ + Sunstone.runAction($(this).val()); + return false; + }); +} + +function graph_autorefresh(){ + setInterval(function(){ + refresh_graphs(); + },GRAPH_AUTOREFRESH_INTERVAL); + +} + +function refresh_graphs(){ + Sunstone.runAction("Host.monitor_all", graph1); + Sunstone.runAction("Host.monitor_all", graph2); + Sunstone.runAction("Host.monitor_all", graph3); + Sunstone.runAction("VM.monitor_all", graph4); + Sunstone.runAction("VM.monitor_all", graph5); +} + $(document).ready(function(){ //Dashboard link listener $("#dashboard_table h3 a").live("click", function (){ @@ -164,13 +241,17 @@ $(document).ready(function(){ showTab(tab); return false; }); - + emptyDashboard(); if (uid!=0) { $("td.oneadmin").hide(); } - - + + quickstart_setup(); + + refresh_graphs(); + graph_autorefresh(); + }); //puts the dashboard values into "retrieving" @@ -180,62 +261,73 @@ function emptyDashboard(){ function updateDashboard(what,json_info){ - db = $('#dashboard_tab'); - switch (what){ - case "hosts": - total_hosts=json_info.length; - active_hosts=0; - $.each(json_info,function(){ - if (parseInt(this.HOST.STATE) < 3){ - active_hosts++;} - }); - $('#total_hosts',db).html(total_hosts); - $('#active_hosts',db).html(active_hosts); - break; - case "clusters": - total_clusters=json_info.length; - $('#total_clusters',db).html(total_clusters); - break; - case "vms": - total_vms=json_info.length; - running_vms=0; + var db = $('#dashboard_tab'); + switch (what){ + case "hosts": + var total_hosts=json_info.length; + var active_hosts=0; + $.each(json_info,function(){ + if (parseInt(this.HOST.STATE) < 3){ + active_hosts++;} + }); + $('#total_hosts',db).html(total_hosts+' / '); + $('#active_hosts',db).html(active_hosts); + break; + case "clusters": + var total_clusters=json_info.length; + $('#total_clusters',db).html(total_clusters); + break; + case "vms": + var total_vms=json_info.length; + var running_vms=0; failed_vms=0; - $.each(json_info,function(){ - vm_state = parseInt(this.VM.STATE); - if (vm_state == 3){ - running_vms++; - } - else if (vm_state == 7) { - failed_vms++; - } - }); - $('#total_vms',db).html(total_vms); - $('#running_vms',db).html(running_vms); - $('#failed_vms',db).html(failed_vms); - break; - case "vnets": - public_vnets=0; - total_vnets=json_info.length; - $.each(json_info,function(){ - if (parseInt(this.VNET.PUBLIC)){ - public_vnets++;} - }); - $('#total_vnets',db).html(total_vnets); - $('#public_vnets',db).html(public_vnets); - break; - case "users": - total_users=json_info.length; - $('#total_users',db).html(total_users); - break; - case "images": - total_images=json_info.length; - public_images=0; - $.each(json_info,function(){ - if (parseInt(this.IMAGE.PUBLIC)){ - public_images++;} - }); - $('#total_images',db).html(total_images); - $('#public_images',db).html(public_images); - break; - } -} + $.each(json_info,function(){ + vm_state = parseInt(this.VM.STATE); + if (vm_state == 3){ + running_vms++; + } + else if (vm_state == 7) { + failed_vms++; + } + }); + $('#total_vms',db).html(total_vms+' / '); + $('#running_vms',db).html(running_vms+' / '); + $('#failed_vms',db).html(failed_vms); + break; + case "vnets": + var public_vnets=0; + var total_vnets=json_info.length; + $.each(json_info,function(){ + if (parseInt(this.VNET.PUBLIC)){ + public_vnets++;} + }); + $('#total_vnets',db).html(total_vnets+' / '); + $('#public_vnets',db).html(public_vnets); + break; + case "users": + var total_users=json_info.length; + $('#total_users',db).html(total_users); + break; + case "images": + var total_images=json_info.length; + var public_images=0; + $.each(json_info,function(){ + if (parseInt(this.IMAGE.PUBLIC)){ + public_images++;} + }); + $('#total_images',db).html(total_images+' / '); + $('#public_images',db).html(public_images); + break; + case "templates": + var total_templates=json_info.length; + var public_templates=0; + $.each(json_info,function(){ + if (parseInt(this.VMTEMPLATE.PUBLIC)){ + public_templates++; + } + }); + $('#total_templates',db).html(total_templates+' / '); + $('#public_templates',db).html(public_templates); + break; + } +} \ No newline at end of file diff --git a/src/sunstone/public/js/plugins/hosts-tab.js b/src/sunstone/public/js/plugins/hosts-tab.js index b29eb412e3..7673b61885 100644 --- a/src/sunstone/public/js/plugins/hosts-tab.js +++ b/src/sunstone/public/js/plugins/hosts-tab.js @@ -231,6 +231,10 @@ var host_actions = { "Host.monitor_all" : { type: "monitor_global", call: OpenNebula.Host.monitor_all, + callback: function(req,response) { + var info = req.request.data[0].monitor; + plot_global_graph(response,info); + }, error: onError }, diff --git a/src/sunstone/public/js/plugins/images-tab.js b/src/sunstone/public/js/plugins/images-tab.js index c8e4276f04..c0cfcf95b2 100644 --- a/src/sunstone/public/js/plugins/images-tab.js +++ b/src/sunstone/public/js/plugins/images-tab.js @@ -663,13 +663,16 @@ function setupCreateImageDialog(){ //Insert HTML in place $('#create_image_dialog').html(create_image_tmpl); + var height = Math.floor($(window).height()*0.8); //set height to a percentage of the window + //Prepare jquery dialog $('#create_image_dialog').dialog({ - autoOpen: false, - modal:true, - width: 520 - }); - + autoOpen: false, + modal:true, + width: 520, + height: height + }); + $('#img_tabs').tabs(); $('#create_image_dialog button').button(); $('#img_type option').first().attr("selected","selected"); diff --git a/src/sunstone/public/js/plugins/templates-tab.js b/src/sunstone/public/js/plugins/templates-tab.js index c58dfc76eb..2511b7af2c 100644 --- a/src/sunstone/public/js/plugins/templates-tab.js +++ b/src/sunstone/public/js/plugins/templates-tab.js @@ -803,6 +803,7 @@ function updateTemplateSelect(){ //update static selectors: $('#create_vm_dialog #template_id').html(templates_select); + $('#speed_virt').html(templates_select); } // Callback to update an element in the dataTable diff --git a/src/sunstone/public/js/plugins/vms-tab.js b/src/sunstone/public/js/plugins/vms-tab.js index 1e3c594248..65b29f663f 100644 --- a/src/sunstone/public/js/plugins/vms-tab.js +++ b/src/sunstone/public/js/plugins/vms-tab.js @@ -44,9 +44,8 @@ var vm_graphs = [ monitor_resources : "net_rx", humanize_figures : true, history_length : VM_HISTORY_LENGTH - }, - -] + } +]; var vms_tab_content = '
\ @@ -364,7 +363,16 @@ var vm_actions = { 'vm_monitor_',info); }, error: onError - } + }, + "VM.monitor_all" : { + type: "monitor_global", + call: OpenNebula.VM.monitor_all, + callback: function(req,response) { + var info = req.request.data[0].monitor; + plot_global_graph(response,info); + }, + error: onError + }, } diff --git a/src/sunstone/public/js/sunstone.js b/src/sunstone/public/js/sunstone.js index 7645edf194..7add76c027 100644 --- a/src/sunstone/public/js/sunstone.js +++ b/src/sunstone/public/js/sunstone.js @@ -17,7 +17,7 @@ var cookie = {}; var username = ''; var uid = ''; -var spinner = 'retrieving'; +var spinner = 'retrieving'; //Sunstone configuration is formed by predifined "actions", main tabs @@ -194,7 +194,7 @@ var Sunstone = { // * The default actions. Simple call the the pre-defined "call" // function with an extraparam if defined. switch (action_cfg.type){ - + case "create": case "register": call({data:data_arg, success: callback, error:err}); @@ -206,11 +206,11 @@ var Sunstone = { call({success: callback, error:err}); break; case "monitor_global": - call({success: callback, error:err, data: {monitor: extra_param}}); + call({timeout: true, success: callback, error:err, data: {monitor: data_arg}}); break; case "monitor": case "monitor_single": - call({success: callback, error:err, data: {id:data_arg, monitor: extra_param}}); + call({timeout: true, success: callback, error:err, data: {id:data_arg, monitor: extra_param}}); break; case "multiple": //run on the list of nodes that come on the data diff --git a/src/sunstone/share/OneMonitor/HostMonitor.rb b/src/sunstone/share/OneMonitor/HostMonitor.rb index 348dc63067..59757a3d14 100644 --- a/src/sunstone/share/OneMonitor/HostMonitor.rb +++ b/src/sunstone/share/OneMonitor/HostMonitor.rb @@ -1,3 +1,19 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, 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 'OneMonitor' class HostMonitor < OneMonitor @@ -22,16 +38,12 @@ class HostMonitor < OneMonitor :used_cpu => "HOST_SHARE/USED_CPU" } - def initialize (log_file,monitoring_elems=HOST_MONITORING_ELEMS) - super log_file,monitoring_elems + def initialize (log_file_folder,monitoring_elems=HOST_MONITORING_ELEMS) + super log_file_folder,monitoring_elems end - def monitor - super HostPool - end - - def snapshot - super HostPool + def factory(client) + HostPool.new(client) end def active (host_hash) diff --git a/src/sunstone/share/OneMonitor/OneMonitor.rb b/src/sunstone/share/OneMonitor/OneMonitor.rb index b7798dae15..64f0707c27 100644 --- a/src/sunstone/share/OneMonitor/OneMonitor.rb +++ b/src/sunstone/share/OneMonitor/OneMonitor.rb @@ -1,3 +1,19 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, 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. # +#--------------------------------------------------------------------------- # + ONE_LOCATION = ENV["ONE_LOCATION"] if !ONE_LOCATION @@ -19,10 +35,10 @@ class OneMonitor when "CSV" then include OneMonitorCSV end - def initialize(log_file_prefix,monitoring_elems) + def initialize(log_file_folder,monitoring_elems) # Authenticate in OpenNebula @client = Client.new - @log_file_prefix = log_file_prefix + @log_file_folder = log_file_folder @monitoring_elems = monitoring_elems @results = [] reinit_global_results @@ -32,7 +48,7 @@ class OneMonitor @results end - def snapshot(poolClass) + def snapshot #init global results rc = monitor #calling the extending class method @@ -47,23 +63,31 @@ class OneMonitor return rc end - def monitor(poolClass) - pool = poolClass.new(@client) + def monitor + pool = factory(@client) rc = pool.info if OpenNebula.is_error?(rc) - then - puts "Error monitoring: #{rc}" + puts "Error monitoring: #{rc.message}" return nil end pool.each do | elem | + time = elem[@monitoring_elems[:time]].to_i + hash = {} @monitoring_elems.each do | key,value | hash[key] = elem[value] end - @results << hash - add_to_global(hash) + + #do not log time = 0, it causes + #graphs being drawn from 1970 + + if time > 0 + @results << hash + add_to_global(hash) + end + @n_active += 1 if active(hash) @n_error += 1 if error(hash) @n_total += 1 @@ -83,7 +107,8 @@ class OneMonitor hash.each do | key,value | @global_results[key] += value.to_i end - @global_results[:time] = hash[:time].to_i + time = hash[:time].to_i + @global_results[:time] = time end end diff --git a/src/sunstone/share/OneMonitor/OneMonitorClient.rb b/src/sunstone/share/OneMonitor/OneMonitorClient.rb new file mode 100644 index 0000000000..7f042d4d29 --- /dev/null +++ b/src/sunstone/share/OneMonitor/OneMonitorClient.rb @@ -0,0 +1,35 @@ +require 'OneMonitorClientUtils' + +class OneMonitorClient + + INPUT_METHOD="CSV" + case INPUT_METHOD + when "CSV" then include OneMonitorCSVClient + end + + def initialize(ids, log_file_folder) + #create filenames to read + ids = [ids] unless ids.class == Array + @file_names = {} + ids.each do | id | + @file_names[id] = OneMonitorClient.full_path(log_file_folder,id) + end + return @file_names + end + + def get_multiple_data(columns,length) + result = [] + @file_names.each do | id,file_name | + result << get_data_for_id(id,columns,length) + end + return result + end + + def get_data_for_id(id, columns, length) + readOneMonitorFile(@file_names[id],columns,length) + end + + def self.full_path(folder,id) + "#{folder}/#{id}" + end +end diff --git a/src/sunstone/share/OneMonitor/OneMonitorClientUtils.rb b/src/sunstone/share/OneMonitor/OneMonitorClientUtils.rb new file mode 100644 index 0000000000..0593b3c686 --- /dev/null +++ b/src/sunstone/share/OneMonitor/OneMonitorClientUtils.rb @@ -0,0 +1,40 @@ +module OneMonitorCSVClient + def readOneMonitorFile(file_name,columns,length) + first_line = `head -1 #{file_name}`.chomp + + if $?.exitstatus != 0 + return [] #silently fail, cannot find this file + end + + n_lines = `wc -l #{file_name} | cut -d' ' -f 1`.to_i + if n_lines <= length.to_i + length = n_lines-1 + end + + fields = first_line.split(',') + poll_time_pos = fields.index("time") + + if !poll_time_pos + return [] #silently fail, no timestamp + end + + tail = `tail -#{length} #{file_name}` + series = [] #will hold several graphs + + columns.each do | column_name | + + graph = [] + column_pos = fields.index(column_name) + next unless column_pos + + tail.each_line do | line | + line_arr = line.delete('"').split(',') + graph << [ line_arr[poll_time_pos].to_i*1000, line_arr[column_pos].to_i ] + end + + series << graph + end + + return series + end +end diff --git a/src/sunstone/share/OneMonitor/OneMonitorUtils.rb b/src/sunstone/share/OneMonitor/OneMonitorUtils.rb index 270d58f217..b512887881 100644 --- a/src/sunstone/share/OneMonitor/OneMonitorUtils.rb +++ b/src/sunstone/share/OneMonitor/OneMonitorUtils.rb @@ -1,3 +1,19 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, 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. # +#--------------------------------------------------------------------------- # + module OneMonitorCSV def save @@ -9,13 +25,12 @@ module OneMonitorCSV @results.each do | mon_hash | id = mon_hash[:id] - log_name = "#{@log_file_prefix}_#{id}.csv" + log_name = "#{@log_file_folder}/#{id}" begin log_file = File.new(log_name,'a') if !File.size?(log_name) - then header = csv_header log_file.puts(header) end @@ -34,7 +49,7 @@ module OneMonitorCSV def save_global_csv begin - global_log_file = "#{@log_file_prefix}_global.csv" + global_log_file = "#{@log_file_folder}/global" global_file = File.new(global_log_file,'a') if !File.size?(global_log_file) diff --git a/src/sunstone/share/OneMonitor/VMMonitor.rb b/src/sunstone/share/OneMonitor/VMMonitor.rb index 1df49a7cf2..e9e0a29de3 100644 --- a/src/sunstone/share/OneMonitor/VMMonitor.rb +++ b/src/sunstone/share/OneMonitor/VMMonitor.rb @@ -1,3 +1,19 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, 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 'OneMonitor' class VMMonitor < OneMonitor @@ -14,16 +30,12 @@ class VMMonitor < OneMonitor :net_rx => "NET_RX" } - def initialize (log_file,monitoring_elems=VM_MONITORING_ELEMS) - super log_file,monitoring_elems + def initialize (log_file_folder,monitoring_elems=VM_MONITORING_ELEMS) + super log_file_folder,monitoring_elems end - def monitor - super VirtualMachinePool - end - - def snapshot - super VirtualMachinePool + def factory(client) + VirtualMachinePool.new(client) end def active (vm_hash) diff --git a/src/sunstone/share/OneMonitor/runOneMonitor.rb b/src/sunstone/share/OneMonitor/runOneMonitor.rb new file mode 100755 index 0000000000..48818a852c --- /dev/null +++ b/src/sunstone/share/OneMonitor/runOneMonitor.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, 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 'HostMonitor.rb' +require 'VMMonitor.rb' + +DEFAULT_INTERVAL= 600 #secs +DEFAULT_HOST_LOG_FOLDER = "#{ENV['ONE_LOCATION']}/logs/host/" +DEFAULT_VM_LOG_FOLDER = "#{ENV['ONE_LOCATION']}/logs/vm/" + + +#ARG0=interval, ARG1=hostfolder, ARG2=vmfolder +MONITOR_INTERVAL= ARGV[0]? ARGV[0].to_i : DEFAULT_INTERVAL #secs +HOST_LOG_FOLDER= ARGV[1]? ARGV[1]: DEFAULT_HOST_LOG_FOLDER +VM_LOG_FOLDER=ARGV[2] ? ARGV[2] : DEFAULT_VM_LOG_FOLDER + +hostm = HostMonitor.new(HOST_LOG_FOLDER) +vmm = VMMonitor.new(VM_LOG_FOLDER) + +while true do + hostm.snapshot + vmm.snapshot + sleep MONITOR_INTERVAL +end diff --git a/src/sunstone/share/OneMonitor/runOneMontitor.rb b/src/sunstone/share/OneMonitor/runOneMontitor.rb deleted file mode 100755 index 03e146a08a..0000000000 --- a/src/sunstone/share/OneMonitor/runOneMontitor.rb +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env ruby - -MONITOR_INTERVAL= ARGV[1]? ARGV[1].to_i : 10 #secs - -$: << File.dirname(__FILE__) - -require 'HostMonitor.rb' -require 'VMMonitor.rb' - -FILE= ARGV[0]? ARGV[0]: "#{ENV['ONE_LOCATION']}/logs/" - -hostm = HostMonitor.new(FILE+"host") -vmm = VMMonitor.new(FILE+"vm") - -while true do - hostm.snapshot - vmm.snapshot - sleep MONITOR_INTERVAL -end - diff --git a/src/sunstone/sunstone-server.rb b/src/sunstone/sunstone-server.rb index a19a16c06e..0ac9d9bc06 100755 --- a/src/sunstone/sunstone-server.rb +++ b/src/sunstone/sunstone-server.rb @@ -31,8 +31,12 @@ else CONFIGURATION_FILE = ONE_LOCATION+"/etc/sunstone-server.conf" end +HOST_LOG_FOLDER = LOG_LOCATION+"/OneMonitor/host" +VM_LOG_FOLDER = LOG_LOCATION+"/OneMonitor/vm" + $: << RUBY_LIB_LOCATION $: << File.dirname(__FILE__)+'/models' +$: << File.dirname(__FILE__)+'/share/OneMonitor' ############################################################################## # Required libraries @@ -91,6 +95,7 @@ helpers do session.clear return [204, ""] end + end before do @@ -161,12 +166,11 @@ end ############################################################################## get '/:resource/monitor' do - - + @SunstoneServer.get_log(params) end get '/:resource/:id/monitor' do - @SunstoneServer.get_log(params[:resource],params[:id],settings.config,params['monitor_resources'],params['history_length']) + @SunstoneServer.get_log(params) end diff --git a/src/sunstone/sunstone-server.rb.startmonitor b/src/sunstone/sunstone-server.rb.startmonitor new file mode 100755 index 0000000000..40240a2fd0 --- /dev/null +++ b/src/sunstone/sunstone-server.rb.startmonitor @@ -0,0 +1,304 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, 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. # +#--------------------------------------------------------------------------- # + +ONE_LOCATION = ENV["ONE_LOCATION"] + +if !ONE_LOCATION + LOG_LOCATION = "/var/log/one" + VAR_LOCATION = "/var/lib/one" + RUBY_LIB_LOCATION = "/usr/lib/one/ruby" + CONFIGURATION_FILE = "/etc/one/sunstone-server.conf" +else + VAR_LOCATION = ONE_LOCATION+"/var" + LOG_LOCATION = ONE_LOCATION+"/var" + RUBY_LIB_LOCATION = ONE_LOCATION+"/lib/ruby" + CONFIGURATION_FILE = ONE_LOCATION+"/etc/sunstone-server.conf" +end + +HOST_LOG_FOLDER = LOG_LOCATION+"/OneMonitor/host" +VM_LOG_FOLDER = LOG_LOCATION+"/OneMonitor/vm" + +#TODO put share +MONITOR_CMD = File.dirname(__FILE__)+'/share/OneMonitor/runOneMonitor.rb' +MONITORING_INTERVAL = 10 #seconds + +$: << RUBY_LIB_LOCATION +$: << File.dirname(__FILE__)+'/models' + +############################################################################## +# Required libraries +############################################################################## +require 'rubygems' +require 'sinatra' + +require 'cloud/Configuration' +require 'SunstoneServer' + +set :config, Configuration.new(CONFIGURATION_FILE) + +############################################################################## +# Sinatra Configuration +############################################################################## +use Rack::Session::Pool +set :host, settings.config[:host] +set :port, settings.config[:port] + +############################################################################## +# Helpers +############################################################################## +helpers do + def authorized? + session[:ip] && session[:ip]==request.ip ? true : false + end + + def build_session + auth = Rack::Auth::Basic::Request.new(request.env) + if auth.provided? && auth.basic? && auth.credentials + user = auth.credentials[0] + sha1_pass = Digest::SHA1.hexdigest(auth.credentials[1]) + + rc = SunstoneServer.authorize(user, sha1_pass) + if rc[1] + session[:user] = user + session[:user_id] = rc[1] + session[:password] = sha1_pass + session[:ip] = request.ip + session[:remember] = params[:remember] + + if params[:remember] + env['rack.session.options'][:expire_after] = 30*60*60*24 + end + + return [204, ""] + else + return [rc.first, ""] + end + end + + return [401, ""] + end + + def destroy_session + session.clear + return [204, ""] + end + + def start_monitor + if !session['monitor'] + config = settings.config + + begin + Dir.mkdir(HOST_LOG_FOLDER) if !File.directory?(HOST_LOG_FOLDER) + Dir.mkdir(VM_LOG_FOLDER) if !File.directory?(VM_LOG_FOLDER) + rescue + return [500,Error.new("Cannot create log folders")] + end + + monitoring_interval = config[:monitoring_interval] + monitoring_interval = MONITORING_INTERVAL if !monitoring_interval + + pipe = IO.popen("#{MONITOR_CMD} #{monitoring_interval} #{HOST_LOG_FOLDER} #{VM_LOG_FOLDER}") + session['monitor'] = pipe + puts "OneMonitor successfully started" + return [200,""] + end + end + + def stop_monitor + pipe = session['monitor'] + if pipe + Process.kill('KILL',pipe.pid) + pipe.close + end + return [200,""] + end +end + +before do + unless request.path=='/login' || request.path=='/' + halt 401 unless authorized? + + @SunstoneServer = SunstoneServer.new(session[:user], session[:password]) + end +end + +after do + unless request.path=='/login' || request.path=='/' + unless session[:remember] + if params[:timeout] == true + env['rack.session.options'][:defer] = true + else + env['rack.session.options'][:expire_after] = 60*10 + end + end + end +end + +############################################################################## +# HTML Requests +############################################################################## +get '/' do + redirect '/login' unless authorized? + + time = Time.now + 60 + response.set_cookie("one-user", + :value=>"#{session[:user]}", + :expires=>time) + response.set_cookie("one-user_id", + :value=>"#{session[:user_id]}", + :expires=>time) + + File.read(File.dirname(__FILE__)+'/templates/index.html') +end + +get '/login' do + File.read(File.dirname(__FILE__)+'/templates/login.html') +end + +############################################################################## +# Login +############################################################################## +post '/login' do + build_session +end + +post '/logout' do + destroy_session +end + +############################################################################## +# Config and Logs +############################################################################## +get '/config' do + @SunstoneServer.get_configuration(session[:user_id]) +end + +get '/vm/:id/log' do + @SunstoneServer.get_vm_log(params[:id]) +end + +############################################################################## +# Logs +############################################################################## + +post '/startmonitor' do + start_monitor +end + +get '/:resource/monitor' do + @SunstoneServer.get_log(params[:resource], + -1, + settings.config, + params['monitor_resources'], + params['history_length']) +end + +get '/:resource/:id/monitor' do + @SunstoneServer.get_log(params[:resource], + params[:id], + settings.config, + params['monitor_resources'], + params['history_length']) +end + + +############################################################################## +# GET Pool information +############################################################################## +get '/:pool' do + @SunstoneServer.get_pool(params[:pool]) +end + +############################################################################## +# GET Resource information +############################################################################## +get '/:resource/:id' do + @SunstoneServer.get_resource(params[:resource], params[:id]) +end + +############################################################################## +# Delete Resource +############################################################################## +delete '/:resource/:id' do + @SunstoneServer.delete_resource(params[:resource], params[:id]) +end + +############################################################################## +# Create a new Resource +############################################################################## +post '/:pool' do + @SunstoneServer.create_resource(params[:pool], request.body.read) +end + +############################################################################## +# Stop the VNC Session of a target VM +############################################################################## +post '/vm/:id/stopvnc' do + vm_id = params[:id] + vnc_hash = session['vnc'] + + if !vnc_hash || !vnc_hash[vm_id] + msg = "It seems there is no VNC proxy running for this machine" + return [403, OpenNebula::Error.new(msg).to_json] + end + + rc = @SunstoneServer.stopvnc(vm_id, vnc_hash[vm_id][:pipe]) + if rc[0] == 200 + session['vnc'].delete(vm_id) + end + + rc +end + +############################################################################## +# Start a VNC Session for a target VM +############################################################################## +post '/vm/:id/startvnc' do + vm_id = params[:id] + + vnc_hash = session['vnc'] + + if !vnc_hash + session['vnc']= {} + elsif vnc_hash[vm_id] + #return existing information + info = vnc_hash[vm_id].clone + info.delete(:pipe) + + return [200, info.to_json] + end + + rc = @SunstoneServer.startvnc(vm_id, settings.config) + if rc[0] == 200 + info = rc[1] + session['vnc'][vm_id] = info.clone + info.delete(:pipe) + + [200, info.to_json] + else + rc + end +end + +############################################################################## +# Perform an action on a Resource +############################################################################## +post '/:resource/:id/action' do + @SunstoneServer.perform_action(params[:resource], params[:id], request.body.read) +end