1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-23 22:50:09 +03:00

Merge branch 'master' of git.opennebula.org:one

This commit is contained in:
Jaime Melis 2013-07-12 12:29:18 +02:00
commit d636981a6b
38 changed files with 10680 additions and 45 deletions

View File

@ -43,6 +43,7 @@ usage() {
echo "-s: install OpenNebula Sunstone"
echo "-G: install OpenNebula Gate"
echo "-o: install OpenNebula Zones (OZones)"
echo "-f: install OpenNebula Flow"
echo "-r: remove Opennebula, only useful if -d was not specified, otherwise"
echo " rm -rf \$ONE_LOCATION would do the job"
echo "-l: creates symlinks instead of copying files, useful for development"
@ -72,6 +73,7 @@ CLIENT="no"
ONEGATE="no"
SUNSTONE="no"
OZONES="no"
ONEFLOW="no"
ONEADMIN_USER=`id -u`
ONEADMIN_GROUP=`id -g`
SRC_DIR=$PWD
@ -86,6 +88,7 @@ while true ; do
-G) ONEGATE="yes"; shift ;;
-s) SUNSTONE="yes"; shift ;;
-o) OZONES="yes"; shift ;;
-f) ONEFLOW="yes"; shift ;;
-u) ONEADMIN_USER="$2" ; shift 2;;
-g) ONEADMIN_GROUP="$2"; shift 2;;
-d) ROOT="$2" ; shift 2 ;;
@ -109,6 +112,7 @@ if [ -z "$ROOT" ] ; then
ONEGATE_LOCATION="$LIB_LOCATION/onegate"
SUNSTONE_LOCATION="$LIB_LOCATION/sunstone"
OZONES_LOCATION="$LIB_LOCATION/ozones"
ONEFLOW_LOCATION="$LIB_LOCATION/oneflow"
SYSTEM_DS_LOCATION="$VAR_LOCATION/datastores/0"
DEFAULT_DS_LOCATION="$VAR_LOCATION/datastores/1"
RUN_LOCATION="/var/run/one"
@ -144,13 +148,20 @@ if [ -z "$ROOT" ] ; then
DELETE_DIRS="$MAKE_DIRS"
CHOWN_DIRS=""
elif [ "$ONEFLOW" = "yes" ]; then
MAKE_DIRS="$BIN_LOCATION $LIB_LOCATION $VAR_LOCATION $ONEFLOW_LOCATION \
$ETC_LOCATION"
DELETE_DIRS="$MAKE_DIRS"
CHOWN_DIRS=""
else
MAKE_DIRS="$BIN_LOCATION $LIB_LOCATION $ETC_LOCATION $VAR_LOCATION \
$INCLUDE_LOCATION $SHARE_LOCATION \
$LOG_LOCATION $RUN_LOCATION $LOCK_LOCATION \
$SYSTEM_DS_LOCATION $DEFAULT_DS_LOCATION $MAN_LOCATION \
$VM_LOCATION $ONEGATE_LOCATION"
$VM_LOCATION $ONEGATE_LOCATION $ONEFLOW_LOCATION"
DELETE_DIRS="$LIB_LOCATION $ETC_LOCATION $LOG_LOCATION $VAR_LOCATION \
$RUN_LOCATION $SHARE_DIRS"
@ -166,6 +177,7 @@ else
ONEGATE_LOCATION="$LIB_LOCATION/onegate"
SUNSTONE_LOCATION="$LIB_LOCATION/sunstone"
OZONES_LOCATION="$LIB_LOCATION/ozones"
ONEFLOW_LOCATION="$LIB_LOCATION/oneflow"
SYSTEM_DS_LOCATION="$VAR_LOCATION/datastores/0"
DEFAULT_DS_LOCATION="$VAR_LOCATION/datastores/1"
INCLUDE_LOCATION="$ROOT/include"
@ -191,12 +203,17 @@ else
MAKE_DIRS="$BIN_LOCATION $LIB_LOCATION $VAR_LOCATION $OZONES_LOCATION \
$ETC_LOCATION"
DELETE_DIRS="$MAKE_DIRS"
elif [ "$ONEFLOW" = "yes" ]; then
MAKE_DIRS="$BIN_LOCATION $LIB_LOCATION $VAR_LOCATION $ONEFLOW_LOCATION \
$ETC_LOCATION"
DELETE_DIRS="$MAKE_DIRS"
else
MAKE_DIRS="$BIN_LOCATION $LIB_LOCATION $ETC_LOCATION $VAR_LOCATION \
$INCLUDE_LOCATION $SHARE_LOCATION $SYSTEM_DS_LOCATION \
$DEFAULT_DS_LOCATION $MAN_LOCATION $OZONES_LOCATION \
$VM_LOCATION $ONEGATE_LOCATION"
$VM_LOCATION $ONEGATE_LOCATION $ONEFLOW_LOCATION"
DELETE_DIRS="$MAKE_DIRS"
@ -366,6 +383,10 @@ OZONES_CLIENT_DIRS="$LIB_LOCATION/ruby \
$LIB_LOCATION/ruby/cli/ozones_helper \
$LIB_LOCATION/ruby/zona"
ONEFLOW_DIRS="$ONEFLOW_LOCATION/lib \
$ONEFLOW_LOCATION/lib/strategy \
$ONEFLOW_LOCATION/lib/models"
LIB_ECO_CLIENT_DIRS="$LIB_LOCATION/ruby \
$LIB_LOCATION/ruby/opennebula \
$LIB_LOCATION/ruby/cloud/ \
@ -397,9 +418,11 @@ elif [ "$SUNSTONE" = "yes" ]; then
MAKE_DIRS="$MAKE_DIRS $SUNSTONE_DIRS $LIB_OCA_CLIENT_DIRS"
elif [ "$OZONES" = "yes" ]; then
MAKE_DIRS="$MAKE_DIRS $OZONES_DIRS $OZONES_CLIENT_DIRS $LIB_OCA_CLIENT_DIRS"
elif [ "$ONEFLOW" = "yes" ]; then
MAKE_DIRS="$MAKE_DIRS $ONEFLOW_DIRS $LIB_OCA_CLIENT_DIRS"
else
MAKE_DIRS="$MAKE_DIRS $SHARE_DIRS $ETC_DIRS $LIB_DIRS $VAR_DIRS \
$OZONES_DIRS $OZONES_CLIENT_DIRS $SUNSTONE_DIRS"
$OZONES_DIRS $OZONES_CLIENT_DIRS $SUNSTONE_DIRS $ONEFLOW_DIRS"
fi
#-------------------------------------------------------------------------------
@ -522,6 +545,7 @@ INSTALL_SUNSTONE_FILES=(
SUNSTONE_VIEWS_FILES:$SUNSTONE_LOCATION/views
SUNSTONE_PUBLIC_JS_FILES:$SUNSTONE_LOCATION/public/js
SUNSTONE_PUBLIC_JS_PLUGINS_FILES:$SUNSTONE_LOCATION/public/js/plugins
SUNSTONE_ROUTES_FILES:$SUNSTONE_LOCATION/routes
SUNSTONE_PUBLIC_CSS_FILES:$SUNSTONE_LOCATION/public/css
SUNSTONE_PUBLIC_VENDOR_CRYPTOJS:$SUNSTONE_LOCATION/public/vendor/crypto-js
SUNSTONE_PUBLIC_VENDOR_EXPLORERCANVAS:$SUNSTONE_LOCATION/public/vendor/explorercanvas
@ -614,6 +638,17 @@ INSTALL_OZONES_ETC_FILES=(
OZONES_ETC_FILES:$ETC_LOCATION
)
INSTALL_ONEFLOW_FILES=(
ONEFLOW_FILES:$ONEFLOW_LOCATION
ONEFLOW_BIN_FILES:$BIN_LOCATION
ONEFLOW_LIB_FILES:$ONEFLOW_LOCATION/lib
ONEFLOW_LIB_STRATEGY_FILES:$ONEFLOW_LOCATION/lib/strategy
ONEFLOW_LIB_MODELS_FILES:$ONEFLOW_LOCATION/lib/models
)
INSTALL_ONEFLOW_ETC_FILES=(
ONEFLOW_ETC_FILES:$ETC_LOCATION
)
INSTALL_ETC_FILES=(
ETC_FILES:$ETC_LOCATION
@ -647,6 +682,8 @@ BIN_FILES="src/nebula/oned \
src/cli/oneacl \
src/cli/onedatastore \
src/cli/onecluster \
src/cli/oneflow \
src/cli/oneflow-template \
src/onedb/onedb \
src/onedb/onezonedb/onezonedb \
src/mad/utils/tty_expect \
@ -1182,38 +1219,39 @@ INSTALL_GEMS_SHARE_FILE="share/install_gems/install_gems"
#-------------------------------------------------------------------------------
OCA_LIB_FILES="src/oca/ruby/opennebula.rb"
RUBY_OPENNEBULA_LIB_FILES="src/oca/ruby/opennebula/host.rb \
src/oca/ruby/opennebula/host_pool.rb \
src/oca/ruby/opennebula/pool.rb \
src/oca/ruby/opennebula/user.rb \
src/oca/ruby/opennebula/user_pool.rb \
src/oca/ruby/opennebula/virtual_machine.rb \
src/oca/ruby/opennebula/virtual_machine_pool.rb \
src/oca/ruby/opennebula/virtual_network.rb \
src/oca/ruby/opennebula/virtual_network_pool.rb \
src/oca/ruby/opennebula/image.rb \
src/oca/ruby/opennebula/image_pool.rb \
src/oca/ruby/opennebula/template.rb \
src/oca/ruby/opennebula/template_pool.rb \
src/oca/ruby/opennebula/document.rb \
src/oca/ruby/opennebula/document_pool.rb \
src/oca/ruby/opennebula/document_json.rb \
src/oca/ruby/opennebula/document_pool_json.rb \
src/oca/ruby/opennebula/group.rb \
src/oca/ruby/opennebula/group_pool.rb \
src/oca/ruby/opennebula/acl.rb \
src/oca/ruby/opennebula/acl_pool.rb \
src/oca/ruby/opennebula/datastore.rb \
src/oca/ruby/opennebula/datastore_pool.rb \
src/oca/ruby/opennebula/cluster.rb \
src/oca/ruby/opennebula/cluster_pool.rb \
src/oca/ruby/opennebula/xml_utils.rb \
src/oca/ruby/opennebula/client.rb \
src/oca/ruby/opennebula/error.rb \
src/oca/ruby/opennebula/pool_element.rb \
src/oca/ruby/opennebula/xml_element.rb \
src/oca/ruby/opennebula/xml_pool.rb \
src/oca/ruby/opennebula/system.rb"
RUBY_OPENNEBULA_LIB_FILES="src/oca/ruby/opennebula/acl_pool.rb \
src/oca/ruby/opennebula/acl.rb \
src/oca/ruby/opennebula/client.rb \
src/oca/ruby/opennebula/cluster_pool.rb \
src/oca/ruby/opennebula/cluster.rb \
src/oca/ruby/opennebula/datastore_pool.rb \
src/oca/ruby/opennebula/datastore.rb \
src/oca/ruby/opennebula/document_json.rb \
src/oca/ruby/opennebula/document_pool_json.rb \
src/oca/ruby/opennebula/document_pool.rb \
src/oca/ruby/opennebula/document.rb \
src/oca/ruby/opennebula/error.rb \
src/oca/ruby/opennebula/group_pool.rb \
src/oca/ruby/opennebula/group.rb \
src/oca/ruby/opennebula/host_pool.rb \
src/oca/ruby/opennebula/host.rb \
src/oca/ruby/opennebula/image_pool.rb \
src/oca/ruby/opennebula/image.rb \
src/oca/ruby/opennebula/pool_element.rb \
src/oca/ruby/opennebula/pool.rb \
src/oca/ruby/opennebula/system.rb \
src/oca/ruby/opennebula/template_pool.rb \
src/oca/ruby/opennebula/template.rb \
src/oca/ruby/opennebula/user_pool.rb \
src/oca/ruby/opennebula/user.rb \
src/oca/ruby/opennebula/virtual_machine_pool.rb \
src/oca/ruby/opennebula/virtual_machine.rb \
src/oca/ruby/opennebula/virtual_network_pool.rb \
src/oca/ruby/opennebula/virtual_network.rb \
src/oca/ruby/opennebula/xml_element.rb \
src/oca/ruby/opennebula/xml_pool.rb \
src/oca/ruby/opennebula/xml_utils.rb \
src/oca/ruby/opennebula/oneflow_client.rb"
#-------------------------------------------------------------------------------
# Common Cloud Files
@ -1405,6 +1443,8 @@ CLI_BIN_FILES="src/cli/onevm \
src/cli/oneacl \
src/cli/onedatastore \
src/cli/onecluster \
src/cli/oneflow \
src/cli/oneflow-template \
src/cli/oneacct"
CLI_CONF_FILES="src/cli/etc/onegroup.yaml \
@ -1432,7 +1472,8 @@ SUNSTONE_BIN_FILES="src/sunstone/bin/sunstone-server \
src/sunstone/bin/novnc-server"
SUNSTONE_ETC_FILES="src/sunstone/etc/sunstone-server.conf \
src/sunstone/etc/sunstone-views.yaml"
src/sunstone/etc/sunstone-views.yaml \
src/sunstone/etc/sunstone-oneflow.conf"
SUNSTONE_ETC_VIEW_FILES="src/sunstone/etc/sunstone-views/admin.yaml \
src/sunstone/etc/sunstone-views/user.yaml"
@ -1485,7 +1526,12 @@ SUNSTONE_PUBLIC_JS_PLUGINS_FILES="\
src/sunstone/public/js/plugins/acls-tab.js \
src/sunstone/public/js/plugins/vnets-tab.js \
src/sunstone/public/js/plugins/marketplace-tab.js \
src/sunstone/public/js/plugins/config-tab.js"
src/sunstone/public/js/plugins/config-tab.js \
src/sunstone/public/js/plugins/oneflow-dashboard.js \
src/sunstone/public/js/plugins/oneflow-services.js \
src/sunstone/public/js/plugins/oneflow-templates.js"
SUNSTONE_ROUTES_FILES="src/sunstone/routes/oneflow.rb"
SUNSTONE_PUBLIC_CSS_FILES="src/sunstone/public/css/app.css \
src/sunstone/public/css/login.css"
@ -1892,6 +1938,35 @@ OZONES_BIN_CLIENT_FILES="src/ozones/Client/bin/onevdc \
OZONES_RUBY_LIB_FILES="src/oca/ruby/OpenNebula.rb"
#-----------------------------------------------------------------------------
# OneFlow files
#-----------------------------------------------------------------------------
ONEFLOW_FILES="src/flow/oneflow-server.rb \
src/flow/config.ru"
ONEFLOW_BIN_FILES="src/flow/bin/oneflow-server"
ONEFLOW_ETC_FILES="src/flow/etc/oneflow-server.conf"
ONEFLOW_LIB_FILES="src/flow/lib/grammar.rb \
src/flow/lib/grammar.treetop \
src/flow/lib/LifeCycleManager.rb \
src/flow/lib/log.rb \
src/flow/lib/models.rb \
src/flow/lib/strategy.rb \
src/flow/lib/validator.rb"
ONEFLOW_LIB_STRATEGY_FILES="src/flow/lib/strategy/straight.rb"
ONEFLOW_LIB_MODELS_FILES="src/flow/lib/models/role.rb \
src/flow/lib/models/service_pool.rb \
src/flow/lib/models/service.rb \
src/flow/lib/models/service_template_pool.rb \
src/flow/lib/models/service_template.rb"
#-----------------------------------------------------------------------------
# MAN files
#-----------------------------------------------------------------------------
@ -1909,6 +1984,8 @@ MAN_FILES="share/man/oneauth.1.gz \
share/man/onedb.1.gz \
share/man/onedatastore.1.gz \
share/man/onecluster.1.gz \
share/man/oneflow.1.gz \
share/man/oneflow-template.1.gz \
share/man/econe-allocate-address.1.gz \
share/man/econe-associate-address.1.gz \
share/man/econe-attach-volume.1.gz \
@ -2022,9 +2099,12 @@ elif [ "$SUNSTONE" = "yes" ]; then
INSTALL_SET="${INSTALL_SUNSTONE_RUBY_FILES[@]} ${INSTALL_SUNSTONE_FILES[@]}"
elif [ "$OZONES" = "yes" ]; then
INSTALL_SET="${INSTALL_OZONES_RUBY_FILES[@]} ${INSTALL_OZONES_FILES[@]}"
elif [ "$ONEFLOW" = "yes" ]; then
INSTALL_SET="${INSTALL_ONEFLOW_FILES[@]}"
else
INSTALL_SET="${INSTALL_FILES[@]} ${INSTALL_OZONES_FILES[@]} \
${INSTALL_SUNSTONE_FILES[@]} ${INSTALL_ONEGATE_FILES[@]}"
${INSTALL_SUNSTONE_FILES[@]} ${INSTALL_ONEGATE_FILES[@]} \
${INSTALL_ONEFLOW_FILES[@]}"
fi
for i in ${INSTALL_SET[@]}; do
@ -2045,11 +2125,14 @@ if [ "$INSTALL_ETC" = "yes" ] ; then
INSTALL_ETC_SET="${INSTALL_ONEGATE_ETC_FILES[@]}"
elif [ "$OZONES" = "yes" ]; then
INSTALL_ETC_SET="${INSTALL_OZONES_ETC_FILES[@]}"
elif [ "$ONEFLOW" = "yes" ]; then
INSTALL_ETC_SET="${INSTALL_ONEFLOW_ETC_FILES[@]}"
else
INSTALL_ETC_SET="${INSTALL_ETC_FILES[@]} \
${INSTALL_SUNSTONE_ETC_FILES[@]} \
${INSTALL_OZONES_ETC_FILES[@]} \
${INSTALL_ONEGATE_ETC_FILES[@]}"
${INSTALL_ONEGATE_ETC_FILES[@]} \
${INSTALL_ONEFLOW_ETC_FILES[@]}"
fi
for i in ${INSTALL_ETC_SET[@]}; do

View File

@ -0,0 +1,63 @@
digraph AppFlow {
graph [
nodesep= 0.5,
ratio=1,
pad=0.5,
];
node [fontname="Helvetica"];
edge [fontname="Menlo"];
user [
fillcolor="black",
style="filled", shape="box",
fontcolor="white", fontsize= "24.0"];
ANY [ shape="box" ];
end [ width=0.15, height=0.15, shape=point ];
running [ label="running /\nwarning"];
subgraph { rank = min; user; pending}
subgraph { rank = same; deploying; failed_deploying; color="white"}
subgraph { rank = same; running; scaling; failed_scaling; color="white"}
subgraph { rank = same; undeploying; failed_undeploying; color="white"}
// subgraph { rank = same; done; end; color="white"}
user -> pending [label="instantiate"];
pending -> deploying [style="dashed"];
deploying -> running [style="dashed"];
running -> undeploying [label="shutdown"];
undeploying -> done [style="dashed"];
done -> end [label="delete"];
// running -> warning [style="dotted"];
// warning -> running [style="dotted"];
running -> scaling [style="dashed"];
scaling -> cooldown [style="dashed"];
cooldown -> running [style="dashed"];
cooldown -> failed_undeploying [style="invis"];
failed_deploying -> deploying [label="recover"];
deploying -> failed_deploying [style="dotted"];
failed_undeploying -> undeploying [label="recover"];
undeploying -> failed_undeploying [style="dotted"];
failed_scaling -> scaling [label="recover"];
scaling -> failed_scaling [style="dotted"];
ANY -> end [label="delete"];
}

View File

@ -0,0 +1,34 @@
{
"name": "my_first_service",
"roles": [
{
"name": "frontend",
"cardinality": 1,
"parents": [
"mysql_server",
"kvm_host",
"nfs_server"
],
"vm_template": 3
},
{
"name": "nfs_server",
"cardinality": 1,
"vm_template": 2
},
{
"name": "mysql_server",
"cardinality": 1,
"vm_template": 5
},
{
"name": "kvm_host",
"parents": [
"nfs_server"
],
"cardinality": 3,
"vm_template": 7
}
],
"deployment": "none"
}

View File

@ -0,0 +1,57 @@
{
"name": "scalability",
"deployment": "straight",
"roles": [
{
"name": "frontend",
"cardinality": 1,
"vm_template": 0,
"min_vms" : 1,
"max_vms" : 5,
"elasticity_policies" : [
{
// +2 VMs when the exp. is true for 3 times in a row,
// separated by 10 seconds
"type" : "CHANGE",
"expression" : "ATT > 50",
"adjust" : 2,
"period_number" : 3,
"period" : 10
},
{
// -10 percent VMs when the exp. is true.
// If 10 percent is less than 2, -2 VMs.
"type" : "PERCENTAGE_CHANGE",
"expression" : "ATT < 10",
"adjust" : -10,
"min_adjust_step" : 2
},
{
// Set cardinality to 4 when the exp. is true
"type" : "CARDINALITY",
"expression" : "SOME_ATT <= 50 && OTHER_ATT > 90",
"adjust" : 4
}
],
"scheduled_policies" : [
{
// Set cardinality to 2 each 10 minutes
"type" : "CARDINALITY",
"recurrence" : "*/10 * * * *",
"adjust" : 2
},
{
// +10 percent at the given date and time
"type" : "PERCENTAGE_CHANGE",
"start_time" : "2nd oct 2013 15:45",
"adjust" : 10
}
]
}
]
}

View File

@ -0,0 +1,35 @@
{
"name": "my_first_service",
"deployment": "straight",
"roles": [
{
"name": "frontend",
"cardinality": 1,
"parents": [
"mysql_server",
"kvm_host",
"nfs_server"
],
"vm_template": 3
},
{
"name": "nfs_server",
"cardinality": 1,
"vm_template": 2
},
{
"name": "mysql_server",
"cardinality": 1,
"vm_template": 5
},
{
"name": "kvm_host",
"parents": [
"nfs_server"
],
"cardinality": 3,
"vm_template": 7
}
]
}

View File

@ -4,7 +4,7 @@ require 'pp'
$nokogiri='nokogiri'
DEFAULT=%w{sunstone quota cloud ozones_server auth_ldap vmware}
DEFAULT=%w{sunstone quota cloud ozones_server auth_ldap vmware oneflow}
if defined?(RUBY_VERSION) && RUBY_VERSION>="1.8.7"
SQLITE='sqlite3'
@ -23,7 +23,8 @@ GROUPS={
:ozones_server_sqlite => %w{json sequel}<<SQLITE,
:ozones_server_mysql => %w{json sequel mysql},
:auth_ldap => 'net-ldap',
:vmware => 'builder'
:vmware => 'builder',
:oneflow => %w{sinatra json treetop parse-cron}
}
PACKAGES=GROUPS.keys

673
src/cli/oneflow Executable file
View File

@ -0,0 +1,673 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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
RUBY_LIB_LOCATION="/usr/lib/one/ruby"
else
RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby"
end
$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+'/cli'
require 'command_parser'
require 'opennebula/oneflow_client'
require 'cli_helper'
require 'one_helper/onevm_helper'
require 'json'
USER_AGENT = "CLI"
# Base Path representing the resource to be used in the requests
RESOURCE_PATH = "/service"
#
# Table
#
SERVICE_TABLE = CLIHelper::ShowTable.new(nil, self) do
column :ID, "ID", :size=>10 do |d|
d["ID"]
end
column :USER, "Username", :left, :size=>15 do |d|
d["UNAME"]
end
column :GROUP, "Group", :left, :size=>15 do |d|
d["GNAME"]
end
column :NAME, "Name", :size=>25, :left=>true do |d|
d["NAME"]
end
column :STATE, "State", :size=>11, :left=>true do |d|
Service.state_str(d["TEMPLATE"]["BODY"]['state'])
end
default :ID, :USER, :GROUP, :NAME, :STATE
end
NODE_TABLE = CLIHelper::ShowTable.new(nil, self) do
column :VM_ID, "ONE identifier for Virtual Machine", :size=>6 do |d|
st = ""
if d['scale_up']
st << "\u2191 "
elsif d['disposed']
st << "\u2193 "
end
if d['vm_info'].nil?
st << d['deploy_id'].to_s
else
st << d['vm_info']['VM']["ID"]
end
st
end
column :NAME, "Name of the Virtual Machine", :left,
:size=>23 do |d|
if !d['vm_info'].nil?
if d['vm_info']['VM']["RESCHED"] == "1"
"*#{d["NAME"]}"
else
d['vm_info']['VM']["NAME"]
end
else
""
end
end
column :USER, "Username of the Virtual Machine owner", :left,
:size=>8 do |d|
if !d['vm_info'].nil?
d['vm_info']['VM']["UNAME"]
else
""
end
end
column :GROUP, "Group of the Virtual Machine", :left,
:size=>8 do |d|
if !d['vm_info'].nil?
d['vm_info']['VM']["GNAME"]
else
""
end
end
column :STAT, "Actual status", :size=>4 do |d,e|
if !d['vm_info'].nil?
OneVMHelper.state_to_str(d['vm_info']['VM']["STATE"], d['vm_info']['VM']["LCM_STATE"])
else
""
end
end
column :UCPU, "CPU percentage used by the VM", :size=>4 do |d|
if !d['vm_info'].nil?
d['vm_info']['VM']["CPU"]
else
""
end
end
column :UMEM, "Memory used by the VM", :size=>7 do |d|
if !d['vm_info'].nil?
OpenNebulaHelper.unit_to_str(d['vm_info']['VM']["MEMORY"].to_i, {})
else
""
end
end
column :HOST, "Host where the VM is running", :left, :size=>20 do |d|
if !d['vm_info'].nil?
if d['vm_info']['VM']['HISTORY_RECORDS'] && d['vm_info']['VM']['HISTORY_RECORDS']['HISTORY']
state_str = VirtualMachine::VM_STATE[d['vm_info']['VM']['STATE'].to_i]
if %w{ACTIVE SUSPENDED}.include? state_str
history = if d['vm_info']['VM']['HISTORY_RECORDS']['HISTORY'].instance_of?(Array)
d['vm_info']['VM']['HISTORY_RECORDS']['HISTORY'].last
else
d['vm_info']['VM']['HISTORY_RECORDS']['HISTORY']
end
history['HOSTNAME']
end
end
else
""
end
end
column :TIME, "Time since the VM was submitted", :size=>10 do |d|
if !d['vm_info'].nil?
stime = d['vm_info']['VM']["STIME"].to_i
etime = d['vm_info']['VM']["ETIME"]=="0" ? Time.now.to_i : d['vm_info']['VM']["ETIME"].to_i
dtime = etime-stime
OpenNebulaHelper.period_to_str(dtime, false)
else
""
end
end
default :VM_ID, :NAME, :STAT, :UCPU, :UMEM, :HOST, :TIME
end
# List the services. This method is used in top and list commands
# @param [Service::Client] client
# @param [Array] args
# @param [Hash] options
# @return [[Integer, String], Integer] Returns the exit_code and optionally
# a String to be printed
def list_services(client, args, options)
response = client.get(RESOURCE_PATH)
if CloudClient::is_error?(response)
[response.code.to_i, response.to_s]
else
#[0,response.body]
if options[:json]
[0,response.body]
else
array_list = JSON.parse(response.body)
SERVICE_TABLE.show(array_list['DOCUMENT_POOL']['DOCUMENT'])
0
end
end
end
# Show the service information. This method is used in top and show commands
# @param [Service::Client] client
# @param [Array] args
# @param [Hash] options
# @return [[Integer, String], Integer] Returns the exit_code and optionally
# a String to be printed
def show_service(client, args, options)
response = client.get("#{RESOURCE_PATH}/#{args[0]}")
if CloudClient::is_error?(response)
[response.code.to_i, response.to_s]
else
#[0,response.body]
if options[:json]
[0,response.body]
else
str="%-20s: %-20s"
str_h1="%-80s"
document_hash = JSON.parse(response.body)
template = document_hash['DOCUMENT']['TEMPLATE']['BODY']
CLIHelper.print_header(str_h1 % "SERVICE #{document_hash['DOCUMENT']['ID']} INFORMATION")
puts str % ["ID", document_hash['DOCUMENT']['ID']]
puts str % ["NAME", document_hash['DOCUMENT']['NAME']]
puts str % ["USER", document_hash['DOCUMENT']['UNAME']]
puts str % ["GROUP",document_hash['DOCUMENT']['GNAME']]
puts str % ["STRATEGY", template['deployment']]
puts str % ["SERVICE STATE", Service.state_str(template['state'])]
puts str % ["SHUTDOWN", template['shutdown_action']] if template['shutdown_action']
puts
CLIHelper.print_header(str_h1 % "PERMISSIONS",false)
["OWNER", "GROUP", "OTHER"].each { |e|
mask = "---"
mask[0] = "u" if document_hash['DOCUMENT']['PERMISSIONS']["#{e}_U"] == "1"
mask[1] = "m" if document_hash['DOCUMENT']['PERMISSIONS']["#{e}_M"] == "1"
mask[2] = "a" if document_hash['DOCUMENT']['PERMISSIONS']["#{e}_A"] == "1"
puts str % [e, mask]
}
puts
template['roles'].each {|role|
CLIHelper.print_header("ROLE #{role['name']}", false)
puts str % ["ROLE STATE", Role.state_str(role['state'])]
puts str % ["PARENTS", role['parents'].join(', ')] if role['parents']
puts str % ["VM TEMPLATE", role['vm_template']]
puts str % ["CARNIDALITY", role['cardinality']]
puts str % ["MIN VMS", role['min_vms']] if role['min_vms']
puts str % ["MAX VMS", role['max_vms']] if role['max_vms']
puts str % ["SHUTDOWN", role['shutdown_action']] if role['shutdown_action']
puts "NODES INFORMATION"
NODE_TABLE.show(role['nodes'])
if !role['elasticity_policies'].nil? || !role['scheduled_policies'].nil?
puts
puts "ELASTICITY RULES"
puts str % ["COOLDOWN", "#{role['cooldown']}s"] if role['cooldown']
if role['elasticity_policies']
puts
# puts "ELASTICITY POLICIES"
CLIHelper::ShowTable.new(nil, self) do
column :ADJUST, "", :left, :size=>12 do |d|
adjust_str(d)
end
column :EXPRESSION, "", :left, :size=>48 do |d|
if !d['expression_evaluated'].nil?
d['expression_evaluated']
else
d['expression']
end
end
column :'EVALS', "", :left, :size=>5 do |d|
"#{d['true_evals'].to_i} / #{d['period_number']}"
end
column :PERIOD, "", :size=>6 do |d|
"#{d['period']}s"
end
column :COOL, "", :size=>5 do |d|
d['cooldown'] ? "#{d['cooldown']}s" : '-'
end
default :ADJUST, :EXPRESSION, :EVALS, :PERIOD, :COOL
end.show([role['elasticity_policies']].flatten, {})
end
if role['scheduled_policies']
puts
# puts "SCHEDULED POLICIES"
CLIHelper::ShowTable.new(nil, self) do
column :ADJUST, "", :left, :size=>12 do |d|
adjust_str(d)
end
column :TIME, "", :left, :size=>67 do |d|
if d['start_time']
Time.parse(d['start_time']).to_s
else
d['recurrence']
end
end
default :ADJUST, :TIME
end.show([role['scheduled_policies']].flatten, {})
end
end
puts
}
puts
CLIHelper.print_header(str_h1 % "LOG MESSAGES",false)
if template['log']
template['log'].each { |log|
t = Time.at(log['timestamp']).strftime("%m/%d/%y %H:%M")
puts "#{t} [#{log['severity']}] #{log['message']}"
}
end
0
end
end
end
def adjust_str(policy)
sign = policy['adjust'].to_i >= 0 ? "+" : "-"
adjust = policy['adjust'].to_i.abs
case policy['type']
when 'CARDINALITY'
"= #{adjust}"
when 'PERCENTAGE_CHANGE'
st = "#{sign} #{adjust} %"
if policy['min_adjust_step']
st << " (#{policy['min_adjust_step']})"
end
st
else
"#{sign} #{adjust}"
end
end
#
# Commands
#
cmd=CommandParser::CmdParser.new(ARGV) do
usage "`oneflow` <command> [<args>] [<options>]"
version OpenNebulaHelper::ONE_VERSION
set :option, Service::DEFAULT_OPTIONS
set :option, CommandParser::VERSION
set :option, CommandParser::HELP
#
# Formatters for arguments
#
set :format, :groupid, OpenNebulaHelper.rname_to_id_desc("GROUP") do |arg|
OpenNebulaHelper.rname_to_id(arg, "GROUP")
end
set :format, :userid, OpenNebulaHelper.rname_to_id_desc("USER") do |arg|
OpenNebulaHelper.rname_to_id(arg, "USER")
end
set :format, :service_id, Service.rname_to_id_desc("SERVICE") do |arg|
Service.rname_to_id(arg, "SERVICE")
end
set :format, :service_id_list, Service.list_to_id_desc("SERVICE") do |arg|
Service.list_to_id(arg, "SERVICE")
end
set :format, :vm_action, "Actions supported: #{Role::SCHEDULE_ACTIONS.join(', ')}" do |arg|
if Role::SCHEDULE_ACTIONS.include?(arg)
[0, arg]
else
[-1, "Action #{arg} is not supported. Actions supported: #{Role::SCHEDULE_ACTIONS.join(', ')}"]
end
end
#
# List
#
list_desc = <<-EOT.unindent
List the available services
EOT
command :list, list_desc, :options => Service::JSON_FORMAT do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
list_services(client, args, options)
end
#
# Show
#
show_desc = <<-EOT.unindent
Show detailed information of a given service
EOT
command :show, show_desc, :service_id, :options => Service::JSON_FORMAT do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
show_service(client, args, options)
end
#
# Top
#
top_desc = <<-EOT.unindent
Top the services or the extended information of the target service if a
id is specified
EOT
command :top, top_desc, [:service_id, nil],
:options => [Service::JSON_FORMAT, Service::TOP, CLIHelper::DELAY] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
delay=options[:delay] ? options[:delay] : 3
begin
while true
CLIHelper.scr_cls
CLIHelper.scr_move(0,0)
if args[0]
rc, message = show_service(client, args, options)
if rc != 0
raise message
end
else
rc, message = list_services(client, args, options)
if rc != 0
raise message
end
end
sleep delay
end
rescue Exception => e
puts e.message
-1
end
end
#
# Delete
#
delete_desc = <<-EOT.unindent
Delete a given service
EOT
command :delete, delete_desc, [:range, :service_id_list] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
client.delete("#{RESOURCE_PATH}/#{service_id}")
}
end
#
# Shutdown
#
shutdown_desc = <<-EOT.unindent
Shutdown a service.
From RUNNING or WARNING shuts down the Service
EOT
command :shutdown, shutdown_desc, [:range, :service_id_list] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
json_action = Service.build_json_action('shutdown')
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
#
# Recover
#
recover_desc = <<-EOT.unindent
Recover a failed service, cleaning the failed VMs.
From FAILED_DEPLOYING continues deploying the Service
From FAILED_SCALING continues scaling the Service
From FAILED_UNDEPLOYING continues shutting down the Service
From COOLDOWN the Service is set to running ignoring the cooldown duration
From WARNING failed VMs are deleted, and new VMs are instantiated
EOT
command :recover, recover_desc, [:range, :service_id_list] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
json_action = Service.build_json_action('recover')
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
#
# Scale
#
scale_desc = <<-EOT.unindent
Scale a role to the given cardinality
EOT
command :'scale', scale_desc, :service_id, :role_name,
:cardinality, :options => [Service::FORCE] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
if !(args[2] =~ /^\d+$/)
puts "Cardinality must be an integer number"
exit -1
end
exit_code = 0
json = "{ \"cardinality\" : #{args[2]},\n"<<
" \"force\" : #{options[:force] == true} }"
response = client.put("#{RESOURCE_PATH}/#{args[0]}/role/#{args[1]}", json)
if CloudClient::is_error?(response)
puts response.to_s
exit_code = response.code.to_i
end
exit_code
end
chgrp_desc = <<-EOT.unindent
Changes the service group
EOT
command :chgrp, chgrp_desc, [:range, :service_id_list], :groupid do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
params = Hash.new
params['group_id'] = args[1].to_i
json_action = Service.build_json_action('chgrp', params)
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
chown_desc = <<-EOT.unindent
Changes the service owner and group
EOT
command :chown, chown_desc, [:range, :service_id_list], :userid, [:groupid, nil] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
params = Hash.new
params['owner_id'] = args[1]
params['group_id'] = args[2] if args[2]
json_action = Service.build_json_action('chown', params)
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
chmod_desc = <<-EOT.unindent
Changes the service permissions
EOT
command :chmod, chmod_desc, [:range, :service_id_list], :octet do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
params = Hash.new
params['octet'] = args[1]
json_action = Service.build_json_action('chmod', params)
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
action_desc = <<-EOT.unindent
Perform an action on all the Virtual Machines of a given role.
Actions supported: #{Role::SCHEDULE_ACTIONS.join(",")}
EOT
command :"action", action_desc, :service_id, :role_name, :vm_action,
:options => [Service::PERIOD, Service::NUMBER] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions([args[0]]) { |service_id|
params = Hash.new
params[:period] = options[:period].to_i if options[:period]
params[:number] = options[:number].to_i if options[:number]
json_action = Service.build_json_action(args[2], params)
client.post("#{RESOURCE_PATH}/#{service_id}/role/#{args[1]}/action", json_action)
}
end
end

451
src/cli/oneflow-template Executable file
View File

@ -0,0 +1,451 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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
RUBY_LIB_LOCATION="/usr/lib/one/ruby"
else
RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby"
end
$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+'/cli'
require 'command_parser'
require 'opennebula/oneflow_client'
require 'cli_helper'
require 'one_helper'
require 'json'
USER_AGENT = "CLI"
# Base Path representing the resource to be used in the requests
RESOURCE_PATH = '/service_template'
#
# Table
#
TABLE = CLIHelper::ShowTable.new(nil, self) do
column :ID, "ID", :size=>10 do |d|
d["ID"]
end
column :USER, "Username", :left, :size=>15 do |d|
d["UNAME"]
end
column :GROUP, "Group", :left, :size=>15 do |d|
d["GNAME"]
end
column :NAME, "Name", :left, :size=>37 do |d|
d["NAME"]
end
default :ID, :USER, :GROUP, :NAME
end
# Show the service template information. This method is used in top and
# show commands
# @param [Service::Client] client
# @param [Array] args
# @param [Hash] options
# @return [[Integer, String], Integer] Returns the exit_code and optionally
# a String to be printed
def show_service_template(client, args, options)
response = client.get("#{RESOURCE_PATH}/#{args[0]}")
if CloudClient::is_error?(response)
[response.code.to_i, response.to_s]
else
#[0,response.body]
if options[:json]
[0,response.body]
else
str="%-20s: %-20s"
str_h1="%-80s"
document_hash = JSON.parse(response.body)
template = document_hash['DOCUMENT']['TEMPLATE']['BODY']
CLIHelper.print_header(str_h1 %
"SERVICE TEMPLATE #{document_hash['DOCUMENT']['ID']} INFORMATION")
puts str % ["ID", document_hash['DOCUMENT']['ID']]
puts str % ["NAME", document_hash['DOCUMENT']['NAME']]
puts str % ["USER", document_hash['DOCUMENT']['UNAME']]
puts str % ["GROUP",document_hash['DOCUMENT']['GNAME']]
puts
CLIHelper.print_header(str_h1 % "PERMISSIONS",false)
["OWNER", "GROUP", "OTHER"].each { |e|
mask = "---"
mask[0] = "u" if document_hash['DOCUMENT']['PERMISSIONS']["#{e}_U"] == "1"
mask[1] = "m" if document_hash['DOCUMENT']['PERMISSIONS']["#{e}_M"] == "1"
mask[2] = "a" if document_hash['DOCUMENT']['PERMISSIONS']["#{e}_A"] == "1"
puts str % [e, mask]
}
puts
CLIHelper.print_header(str_h1 % "TEMPLATE CONTENTS",false)
puts JSON.pretty_generate(template)
0
end
end
end
# List the services. This method is used in top and list commands
# @param [Service::Client] client
# @param [Array] args
# @param [Hash] options
# @return [[Integer, String], Integer] Returns the exit_code and optionally
# a String to be printed
def list_service_templates(client, args, options)
response = client.get(RESOURCE_PATH)
if CloudClient::is_error?(response)
[response.code.to_i, response.to_s]
else
#[0,response.body]
if options[:json]
[0,response.body]
else
array_list = JSON.parse(response.body)
TABLE.show(array_list['DOCUMENT_POOL']['DOCUMENT'])
0
end
end
end
#
# Commands
#
cmd=CommandParser::CmdParser.new(ARGV) do
usage "`oneflow-template` <command> [<args>] [<options>]"
version OpenNebulaHelper::ONE_VERSION
set :option, Service::DEFAULT_OPTIONS
set :option, CommandParser::VERSION
set :option, CommandParser::HELP
#
# Formatters for arguments
#
set :format, :groupid, OpenNebulaHelper.rname_to_id_desc("GROUP") do |arg|
OpenNebulaHelper.rname_to_id(arg, "GROUP")
end
set :format, :userid, OpenNebulaHelper.rname_to_id_desc("USER") do |arg|
OpenNebulaHelper.rname_to_id(arg, "USER")
end
set :format, :template_id, Service.rname_to_id_desc("SERVICE TEMPLATE") do |arg|
Service.rname_to_id(arg, "SERVICE TEMPLATE")
end
set :format, :templateid_list, Service.list_to_id_desc("SERVICE TEMPLATE") do |arg|
Service.list_to_id(arg, "SERVICE TEMPLATE")
end
#
# List
#
list_desc = <<-EOT.unindent
List the available Service Templates
EOT
command :list, list_desc, :options => Service::JSON_FORMAT do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
list_service_templates(client, args, options)
end
#
# Top
#
top_desc = <<-EOT.unindent
List the available Service Templates continuously
EOT
command :top, top_desc,
:options => [Service::JSON_FORMAT, Service::TOP, CLIHelper::DELAY] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
delay=options[:delay] ? options[:delay] : 3
begin
while true
CLIHelper.scr_cls
CLIHelper.scr_move(0,0)
rc, message = list_service_templates(client, args, options)
if rc != 0
raise message
end
sleep delay
end
rescue Exception => e
puts e.message
-1
end
end
#
# Create
#
create_desc = <<-EOT.unindent
Create a new Service Template
EOT
command :create, create_desc, :file, :options => Service::JSON_FORMAT do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
response = client.post(RESOURCE_PATH, File.read(args[0]))
if CloudClient::is_error?(response)
[response.code.to_i, response.to_s]
else
if options[:json]
[0,response.body]
else
template = JSON.parse(response.body)
puts "ID: #{template['DOCUMENT']['ID']}"
0
end
end
end
#
# Show
#
show_desc = <<-EOT.unindent
Show detailed information of a given Service Template
EOT
command :show, show_desc, :template_id, :options => Service::JSON_FORMAT do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
show_service_template(client, args, options)
end
#
# Delete
#
delete_desc = <<-EOT.unindent
Delete a given Service Template
EOT
command :delete, delete_desc, [:range, :templateid_list] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |template_id|
client.delete("#{RESOURCE_PATH}/#{template_id}")
}
end
#
# Instantiate
#
instantiate_desc = <<-EOT.unindent
Instantiate a Service Template
EOT
command :instantiate, instantiate_desc, :template_id,
:options => [Service::JSON_FORMAT, Service::TOP] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
json_str = Service.build_json_action('instantiate')
response = client.post("#{RESOURCE_PATH}/#{args[0]}/action", json_str)
if CloudClient::is_error?(response)
[response.code.to_i, response.to_s]
else
if options[:json]
[0,response.body]
else
template = JSON.parse(response.body)
puts "ID: #{template['DOCUMENT']['ID']}"
0
end
end
end
chgrp_desc = <<-EOT.unindent
Changes the service template group
EOT
command :chgrp, chgrp_desc, [:range, :templateid_list], :groupid do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
params = Hash.new
params['group_id'] = args[1].to_i
json_action = Service.build_json_action('chgrp', params)
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
chown_desc = <<-EOT.unindent
Changes the service template owner and group
EOT
command :chown, chown_desc, [:range, :templateid_list], :userid, [:groupid, nil] do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
params = Hash.new
params['owner_id'] = args[1]
params['group_id'] = args[2] if args[2]
json_action = Service.build_json_action('chown', params)
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
chmod_desc = <<-EOT.unindent
Changes the service template permissions
EOT
command :chmod, chmod_desc, [:range, :templateid_list], :octet do
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
Service.perform_actions(args[0]) { |service_id|
params = Hash.new
params['octet'] = args[1]
json_action = Service.build_json_action('chmod', params)
client.post("#{RESOURCE_PATH}/#{service_id}/action", json_action)
}
end
update_desc = <<-EOT.unindent
Update the template contents. If a path is not provided the editor will
be launched to modify the current content.
EOT
command :update, update_desc, :templateid, [:file, nil] do
template_id = args[0]
client = Service::Client.new(
:username => options[:username],
:password => options[:password],
:url => options[:server],
:user_agent => USER_AGENT)
if args[1]
path = args[1]
else
require 'tempfile'
tmp = Tempfile.new(template_id.to_s)
path = tmp.path
response = client.get("#{RESOURCE_PATH}/#{template_id}")
if CloudClient::is_error?(response)
exit_with_code response.code.to_i, response.to_s
else
document_hash = JSON.parse(response.body)
template = document_hash['DOCUMENT']['TEMPLATE']['BODY']
tmp << JSON.pretty_generate(template)
tmp.flush
editor_path = ENV["EDITOR"] ? ENV["EDITOR"] : OpenNebulaHelper::EDITOR_PATH
system("#{editor_path} #{path}")
unless $?.exitstatus == 0
puts "Editor not defined"
exit -1
end
tmp.close
end
end
exit_code = 0
t_str = File.read(path)
response = client.put("#{RESOURCE_PATH}/#{template_id}", t_str)
if CloudClient::is_error?(response)
puts response.to_s
exit_code = response.code.to_i
end
exit_code
end
end

7
src/flow/Gemfile Normal file
View File

@ -0,0 +1,7 @@
source "http://rubygems.org"
gem 'sinatra'
gem 'json'
gem 'xml-simple'
gem 'treetop'
gem 'parse-cron'

28
src/flow/Gemfile.lock Normal file
View File

@ -0,0 +1,28 @@
GEM
remote: http://rubygems.org/
specs:
json (1.7.3)
parse-cron (0.1.2)
polyglot (0.3.3)
rack (1.4.1)
rack-protection (1.2.0)
rack
sinatra (1.3.2)
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
tilt (1.3.3)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
xml-simple (1.1.1)
PLATFORMS
ruby
DEPENDENCIES
json
parse-cron
sinatra
treetop
xml-simple

123
src/flow/bin/oneflow-server Executable file
View File

@ -0,0 +1,123 @@
#! /bin/sh
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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. #
#--------------------------------------------------------------------------- #
if [ -z "$ONE_LOCATION" ]; then
FLOW_PID=/var/run/one/oneflow.pid
FLOW_SERVER=/usr/lib/one/oneflow/oneflow-server.rb
FLOW_LOCK_FILE=/var/lock/one/.oneflow.lock
FLOW_LOG=/var/log/one/oneflow.log
FLOW_LOG_ERROR=/var/log/one/oneflow.error
FLOW_CONF=/etc/one/oneflow-server.conf
else
FLOW_PID=$ONE_LOCATION/var/oneflow.pid
FLOW_SERVER=$ONE_LOCATION/lib/oneflow/oneflow-server.rb
FLOW_LOCK_FILE=$ONE_LOCATION/var/.oneflow.lock
FLOW_LOG=$ONE_LOCATION/var/oneflow.log
FLOW_LOG_ERROR=$ONE_LOCATION/var/oneflow.error
FLOW_CONF=$ONE_LOCATION/etc/oneflow-server.conf
fi
setup()
{
if [ -f $FLOW_LOCK_FILE ]; then
if [ -f $FLOW_PID ]; then
FLOWPID=`cat $FLOW_PID`
ps $FLOWPID > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "OneFlow Server is still running (PID:$FLOWPID)."
echo "Please try 'oneflow-server stop' first."
exit 1
fi
fi
echo "Stale .lock detected. Erasing it."
rm $FLOW_LOCK_FILE
fi
}
start()
{
if [ ! -f "$FLOW_SERVER" ]; then
echo "Cannot find $FLOW_SERVER."
exit 1
fi
touch $FLOW_LOCK_FILE
# Start the oneflow-server daemon
ruby $FLOW_SERVER >$FLOW_LOG 2>$FLOW_LOG_ERROR &
LASTRC=$?
LASTPID=$!
if [ $LASTRC -ne 0 ]; then
echo "Error executing oneflow-server."
echo "Check $FLOW_LOG_ERROR and $FLOW_LOG for more information"
exit 1
else
echo $LASTPID > $FLOW_PID
fi
sleep 2
ps $LASTPID > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Error executing oneflow-server."
echo "Check $FLOW_LOG_ERROR and $FLOW_LOG for more information"
exit 1
fi
echo "oneflow-server started"
}
#
# Function that stops the daemon/service
#
stop()
{
if [ ! -f $FLOW_PID ]; then
echo "Couldn't find oneflow-server process pid."
exit 1
fi
# Kill the oneflow-server daemon
kill -INT `cat $FLOW_PID` > /dev/null 2>&1
# Remove pid files
rm -f $FLOW_PID > /dev/null 2>&1
rm -f $FLOW_LOCK_FILE &> /dev/null
echo "oneflow-server stopped"
}
case "$1" in
start)
setup
start
;;
stop)
stop
;;
*)
echo "Usage: oneflow-server {start|stop}" >&2
exit 3
;;
esac

20
src/flow/config.ru Normal file
View File

@ -0,0 +1,20 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'oneflow-server'
run Sinatra::Application

View File

@ -0,0 +1,63 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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. #
#--------------------------------------------------------------------------- #
################################################################################
# Server Configuration
################################################################################
# OpenNebula daemon contact information
#
:one_xmlrpc: http://localhost:2633/RPC2
# Time in seconds between Life Cycle Manager steps
#
:lcm_interval: 30
# Host and port where OneFlow server will run
:host: 127.0.0.1
:port: 2474
################################################################################
# Defaults
################################################################################
# Default cooldown period after a scale operation, in seconds
:default_cooldown: 300
# Default shutdown action. Values: 'shutdown', 'shutdown-hard'
:shutdown_action: 'shutdown'
# Default oneflow action options when only one is supplied
:action_number: 1
:action_period: 60
#############################################################
# Auth
#############################################################
# Authentication driver to communicate with OpenNebula core
# - cipher, for symmetric cipher encryption of tokens
# - x509, for x509 certificate encryption of tokens
:core_auth: cipher
################################################################################
# Log
################################################################################
# Log debug level
# 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG
#
:debug_level: 2

View File

@ -0,0 +1,174 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'strategy'
class ServiceLCM
LOG_COMP = "LCM"
def initialize(sleep_time, cloud_auth)
@sleep_time = sleep_time
@cloud_auth = cloud_auth
end
def loop()
Log.info LOG_COMP, "Starting Life Cycle Manager"
while true
srv_pool = ServicePool.new(@cloud_auth.client)
rc = srv_pool.info_all()
if OpenNebula.is_error?(rc)
Log.error LOG_COMP, "Error retrieving the Service Pool: #{rc.message}"
else
srv_pool.each_xpath('DOCUMENT/ID') { |id|
service = srv_pool.get(id.to_i) { |service|
owner_client = @cloud_auth.client(service.owner_name)
service.replace_client(owner_client)
Log.debug LOG_COMP, "Loop for service #{service.id()} #{service.name()}" \
" #{service.state_str()} #{service.strategy()}"
strategy = get_deploy_strategy(service)
case service.state()
when Service::STATE['PENDING']
service.set_state(Service::STATE['DEPLOYING'])
rc = strategy.boot_step(service)
if !rc[0]
service.set_state(Service::STATE['FAILED_DEPLOYING'])
end
when Service::STATE['DEPLOYING']
strategy.monitor_step(service)
if service.all_roles_running?
service.set_state(Service::STATE['RUNNING'])
elsif service.any_role_failed?
service.set_state(Service::STATE['FAILED_DEPLOYING'])
else
rc = strategy.boot_step(service)
if !rc[0]
service.set_state(Service::STATE['FAILED_DEPLOYING'])
end
end
when Service::STATE['RUNNING'], Service::STATE['WARNING']
strategy.monitor_step(service)
if service.all_roles_running?
if service.state() == Service::STATE['WARNING']
service.set_state(Service::STATE['RUNNING'])
end
else
if service.state() == Service::STATE['RUNNING']
service.set_state(Service::STATE['WARNING'])
end
end
if strategy.apply_scaling_policies(service)
service.set_state(Service::STATE['SCALING'])
rc = strategy.scale_step(service)
if !rc[0]
service.set_state(Service::STATE['FAILED_SCALING'])
end
end
when Service::STATE['SCALING']
strategy.monitor_step(service)
if service.any_role_failed_scaling?
service.set_state(Service::STATE['FAILED_SCALING'])
elsif service.any_role_cooldown?
service.set_state(Service::STATE['COOLDOWN'])
elsif !service.any_role_scaling?
service.set_state(Service::STATE['RUNNING'])
else
rc = strategy.scale_step(service)
if !rc[0]
service.set_state(Service::STATE['FAILED_SCALING'])
end
end
when Service::STATE['COOLDOWN']
strategy.monitor_step(service)
if !service.any_role_cooldown?
service.set_state(Service::STATE['RUNNING'])
end
when Service::STATE['FAILED_SCALING']
strategy.monitor_step(service)
if !service.any_role_failed_scaling?
service.set_state(Service::STATE['SCALING'])
end
when Service::STATE['UNDEPLOYING']
strategy.monitor_step(service)
if service.all_roles_done?
service.set_state(Service::STATE['DONE'])
elsif service.any_role_failed?
service.set_state(Service::STATE['FAILED_UNDEPLOYING'])
else
rc = strategy.shutdown_step(service)
if !rc[0]
service.set_state(Service::STATE['FAILED_UNDEPLOYING'])
end
end
when Service::STATE['FAILED_DEPLOYING']
strategy.monitor_step(service)
if !service.any_role_failed?
service.set_state(Service::STATE['DEPLOYING'])
end
when Service::STATE['FAILED_UNDEPLOYING']
strategy.monitor_step(service)
if !service.any_role_failed?
service.set_state(Service::STATE['UNDEPLOYING'])
end
end
rc = service.update()
if OpenNebula.is_error?(rc)
Log.error LOG_COMP, "Error trying to update " <<
"Service #{service.id()} : #{rc.message}"
end
}
}
end
sleep @sleep_time
end
end
private
# Returns the deployment strategy for the given Service
# @param [Service] service the service
# @return [Strategy] the deployment Strategy
def get_deploy_strategy(service)
strategy = Strategy.new
case service.strategy
when 'straight'
strategy.extend(Straight)
end
return strategy
end
end

1153
src/flow/lib/grammar.rb Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,234 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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. #
#--------------------------------------------------------------------------- #
grammar ElasticityGrammar
rule expression
space exp:(boolean_exp / logic_cond) space {
def result(role)
return exp.result(role)
end
}
end
rule boolean_exp
left:logic_cond space op:('&&' / '&') space right:expression {
def result(role)
l_val, l_st = left.result(role)
r_val, r_st = right.result(role)
st = "#{l_st} #{op.text_value} #{r_st}"
val = l_val && r_val
return [val, st]
end
}
/
left:logic_cond space op:('||' / '|') space right:expression {
def result(role)
l_val, l_st = left.result(role)
r_val, r_st = right.result(role)
st = "#{l_st} #{op.text_value} #{r_st}"
val = l_val || r_val
return [val, st]
end
}
end
rule logic_cond
# 'true' {
# def result(role)
# return true
# end
# }
# /
# 'false' {
# def result(role)
# return false
# end
# }
# /
left:operand space comp_op space right:operand {
def result(role)
l_val, l_st = left.result(role)
r_val, r_st = right.result(role)
st = "#{l_st} #{comp_op.text_value} #{r_st}"
if l_val.nil? || r_val.nil?
# An attribute was not found, we return false instead
# of assuming a value of 0
val = false
else
val = comp_op.apply(l_val, r_val)
end
return [val, st]
end
}
/
'!' space expression {
def result(role)
e_val, e_st = expression.result(role)
val = !e_val
st = "!#{e_st}"
return [val, st]
end
}
/
'(' space expression space ')' {
def result(role)
e_val, e_st = expression.result(role)
st = "(#{e_st})"
return [e_val, st]
end
}
end
rule comp_op
('==' / '=') {
def apply(a,b)
a == b
end
}
/
('!=' / '<>') {
def apply(a,b)
a != b
end
}
/
'>=' {
def apply(a,b)
a >= b
end
}
/
'>' {
def apply(a,b)
a > b
end
}
/
'<=' {
def apply(a,b)
a <= b
end
}
/
'<' {
def apply(a,b)
a < b
end
}
end
rule operand
( number ) {
def result
number.result(role)
end
}
/
( variable ) {
def result
variable.result(role)
end
}
end
rule number
'-'? [0-9]+ '.' [0-9]+ {
def result(role)
val = text_value.to_f
st = val.to_s
return [val, st]
end
}
/
'-'? [0-9]+ {
def result(role)
val = text_value.to_i
st = val.to_s
return [val, st]
end
}
end
rule variable
( '"' [a-zA-Z] [0-9a-zA-Z_]* '"'
/ '\'' [a-zA-Z] [0-9a-zA-Z_]* '\''
/ [a-zA-Z] [0-9a-zA-Z_]*
) {
def result(role)
nodes = role.get_nodes
total = 0
n_nodes = 0
att = text_value.upcase
nodes.each { |node|
if node && node['vm_info']
vm_state = node['vm_info']['VM']['STATE']
lcm_state = node['vm_info']['VM']['LCM_STATE']
# Use values from VMs in RUNNING only
if vm_state != '3' || lcm_state != '3'
next
end
if node['vm_info']['VM']['USER_TEMPLATE'][att]
total += (node['vm_info']['VM']['USER_TEMPLATE'][att]).to_f
n_nodes += 1
elsif node['vm_info']['VM'][att]
total += (node['vm_info']['VM'][att]).to_f
n_nodes += 1
elsif node['vm_info']['VM']['TEMPLATE'][att]
total += (node['vm_info']['VM']['TEMPLATE'][att]).to_f
n_nodes += 1
end
end
}
# The attribute wasn't found for any of the nodes
if n_nodes == 0
val = nil
st = "#{att}[--]"
else
val = total / n_nodes
st = "#{att}[#{val.to_s}]"
end
return [val, st]
end
}
end
rule space
' '*
end
end

157
src/flow/lib/log.rb Normal file
View File

@ -0,0 +1,157 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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. #
#--------------------------------------------------------------------------- #
class Log
class << self
# This class handles logging for each service and for the service server
# Log.info("SERVER", "It works"), it will be written in the main log
# Log.info("LCM", "Service 3 started", 3), it will be written in a
# specific log for service number 3
LOG_COMP = "LOG"
DEBUG_LEVEL = [
Logger::ERROR, # 0
Logger::WARN, # 1
Logger::INFO, # 2
Logger::DEBUG # 3
]
#LOG_LOCATION = '/var/log/one'
@log_level = Logger::DEBUG
# Mon Feb 27 06:02:30 2012 [Clo] [E]: Error message example
MSG_FORMAT = %{%s [%s]: [%s] %s\n}
# Mon Feb 27 06:02:30 2012
DATE_FORMAT = "%a %b %d %H:%M:%S %Y"
# Message to be used in CloudLogger
CLOUD_LOGGER_MSG = %{[%s] %s}
# Sets the server logger
# @param [CloudLogger::CloudLogger, Logger] logger
def logger=(logger)
@@logger = logger
end
# Sets the log level
# @param [Integer] log_level (0:ERROR, 1:WARN, 2:INFO, 3:DEBUG)
def level=(log_level)
@@log_level = DEBUG_LEVEL[log_level]
end
# Writes a info message to the log. If a service_id is specified the
# message will be written to the service log, otherwise the server log
# will be used.
# @param [String] component
# @param [String] message
# @param [String] service_id
#
# @example
# Mon Feb 27 06:02:30 2012 [<component>] [I]: <message>
def info(component, message, service_id=nil)
if service_id
add(Logger::INFO, component, message, service_id)
else
@@logger.info(CLOUD_LOGGER_MSG % [component, message])
end
end
# Writes a debug message to the log. If a service_id is specified the
# message will be written to the service log, otherwise the server log
# will be used.
# @param [String] component
# @param [String] message
# @param [String] service_id
#
# @example
# Mon Feb 27 06:02:30 2012 [<component>] [D]: <message>
def debug(component, message, service_id=nil)
if service_id
add(Logger::DEBUG, component, message, service_id)
else
@@logger.debug(CLOUD_LOGGER_MSG % [component, message])
end
end
# Writes a error message to the log. If a service_id is specified the
# message will be written to the service log, otherwise the server log
# will be used.
# @param [String] component
# @param [String] message
# @param [String] service_id
#
# @example
# Mon Feb 27 06:02:30 2012 [<component>] [E]: <message>
def error(component, message, service_id=nil)
if service_id
add(Logger::ERROR, component, message, service_id)
else
@@logger.error(CLOUD_LOGGER_MSG % [component, message])
end
end
# Writes a warn message to the log. If a service_id is specified the
# message will be written to the service log, otherwise the server log
# will be used.
# @param [String] component
# @param [String] message
# @param [String] service_id
#
# @example
# Mon Feb 27 06:02:30 2012 [<component>] [W]: <message>
def warn(component, message, service_id=nil)
if service_id
add(Logger::WARN, component, message, service_id)
else
@@logger.warn(CLOUD_LOGGER_MSG % [component, message])
end
end
private
def add(severity, component, message, service_id)
if severity < @@log_level
return true
end
begin
msg = MSG_FORMAT % [
Time.now.strftime(DATE_FORMAT),
Logger::SEV_LABEL[severity][0..0],
component,
message ]
open("#{LOG_LOCATION}/oneflow/#{service_id}.log", 'a') do |f|
f << msg
end
rescue Errno::ENOENT => e
FileUtils.mkdir("#{LOG_LOCATION}/oneflow/")
open("#{LOG_LOCATION}/oneflow/#{service_id}.log", 'a') do |f|
f << msg
end
rescue => e
message = "Could not log into #{LOG_LOCATION}/oneflow/#{service_id}.log: #{e.message}"
error LOG_COMP, message
end
end
end
end

30
src/flow/lib/models.rb Normal file
View File

@ -0,0 +1,30 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'opennebula'
include OpenNebula
require 'opennebula/document_json'
require 'opennebula/document_pool_json'
require 'validator'
require 'models/role.rb'
require 'models/service_pool.rb'
require 'models/service.rb'
require 'models/service_template_pool.rb'
require 'models/service_template.rb'

899
src/flow/lib/models/role.rb Normal file
View File

@ -0,0 +1,899 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'treetop'
require 'grammar'
require 'parse-cron'
module OpenNebula
class Role
# Actions that can be performed on the VMs of a given Role
SCHEDULE_ACTIONS = [
'shutdown',
'delete',
'hold',
'release',
'stop',
'shutdown-hard',
'suspend',
'resume',
'boot',
'delete-recreate',
'reboot',
'reboot-hard',
'poweroff',
'snapshot-create'
]
STATE = {
'PENDING' => 0,
'DEPLOYING' => 1,
'RUNNING' => 2,
'UNDEPLOYING' => 3,
'WARNING' => 4,
'DONE' => 5,
'FAILED_UNDEPLOYING' => 6,
'FAILED_DEPLOYING' => 7,
'SCALING' => 8,
'FAILED_SCALING' => 9,
'COOLDOWN' => 10
}
STATE_STR = [
'PENDING',
'DEPLOYING',
'RUNNING',
'UNDEPLOYING',
'WARNING',
'DONE',
'FAILED_UNDEPLOYING',
'FAILED_DEPLOYING',
'SCALING',
'FAILED_SCALING',
'COOLDOWN'
]
LOG_COMP = "ROL"
def initialize(body, service)
@body = body
@service = service
@body['nodes'] ||= []
@body['disposed_nodes'] ||= []
end
def name
return @body['name']
end
# Returns the role state
# @return [Integer] the role state
def state
return @body['state'].to_i
end
# Returns the role parents
# @return [Array] the role parents
def parents
return @body['parents'] || []
end
# Returns the role cardinality
# @return [Integer] the role cardinality
def cardinality
return @body['cardinality'].to_i
end
# Sets a new cardinality for this role
# @param [Integer] the new cardinality
def set_cardinality(target_cardinality)
dir = target_cardinality > cardinality ? "up" : "down"
msg = "Role #{name} scaling #{dir} from #{cardinality} to #{target_cardinality} nodes"
Log.info LOG_COMP, msg, @service.id()
@service.log_info(msg)
@body['cardinality'] = target_cardinality.to_i
end
# Updates the cardinality with the current number of nodes
def update_cardinality()
@body['cardinality'] = @body['nodes'].size()
end
# Returns the role max cardinality
# @return [Integer,nil] the role cardinality, or nil if it was not defined
def max_cardinality
max = @body['max_vms']
return nil if max.nil?
return max.to_i
end
# Returns the role min cardinality
# @return [Integer,nil] the role cardinality, or nil if it was not defined
def min_cardinality
min = @body['min_vms']
return nil if min.nil?
return min.to_i
end
# Returns the string representation of the service state
# @return [String] the state string
def state_str
return STATE_STR[state]
end
# Returns the nodes of the role
# @return [Array] the nodes
def get_nodes
@body['nodes']
end
# Sets a new state
# @param [Integer] the new state
# @return [true, false] true if the value was changed
def set_state(state)
if state < 0 || state > STATE_STR.size
return false
end
@body['state'] = state.to_s
if state == STATE['SCALING']
elasticity_pol = @body['elasticity_policies']
if !elasticity_pol.nil?
elasticity_pol.each do |policy|
policy.delete('true_evals')
end
end
end
Log.info LOG_COMP, "Role #{name} new state: #{STATE_STR[state]}", @service.id()
return true
end
# Retrieves the VM information for each Node in this Role. If a Node
# is to be disposed and it is found in DONE, it will be cleaned
#
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def info
success = true
nodes = @body['nodes']
new_nodes = []
disposed_nodes = @body['disposed_nodes']
nodes.each do |node|
vm_id = node['deploy_id']
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
rc = vm.info
if OpenNebula.is_error?(rc)
msg = "Role #{name} : VM #{vm_id} monitorization failed; #{rc.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
success = false
node['vm_info'] = nil
new_nodes << node
else
node['vm_info'] = vm.to_hash
vm_state = node['vm_info']['VM']['STATE']
lcm_state = node['vm_info']['VM']['LCM_STATE']
if (vm_state == '6')
# Store the VM id in the array of disposed nodes
disposed_nodes << vm_id
else
if (node['scale_up'] == "1" && vm_state == '3' && lcm_state == '3')
# If the VM was a scale-up and it reaches RUNNING,
# clear the flag
node.delete('scale_up')
end
new_nodes << node
end
end
end
@body['nodes'] = new_nodes
if !success
return OpenNebula::Error.new()
end
return nil
end
# Deploys all the nodes in this role
# @return [Array<true, nil>, Array<false, String>] true if all the VMs
# were created, false and the error reason if there was a problem
# creating the VMs
def deploy(scale_up=false)
n_nodes = cardinality() - get_nodes.size
n_nodes.times { |i|
vm_name = "#{@body['name']}_#{i}_(service_#{@service.id()})"
template_id = @body['vm_template']
Log.debug LOG_COMP, "Role #{name} : Trying to instantiate template "\
"#{template_id}, with name #{vm_name}", @service.id()
template = OpenNebula::Template.new_with_id(template_id, @service.client)
extra_template = "SERVICE_ID = #{@service.id()}\n"\
"ROLE_NAME = #{@body['name']}"
vm_id = template.instantiate(vm_name, false, extra_template)
if OpenNebula.is_error?(vm_id)
msg = "Role #{name} : Instantiate failed for template #{template_id}; #{vm_id.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
return [false, "Error trying to instantiate the VM Template" \
" #{template_id} in Role #{self.name}: #{vm_id.message}"]
end
Log.debug LOG_COMP, "Role #{name} : Instantiate success, VM ID #{vm_id}", @service.id()
node = {
'deploy_id' => vm_id,
}
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
rc = vm.info
if OpenNebula.is_error?(rc)
node['vm_info'] = nil
else
node['vm_info'] = vm.to_hash
end
if scale_up
node['scale_up'] = '1'
end
@body['nodes'] << node
}
return [true, nil]
end
# Shutdown all the nodes in this role
#
# @param scale_down [true, false] true to shutdown and dispose the
# number of VMs needed to get down to cardinality nodes
# @return [Array<true, nil>, Array<false, String>] true if all the VMs
# were shutdown, false and the error reason if there was a problem
# shutting down the VMs
def shutdown(scale_down=false)
success = true
nodes = get_nodes
if scale_down
n_nodes = nodes.size - cardinality()
else
n_nodes = nodes.size
end
shutdown_nodes(nodes[0..n_nodes-1], scale_down)
return [success, nil]
end
# Delete all the nodes in this role
# @return [Array<true, nil>] All the VMs are deleted, and the return
# ignored
def delete
get_nodes.each { |node|
vm_id = node['deploy_id']
Log.debug LOG_COMP, "Role #{name} : Deleting VM #{vm_id}", @service.id()
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
rc = vm.finalize
if OpenNebula.is_error?(rc)
msg = "Role #{name} : Delete failed for VM #{vm_id}; #{rc.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
else
Log.debug LOG_COMP, "Role #{name} : Delete success for VM #{vm_id}", @service.id()
end
}
return [true, nil]
end
# Changes the owner/group of all the nodes in this role
#
# @param [Integer] uid the new owner id. Set to -1 to leave the current one
# @param [Integer] gid the new group id. Set to -1 to leave the current one
#
# @return [Array<true, nil>, Array<false, String>] true if all the VMs
# were updated, false and the error reason if there was a problem
# updating the VMs
def chown(uid, gid)
get_nodes.each { |node|
vm_id = node['deploy_id']
Log.debug LOG_COMP, "Role #{name} : Chown for VM #{vm_id}", @service.id()
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
rc = vm.chown(uid, gid)
if OpenNebula.is_error?(rc)
msg = "Role #{name} : Chown failed for VM #{vm_id}; #{rc.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
return [false, rc.message]
else
Log.debug LOG_COMP, "Role #{name} : Chown success for VM #{vm_id}", @service.id()
end
}
return [true, nil]
end
# Schedule the given action on all the VMs that belong to the Role
# @param [String] action one of the available actions defined in SCHEDULE_ACTIONS
# @param [Integer] period
# @param [Integer] vm_per_period
def batch_action(action, period, vms_per_period)
vms_id = []
# TODO: check action is a valid string, period vm_per_period integer
error_msgs = []
nodes = @body['nodes']
now = Time.now.to_i
do_offset = ( !period.nil? && period.to_i > 0 &&
!vms_per_period.nil? && vms_per_period.to_i > 0 )
time_offset = 0
nodes.each_with_index do |node, index|
vm_id = node['deploy_id']
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
rc = vm.info
if OpenNebula.is_error?(rc)
msg = "Role #{name} : VM #{vm_id} monitorization failed; #{rc.message}"
error_msgs << msg
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
else
ids = vm.retrieve_elements('USER_TEMPLATE/SCHED_ACTION/ID')
id = 0
if (!ids.nil? && !ids.empty?)
ids.map! {|e| e.to_i }
id = ids.max + 1
end
tmp_str = vm.user_template_str
if do_offset
time_offset = (index / vms_per_period.to_i).floor * period.to_i
end
tmp_str << "\nSCHED_ACTION = "<<
"[ID = #{id}, ACTION = #{action}, TIME = #{now + time_offset}]"
rc = vm.update(tmp_str)
if OpenNebula.is_error?(rc)
msg = "Role #{name} : VM #{vm_id} error scheduling action; #{rc.message}"
error_msgs << msg
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
else
vms_id << vm.id
end
end
end
log_msg = "Action:#{action} scheduled on Role:#{self.name} VMs:#{vms_id.join(',')}"
Log.info LOG_COMP, log_msg, @service.id()
if error_msgs.empty?
return [true, log_msg]
else
error_msgs << log_msg
return [false, error_msgs.join('\n')]
end
end
########################################################################
# Scalability
########################################################################
# Returns a positive, 0, or negative number of nodes to adjust,
# according to the elasticity and scheduled policies
# @return [Array<Integer>] positive, 0, or negative number of nodes to
# adjust, plus the cooldown period duration
def scale?()
elasticity_pol = @body['elasticity_policies']
scheduled_pol = @body['scheduled_policies']
elasticity_pol ||= []
scheduled_pol ||= []
scheduled_pol.each do |policy|
diff = scale_time?(policy)
return [diff, 0] if diff != 0
end
elasticity_pol.each do |policy|
diff, cooldown_duration = scale_attributes?(policy)
if diff != 0
cooldown_duration = @body['cooldown'] if cooldown_duration.nil?
cooldown_duration = @@default_cooldown if cooldown_duration.nil?
return [diff, cooldown_duration]
end
end
# Implicit rule that scales up to maintain the min_cardinality, with
# no cooldown period
if cardinality < min_cardinality.to_i
return [min_cardinality.to_i - cardinality, 0]
end
return [0, 0]
end
# Scales up or down the number of nodes needed to match the current
# cardinality
#
# @return [Array<true, nil>, Array<false, String>] true if all the VMs
# were created/shut down, false and the error reason if there
# was a problem
def scale()
n_nodes = 0
get_nodes.each do |node|
n_nodes += 1 if node['disposed'] != "1"
end
diff = cardinality - n_nodes
if diff > 0
return deploy(true)
elsif diff < 0
return shutdown(true)
end
return [true, nil]
end
# Updates the duration for the next cooldown
# @param cooldown_duration [Integer] duration for the next cooldown
def set_cooldown_duration(cooldown_duration)
@body['cooldown_duration'] = cooldown_duration.to_i
end
# Updates the duration for the next cooldown with the default value
def set_default_cooldown_duration()
cooldown_duration = @body['cooldown']
cooldown_duration = @@default_cooldown if cooldown_duration.nil?
set_cooldown_duration(cooldown_duration)
end
# Sets the cooldown end time from now + the duration set in set_cooldown_duration
# @return [true, false] true if the cooldown duration is bigger than 0
def apply_cooldown_duration()
cooldown_duration = @body['cooldown_duration'].to_i
if cooldown_duration != 0
@body['cooldown_end'] = Time.now.to_i + cooldown_duration
@body.delete('cooldown_duration')
return true
end
return false
end
# Returns true if the cooldown period ended
# @return [true, false] true if the cooldown period ended
def cooldown_over?()
return Time.now.to_i >= @body['cooldown_end'].to_i
end
def self.init_default_cooldown(default_cooldown)
@@default_cooldown = default_cooldown
end
def self.init_default_shutdown(shutdown_action)
@@default_shutdown = shutdown_action
end
# Updates the role
# @param [Hash] template
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def update(template)
force = template['force'] == true
new_cardinality = template["cardinality"]
if new_cardinality.nil?
return nil
end
new_cardinality = new_cardinality.to_i
if !force
if new_cardinality < min_cardinality().to_i
return OpenNebula::Error.new(
"Minimum cardinality is #{min_cardinality()}")
elsif !max_cardinality().nil? && new_cardinality > max_cardinality().to_i
return OpenNebula::Error.new(
"Maximum cardinality is #{max_cardinality()}")
end
end
set_cardinality(new_cardinality)
return nil
end
########################################################################
# Recover
########################################################################
def recover_deployment()
recover()
end
def recover_warning()
recover()
deploy()
end
def recover_scale()
recover()
retry_scale()
end
########################################################################
########################################################################
private
# Returns a positive, 0, or negative number of nodes to adjust,
# according to a SCHEDULED type policy
# @param [Hash] A SCHEDULED type policy
# @return [Integer] positive, 0, or negative number of nodes to adjust
def scale_time?(elasticity_pol)
now = Time.now.to_i
last_eval = elasticity_pol['last_eval'].to_i
elasticity_pol['last_eval'] = now
# If this is the first time this is evaluated, ignore it.
# We don't want to execute actions planned in the past when the
# server starts.
if last_eval == 0
return 0
end
start_time = elasticity_pol['start_time']
target_vms = elasticity_pol['adjust']
if target_vms.nil?
# TODO error msg
return 0
end
if !(start_time.nil? || start_time.empty?)
begin
start_time = Time.parse(start_time).to_i
rescue ArgumentError
# TODO error msg
return 0
end
else
recurrence = elasticity_pol['recurrence']
if recurrence.nil? || recurrence.empty?
# TODO error msg
return 0
end
begin
cron_parser = CronParser.new(recurrence)
# This returns the next planned time, starting from the last
# step
start_time = cron_parser.next(Time.at(last_eval)).to_i
rescue
# TODO error msg bad format
return 0
end
end
# Only actions planned between last step and this one are triggered
if start_time > last_eval && start_time <= now
Log.debug LOG_COMP, "Role #{name} : scheduled scalability for "\
"#{Time.at(start_time)} triggered", @service.id()
new_cardinality = calculate_new_cardinality(elasticity_pol)
return new_cardinality - cardinality()
end
return 0
end
# Returns a positive, 0, or negative number of nodes to adjust,
# according to a policy based on attributes
# @param [Hash] A policy based on attributes
# @return [Array<Integer>] positive, 0, or negative number of nodes to
# adjust, plus the cooldown period duration
def scale_attributes?(elasticity_pol)
now = Time.now.to_i
# TODO: enforce true_up_evals type in ServiceTemplate::ROLE_SCHEMA ?
period_duration = elasticity_pol['period'].to_i
period_number = elasticity_pol['period_number'].to_i
last_eval = elasticity_pol['last_eval'].to_i
true_evals = elasticity_pol['true_evals'].to_i
expression = elasticity_pol['expression']
if !last_eval.nil?
if now < (last_eval + period_duration)
return [0, 0]
end
end
elasticity_pol['last_eval'] = now
new_cardinality = cardinality()
new_evals = 0
exp_value, exp_st = scale_rule(expression)
if exp_value
new_evals = true_evals + 1
new_evals = period_number if new_evals > period_number
if new_evals >= period_number
Log.debug LOG_COMP, "Role #{name} : elasticy policy #{exp_st} "\
"triggered", @service.id()
new_cardinality = calculate_new_cardinality(elasticity_pol)
end
end
elasticity_pol['true_evals'] = new_evals
elasticity_pol['expression_evaluated'] = exp_st
return [new_cardinality - cardinality(), elasticity_pol['cooldown']]
end
# Returns true if the scalability rule is triggered
# @return true if the scalability rule is triggered
def scale_rule(elas_expr)
parser = ElasticityGrammarParser.new
if elas_expr.nil? || elas_expr.empty?
return false
end
treetop = parser.parse(elas_expr)
if treetop.nil?
return [false, "Parse error. '#{elas_expr}': #{parser.failure_reason}"]
end
val, st = treetop.result(self)
return [val, st]
end
def calculate_new_cardinality(elasticity_pol)
type = elasticity_pol['type']
adjust = elasticity_pol['adjust'].to_i
# Min is a hard limit, if the current cardinality + adjustment does
# not reach it, the difference is added
max = [cardinality(), max_cardinality.to_i].max()
# min = [cardinality(), min_cardinality.to_i].min()
min = min_cardinality.to_i
case type.upcase
when 'CHANGE'
new_cardinality = cardinality() + adjust
when 'PERCENTAGE_CHANGE'
min_adjust_step = elasticity_pol['min_adjust_step'].to_i
change = cardinality() * adjust / 100.0
sign = change > 0 ? 1 : -1
change = change.abs
if change < 1
change = 1
else
change = change.to_i
end
change = sign * [change, min_adjust_step].max
new_cardinality = cardinality() + change
when 'CARDINALITY'
new_cardinality = adjust
else
# TODO: error message
return cardinality()
end
# The cardinality can be forced to be outside the min,max
# range. If that is the case, the scale up/down will not
# move further outside the range. It will move towards the
# range with the adjustement set, instead of jumping the
# difference
if (adjust > 0)
new_cardinality = max if new_cardinality > max
elsif (adjust < 0)
new_cardinality = min if new_cardinality < min
end
return new_cardinality
end
# For a failed scale up, the cardinality is updated to the actual value
# For a failed scale down, the shutdown actions are retried
def retry_scale()
nodes_dispose = get_nodes.select { |node|
node['disposed'] == "1"
}
shutdown_nodes(nodes_dispose, true)
set_cardinality( get_nodes.size() - n_dispose.size() )
end
# Deletes VMs in DONE or FAILED, and sends a boot action to VMs in UNKNOWN
def recover()
nodes = @body['nodes']
new_nodes = []
disposed_nodes = @body['disposed_nodes']
nodes.each do |node|
vm_state = nil
vm_id = node['deploy_id']
if node['vm_info'] && node['vm_info']['VM'] && node['vm_info']['VM']['STATE']
vm_state = node['vm_info']['VM']['STATE']
lcm_state = node['vm_info']['VM']['LCM_STATE']
end
if vm_state == '6' # DONE
# Store the VM id in the array of disposed nodes
disposed_nodes << vm_id
elsif vm_state == '7' # FAILED
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
rc = vm.finalize
if !OpenNebula.is_error?(rc)
# Store the VM id in the array of disposed nodes
disposed_nodes << vm_id
Log.debug LOG_COMP, "Role #{name} : Delete success for VM #{vm_id}", @service.id()
else
msg = "Role #{name} : Delete failed for VM #{vm_id}; #{rc.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
success = false
new_nodes << node
end
elsif vm_state == '3' && lcm_state == '16' # UNKNOWN
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
vm.boot
else
new_nodes << node
end
end
@body['nodes'] = new_nodes
end
# Shuts down all the given roles
# @param scale_down [true,false] True to set the 'disposed' node flag
def shutdown_nodes(nodes, scale_down)
action = @body['shutdown_action']
if action.nil?
action = @service.get_shutdown_action()
end
if action.nil?
action = @@default_shutdown
end
nodes.each { |node|
vm_id = node['deploy_id']
Log.debug LOG_COMP, "Role #{name} : Shutting down VM #{vm_id}", @service.id()
vm = OpenNebula::VirtualMachine.new_with_id(vm_id, @service.client)
if action == 'shutdown-hard'
rc = vm.shutdown(true)
else
rc = vm.shutdown
end
if scale_down
node['disposed'] = '1'
end
if OpenNebula.is_error?(rc)
msg = "Role #{name} : Shutdown failed for VM #{vm_id}, will perform a Delete; #{rc.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
rc = vm.finalize
if OpenNebula.is_error?(rc)
msg = "Role #{name} : Delete failed for VM #{vm_id}; #{rc.message}"
Log.error LOG_COMP, msg, @service.id()
@service.log_error(msg)
success = false
#return [false, rc.message]
else
Log.debug LOG_COMP, "Role #{name} : Delete success for VM #{vm_id}", @service.id()
end
else
Log.debug LOG_COMP, "Role #{name} : Shutdown success for VM #{vm_id}", @service.id()
end
}
end
end
end

View File

@ -0,0 +1,409 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 OpenNebula
class Service < DocumentJSON
DOCUMENT_TYPE = 100
STATE = {
'PENDING' => 0,
'DEPLOYING' => 1,
'RUNNING' => 2,
'UNDEPLOYING' => 3,
'WARNING' => 4,
'DONE' => 5,
'FAILED_UNDEPLOYING' => 6,
'FAILED_DEPLOYING' => 7,
'SCALING' => 8,
'FAILED_SCALING' => 9,
'COOLDOWN' => 10
}
STATE_STR = [
'PENDING',
'DEPLOYING',
'RUNNING',
'UNDEPLOYING',
'WARNING',
'DONE',
'FAILED_UNDEPLOYING',
'FAILED_DEPLOYING',
'SCALING',
'FAILED_SCALING',
'COOLDOWN'
]
LOG_COMP = "SER"
# Returns the service state
# @return [Integer] the service state
def state
return @body['state'].to_i
end
# Returns the service strategy
# @return [String] the service strategy
def strategy
return @body['deployment']
end
# Returns the string representation of the service state
# @return the state string
def state_str
return STATE_STR[state]
end
# Sets a new state
# @param [Integer] the new state
# @return [true, false] true if the value was changed
def set_state(state)
if state < 0 || state > STATE_STR.size
return false
end
@body['state'] = state
msg = "New state: #{STATE_STR[state]}"
Log.info LOG_COMP, msg, self.id()
self.log_info(msg)
return true
end
# Returns the owner username
# @return [String] the service's owner username
def owner_name()
return self['UNAME']
end
# Replaces this object's client with a new one
# @param [OpenNebula::Client] owner_client the new client
def replace_client(owner_client)
@client = owner_client
end
# Returns all the node Roles
# @return [Hash<String,Role>] all the node Roles
def get_roles
return @roles
end
# Returns true if all the nodes are correctly deployed
# @return [true, false] true if all the nodes are correctly deployed
def all_roles_running?()
@roles.each { |name, role|
if role.state != Role::STATE['RUNNING']
return false
end
}
return true
end
# Returns true if all the nodes are in done state
# @return [true, false] true if all the nodes are correctly deployed
def all_roles_done?()
@roles.each { |name, role|
if role.state != Role::STATE['DONE']
return false
end
}
return true
end
# Returns true if any of the roles is in failed state
# @return [true, false] true if any of the roles is in failed state
def any_role_failed?()
failed_states = [
Role::STATE['FAILED_DEPLOYING'],
Role::STATE['FAILED_UNDEPLOYING']]
@roles.each { |name, role|
if failed_states.include?(role.state)
return true
end
}
return false
end
def any_role_scaling?()
@roles.each do |name, role|
if role.state == Role::STATE['SCALING']
return true
end
end
return false
end
def any_role_failed_scaling?()
@roles.each do |name, role|
if role.state == Role::STATE['FAILED_SCALING']
return true
end
end
return false
end
def any_role_cooldown?()
@roles.each do |name, role|
if role.state == Role::STATE['COOLDOWN']
return true
end
end
return false
end
# Create a new service based on the template provided
# @param [String] template_json
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def allocate(template_json)
template = JSON.parse(template_json)
template['state'] = STATE['PENDING']
super(template.to_json, template['name'])
end
# Shutdown the service. This action is called when user wants to shutdwon
# the Service
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def shutdown
if ![Service::STATE['FAILED_SCALING'],
Service::STATE['DONE']].include?(self.state)
self.set_state(Service::STATE['UNDEPLOYING'])
return self.update
else
return OpenNebula::Error.new("Action shutdown: Wrong state" \
" #{self.state_str()}")
end
end
# Recover a failed service.
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def recover
if [Service::STATE['FAILED_DEPLOYING']].include?(self.state)
@roles.each do |name, role|
if role.state == Role::STATE['FAILED_DEPLOYING']
role.set_state(Role::STATE['PENDING'])
role.recover_deployment()
end
end
self.set_state(Service::STATE['DEPLOYING'])
elsif self.state == Service::STATE['FAILED_SCALING']
@roles.each do |name, role|
if role.state == Role::STATE['FAILED_SCALING']
role.recover_scale()
role.set_state(Role::STATE['SCALING'])
end
end
self.set_state(Service::STATE['SCALING'])
elsif self.state == Service::STATE['FAILED_UNDEPLOYING']
@roles.each do |name, role|
if role.state == Role::STATE['FAILED_UNDEPLOYING']
role.set_state(Role::STATE['RUNNING'])
end
end
self.set_state(Service::STATE['UNDEPLOYING'])
elsif self.state == Service::STATE['COOLDOWN']
@roles.each do |name, role|
if role.state == Role::STATE['COOLDOWN']
role.set_state(Role::STATE['RUNNING'])
end
end
self.set_state(Service::STATE['RUNNING'])
elsif self.state == Service::STATE['WARNING']
@roles.each do |name, role|
if role.state == Role::STATE['WARNING']
role.recover_warning()
end
end
else
return OpenNebula::Error.new("Action recover: Wrong state" \
" #{self.state_str()}")
end
return self.update
end
# Delete the service. All the VMs are also deleted from OpenNebula.
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def delete
@roles.each { |name, role|
role.delete()
}
return super()
end
# Retrieves the information of the Service and all its Nodes.
#
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def info
rc = super
if OpenNebula.is_error?(rc)
return rc
end
@roles = {}
if @body['roles']
@body['roles'].each { |elem|
elem['state'] ||= Role::STATE['PENDING']
role = Role.new(elem, self)
@roles[role.name] = role
}
end
return nil
end
# Add an info message in the service information that will be stored
# in OpenNebula
# @param [String] message
def log_info(message)
add_log(Logger::INFO, message)
end
# Add an error message in the service information that will be stored
# in OpenNebula
# @param [String] message
def log_error(message)
add_log(Logger::ERROR, message)
end
# Retrieve the service client
def client
@client
end
# Changes the owner/group
#
# @param [Integer] uid the new owner id. Set to -1 to leave the current one
# @param [Integer] gid the new group id. Set to -1 to leave the current one
#
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def chown(uid, gid)
old_uid = self['UID'].to_i
old_gid = self['GID'].to_i
rc = super(uid, gid)
if OpenNebula.is_error?(rc)
return rc
end
@roles.each { |name, role|
rc = role.chown(uid, gid)
break if rc[0] == false
}
if rc[0] == false
self.log_error("Chown operation failed, will try to rollback all VMs to the old user and group")
update()
super(old_uid, old_gid)
@roles.each { |name, role|
role.chown(old_uid, old_gid)
}
return OpenNebula::Error.new(rc[1])
end
return nil
end
# Updates a role
# @param [String] role_name
# @param [String] template_json
# @return [nil, OpenNebula::Error] nil in case of success, Error
# otherwise
def update_role(role_name, template_json)
if ![Service::STATE['RUNNING'], Service::STATE['WARNING']].include?(self.state)
return OpenNebula::Error.new("Update role: Wrong state" \
" #{self.state_str()}")
end
template = JSON.parse(template_json)
# TODO: Validate template?
role = @roles[role_name]
if role.nil?
return OpenNebula::Error.new("ROLE \"#{role_name}\" does not exist")
end
rc = role.update(template)
if OpenNebula.is_error?(rc)
return rc
end
# TODO: The update may not change the cardinality, only
# the max and min vms...
role.set_state(Role::STATE['SCALING'])
role.set_default_cooldown_duration()
self.set_state(Service::STATE['SCALING'])
return self.update
end
def get_shutdown_action()
return @body['shutdown_action']
end
private
# @param [Logger::Severity] severity
# @param [String] message
def add_log(severity, message)
severity_str = Logger::SEV_LABEL[severity][0..0]
@body['log'] ||= Array.new
@body['log'] << {
:timestamp => Time.now.to_i,
:severity => severity_str,
:message => message
}
end
end
end

View File

@ -0,0 +1,97 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 OpenNebula
class ServicePool < DocumentPoolJSON
DOCUMENT_TYPE = 100
@@mutex = Mutex.new
@@mutex_hash = Hash.new
# Class constructor
#
# @param [OpenNebula::Client] client the xml-rpc client
# @param [Integer] user_id the filter flag, see
# http://opennebula.org/documentation:rel3.6:api
#
# @return [DocumentPool] the new object
def initialize(client, user_id=-1)
super(client, user_id)
end
def factory(element_xml)
service = OpenNebula::Service.new(element_xml, @client)
service.load_body
service
end
# Retrieves a Service element from OpenNebula. The Service::info()
# method is called
#
# @param [Integer] service_id Numerical Id of the service to retrieve
# @yieldparam [Service] this block will have the service's mutex locked.
# The mutex will be unlocked after the block execution.
#
# @return [Service, OpenNebula::Error] The Service in case of success
def get(service_id, &block)
service_id = service_id.to_i if service_id
service = Service.new_with_id(service_id, @client)
rc = service.info
if OpenNebula.is_error?(rc)
return rc
else
if block_given?
obj_mutex = nil
entry = nil
@@mutex.synchronize {
# entry is an array of [Mutex, waiting]
# waiting is the number of threads waiting on this mutex
entry = @@mutex_hash[service_id]
if entry.nil?
entry = [Mutex.new, 0]
@@mutex_hash[service_id] = entry
end
obj_mutex = entry[0]
entry[1] = entry[1] + 1
if @@mutex_hash.size > 10000
@@mutex_hash.delete_if { |s_id, entry|
entry[1] == 0
}
end
}
obj_mutex.synchronize {
block.call(service)
}
@@mutex.synchronize {
entry[1] = entry[1] - 1
}
end
return service
end
end
end
end

View File

@ -0,0 +1,311 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'parse-cron'
module OpenNebula
class ServiceTemplate < DocumentJSON
ROLE_SCHEMA = {
:type => :object,
:properties => {
'name' => {
:type => :string,
:required => true
},
'cardinality' => {
:type => :integer,
:default => 1,
:minimum => 0
},
'vm_template' => {
:type => :integer,
:required => true
},
'parents' => {
:type => :array,
:items => {
:type => :string
}
},
'shutdown_action' => {
:type => :string,
:enum => %w{shutdown shutdown-hard},
:required => false
},
'min_vms' => {
:type => :integer,
:required => false,
:minimum => 0
},
'max_vms' => {
:type => :integer,
:required => false,
:minimum => 0
},
'cooldown' => {
:type => :integer,
:required => false,
:minimum => 0
},
'elasticity_policies' => {
:type => :array,
:items => {
:type => :object,
:properties => {
'type' => {
:type => :string,
:enum => %w{CHANGE CARDINALITY PERCENTAGE_CHANGE},
:required => true
},
'adjust' => {
:type => :integer,
:required => true
},
'min_adjust_step' => {
:type => :integer,
:required => false,
:minimum => 1
},
'period_number' => {
:type => :integer,
:required => false,
:minimum => 0
},
'period' => {
:type => :integer,
:required => false,
:minimum => 0
},
'expression' => {
:type => :string,
:required => true
},
'cooldown' => {
:type => :integer,
:required => false,
:minimum => 0
}
#'statistic' => {
# # SampleCount | Average | Sum | Minimum | Maximum
# :type => :string
#}
}
}
},
'scheduled_policies' => {
:type => :array,
:items => {
:type => :object,
:properties => {
'type' => {
:type => :string,
:enum => %w{CHANGE CARDINALITY PERCENTAGE_CHANGE},
:required => true
},
'adjust' => {
:type => :integer,
:required => true
},
'min_adjust_step' => {
:type => :integer,
:required => false,
:minimum => 1
},
'start_time' => {
:type => :string,
:required => false
},
'recurrence' => {
:type => :string,
:required => false
}
}
}
}
}
}
SCHEMA = {
:type => :object,
:properties => {
'name' => {
:type => :string,
:required => true
},
'deployment' => {
:type => :string,
:enum => %w{none straight},
:default => 'none'
},
'shutdown_action' => {
:type => :string,
:enum => %w{shutdown shutdown-hard},
:required => false
},
'roles' => {
:type => :array,
:items => ROLE_SCHEMA,
:required => true
}
}
}
DOCUMENT_TYPE = 101
def allocate(template_json)
template = JSON.parse(template_json)
validator = Validator::Validator.new(
:default_values => true,
:delete_extra_properties => false
)
validator.validate!(template, SCHEMA)
validate_values(template)
super(template.to_json, template['name'])
end
# Retrieves the template
#
# @return [String] json template
def template
@body.to_json
end
def update(template_json)
template = JSON.parse(template_json)
validator = Validator::Validator.new(
:default_values => true,
:delete_extra_properties => false
)
validator.validate!(template, SCHEMA)
validate_values(template)
super(template.to_json)
end
private
def validate_values(template)
parser = ElasticityGrammarParser.new
roles = template['roles']
roles.each_with_index do |role, role_index|
roles[role_index+1..-1].each do |other_role|
if role['name'] == other_role['name']
raise Validator::ParseException,
"Role name '#{role['name']}' is repeated"
end
end
if (!role['min_vms'].nil? && role['min_vms'].to_i > role['cardinality'].to_i)
raise Validator::ParseException,
"Role '#{role['name']}' 'cardinality' must be greater than "\
"or equal to 'min_vms'"
end
if !role['max_vms'].nil? &&
role['max_vms'].to_i < role['cardinality'].to_i
raise Validator::ParseException,
"Role '#{role['name']}' 'cardinality' must be lower than "\
"or equal to 'max_vms'"
end
if ((role['elasticity_policies'] && role['elasticity_policies'].size > 0) ||
(role['scheduled_policies'] && role['scheduled_policies'].size > 0))
if role['min_vms'].nil? || role['max_vms'].nil?
raise Validator::ParseException,
"Role '#{role['name']}' with 'elasticity_policies' or "<<
"'scheduled_policies' must define both 'min_vms'"<<
"and 'max_vms'"
end
end
if role['elasticity_policies']
role['elasticity_policies'].each_with_index do |policy, index|
exp = policy['expression']
if exp.empty?
raise Validator::ParseException,
"Role '#{role['name']}', elasticity policy "\
"##{index} 'expression' cannot be empty"
end
treetop = parser.parse(exp)
if treetop.nil?
raise Validator::ParseException,
"Role '#{role['name']}', elasticity policy "\
"##{index} 'expression' parse error: #{parser.failure_reason}"
end
end
end
if role['scheduled_policies']
role['scheduled_policies'].each_with_index do |policy, index|
start_time = policy['start_time']
recurrence = policy['recurrence']
if !start_time.nil?
if !policy['recurrence'].nil?
raise Validator::ParseException,
"Role '#{role['name']}', scheduled policy "\
"##{index} must define "\
"'start_time' or 'recurrence', but not both"
end
begin
Time.parse(start_time)
rescue ArgumentError
raise Validator::ParseException,
"Role '#{role['name']}', scheduled policy "\
"##{index} 'start_time' is not a valid Time. "\
"Try with YYYY-MM-DD hh:mm:ss or YYYY-MM-DDThh:mm:ssZ"
end
elsif !recurrence.nil?
begin
cron_parser = CronParser.new(recurrence)
start_time = cron_parser.next()
rescue Exception => e
raise Validator::ParseException,
"Role '#{role['name']}', scheduled policy "\
"##{index} 'recurrence' is not a valid "\
"cron expression"
end
else
raise Validator::ParseException,
"Role '#{role['name']}', scheduled policy ##{index} needs to define either "<<
"'start_time' or 'recurrence'"
end
end
end
end
end
end
end

View File

@ -0,0 +1,28 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 OpenNebula
class ServiceTemplatePool < DocumentPoolJSON
DOCUMENT_TYPE = 101
def factory(element_xml)
s_template = OpenNebula::ServiceTemplate.new(element_xml, @client)
s_template.load_body
s_template
end
end
end

382
src/flow/lib/strategy.rb Normal file
View File

@ -0,0 +1,382 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'strategy/straight'
class Strategy
LOG_COMP = "STR"
# Performs a boot step, deploying all nodes that meet the requirements
# @param [Service] service service to boot
# @return [Array<true, nil>, Array<false, String>] true if all the nodes
# were created, false and the error reason if there was a problem
# creating the VMs
def boot_step(service)
Log.debug LOG_COMP, "Boot step", service.id()
roles_deploy = get_roles_deploy(service)
roles_deploy.each { |name, role|
Log.debug LOG_COMP, "Deploying role #{name}", service.id()
rc = role.deploy
if !rc[0]
role.set_state(Role::STATE['FAILED_DEPLOYING'])
return rc
else
role.set_state(Role::STATE['DEPLOYING'])
end
}
return [true, nil]
end
# Performs a shutdown step, shutting down all nodes that meet the requirements
# @param [Service] service service to boot
# @return [Array<true, nil>, Array<false, String>] true if all the nodes
# were created, false and the error reason if there was a problem
# creating the VMs
def shutdown_step(service)
Log.debug LOG_COMP, "Shutdown step", service.id()
roles_shutdown = get_roles_shutdown(service)
roles_shutdown.each { |name, role|
Log.debug LOG_COMP, "Shutting down role #{name}", service.id()
rc = role.shutdown
if !rc[0]
role.set_state(Role::STATE['FAILED_UNDEPLOYING'])
return rc
else
role.set_state(Role::STATE['UNDEPLOYING'])
end
}
return [true, nil]
end
# If a role needs to scale, its cardinality is updated, and its state is set
# to SCALING. Only one role is set to scale.
# @param [Service] service
# @return [true|false] true if any role needs to scale
def apply_scaling_policies(service)
Log.debug LOG_COMP, "Apply scaling policies", service.id()
service.get_roles.each do |name, role|
diff, cooldown_duration = role.scale?
if diff != 0
Log.debug LOG_COMP, "Role #{name} needs to scale #{diff} nodes", service.id()
role.set_cardinality(role.cardinality() + diff)
role.set_state(Role::STATE['SCALING'])
role.set_cooldown_duration(cooldown_duration)
return true
end
end
return false
end
# If a role is scaling, the nodes are created/destroyed to match the current
# cardinality
# @return [Array<true, nil>, Array<false, String>] true if the action was
# performed, false and the error reason if there was a problem
def scale_step(service)
Log.debug LOG_COMP, "Scale step", service.id()
service.get_roles.each do |name, role|
if role.state == Role::STATE['SCALING']
rc = role.scale()
if !rc[0]
role.set_state(Role::STATE['FAILED_SCALING'])
return rc
end
end
end
return [true, nil]
end
# Performs a monitor step, check if the roles already deployed are running
# @param [Service] service service to monitor
# @return [nil]
def monitor_step(service)
Log.debug LOG_COMP, "Monitor step", service.id()
roles_monitor = get_roles_monitor(service)
roles_monitor.each { |name, role|
Log.debug LOG_COMP, "Monitoring role #{name}", service.id()
rc = role.info
case role.state()
when Role::STATE['RUNNING']
if OpenNebula.is_error?(rc) || role_nodes_warning?(role)
role.set_state(Role::STATE['WARNING'])
end
role.update_cardinality()
when Role::STATE['WARNING']
if !OpenNebula.is_error?(rc) && !role_nodes_warning?(role)
role.set_state(Role::STATE['RUNNING'])
end
role.update_cardinality()
when Role::STATE['DEPLOYING']
if OpenNebula.is_error?(rc)
role.set_state(Role::STATE['FAILED_DEPLOYING'])
elsif role_nodes_running?(role)
role.set_state(Role::STATE['RUNNING'])
elsif any_node_failed?(role)
role.set_state(Role::STATE['FAILED_DEPLOYING'])
end
when Role::STATE['SCALING']
if OpenNebula.is_error?(rc)
role.set_state(Role::STATE['FAILED_SCALING'])
elsif role_finished_scaling?(role)
if role.apply_cooldown_duration()
role.set_state(Role::STATE['COOLDOWN'])
else
role.set_state(Role::STATE['RUNNING'])
end
elsif any_node_failed_scaling?(role)
role.set_state(Role::STATE['FAILED_SCALING'])
end
when Role::STATE['COOLDOWN']
if role.cooldown_over?
role.set_state(Role::STATE['RUNNING'])
end
role.update_cardinality()
when Role::STATE['UNDEPLOYING']
if OpenNebula.is_error?(rc)
role.set_state(Role::STATE['FAILED_UNDEPLOYING'])
elsif role_nodes_done?(role)
role.set_state(Role::STATE['DONE'])
elsif any_node_failed?(role)
role.set_state(Role::STATE['FAILED_UNDEPLOYING'])
end
when Role::STATE['FAILED_DEPLOYING']
if !OpenNebula.is_error?(rc) && role_nodes_running?(role)
role.set_state(Role::STATE['RUNNING'])
end
when Role::STATE['FAILED_UNDEPLOYING']
if !OpenNebula.is_error?(rc) && role_nodes_done?(role)
role.set_state(Role::STATE['DONE'])
end
when Role::STATE['FAILED_SCALING']
if !OpenNebula.is_error?(rc) && role_finished_scaling?(role)
role.set_state(Role::STATE['SCALING'])
end
end
}
end
protected
# All subclasses must define these methods
# Returns all node Roles ready to be deployed
# @param [Service] service
# @return [Hash<String, Role>] Roles
def get_roles_deploy(service)
result = service.get_roles.select {|name, role|
role.state == Role::STATE['PENDING'] ||
role.state == Role::STATE['DEPLOYING']
}
# Ruby 1.8 compatibility
if result.instance_of?(Array)
result = Hash[result]
end
result
end
# Returns all node Roles be monitored
# @param [Service] service
# @return [Hash<String, Role>] Roles
def get_roles_monitor(service)
result = service.get_roles.select {|name, role|
![Role::STATE['PENDING'], Role::STATE['DONE']].include?(role.state)
}
# Ruby 1.8 compatibility
if result.instance_of?(Array)
result = Hash[result]
end
result
end
# Returns all node Roles ready to be shutdown
# @param [Service] service
# @return [Hash<String, Role>] Roles
def get_roles_shutdown(service)
result = service.get_roles.select {|name, role|
![Role::STATE['UNDEPLOYING'],
Role::STATE['DONE'],
Role::STATE['FAILED_UNDEPLOYING']].include?(role.state)
}
# Ruby 1.8 compatibility
if result.instance_of?(Array)
result = Hash[result]
end
result
end
# Determine if the role nodes are running
# @param [Role] role
# @return [true|false]
def role_nodes_running?(role)
if role.get_nodes.size() != role.cardinality()
return false
end
role.get_nodes.each { |node|
if node && node['vm_info']
vm_state = node['vm_info']['VM']['STATE']
lcm_state = node['vm_info']['VM']['LCM_STATE']
# !(ACTIVE && RUNNING)
if (vm_state != '3') || (lcm_state != '3')
return false
end
else
return false
end
}
return true
end
# Returns true if any VM is in UNKNOWN or FAILED
# @param [Role] role
# @return [true|false]
def role_nodes_warning?(role)
role.get_nodes.each do |node|
if node && node['vm_info']
vm_state = node['vm_info']['VM']['STATE']
lcm_state = node['vm_info']['VM']['LCM_STATE']
# UNKNOWN or FAILED
if (vm_state == '3' && lcm_state == '16') || vm_state == '7'
return true
end
end
end
return false
end
# Determine if any of the role nodes failed
# @param [Role] role
# @return [true|false]
def any_node_failed?(role)
role.get_nodes.each { |node|
if node && node['vm_info']
vm_state = node['vm_info']['VM']['STATE']
if vm_state == '7' # FAILED
return true
end
end
}
return false
end
# Determine if the role nodes are in done state
# @param [Role] role
# @return [true|false]
def role_nodes_done?(role)
role.get_nodes.each { |node|
if node && node['vm_info']
vm_state = node['vm_info']['VM']['STATE']
if vm_state != '6' # DONE
return false
end
else
return false
end
}
return true
end
# Determine if any of the role nodes failed to scale
# @param [Role] role
# @return [true|false]
def any_node_failed_scaling?(role)
role.get_nodes.each { |node|
if node && node['vm_info'] &&
(node['disposed'] == '1' || node['scale_up'] == '1') &&
node['vm_info']['VM']['STATE'] == '7' # FAILED
return true
end
}
return false
end
def role_finished_scaling?(role)
role.get_nodes.each { |node|
if node && node['vm_info']
# For scale up, check new nodes are running, or past running
if node['scale_up'] == '1'
vm_state = node['vm_info']['VM']['STATE'].to_i
lcm_state = node['vm_info']['VM']['LCM_STATE'].to_i
# If any node didn't make it through the initial deployment,
# return false
# INIT, PENDING, HOLD ||
# ACTIVE && (LCM_INIT, PROLOG, BOOT)
if vm_state == 7 ||
((vm_state < 3) || (vm_state == 3 && lcm_state < 3 ))
return false
end
end
else
return false
end
}
# TODO: If a shutdown ends in running again (VM doesn't have acpi),
# the role/service will stay in SCALING
# For scale down, it will finish when scaling nodes are deleted
return role.get_nodes.size() == role.cardinality()
end
end

View File

@ -0,0 +1,151 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 Straight
# Using this strategy the service is deployed based on a directed
# acyclic graph where each node defines its parents.
#
# For example:
#
# mysql nfs
# | | \
# | | kvm
# \ | /
# front-end
#
# The above graph represents the following service:
#
# "name": "my_first_service",
# "roles": [
# {
# "name": "front-end",
# "parents": [
# "mysql",
# "kvm",
# "nfs"
# ],
# ...
# },
# {
# "name": "nfs",
# ...
# },
# {
# "name": "mysql",
# ...
# },
# {
# "name": "kvm",
# "parents": "nfs",
# ...
# }
# ],
# "deployment": "straight"
# }
#
# The roles will be deployed in the following order:
# 1. myslq & nfs
# 2. kvm
# 3. front-end
#
# And the service will be shutdown in the reverse order
# 1. front-end
# 2. kvm & myslq
# 3. nfs
# Returns all node Roles ready to be deployed
# @param [Service] service
# @return [Hash<String, Role>] Roles
def get_roles_deploy(service)
roles = service.get_roles
running_roles = roles.select {|name, role|
role.state == Role::STATE['RUNNING']
}
# Ruby 1.8 compatibility
if running_roles.instance_of?(Array)
running_roles = Hash[running_roles]
end
result = roles.select {|name, role|
check = true
if role.state == Role::STATE['PENDING']
role.parents.each { |parent|
if !running_roles.include?(parent)
check = false
break
end
}
elsif role.state == Role::STATE['DEPLOYING']
check = true
else
check = false
end
check
}
# Ruby 1.8 compatibility
if result.instance_of?(Array)
result = Hash[result]
end
result
end
# Returns all node Roles ready to be shutdown
# @param [Service] service
# @return [Hash<String, Role>] Roles
def get_roles_shutdown(service)
roles = service.get_roles
# Get all the parents from running roles
parents = []
running_roles = {}
roles.each { |name, role|
# All roles can be shutdown, except the ones in these states
if (![Role::STATE['UNDEPLOYING'],
Role::STATE['DONE'],
Role::STATE['FAILED_UNDEPLOYING']].include?(role.state) )
running_roles[name]= role
end
# Only the parents of DONE roles can be shutdown
if (role.state != Role::STATE['DONE'] )
parents += role.parents
end
}
# Select the nodes that are not parent from any node
result = running_roles.select {|name, role|
!parents.include?(name)
}
# Ruby 1.8 compatibility
if result.instance_of?(Array)
result = Hash[result]
end
result
end
end

435
src/flow/lib/validator.rb Normal file
View File

@ -0,0 +1,435 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'uri'
class Hash
# Returns a new hash containing the contents of other_hash and the
# contents of self. If the value for entries with duplicate keys
# is a Hash, it will be merged recursively, otherwise it will be that
# of other_hash.
#
# @param [Hash] other_hash
#
# @return [Hash] Containing the merged values
#
# @example Merging two hashes
# h1 = {:a => 3, {:b => 3, :c => 7}}
# h2 = {:a => 22, c => 4, {:b => 5}}
#
# h1.deep_merge(h2) #=> {:a => 22, c => 4, {:b => 5, :c => 7}}
def deep_merge(other_hash)
target = dup
other_hash.each do |hash_key, hash_value|
if hash_value.is_a?(Hash) and self[hash_key].is_a?(Hash)
target[hash_key] = self[hash_key].deep_merge(hash_value)
elsif hash_value.is_a?(Array) and self[hash_key].is_a?(Array)
hash_value.each_with_index { |elem, i|
if self[hash_key][i].is_a?(Hash) and elem.is_a?(Hash)
target[hash_key][i] = self[hash_key][i].deep_merge(elem)
else
target[hash_key] = hash_value
end
}
else
target[hash_key] = hash_value
end
end
target
end
end
module Validator
class ParseException < StandardError; end
class SchemaException < StandardError; end
class Validator
# @param [Hash] opts the options to validate a body
# @option opts [Boolean] :default_values Set default values if the schema
# specifies it (if true)
# @option opts [Booblean] :delete_extra_properties If the body contains properties
# not specified in the schema delete them from the body (if true)
# or raise an exception (if false)
def initialize(opts={})
@opts = {
:default_values => true,
:delete_extra_properties => false
}.merge(opts)
end
# Recursively validate and modify a JSON body based on a schema.
#
# @see http://tools.ietf.org/html/draft-zyp-json-schema-03
#
# @param [Hash, Array, String, nil] body JSON represented as Ruby objects
# @param [Hash] schema that will be used to validate
# @param [String] key of the body that will be validated in this step
#
# @return [Hash, Array, String, nil] The modified body
#
# @raise [SchemaException] If the schema is not correctly defined
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate a User
# schema = {
# :type => :object,
# :properties => {
# 'username' => {
# :type => :string
# }
# }
# }
#
# hash = {
# 'username' => 'pepe'
# }
#
# Validator.validate!(hash, schema)
# #=> {'username' => 'pepe'}
#
# @note The parameter body will be modified
# @note Schema options supported
# :extends
# :type => [:object, :array, :string, :null]
#
def validate!(body, schema, key="")
if schema[:extends]
base_schema = schema.delete(:extends)
schema = base_schema.deep_merge(schema)
end
case schema[:type]
when :object then validate_object(body, schema, key)
when :array then validate_array(body, schema, key)
when :string then validate_string(body, schema, key)
when :integer then validate_integer(body, schema, key)
when :null then validate_null(body, schema, key)
else raise SchemaException, "type #{schema[:type]} is not a valid type"
end
end
private
# Validate an object type
#
# @param [Hash] body to be validated
# @param [Hash] schema_object of the objectto validate the body
# @param [String] key of the body that will be validated in this step
#
# @return [Hash] The modified body
#
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate with default values
# schema_body = {
# :type => :object,
# :properties => {
# 'username' => {
# :type => :string,
# :default => 'def'
# }
# }
#
# body = {}
#
# Validator.validate_object(body, schema_body)
# #=> {'username' => 'def'}
#
# @note The parameter body will be modified
# @note Schema options supported
# :properties
# :required
# :default
#
def validate_object(body, schema_object, key)
unless body.is_a?(Hash)
raise ParseException, "KEY: #{key} must be a Hash; SCHEMA:"\
"#{schema_object}"
end
new_body = body.dup
schema_object[:properties].each{ |schema_key, schema_value|
body_value = new_body.delete(schema_key)
if body_value
body[schema_key] = validate!(body_value, schema_value,
schema_key)
else
if schema_value[:required]
raise ParseException, "KEY: '#{schema_key}' is required;"\
" SCHEMA: #{schema_value}"
end
if @opts[:default_values] && schema_value[:default]
body[schema_key] = schema_value[:default]
end
end
}
# raise error if body.keys is not empty
unless new_body.keys.empty?
if @opts[:delete_extra_properties]
new_body.keys.each{ |key|
body.delete(key)
}
else
raise ParseException, "KEY: #{new_body.keys.join(', ')} not"\
" allowed; SCHEMA: #{schema_object}"
end
end
body
end
# Validate an array type
#
# @param [Array] body to be validated
# @param [Hash] schema_array of the object to validate the body
# @param [String] schema_key of the body that will be validated in this step
#
# @return [Hash] The modified body
#
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate array
# schema = {
# :type => :array,
# :items => {
# :type => :string
# }
# }
#
# body = ['pepe', 'luis', 'juan']
#
# Validator.validate_array(body, schema)
# #=> 'username' => ['pepe', 'luis', 'juan']
#
# @note The parameter body will be modified
# @note Schema options supported
# :items
#
def validate_array(body, schema_array, schema_key)
if body.instance_of?(Array)
body.collect { |body_item|
validate!(body_item, schema_array[:items], schema_key)
}
else
raise ParseException, "KEY: '#{schema_key}' must be an Array;"\
" SCHEMA: #{schema_array}"
end
end
# Validate an integer type
#
# @param [Array] body to be validated
# @param [Hash] schema_array of the object to validate the body
# @param [String] schema_key of the body that will be validated in this step
#
# @return [Hash] The modified body
#
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate array
# schema = {
# :type => :integer
# }
#
# body = 5
#
# Validator.validate_integer(body, schema)
# #=> 5
#
#
def validate_integer(body, schema_array, schema_key)
value = Integer(body)
if schema_array[:maximum]
excl = schema_array[:exclusiveMaximum]
max = schema_array[:maximum]
if !(excl ? value < max : value <= max)
raise ParseException, "KEY: '#{schema_key}' must be "\
"lower than #{excl ? '' : 'or equal to'} #{max};"\
" SCHEMA: #{schema_array}"
end
end
if schema_array[:minimum]
excl = schema_array[:exclusiveMinimum]
min = schema_array[:minimum]
if !(excl ? value > min : value >= min)
raise ParseException, "KEY: '#{schema_key}' must be "\
"greater than #{excl ? '' : 'or equal to'} #{min};"\
" SCHEMA: #{schema_array}"
end
end
value
rescue ArgumentError
raise ParseException, "KEY: '#{schema_key}' must be an Integer;"\
" SCHEMA: #{schema_array}"
end
# Validate an null type
#
# @param [nil] body to be validated
# @param [Hash] schema_null of the object to validate the body
# @param [String] schema_key of the body that will be validated in this step
#
# @return [nil]
#
# @raise [ParseException] if the body is not nil
#
# @example Validate array
# schema = {
# :type => :null
# }
#
# body = nil
#
# Validator.validate_null(body, schema)
# #=> nil
#
#
def validate_null(body, schema_null, schema_key)
if body != nil
raise ParseException, "KEY: '#{schema_key}' is not allowed;"\
" SCHEMA: #{schema_null}"
end
end
# Validate an string type
#
# @param [String] body to be validated
# @param [Hash] schema_string of the object to validate the body
# @param [String] schema_key of the body that will be validated in this step
#
# @return [String] The modified body
#
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate array
# schema = {
# :type => :string
# }
#
# body = "pepe"
#
# Validator.validate_string(body, schema)
# #=> "pepe"
#
# @note The parameter body will be modified
# @note Schema options supported
# :format
# :enum
#
def validate_string(body, schema_string, schema_key)
if body.instance_of?(String)
if schema_string[:format]
check_format(body, schema_string, schema_key)
elsif schema_string[:enum]
check_enum(body, schema_string, schema_key)
else
body
end
else
raise ParseException, "KEY: '#{schema_key}' must be a String;"\
" SCHEMA: #{schema_string}"
end
end
# Validate an string format
#
# @param [String] body_value to be validated
# @param [Hash] schema_string of the object to validate the body
# @param [String] schema_key of the body that will be validated in this step
#
# @return [String] The modified body
#
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate array
# schema = {
# :type => :string,
# :format => :url
# }
#
# body = "http://localhost:4567"
#
# Validator.check_format(body, schema)
# #=> "http://localhost:4567"
#
# @note The parameter body will be modified
# @note Schema options supported
# :url
#
def check_format(body_value, schema_string, schema_key)
case schema_string[:format]
when :uri
begin
require 'uri'
uri = URI.parse(body_value)
rescue
raise ParseException, "KEY: '#{schema_key}' must be a valid URL;"\
" SCHEMA: #{schema_string} #{$!.message}"
end
body_value
end
body_value
end
# Validate an string enum
#
# @param [String] body_value to be validated
# @param [Hash] schema_string of the object to validate the body
# @param [String] schema_key of the body that will be validated in this step
#
# @return [String] The modified body
#
# @raise [ParseException] if the body does not meet the schema definition
#
# @example Validate array
# schema = {
# :type => :string,
# :enum => ['juan', 'luis']
# }
#
# body = "juan"
#
# Validator.check_enum(body, schema)
# #=> "juan"
#
# @note The parameter body will be modified
# @note Schema options supported
# :enum
#
def check_enum(body_value, schema_string, schema_key)
if schema_string[:enum].include?(body_value)
body_value
else
raise ParseException, "KEY: '#{schema_key}' must be one of"\
" #{schema_string[:enum].join(', ')}; SCHEMA: #{schema_string}"
end
end
end
end

468
src/flow/oneflow-server.rb Normal file
View File

@ -0,0 +1,468 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'rubygems'
require 'sinatra'
require 'yaml'
ONE_LOCATION = ENV["ONE_LOCATION"]
if !ONE_LOCATION
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
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
ONEFLOW_AUTH = VAR_LOCATION + "/.one/oneflow_auth"
ONEFLOW_LOG = LOG_LOCATION + "/oneflow.log"
CONFIGURATION_FILE = ETC_LOCATION + "/oneflow-server.conf"
$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+'/cloud'
$: << LIB_LOCATION+'/oneflow/lib'
require 'CloudAuth'
require 'CloudServer'
require 'models'
require 'log'
##############################################################################
# Configuration
##############################################################################
begin
conf = YAML.load_file(CONFIGURATION_FILE)
rescue Exception => e
STDERR.puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}"
exit 1
end
conf[:debug_level] ||= 2
conf[:lcm_interval] ||= 30
conf[:default_cooldown] ||= 300
conf[:shutdown_action] ||= 'shutdown'
conf[:action_number] ||= 1
conf[:action_period] ||= 60
conf[:auth] = 'opennebula'
set :bind, conf[:host]
set :port, conf[:port]
set :config, conf
include CloudLogger
logger = enable_logging ONEFLOW_LOG, conf[:debug_level].to_i
use Rack::Session::Pool, :key => 'oneflow'
Log.logger = logger
Log.level = conf[:debug_level].to_i
LOG_COMP = "ONEFLOW"
Log.info LOG_COMP, "Starting server"
begin
ENV["ONE_CIPHER_AUTH"] = ONEFLOW_AUTH
cloud_auth = CloudAuth.new(conf)
rescue => e
message = "Error initializing authentication system : #{e.message}"
Log.error LOG_COMP, message
STDERR.puts message
exit -1
end
set :cloud_auth, cloud_auth
##############################################################################
# Helpers
##############################################################################
before do
auth = Rack::Auth::Basic::Request.new(request.env)
if auth.provided? && auth.basic?
username, password = auth.credentials
@client = OpenNebula::Client.new("#{username}:#{password}")
else
error 401, "A username and password must be provided"
end
end
##############################################################################
# Defaults
##############################################################################
Role.init_default_cooldown(conf[:default_cooldown])
Role.init_default_shutdown(conf[:shutdown_action])
##############################################################################
# LCM thread
##############################################################################
t = Thread.new {
require 'LifeCycleManager'
ServiceLCM.new(conf[:lcm_interval], settings.cloud_auth).loop
}
t.abort_on_exception = true
##############################################################################
# Service
##############################################################################
get '/service' do
service_pool = OpenNebula::ServicePool.new(@client, OpenNebula::Pool::INFO_ALL)
rc = service_pool.info
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 200
body service_pool.to_json
end
get '/service/:id' do
service_pool = OpenNebula::ServicePool.new(@client)
service = service_pool.get(params[:id])
if OpenNebula.is_error?(service)
error CloudServer::HTTP_ERROR_CODE[service.errno], service.message
end
status 200
body service.to_json
end
delete '/service/:id' do
service_pool = OpenNebula::ServicePool.new(@client)
rc = nil
service = service_pool.get(params[:id]) { |service|
rc = service.delete
}
if OpenNebula.is_error?(service)
error CloudServer::HTTP_ERROR_CODE[service.errno], service.message
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 201
end
post '/service/:id/action' do
service_pool = OpenNebula::ServicePool.new(@client)
action = JSON.parse(request.body.read)['action']
opts = action['params']
rc = nil
service = service_pool.get(params[:id]) { |service|
rc = case action['perform']
when 'shutdown'
service.shutdown
when 'recover', 'deploy'
service.recover
when 'chown'
if opts && opts['owner_id']
args = Array.new
args << opts['owner_id'].to_i
args << (opts['group_id'] || -1).to_i
ret = service.chown(*args)
if !OpenNebula.is_error?(ret)
Log.info(LOG_COMP, "Service owner changed to #{args[0]}:#{args[1]}", params[:id])
end
ret
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to specify a UID")
end
when 'chgrp'
if opts && opts['group_id']
ret = service.chown(-1, opts['group_id'].to_i)
if !OpenNebula.is_error?(ret)
Log.info(LOG_COMP, "Service group changed to #{opts['group_id']}", params[:id])
end
ret
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to specify a GID")
end
when 'chmod'
if opts && opts['octet']
ret = service.chmod_octet(opts['octet'])
if !OpenNebula.is_error?(ret)
Log.info(LOG_COMP, "Service permissions changed to #{opts['octet']}", params[:id])
end
ret
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to specify an OCTET")
end
else
OpenNebula::Error.new("Action #{action['perform']} not supported")
end
}
if OpenNebula.is_error?(service)
error CloudServer::HTTP_ERROR_CODE[service.errno], service.message
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 201
end
put '/service/:id/role/:name' do
service_pool = OpenNebula::ServicePool.new(@client)
rc = nil
service = service_pool.get(params[:id]) do |service|
begin
rc = service.update_role(params[:name], request.body.read)
rescue Validator::ParseException, JSON::ParserError
return error 400, $!.message
end
end
if OpenNebula.is_error?(service)
error CloudServer::HTTP_ERROR_CODE[service.errno], service.message
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 200
end
post '/service/:id/role/:role_name/action' do
service_pool = OpenNebula::ServicePool.new(@client)
action = JSON.parse(request.body.read)['action']
opts = action['params']
rc = nil
service = service_pool.get(params[:id]) { |service|
roles = service.get_roles
role = roles[params[:role_name]]
if role.nil?
rc = OpenNebula::Error.new("Role '#{params[:role_name]}' not found")
else
# Use defaults only if one of the options is supplied
if opts['period'].nil? ^ opts['number'].nil?
opts['period'] = conf[:action_period] if opts['period'].nil?
opts['number'] = conf[:action_number] if opts['number'].nil?
end
rc = role.batch_action(action['perform'], opts['period'], opts['number'])
end
}
if OpenNebula.is_error?(service)
error CloudServer::HTTP_ERROR_CODE[service.errno], service.message
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 201
body rc.to_json
end
##############################################################################
# Service Template
##############################################################################
get '/service_template' do
s_template_pool = OpenNebula::ServiceTemplatePool.new(@client, OpenNebula::Pool::INFO_ALL)
rc = s_template_pool.info
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 200
body s_template_pool.to_json
end
get '/service_template/:id' do
service_template = OpenNebula::ServiceTemplate.new_with_id(params[:id], @client)
rc = service_template.info
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 200
body service_template.to_json
end
delete '/service_template/:id' do
service_template = OpenNebula::ServiceTemplate.new_with_id(params[:id], @client)
rc = service_template.delete
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
status 201
end
put '/service_template/:id' do
service_template = OpenNebula::ServiceTemplate.new_with_id(params[:id], @client)
begin
rc = service_template.update(request.body.read)
rescue Validator::ParseException, JSON::ParserError
error 400, $!.message
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
service_template.info
status 200
body service_template.to_json
end
post '/service_template' do
s_template = OpenNebula::ServiceTemplate.new(
OpenNebula::ServiceTemplate.build_xml,
@client)
begin
rc = s_template.allocate(request.body.read)
rescue Validator::ParseException, JSON::ParserError
error 400, $!.message
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
s_template.info
status 201
#body Parser.render(rc)
body s_template.to_json
end
post '/service_template/:id/action' do
service_template = OpenNebula::ServiceTemplate.new_with_id(params[:id], @client)
action = JSON.parse(request.body.read)['action']
opts = action['params']
rc = case action['perform']
when 'instantiate'
rc = service_template.info
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
service = OpenNebula::Service.new(OpenNebula::Service.build_xml, @client)
rc = service.allocate(service_template.template)
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
end
service.info
body service.to_json
when 'chown'
if opts && opts['owner_id']
args = Array.new
args << opts['owner_id'].to_i
args << (opts['group_id'].to_i || -1)
service_template.chown(*args)
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to specify a UID")
end
when 'chgrp'
if opts && opts['group_id']
service_template.chown(-1, opts['group_id'].to_i)
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to specify a GID")
end
when 'chmod'
if opts && opts['octet']
service_template.chmod_octet(opts['octet'])
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to specify an OCTET")
end
when 'update'
if opts && opts['template_json']
begin
rc = service_template.update(opts['template_json'])
rescue Validator::ParseException, JSON::ParserError
OpenNebula::Error.new($!.message)
end
else
OpenNebula::Error.new("Action #{action['perform']}: " <<
"You have to provide a template")
end
else
OpenNebula::Error.new("Action #{action['perform']} not supported")
end
if OpenNebula.is_error?(rc)
error CloudServer::HTTP_ERROR_CODE[rc.errno], rc.message
else
status 201
end
end

View File

@ -0,0 +1,387 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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 'uri'
require 'cloud/CloudClient'
include CloudCLI
module Role
# Actions that can be performed on the VMs of a given Role
SCHEDULE_ACTIONS = [
'shutdown',
'shutdown-hard',
'undeploy',
'undeploy-hard',
'hold',
'release',
'stop',
'suspend',
'resume',
'boot',
'delete',
'delete-recreate',
'reboot',
'reboot-hard',
'poweroff',
'poweroff-hard',
'snapshot-create'
]
STATE = {
'PENDING' => 0,
'DEPLOYING' => 1,
'RUNNING' => 2,
'UNDEPLOYING' => 3,
'WARNING' => 4,
'DONE' => 5,
'FAILED_UNDEPLOYING' => 6,
'FAILED_DEPLOYING' => 7,
'SCALING' => 8,
'FAILED_SCALING' => 9,
'COOLDOWN' => 10
}
STATE_STR = [
'PENDING',
'DEPLOYING',
'RUNNING',
'UNDEPLOYING',
'WARNING',
'DONE',
'FAILED_UNDEPLOYING',
'FAILED_DEPLOYING',
'SCALING',
'FAILED_SCALING',
'COOLDOWN'
]
# Returns the string representation of the role state
# @param [String] state String number representing the state
# @return the state string
def self.state_str(state_number)
return STATE_STR[state_number.to_i]
end
end
module Service
STATE = {
'PENDING' => 0,
'DEPLOYING' => 1,
'RUNNING' => 2,
'UNDEPLOYING' => 3,
'WARNING' => 4,
'DONE' => 5,
'FAILED_UNDEPLOYING' => 6,
'FAILED_DEPLOYING' => 7,
'SCALING' => 8,
'FAILED_SCALING' => 9,
'COOLDOWN' => 10
}
STATE_STR = [
'PENDING',
'DEPLOYING',
'RUNNING',
'UNDEPLOYING',
'WARNING',
'DONE',
'FAILED_UNDEPLOYING',
'FAILED_DEPLOYING',
'SCALING',
'FAILED_SCALING',
'COOLDOWN'
]
# Returns the string representation of the service state
# @param [String] state String number representing the state
# @return the state string
def self.state_str(state_number)
return STATE_STR[state_number.to_i]
end
# Build a json specifying an action
# @param [String] perform action to be performed (i.e: shutdowm)
# @param [Hash, nil] params contains the params for the action
# @return [String] json representing the action
def self.build_json_action(perform, params=nil)
body = Hash.new
body['perform'] = perform
body['params'] = params if params
action = Hash.new
action['action'] = body
JSON.pretty_generate action
end
# CLI options
DEFAULT_OPTIONS = [
ENDPOINT = {
:name => "server",
:short => "-s url",
:large => "--server url",
:format => String,
:description => "Service endpoint"
},
USERNAME={
:name => "username",
:short => "-u name",
:large => "--username name",
:format => String,
:description => "User name"
},
PASSWORD={
:name => "password",
:short => "-p pass",
:large => "--password pass",
:format => String,
:description => "User password"
}
]
JSON_FORMAT = {
:name => "json",
:short => "-j",
:large => "--json",
:description => "Print the resource in JSON"
}
TOP = {
:name => "top",
:short => "-t",
:large => "--top",
:description => "Top for the command"
}
PERIOD = {
:name => "period",
:short => "-p x",
:large => "--period x",
:format => Integer,
:description => "Seconds between each group of actions"
}
NUMBER = {
:name => "number",
:short => "-n x",
:large => "--number x",
:format => Integer,
:description => "Number of VMs to apply the action to each period"
}
FORCE = {
:name => "force",
:short => "-f",
:large => "--force",
:description => "Force the new cardinality even if it is outside the limits"
}
# Format helpers
# def self.rname_to_id(name, poolname, options)
def self.rname_to_id(name, poolname)
return 0, name.to_i if name.match(/^[0123456789]+$/)
client = Service::Client.new()
resource_path = case poolname
when "SERVICE" then "/service"
when "SERVICE TEMPLATE" then "/service_template"
end
response = client.get(resource_path)
if CloudClient::is_error?(response)
return -1, "OpenNebula #{poolname} name not found," <<
" use the ID instead"
end
pool = JSON.parse(response.body)
name_to_id(name, pool, poolname)
end
def self.rname_to_id_desc(poolname)
"OpenNebula #{poolname} name or id"
end
def self.name_to_id(name, pool, ename)
if pool['DOCUMENT_POOL']['DOCUMENT'].nil?
return -1, "#{ename} named #{name} not found."
end
objects = pool['DOCUMENT_POOL']['DOCUMENT'].select {|object| object['NAME'] == name }
if objects.length>0
if objects.length>1
return -1, "There are multiple #{ename}s with name #{name}."
else
result = objects.first['ID']
end
else
return -1, "#{ename} named #{name} not found."
end
return 0, result
end
def self.list_to_id(names, poolname)
client = Service::Client.new()
resource_path = case poolname
when "SERVICE" then "/service"
when "SERVICE TEMPLATE" then "/service_template"
end
response = client.get(resource_path)
if CloudClient::is_error?(response)
return -1, "OpenNebula #{poolname} name not found," <<
" use the ID instead"
end
pool = JSON.parse(response.body)
result = names.split(',').collect { |name|
if name.match(/^[0123456789]+$/)
name.to_i
else
rc = name_to_id(name, pool, poolname)
if rc.first == -1
return rc[0], rc[1]
end
rc[1]
end
}
return 0, result
end
def self.list_to_id_desc(poolname)
"Comma-separated list of OpenNebula #{poolname} names or ids"
end
# Perform an action on several resources
# @param [Array] ids resources ids
# @param [Block] block action to be performed
# @return [Integer] exit_code
def self.perform_actions(ids, &block)
exit_code = 0
ids.each do |id|
response = block.call(id)
if CloudClient::is_error?(response)
puts response.to_s
exit_code = response.code.to_i
end
end
exit_code
end
class Client
def initialize(opts={})
@username = opts[:username] || ENV['ONEFLOW_USER']
@password = opts[:password] || ENV['ONEFLOW_PASSWORD']
url = opts[:url] || ENV['ONEFLOW_URL'] || 'http://localhost:2474'
if @username.nil? && @password.nil?
if ENV["ONE_AUTH"] and !ENV["ONE_AUTH"].empty? and File.file?(ENV["ONE_AUTH"])
one_auth = File.read(ENV["ONE_AUTH"])
elsif File.file?(ENV["HOME"]+"/.one/one_auth")
one_auth = File.read(ENV["HOME"]+"/.one/one_auth")
end
one_auth.rstrip!
@username, @password = one_auth.split(':')
end
@uri = URI.parse(url)
@user_agent = "OpenNebula #{CloudClient::VERSION} " <<
"(#{opts[:user_agent]||"Ruby"})"
@host = nil
@port = nil
if ENV['http_proxy']
uri_proxy = URI.parse(ENV['http_proxy'])
@host = uri_proxy.host
@port = uri_proxy.port
end
end
def get(path)
req = Net::HTTP::Proxy(@host, @port)::Get.new(path)
do_request(req)
end
def delete(path)
req =Net::HTTP::Proxy(@host, @port)::Delete.new(path)
do_request(req)
end
def post(path, body)
req = Net::HTTP::Proxy(@host, @port)::Post.new(path)
req.body = body
do_request(req)
end
def put(path, body)
req = Net::HTTP::Proxy(@host, @port)::Put.new(path)
req.body = body
do_request(req)
end
def login
req = Net::HTTP::Proxy(@host, @port)::Post.new('/login')
do_request(req)
end
def logout
req = Net::HTTP::Proxy(@host, @port)::Post.new('/logout')
do_request(req)
end
private
def do_request(req)
req.basic_auth @username, @password
req['User-Agent'] = @user_agent
res = CloudClient::http_start(@uri, @timeout) do |http|
http.request(req)
end
res
end
end
end

View File

@ -30,10 +30,13 @@ module Migrator
begin
FileUtils.cp("#{VAR_LOCATION}/.one/sunstone_auth",
"#{VAR_LOCATION}/.one/onegate_auth", :preserve => true)
FileUtils.cp("#{VAR_LOCATION}/.one/sunstone_auth",
"#{VAR_LOCATION}/.one/oneflow_auth", :preserve => true)
rescue
puts "Error trying to copy #{VAR_LOCATION}/.one/sunstone_auth "<<
"to #{VAR_LOCATION}/.one/onegate_auth."
puts "Please copy the file manually."
"to #{VAR_LOCATION}/.one/onegate_auth and #{VAR_LOCATION}/.one/oneflow_auth."
puts "Please copy the files manually."
end
@db.run "ALTER TABLE user_pool RENAME TO old_user_pool;"

View File

@ -0,0 +1,23 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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. #
#--------------------------------------------------------------------------- #
################################################################################
# OneFlow
################################################################################
# OneFlow endpoint
#
:oneflow_server: http://localhost:2474/

View File

@ -124,3 +124,6 @@
#:routes:
# - custom
# - other
:routes:
- oneflow

View File

@ -16,6 +16,9 @@ enabled_tabs:
- datastores-tab
- vnets-tab
- marketplace-tab
#- oneflow-dashboard
#- oneflow-services
#- oneflow-templates
tabs:
dashboard-tab:
panel_tabs:
@ -341,3 +344,86 @@ tabs:
actions:
Marketplace.refresh: true
Marketplace.import: true
oneflow-dashboard:
panel_tabs:
table_columns:
actions:
oneflow-services:
panel_tabs:
service_info_tab: true
service_roles_tab: true
service_log_tab: true
panel_tabs_actions:
service_roles_tab:
Role.scale: true
Role.hold: true
Role.release: true
Role.suspend: true
Role.resume: true
Role.stop: true
Role.boot: true
Role.reboot: true
Role.reboot_hard: true
Role.poweroff: true
Role.poweroff_hard: true
Role.shutdown: true
Role.shutdown_hard: true
Role.delete: true
Role.delete_recreate: true
RoleVM.chown: true
RoleVM.chgrp: true
RoleVM.deploy: true
RoleVM.migrate: true
RoleVM.migrate_live: true
RoleVM.hold: true
RoleVM.release: true
RoleVM.suspend: true
RoleVM.resume: true
RoleVM.stop: true
RoleVM.boot: true
RoleVM.reboot: true
RoleVM.reboot_hard: true
RoleVM.poweroff: true
RoleVM.poweroff_hard: true
RoleVM.undeploy: true
RoleVM.undeploy_hard: true
RoleVM.shutdown: true
RoleVM.shutdown_hard: true
RoleVM.delete: true
RoleVM.delete_recreate: true
RoleVM.resched: true
RoleVM.unresched: true
RoleVM.recover: true
table_columns:
- 0 # Checkbox
- 1 # ID
- 2 # Owner
- 3 # Group
- 4 # Name
- 5 # State
actions:
Service.refresh: true
Service.chown: true
Service.chgrp: true
Service.chmod: true
Service.shutdown: true
Service.recover: true
Service.delete: true
oneflow-templates:
panel_tabs:
service_template_info_panel: true
service_template_roles_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID
- 2 # Owner
- 3 # Group
- 4 # Name
actions:
ServiceTemplate.refresh: true
ServiceTemplate.create_dialog: true
ServiceTemplate.instantiate: true
ServiceTemplate.chown: true
ServiceTemplate.chgrp: true
ServiceTemplate.chmod: true
ServiceTemplate.delete: true

View File

@ -16,6 +16,9 @@ enabled_tabs:
- datastores-tab
- vnets-tab
- marketplace-tab
#- oneflow-dashboard
#- oneflow-services
#- oneflow-templates
tabs:
dashboard-tab:
panel_tabs:
@ -342,3 +345,86 @@ tabs:
actions:
Marketplace.refresh: true
Marketplace.import: true
oneflow-dashboard:
panel_tabs:
table_columns:
actions:
oneflow-services:
panel_tabs:
service_info_tab: true
service_roles_tab: true
service_log_tab: true
panel_tabs_actions:
service_roles_tab:
Role.scale: true
Role.hold: true
Role.release: true
Role.suspend: true
Role.resume: true
Role.stop: true
Role.boot: true
Role.reboot: true
Role.reboot_hard: true
Role.poweroff: true
Role.poweroff_hard: true
Role.shutdown: true
Role.shutdown_hard: true
Role.delete: true
Role.delete_recreate: true
RoleVM.chown: false
RoleVM.chgrp: false
RoleVM.deploy: false
RoleVM.migrate: false
RoleVM.migrate_live: false
RoleVM.hold: true
RoleVM.release: true
RoleVM.suspend: true
RoleVM.resume: true
RoleVM.stop: true
RoleVM.boot: true
RoleVM.reboot: true
RoleVM.reboot_hard: true
RoleVM.poweroff: true
RoleVM.poweroff_hard: true
RoleVM.undeploy: true
RoleVM.undeploy_hard: true
RoleVM.shutdown: true
RoleVM.shutdown_hard: true
RoleVM.delete: true
RoleVM.delete_recreate: true
RoleVM.resched: false
RoleVM.unresched: false
RoleVM.recover: false
table_columns:
- 0 # Checkbox
- 1 # ID
- 2 # Owner
- 3 # Group
- 4 # Name
- 5 # State
actions:
Service.refresh: true
Service.chown: false
Service.chgrp: false
Service.chmod: true
Service.shutdown: true
Service.recover: true
Service.delete: true
oneflow-templates:
panel_tabs:
service_template_info_panel: true
service_template_roles_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID
- 2 # Owner
- 3 # Group
- 4 # Name
actions:
ServiceTemplate.refresh: true
ServiceTemplate.create_dialog: true
ServiceTemplate.instantiate: true
ServiceTemplate.chown: false
ServiceTemplate.chgrp: false
ServiceTemplate.chmod: true
ServiceTemplate.delete: true

View File

@ -0,0 +1,22 @@
// ------------------------------------------------------------------------ //
// Copyright 2010-2013, C12G Labs S.L. //
// //
// 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. //
//------------------------------------------------------------------------- //
var oneflow_dashboard_tab = {
title: '<i class="icon-sitemap"></i>OneFlow'
}
Sunstone.addMainTab('oneflow_dashboard_tab',oneflow_dashboard_tab);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
# -------------------------------------------------------------------------- #
# Copyright 2010-2013, C12G Labs S.L. #
# #
# 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. #
#--------------------------------------------------------------------------- #
ONEFLOW_CONF_FILE = ETC_LOCATION + "/sunstone-oneflow.conf"
$: << RUBY_LIB_LOCATION+"/oneflow"
require 'opennebula/oneflow_client'
begin
oneflow_conf = YAML.load_file(ONEFLOW_CONF_FILE)
rescue Exception => e
STDERR.puts "Error parsing config file #{ONEFLOW_CONF_FILE}: #{e.message}"
exit 1
end
set :oneflow_config, oneflow_conf
helpers do
def af_build_client
flow_client = settings.cloud_auth.client(session[:user])
split_array = flow_client.one_auth.split(':')
Service::Client.new(
:url => settings.oneflow_config[:oneflow_server],
:user_agent => "Sunstone",
:username => split_array.shift,
:password => split_array.join(':'))
end
def af_format_response(resp)
if CloudClient::is_error?(resp)
logger.error("[OneFlow] " + resp.to_s)
error = Error.new(resp.to_s)
error resp.code.to_i, error.to_json
else
body resp.body.to_s
end
end
end
##############################################################################
# Service
##############################################################################
get '/service' do
client = af_build_client
resp = client.get('/service')
af_format_response(resp)
end
get '/service/:id' do
client = af_build_client
resp = client.get('/service/' + params[:id])
af_format_response(resp)
end
delete '/service/:id' do
client = af_build_client
resp = client.delete('/service/' + params[:id])
af_format_response(resp)
end
post '/service/:id/action' do
client = af_build_client
resp = client.post('/service/' + params[:id] + '/action', request.body.read)
af_format_response(resp)
end
post '/service/:id/role/:role_name/action' do
client = af_build_client
resp = client.post('/service/' + params[:id] + '/role/' + params[:role_name] + '/action', request.body.read)
af_format_response(resp)
end
put '/service/:id/role/:role_name' do
client = af_build_client
resp = client.put('/service/' + params[:id] + '/role/' + params[:role_name], request.body.read)
af_format_response(resp)
end
##############################################################################
# Service Template
##############################################################################
get '/service_template' do
client = af_build_client
resp = client.get('/service_template')
af_format_response(resp)
end
get '/service_template/:id' do
client = af_build_client
resp = client.get('/service_template/' + params[:id])
af_format_response(resp)
end
delete '/service_template/:id' do
client = af_build_client
resp = client.delete('/service_template/' + params[:id])
af_format_response(resp)
end
post '/service_template/:id/action' do
client = af_build_client
resp = client.post('/service_template/' + params[:id] + '/action', request.body.read)
af_format_response(resp)
end
post '/service_template' do
client = af_build_client
resp = client.post('/service_template', request.body.read)
af_format_response(resp)
end

View File

@ -72,7 +72,7 @@ UserPool::UserPool(SqlDB * db,
const char * one_auth;
ifstream file;
string filenames[4];
string filenames[5];
string error_str;
Nebula& nd = Nebula::instance();
@ -147,10 +147,11 @@ UserPool::UserPool(SqlDB * db,
filenames[1] = nd.get_var_location() + "/.one/occi_auth";
filenames[2] = nd.get_var_location() + "/.one/ec2_auth";
filenames[3] = nd.get_var_location() + "/.one/onegate_auth";
filenames[4] = nd.get_var_location() + "/.one/oneflow_auth";
mkdir(string(nd.get_var_location() + "/.one").c_str(), S_IRWXU);
for (i=0 ; i < 4; i++)
for (i=0 ; i < 5; i++)
{
int cfile = creat(filenames[i].c_str(), S_IRUSR | S_IWUSR);
close(cfile);