From 10821180ee214f01c1ef86fb186e05cfdeec0f20 Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Tue, 9 Oct 2018 11:42:17 +0200 Subject: [PATCH 1/2] F #2427: Add support to NICs with NETWORK_MODE=auto. This commits includes logic in oned to delay NIC/context resolution till deployment time. Also scheduler has been extended to use the match-making algorithm to networks. Sunstone interface changes to select auto networks. Co-authored-by: juanmont --- src/cli/one_helper.rb | 2316 ++++++++++++++++++++--------------------- 1 file changed, 1158 insertions(+), 1158 deletions(-) diff --git a/src/cli/one_helper.rb b/src/cli/one_helper.rb index 6ec50d68d0c..20d466ca28d 100644 --- a/src/cli/one_helper.rb +++ b/src/cli/one_helper.rb @@ -19,1230 +19,1230 @@ require 'open3' require 'io/console' -begin - require 'opennebula' -rescue Exception => e - puts "Error: "+e.message.to_s - exit(-1) -end - -include OpenNebula - -module OpenNebulaHelper - ONE_VERSION=<<-EOT -OpenNebula #{OpenNebula::VERSION} -Copyright 2002-2018, OpenNebula Project, OpenNebula Systems - -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 -EOT - - if ONE_LOCATION - TABLE_CONF_PATH=ONE_LOCATION+"/etc/cli" - VAR_LOCATION=ONE_LOCATION+"/var" if !defined?(VAR_LOCATION) - CLI_ADDONS_LOCATION=ONE_LOCATION+"/lib/ruby/cli/addons" - else - TABLE_CONF_PATH="/etc/one/cli" - VAR_LOCATION="/var/lib/one" if !defined?(VAR_LOCATION) - CLI_ADDONS_LOCATION="/usr/lib/one/ruby/cli/addons" - end - - EDITOR_PATH='/usr/bin/vi' - - ######################################################################## - # Options - ######################################################################## - XML={ - :name => "xml", - :short => "-x", - :large => "--xml", - :description => "Show the resource in xml format" - } - - NUMERIC={ - :name => "numeric", - :short => "-n", - :large => "--numeric", - :description => "Do not translate user and group IDs" - } - - KILOBYTES={ - :name => "kilobytes", - :short => "-k", - :large => "--kilobytes", - :description => "Show units in kilobytes" - } - - DESCRIBE={ - :name => "describe", - :large => "--describe", - :description => "Describe list columns" - } - - APPEND = { - :name => "append", - :short => "-a", - :large => "--append", - :description => "Append new attributes to the current template" - } - - # Command line VM template options - TEMPLATE_NAME_VM={ - :name => 'name', - :large => '--name name', - :description => - 'Name for the new VM', - :format => String - } - - DRY={ - :name => 'dry', - :large => '--dry', - :description => 'Just print the template' - } - - CLIENT_OPTIONS=[ - { - :name => 'user', - :large => '--user name', - :description => 'User name used to connect to OpenNebula', - :format => String, - :proc => lambda do |o, options| - OneHelper.set_user(o) - [0, o] - end - }, - { - :name => 'password', - :large => '--password password', - :description => 'Password to authenticate with OpenNebula', - :format => String, - :proc => lambda do |o, options| - OneHelper.set_password(o) - [0, o] - end - }, - { - :name => 'endpoint', - :large => '--endpoint endpoint', - :description => 'URL of OpenNebula xmlrpc frontend', - :format => String, - :proc => lambda do |o, options| - OneHelper.set_endpoint(o) - [0, o] - end - } - ] - - GROUP_OPTIONS=[ - { - :name => 'name', - :large => '--name name', - :short => "-n", - :description => - 'Name for the new group', - :format => String - }, - { - :name => 'admin_user', - :large => '--admin_user name', - :short => "-u", - :description => - 'Creates an admin user for the group with name', - :format => String - }, - { - :name => 'admin_password', - :large => '--admin_password pass', - :short => "-p", - :description => - 'Password for the admin user of the group', - :format => String - }, - { - :name => 'admin_driver', - :large => '--admin_driver driver', - :short => "-d", - :description => - 'Auth driver for the admin user of the group', - :format => String - }, - { - :name => 'resources', - :large => '--resources res_str', - :short => "-r", - :description => - "Which resources can be created by group users "<< - "(VM+NET+IMAGE+TEMPLATE by default)", - :format => String - } - ] - - #NOTE: Other options defined using this array, add new options at the end - TEMPLATE_OPTIONS=[ - { - :name => 'cpu', - :large => '--cpu cpu', - :description => - "CPU percentage reserved for the VM (1=100% one\n"<< - " "*31<<"CPU)", - :format => Float - }, - { - :name => 'vcpu', - :large => '--vcpu vcpu', - :description => - "Number of virtualized CPUs", - :format => Integer - }, - { - :name => 'arch', - :large => '--arch arch', - :description => - 'Architecture of the VM, e.g.: i386 or x86_64', - :format => String - }, - { - :name => 'memory', - :large => '--memory memory', - :description => 'Memory amount given to the VM. By default the '<< - "unit is megabytes. To use gigabytes add a 'g', floats "<< - "can be used: 8g=8192, 0.5g=512", - :format => String, - :proc => lambda do |o,options| - m=o.strip.match(/^(\d+(?:\.\d+)?)(m|mb|g|gb)?$/i) - - if !m - [-1, 'Memory value malformed'] - else - multiplier=case m[2] - when /(g|gb)/i - 1024 - else - 1 - end - - value=m[1].to_f*multiplier - - [0, value.floor] - end - end - }, - { - :name => 'disk', - :large => '--disk image0,image1', - :description => "Disks to attach. To use an image owned by"<< - " other user use user[disk]. Add any additional"<< - " attributes separated by ':' and in the shape of"<< - " KEY=VALUE. For example, if the disk must be"<< - " resized, use image0:size=1000 . Or"<< - " image0:size=1000:target=vda,image1:target=vdb", - :format => Array - }, - { - :name => 'nic', - :large => '--nic network0,network1', - :description => "Networks to attach. To use a network owned by"<< - " other user use user[network]. Additional"<< - " attributes are supported like with the --disk"<< - " option. Also you can use auto if you want that" << - " OpenNebula select automatically the network", - :format => Array - }, - { - :name => 'raw', - :large => '--raw string', - :description => "Raw string to add to the template. Not to be\n"<< - " "*31<<"confused with the RAW attribute", - :format => String - }, - { - :name => 'vnc', - :large => '--vnc', - :description => 'Add VNC server to the VM' - }, - { - :name => 'vnc_password', - :large => '--vnc-password password', - :format => String, - :description => 'VNC password' - }, - { - :name => 'vnc_listen', - :large => '--vnc-listen ip', - :format => String, - :description => 'VNC IP where to listen for connections. '<< - 'By default is 0.0.0.0 (all interfaces).' - }, - { - :name => 'vnc_keymap', - :large => '--vnc-keymap keymap', - :format => String, - :description => 'VNC keyboard layout' - }, - { - :name => 'spice', - :large => '--spice', - :description => 'Add spice server to the VM' - }, - { - :name => 'spice_password', - :large => '--spice-password password', - :format => String, - :description => 'spice password' - }, - { - :name => 'spice_listen', - :large => '--spice-listen ip', - :format => String, - :description => 'spice IP where to listen for connections. '<< - 'By default is 0.0.0.0 (all interfaces).' - }, - { - :name => 'spice_keymap', - :large => '--spice-keymap keymap', - :format => String, - :description => 'spice keyboard layout' - }, - { - :name => 'ssh', - :large => '--ssh [file]', - :description => "Add an ssh public key to the context. If the \n"<< - (' '*31) << "file is omited then the user variable \n"<< - (' '*31) << "SSH_PUBLIC_KEY will be used.", - :format => String, - :proc => lambda do |o, options| - if !o - [0, true] - else - [0, o] - end - end - }, - { - :name => 'net_context', - :large => '--net_context', - :description => 'Add network contextualization parameters' - }, - { - :name => 'context', - :large => '--context line1,line2,line3', - :format => Array, - :description => 'Lines to add to the context section' - }, - { - :name => 'boot', - :large => '--boot device_list', - :description => 'Set boot device list e.g. disk0,disk2,nic0', - :format => String - }, - { - :name => 'files_ds', - :large => '--files_ds file1,file2', - :format => Array, - :description => 'Add files to the contextualization CD from the' << - 'files datastore' - }, - { - :name => 'init', - :large => '--init script1,script2', - :format => Array, - :description => 'Script or scripts to start in context' - }, - { - :name => 'startscript', - :large => '--startscript [file]', - :format => String, - :description => 'Start script to be executed' - }, - { - :name => 'report_ready', - :large => '--report_ready', - :description => 'Sends READY=YES to OneGate, useful for OneFlow' - }, - { - :name => 'vcenter_vm_folder', - :large => '--vcenter_vm_folder path', - :format => String, - :description => "In a vCenter environment sets the the VMs and Template folder where the VM will be placed in." \ - " The path uses slashes to separate folders. For example: --vcenter_vm_folder \"/Management/VMs\"" - }, - { - :name => 'as_uid', - :large => '--as_uid uid', - :format => Integer, - :description => 'The User ID to instantiate the VM' - }, - { - :name => 'as_gid', - :large => '--as_gid gid', - :format => Integer, - :description => 'The Group ID to instantiate the VM' - } - ] - - FORCE={ - :name => 'force', - :large => '--force', - :description => 'Overwrite the file' - } - - TEMPLATE_OPTIONS_VM = [TEMPLATE_NAME_VM] + TEMPLATE_OPTIONS + [DRY] - - CAPACITY_OPTIONS_VM = [TEMPLATE_OPTIONS[0], TEMPLATE_OPTIONS[1], - TEMPLATE_OPTIONS[3]] - - UPDATECONF_OPTIONS_VM = TEMPLATE_OPTIONS[6..15] + [TEMPLATE_OPTIONS[2], - TEMPLATE_OPTIONS[17], TEMPLATE_OPTIONS[18]] - - OPTIONS = XML, NUMERIC, KILOBYTES - - class OneHelper - attr_accessor :client - - def self.get_client(options={}, force=false) - if !force && defined?(@@client) - @@client - else - - secret=nil - password=nil - - if defined?(@@user) - user=@@user - password=@@password if defined?(@@password) - else - user=options[:user] - end - - if user - password=password||options[:password]||self.get_password - secret="#{user}:#{password}" - end - - if defined?(@@endpoint) - endpoint=@@endpoint - else - endpoint=options[:endpoint] - end - - # This breaks the CLI SSL support for Ruby 1.8.7, but is necessary - # in order to do template updates, otherwise you get the broken pipe - # error (bug #3341) - if RUBY_VERSION < '1.9' - sync = false - else - sync = true - end - options[:sync] = sync - @@client=OpenNebula::Client.new(secret, endpoint, options) - end - end - - def self.client - if defined?(@@client) - @@client - else - self.get_client - end - end - - def self.set_user(user) - @@user=user - end + begin + require 'opennebula' + rescue Exception => e + puts "Error: "+e.message.to_s + exit(-1) + end + + include OpenNebula + + module OpenNebulaHelper + ONE_VERSION=<<-EOT + OpenNebula #{OpenNebula::VERSION} + Copyright 2002-2018, OpenNebula Project, OpenNebula Systems + + 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 + EOT + + if ONE_LOCATION + TABLE_CONF_PATH=ONE_LOCATION+"/etc/cli" + VAR_LOCATION=ONE_LOCATION+"/var" if !defined?(VAR_LOCATION) + CLI_ADDONS_LOCATION=ONE_LOCATION+"/lib/ruby/cli/addons" + else + TABLE_CONF_PATH="/etc/one/cli" + VAR_LOCATION="/var/lib/one" if !defined?(VAR_LOCATION) + CLI_ADDONS_LOCATION="/usr/lib/one/ruby/cli/addons" + end + + EDITOR_PATH='/usr/bin/vi' + + ######################################################################## + # Options + ######################################################################## + XML={ + :name => "xml", + :short => "-x", + :large => "--xml", + :description => "Show the resource in xml format" + } + + NUMERIC={ + :name => "numeric", + :short => "-n", + :large => "--numeric", + :description => "Do not translate user and group IDs" + } + + KILOBYTES={ + :name => "kilobytes", + :short => "-k", + :large => "--kilobytes", + :description => "Show units in kilobytes" + } + + DESCRIBE={ + :name => "describe", + :large => "--describe", + :description => "Describe list columns" + } + + APPEND = { + :name => "append", + :short => "-a", + :large => "--append", + :description => "Append new attributes to the current template" + } + + # Command line VM template options + TEMPLATE_NAME_VM={ + :name => 'name', + :large => '--name name', + :description => + 'Name for the new VM', + :format => String + } + + DRY={ + :name => 'dry', + :large => '--dry', + :description => 'Just print the template' + } + + CLIENT_OPTIONS=[ + { + :name => 'user', + :large => '--user name', + :description => 'User name used to connect to OpenNebula', + :format => String, + :proc => lambda do |o, options| + OneHelper.set_user(o) + [0, o] + end + }, + { + :name => 'password', + :large => '--password password', + :description => 'Password to authenticate with OpenNebula', + :format => String, + :proc => lambda do |o, options| + OneHelper.set_password(o) + [0, o] + end + }, + { + :name => 'endpoint', + :large => '--endpoint endpoint', + :description => 'URL of OpenNebula xmlrpc frontend', + :format => String, + :proc => lambda do |o, options| + OneHelper.set_endpoint(o) + [0, o] + end + } + ] + + GROUP_OPTIONS=[ + { + :name => 'name', + :large => '--name name', + :short => "-n", + :description => + 'Name for the new group', + :format => String + }, + { + :name => 'admin_user', + :large => '--admin_user name', + :short => "-u", + :description => + 'Creates an admin user for the group with name', + :format => String + }, + { + :name => 'admin_password', + :large => '--admin_password pass', + :short => "-p", + :description => + 'Password for the admin user of the group', + :format => String + }, + { + :name => 'admin_driver', + :large => '--admin_driver driver', + :short => "-d", + :description => + 'Auth driver for the admin user of the group', + :format => String + }, + { + :name => 'resources', + :large => '--resources res_str', + :short => "-r", + :description => + "Which resources can be created by group users "<< + "(VM+NET+IMAGE+TEMPLATE by default)", + :format => String + } + ] + + #NOTE: Other options defined using this array, add new options at the end + TEMPLATE_OPTIONS=[ + { + :name => 'cpu', + :large => '--cpu cpu', + :description => + "CPU percentage reserved for the VM (1=100% one\n"<< + " "*31<<"CPU)", + :format => Float + }, + { + :name => 'vcpu', + :large => '--vcpu vcpu', + :description => + "Number of virtualized CPUs", + :format => Integer + }, + { + :name => 'arch', + :large => '--arch arch', + :description => + 'Architecture of the VM, e.g.: i386 or x86_64', + :format => String + }, + { + :name => 'memory', + :large => '--memory memory', + :description => 'Memory amount given to the VM. By default the '<< + "unit is megabytes. To use gigabytes add a 'g', floats "<< + "can be used: 8g=8192, 0.5g=512", + :format => String, + :proc => lambda do |o,options| + m=o.strip.match(/^(\d+(?:\.\d+)?)(m|mb|g|gb)?$/i) + + if !m + [-1, 'Memory value malformed'] + else + multiplier=case m[2] + when /(g|gb)/i + 1024 + else + 1 + end + + value=m[1].to_f*multiplier + + [0, value.floor] + end + end + }, + { + :name => 'disk', + :large => '--disk image0,image1', + :description => "Disks to attach. To use an image owned by"<< + " other user use user[disk]. Add any additional"<< + " attributes separated by ':' and in the shape of"<< + " KEY=VALUE. For example, if the disk must be"<< + " resized, use image0:size=1000 . Or"<< + " image0:size=1000:target=vda,image1:target=vdb", + :format => Array + }, + { + :name => 'nic', + :large => '--nic network0,network1', + :description => "Networks to attach. To use a network owned by"<< + " other user use user[network]. Additional"<< + " attributes are supported like with the --disk"<< + " option. Also you can use auto if you want that" << + " OpenNebula select automatically the network", + :format => Array + }, + { + :name => 'raw', + :large => '--raw string', + :description => "Raw string to add to the template. Not to be\n"<< + " "*31<<"confused with the RAW attribute", + :format => String + }, + { + :name => 'vnc', + :large => '--vnc', + :description => 'Add VNC server to the VM' + }, + { + :name => 'vnc_password', + :large => '--vnc-password password', + :format => String, + :description => 'VNC password' + }, + { + :name => 'vnc_listen', + :large => '--vnc-listen ip', + :format => String, + :description => 'VNC IP where to listen for connections. '<< + 'By default is 0.0.0.0 (all interfaces).' + }, + { + :name => 'vnc_keymap', + :large => '--vnc-keymap keymap', + :format => String, + :description => 'VNC keyboard layout' + }, + { + :name => 'spice', + :large => '--spice', + :description => 'Add spice server to the VM' + }, + { + :name => 'spice_password', + :large => '--spice-password password', + :format => String, + :description => 'spice password' + }, + { + :name => 'spice_listen', + :large => '--spice-listen ip', + :format => String, + :description => 'spice IP where to listen for connections. '<< + 'By default is 0.0.0.0 (all interfaces).' + }, + { + :name => 'spice_keymap', + :large => '--spice-keymap keymap', + :format => String, + :description => 'spice keyboard layout' + }, + { + :name => 'ssh', + :large => '--ssh [file]', + :description => "Add an ssh public key to the context. If the \n"<< + (' '*31) << "file is omited then the user variable \n"<< + (' '*31) << "SSH_PUBLIC_KEY will be used.", + :format => String, + :proc => lambda do |o, options| + if !o + [0, true] + else + [0, o] + end + end + }, + { + :name => 'net_context', + :large => '--net_context', + :description => 'Add network contextualization parameters' + }, + { + :name => 'context', + :large => '--context line1,line2,line3', + :format => Array, + :description => 'Lines to add to the context section' + }, + { + :name => 'boot', + :large => '--boot device_list', + :description => 'Set boot device list e.g. disk0,disk2,nic0', + :format => String + }, + { + :name => 'files_ds', + :large => '--files_ds file1,file2', + :format => Array, + :description => 'Add files to the contextualization CD from the' << + 'files datastore' + }, + { + :name => 'init', + :large => '--init script1,script2', + :format => Array, + :description => 'Script or scripts to start in context' + }, + { + :name => 'startscript', + :large => '--startscript [file]', + :format => String, + :description => 'Start script to be executed' + }, + { + :name => 'report_ready', + :large => '--report_ready', + :description => 'Sends READY=YES to OneGate, useful for OneFlow' + }, + { + :name => 'vcenter_vm_folder', + :large => '--vcenter_vm_folder path', + :format => String, + :description => "In a vCenter environment sets the the VMs and Template folder where the VM will be placed in." \ + " The path uses slashes to separate folders. For example: --vcenter_vm_folder \"/Management/VMs\"" + }, + { + :name => 'as_uid', + :large => '--as_uid uid', + :format => Integer, + :description => 'The User ID to instantiate the VM' + }, + { + :name => 'as_gid', + :large => '--as_gid gid', + :format => Integer, + :description => 'The Group ID to instantiate the VM' + } + ] + + FORCE={ + :name => 'force', + :large => '--force', + :description => 'Overwrite the file' + } + + TEMPLATE_OPTIONS_VM = [TEMPLATE_NAME_VM] + TEMPLATE_OPTIONS + [DRY] + + CAPACITY_OPTIONS_VM = [TEMPLATE_OPTIONS[0], TEMPLATE_OPTIONS[1], + TEMPLATE_OPTIONS[3]] + + UPDATECONF_OPTIONS_VM = TEMPLATE_OPTIONS[6..15] + [TEMPLATE_OPTIONS[2], + TEMPLATE_OPTIONS[17], TEMPLATE_OPTIONS[18]] + + OPTIONS = XML, NUMERIC, KILOBYTES + + class OneHelper + attr_accessor :client + + def self.get_client(options={}, force=false) + if !force && defined?(@@client) + @@client + else + + secret=nil + password=nil + + if defined?(@@user) + user=@@user + password=@@password if defined?(@@password) + else + user=options[:user] + end + + if user + password=password||options[:password]||self.get_password + secret="#{user}:#{password}" + end + + if defined?(@@endpoint) + endpoint=@@endpoint + else + endpoint=options[:endpoint] + end + + # This breaks the CLI SSL support for Ruby 1.8.7, but is necessary + # in order to do template updates, otherwise you get the broken pipe + # error (bug #3341) + if RUBY_VERSION < '1.9' + sync = false + else + sync = true + end + options[:sync] = sync + @@client=OpenNebula::Client.new(secret, endpoint, options) + end + end + + def self.client + if defined?(@@client) + @@client + else + self.get_client + end + end + + def self.set_user(user) + @@user=user + end + + def self.set_password(password) + @@password=password + end + + def self.set_endpoint(endpoint) + @@endpoint=endpoint + end + + if RUBY_VERSION>="1.9.3" + require 'io/console' + def self.get_password + print "Password: " + pass=nil + STDIN.noecho {|io| pass=io.gets } + puts + + pass.chop! if pass + @@password=pass + pass + end + else + # This function is copied from ruby net/imap.rb + def self.get_password + print "Password: " + system("stty", "-echo") + begin + @@password = STDIN.gets.chop + return @@password + ensure + system("stty", "echo") + print "\n" + end + end + end + + def initialize(secret=nil, endpoint=nil) + @client=nil + + @translation_hash = nil + end + + def set_client(options) + @client=OpenNebulaHelper::OneHelper.get_client(options, true) + end + + def create_resource(options, &block) + resource = factory + + rc = block.call(resource) + if OpenNebula.is_error?(rc) + return -1, rc.message + else + puts "ID: #{resource.id.to_s}" + return 0 + end + end + + #----------------------------------------------------------------------- + # List pool functions + #----------------------------------------------------------------------- + def start_pager + pager = ENV['ONE_PAGER'] || 'less' + + # Start pager, defaults to less + p_r, p_w = IO.pipe + + lpid = fork do + $stdin.reopen(p_r) + + p_r.close + p_w.close + + Kernel.select [$stdin] + + exec([pager, pager]) + end + + # Send listing to pager pipe + $stdout.close + $stdout = p_w.dup + + p_w.close + p_r.close + + return lpid + end + + def stop_pager(lpid) + $stdout.close + + begin + Process.wait(lpid) + rescue Errno::ECHILD + end + end + + def print_page(pool, options) + page = nil + + if options[:xml] + elements = 0 + page = "" + + pool.each {|e| + elements += 1 + page << e.to_xml(true) << "\n" + } + else + + pname = pool.pool_name + ename = pool.element_name + + page = pool.to_hash + elems = page["#{pname}"]["#{ename}"] + + if elems.class == Array + elements = elems.length + else + elements = 1 + end + end - def self.set_password(password) - @@password=password - end + return elements, page + end - def self.set_endpoint(endpoint) - @@endpoint=endpoint - end + #----------------------------------------------------------------------- + # List the pool in table form, it uses pagination for interactive + # output + #----------------------------------------------------------------------- + def list_pool_table(table, pool, options, filter_flag) + if $stdout.isatty and (!options.key?:no_pager) + size = $stdout.winsize[0] - 1 - if RUBY_VERSION>="1.9.3" - require 'io/console' - def self.get_password - print "Password: " - pass=nil - STDIN.noecho {|io| pass=io.gets } - puts - - pass.chop! if pass - @@password=pass - pass - end - else - # This function is copied from ruby net/imap.rb - def self.get_password - print "Password: " - system("stty", "-echo") - begin - @@password = STDIN.gets.chop - return @@password - ensure - system("stty", "echo") - print "\n" - end - end - end + # ----------- First page, check if pager is needed ------------- + rc = pool.get_page(size, 0) + ps = "" - def initialize(secret=nil, endpoint=nil) - @client=nil + return -1, rc.message if OpenNebula.is_error?(rc) - @translation_hash = nil - end + elements, hash = print_page(pool, options) - def set_client(options) - @client=OpenNebulaHelper::OneHelper.get_client(options, true) - end + ppid = -1 - def create_resource(options, &block) - resource = factory + if elements >= size + ppid = start_pager + end - rc = block.call(resource) - if OpenNebula.is_error?(rc) - return -1, rc.message - else - puts "ID: #{resource.id.to_s}" - return 0 - end - end + table.show(hash, options) - #----------------------------------------------------------------------- - # List pool functions - #----------------------------------------------------------------------- - def start_pager - pager = ENV['ONE_PAGER'] || 'less' + if elements < size + return 0 + elsif !pool.is_paginated? + stop_pager(ppid) + return 0 + end - # Start pager, defaults to less - p_r, p_w = IO.pipe + # ------- Rest of the pages in the pool, piped to pager -------- + current = size - lpid = fork do - $stdin.reopen(p_r) + options[:noheader] = true - p_r.close - p_w.close + loop do + rc = pool.get_page(size, current) - Kernel.select [$stdin] + return -1, rc.message if OpenNebula.is_error?(rc) - exec([pager, pager]) - end - - # Send listing to pager pipe - $stdout.close - $stdout = p_w.dup + current += size - p_w.close - p_r.close + begin + Process.waitpid(ppid, Process::WNOHANG) + rescue Errno::ECHILD + break + end - return lpid - end + elements, hash = print_page(pool, options) - def stop_pager(lpid) - $stdout.close + table.show(hash, options) - begin - Process.wait(lpid) - rescue Errno::ECHILD - end - end + $stdout.flush - def print_page(pool, options) - page = nil + break if elements < size + end - if options[:xml] - elements = 0 - page = "" + stop_pager(ppid) + else + array = pool.get_hash + return -1, array.message if OpenNebula.is_error?(array) - pool.each {|e| - elements += 1 - page << e.to_xml(true) << "\n" - } - else - - pname = pool.pool_name - ename = pool.element_name + rname = self.class.rname + elements = array["#{rname}_POOL"][rname] - page = pool.to_hash - elems = page["#{pname}"]["#{ename}"] + if options[:ids] && elements + elements.reject! do |element| + !options[:ids].include?(element['ID'].to_i) + end + end - if elems.class == Array - elements = elems.length - else - elements = 1 - end - end + table.show(array, options) + end - return elements, page - end + return 0 + end - #----------------------------------------------------------------------- - # List the pool in table form, it uses pagination for interactive - # output - #----------------------------------------------------------------------- - def list_pool_table(table, pool, options, filter_flag) - if $stdout.isatty and (!options.key?:no_pager) - size = $stdout.winsize[0] - 1 + #----------------------------------------------------------------------- + # List pool in XML format, pagination is used in interactive output + #----------------------------------------------------------------------- + def list_pool_xml(pool, options, filter_flag) + if $stdout.isatty + size = $stdout.winsize[0] - 1 - # ----------- First page, check if pager is needed ------------- - rc = pool.get_page(size, 0) - ps = "" + # ----------- First page, check if pager is needed ------------- + rc = pool.get_page(size, 0) + ps = "" - return -1, rc.message if OpenNebula.is_error?(rc) + return -1, rc.message if OpenNebula.is_error?(rc) - elements, hash = print_page(pool, options) + pname = pool.pool_name - ppid = -1 + elements, page = print_page(pool, options) - if elements >= size - ppid = start_pager - end + ppid = -1 - table.show(hash, options) + if elements >= size + ppid = start_pager + end - if elements < size - return 0 - elsif !pool.is_paginated? - stop_pager(ppid) - return 0 - end - - # ------- Rest of the pages in the pool, piped to pager -------- - current = size + puts "<#{pname}>" - options[:noheader] = true + puts page - loop do - rc = pool.get_page(size, current) + if elements < size + puts "" + return 0 + end - return -1, rc.message if OpenNebula.is_error?(rc) + # ------- Rest of the pages in the pool, piped to pager -------- + current = size - current += size - - begin - Process.waitpid(ppid, Process::WNOHANG) - rescue Errno::ECHILD - break - end + loop do + rc = pool.get_page(size, current) - elements, hash = print_page(pool, options) + return -1, rc.message if OpenNebula.is_error?(rc) - table.show(hash, options) + current += size - $stdout.flush + elements, page = print_page(pool, options) - break if elements < size - end + puts page - stop_pager(ppid) - else - array = pool.get_hash - return -1, array.message if OpenNebula.is_error?(array) + $stdout.flush - rname = self.class.rname - elements = array["#{rname}_POOL"][rname] + break if elements < size + end - if options[:ids] && elements - elements.reject! do |element| - !options[:ids].include?(element['ID'].to_i) - end - end + puts "" - table.show(array, options) - end + stop_pager(ppid) + else + rc = pool.info - return 0 - end + return -1, rc.message if OpenNebula.is_error?(rc) - #----------------------------------------------------------------------- - # List pool in XML format, pagination is used in interactive output - #----------------------------------------------------------------------- - def list_pool_xml(pool, options, filter_flag) - if $stdout.isatty - size = $stdout.winsize[0] - 1 + puts pool.to_xml(true) + end - # ----------- First page, check if pager is needed ------------- - rc = pool.get_page(size, 0) - ps = "" + return 0 + end - return -1, rc.message if OpenNebula.is_error?(rc) + #----------------------------------------------------------------------- + # List pool table in top-like form + #----------------------------------------------------------------------- + def list_pool_top(table, pool, options) + table.top(options) { + array = pool.get_hash - pname = pool.pool_name + return -1, array.message if OpenNebula.is_error?(array) - elements, page = print_page(pool, options) + array + } - ppid = -1 + return 0 + end - if elements >= size - ppid = start_pager - end - puts "<#{pname}>" + def list_pool(options, top=false, filter_flag=nil) + table = format_pool(options) - puts page + if options[:describe] + table.describe_columns - if elements < size - puts "" - return 0 - end + return 0 + end - # ------- Rest of the pages in the pool, piped to pager -------- - current = size + filter_flag ||= OpenNebula::Pool::INFO_ALL - loop do - rc = pool.get_page(size, current) + pool = factory_pool(filter_flag) - return -1, rc.message if OpenNebula.is_error?(rc) + if top + return list_pool_top(table, pool, options) + elsif options[:xml] + return list_pool_xml(pool, options, filter_flag) + else + return list_pool_table(table, pool, options, filter_flag) + end - current += size + return 0 + end - elements, page = print_page(pool, options) + def show_resource(id, options) + resource = retrieve_resource(id) - puts page + rc = resource.info + return -1, rc.message if OpenNebula.is_error?(rc) - $stdout.flush + if options[:xml] + return 0, resource.to_xml(true) + else + format_resource(resource, options) + return 0 + end + end - break if elements < size - end - - puts "" - - stop_pager(ppid) - else - rc = pool.info - - return -1, rc.message if OpenNebula.is_error?(rc) - - puts pool.to_xml(true) - end - - return 0 - end - - #----------------------------------------------------------------------- - # List pool table in top-like form - #----------------------------------------------------------------------- - def list_pool_top(table, pool, options) - table.top(options) { - array = pool.get_hash - - return -1, array.message if OpenNebula.is_error?(array) - - array - } - - return 0 - end - - - def list_pool(options, top=false, filter_flag=nil) - table = format_pool(options) - - if options[:describe] - table.describe_columns - - return 0 - end - - filter_flag ||= OpenNebula::Pool::INFO_ALL - - pool = factory_pool(filter_flag) - - if top - return list_pool_top(table, pool, options) - elsif options[:xml] - return list_pool_xml(pool, options, filter_flag) - else - return list_pool_table(table, pool, options, filter_flag) - end - - return 0 - end - - def show_resource(id, options) - resource = retrieve_resource(id) - - rc = resource.info - return -1, rc.message if OpenNebula.is_error?(rc) - - if options[:xml] - return 0, resource.to_xml(true) - else - format_resource(resource, options) - return 0 - end - end - - def perform_action(id, options, verbose, &block) - resource = retrieve_resource(id) - - rc = block.call(resource) - if OpenNebula.is_error?(rc) - return -1, rc.message - else - if options[:verbose] - puts "#{self.class.rname} #{id}: #{verbose}" - end - return 0 - end - end - - def perform_actions(ids,options,verbose,&block) - exit_code = 0 - ids.each do |id| - rc = perform_action(id,options,verbose,&block) - - unless rc[0]==0 - puts rc[1] - exit_code=rc[0] - end - end - - exit_code - end - - ######################################################################## - # Id translation - ######################################################################## - def user_name(resource, options={}) - if options[:numeric] - resource['UID'] - else - resource['UNAME'] - end - end - - def group_name(resource, options={}) - if options[:numeric] - resource['GID'] - else - resource['GNAME'] - end - end - - ######################################################################## - # Formatters for arguments - ######################################################################## - def to_id(name) - return 0, name.to_i if name.match(/^[0123456789]+$/) - - rc = get_pool - return rc if rc.first != 0 - - pool = rc[1] - poolname = self.class.rname - - OneHelper.name_to_id(name, pool, poolname) - end - - def self.to_id_desc - "OpenNebula #{self.rname} name or id" - end - - def list_to_id(names) - rc = get_pool - return rc if rc.first != 0 - - pool = rc[1] - poolname = self.class.rname - - result = names.split(',').collect { |name| - if name.match(/^[0123456789]+$/) - name.to_i - else - rc = OneHelper.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 - "Comma-separated list of OpenNebula #{self.rname} names or ids" - end - - def self.name_to_id(name, pool, ename) - if ename=="CLUSTER" and name.upcase=="ALL" - return 0, "ALL" - end - - objects=pool.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 filterflag_to_i(str) - filter_flag = case str - when "a", "all" then OpenNebula::Pool::INFO_ALL - when "m", "mine" then OpenNebula::Pool::INFO_MINE - when "g", "group" then OpenNebula::Pool::INFO_GROUP - when "G", "primary group" then OpenNebula::Pool::INFO_PRIMARY_GROUP - else - if str.match(/^[0123456789]+$/) - str.to_i - else - rc = OpenNebulaHelper.rname_to_id(str, "USER") - if rc.first==-1 - return rc - else - rc[1] - end - end - end - - return 0, filter_flag - end - - def self.filterflag_to_i_desc - desc=<<-EOT -a, all all the known #{self.rname}s -m, mine the #{self.rname} belonging to the user in ONE_AUTH -g, group 'mine' plus the #{self.rname} belonging to the groups - the user is member of -G, primary group the #{self.rname} owned the user's primary group -uid #{self.rname} of the user identified by this uid -user #{self.rname} of the user identified by the username -EOT - end - - def self.table_conf(conf_file=self.conf_file) - path = "#{ENV["HOME"]}/.one/cli/#{conf_file}" - - if File.exists?(path) - return path - else - return "#{TABLE_CONF_PATH}/#{conf_file}" - end - end - - def retrieve_resource(id) - factory(id) - end - - private - - def pool_to_array(pool) - if !pool.instance_of?(Hash) - phash = pool.to_hash - else - phash = pool - end - - rname = self.class.rname - - if phash["#{rname}_POOL"] && - phash["#{rname}_POOL"]["#{rname}"] - if phash["#{rname}_POOL"]["#{rname}"].instance_of?(Array) - phash = phash["#{rname}_POOL"]["#{rname}"] - else - phash = [phash["#{rname}_POOL"]["#{rname}"]] - end - else - phash = Array.new - end - - phash - end - - def get_pool - user_flag = OpenNebula::Pool::INFO_ALL - pool = factory_pool(user_flag) - - rc = pool.info - if OpenNebula.is_error?(rc) - if rc.message.empty? - return -1, "OpenNebula #{self.class.rname} name not " << - "found, use the ID instead" - else - return -1,rc.message - end - end - - return 0, pool - end - end - - def OpenNebulaHelper.rname_to_id(name, poolname) - return 0, name.to_i if name.match(/^[0123456789]+$/) - - client=OneHelper.client - - pool = case poolname - when "HOST" then OpenNebula::HostPool.new(client) - when "GROUP" then OpenNebula::GroupPool.new(client) - when "USER" then OpenNebula::UserPool.new(client) - when "DATASTORE" then OpenNebula::DatastorePool.new(client) - when "CLUSTER" then OpenNebula::ClusterPool.new(client) - when "VNET" then OpenNebula::VirtualNetworkPool.new(client) - when "IMAGE" then OpenNebula::ImagePool.new(client) - when "VMTEMPLATE" then OpenNebula::TemplatePool.new(client) - when "VM" then OpenNebula::VirtualMachinePool.new(client) - when "ZONE" then OpenNebula::ZonePool.new(client) - when "MARKETPLACE" then OpenNebula::MarketPlacePool.new(client) - end - - rc = pool.info - if OpenNebula.is_error?(rc) - return -1, "OpenNebula #{poolname} name not found," << - " use the ID instead" - end - - OneHelper.name_to_id(name, pool, poolname) - end - - def OpenNebulaHelper.size_in_mb(size) - m = size.match(/^(\d+(?:\.\d+)?)(t|tb|m|mb|g|gb)?$/i) - - if !m - # return OpenNebula::Error.new('Size value malformed') - return -1, 'Size value malformed' - else - multiplier=case m[2] - when /(t|tb)/i - 1024*1024 - when /(g|gb)/i - 1024 - else - 1 - end - - value=m[1].to_f*multiplier - - # return value.ceil - return 0, value.ceil - end - end - - def OpenNebulaHelper.rname_to_id_desc(poolname) - "OpenNebula #{poolname} name or id" - end - - def OpenNebulaHelper.boolean_to_str(str) - if str.to_i == 1 - "Yes" - else - "No" - end - end - - def OpenNebulaHelper.time_to_str(time, print_seconds=true, - print_hours=true, print_years=false) - - value = time.to_i - - if value==0 - value='-' - else - if print_hours - if print_seconds - if print_years - value=Time.at(value).strftime("%m/%d/%y %H:%M:%S") - else - value=Time.at(value).strftime("%m/%d %H:%M:%S") - end - else - if print_years - value=Time.at(value).strftime("%m/%d/%y %H:%M") - else - value=Time.at(value).strftime("%m/%d %H:%M") - end - end - else - if print_years - value=Time.at(value).strftime("%m/%d/%y") - else - value=Time.at(value).strftime("%m/%d") - end - end - end - - return value - end - - def OpenNebulaHelper.period_to_str(time, print_seconds=true) - seconds=time.to_i - minutes, seconds=seconds.divmod(60) - hours, minutes=minutes.divmod(60) - days, hours=hours.divmod(24) - - if print_seconds - "%3dd %02dh%02dm%02ds" % [days, hours, minutes, seconds] - else - "%3dd %02dh%02dm" % [days, hours, minutes] - end - end - - def OpenNebulaHelper.short_period_to_str(time, print_seconds=true) - seconds=time.to_i - minutes, seconds=seconds.divmod(60) - hours, minutes=minutes.divmod(60) - - if print_seconds - "%3dh%02dm%02ds" % [hours, minutes, seconds] - else - "%3dh%02dm" % [hours, minutes] - end - end - - BinarySufix = ["K", "M", "G", "T" ] - - def OpenNebulaHelper.unit_to_str(value, options, unit="K") - if options[:kilobytes] - value - else - i=BinarySufix.index(unit).to_i - - while value > 1024 && i < 3 do - value /= 1024.0 - i+=1 - end - - value = (value * 10).round / 10.0 - - value = value.to_i if value - value.round == 0 - st = value.to_s + BinarySufix[i] - end - end - - # If the cluster name is empty, returns a '-' char. - # - # @param str [String || Hash] Cluster name, or empty Hash (when ) - # @return [String] the same Cluster name, or '-' if it is empty - def OpenNebulaHelper.cluster_str(str) - if str != nil && !str.empty? - str - else - "-" - end - end - - def OpenNebulaHelper.clusters_str(clusters) - if clusters.nil? - "-" - else - [clusters].flatten.join(',') - end - - end - - def OpenNebulaHelper.update_template(id, resource, path=nil, xpath='TEMPLATE') - return update_template_helper(false, id, resource, path, xpath) - end - - def OpenNebulaHelper.append_template(id, resource, path=nil, xpath='TEMPLATE') - return update_template_helper(true, id, resource, path, xpath) - end - - def OpenNebulaHelper.update_template_helper(append, id, resource, path, xpath, update=true) - if path - return File.read(path) - elsif append - return editor_input() - else - if update - rc = resource.info - - if OpenNebula.is_error?(rc) - puts rc.message - exit -1 - end - end - - return editor_input(resource.template_like_str(xpath)) - end - end - - def OpenNebulaHelper.editor_input(contents=nil) - require 'tempfile' - - tmp = Tempfile.new("one_cli") - - if contents - tmp << contents - tmp.flush - end - - editor_path = ENV["EDITOR"] ? ENV["EDITOR"] : EDITOR_PATH - system("#{editor_path} #{tmp.path}") - - unless $?.exitstatus == 0 - puts "Editor not defined" - exit -1 - end - - tmp.close - - str = File.read(tmp.path) - return str - end - - def self.parse_user_object(user_object) - reg=/^([^\[]+)(?:\[([^\]]+)\])?$/ - - m=user_object.match(reg) - - return nil if !m - - user=nil - if m[2] - user=m[1] - object=m[2] - else - object=m[1] - end - - [user, object] - end - - def self.create_disk_net(objects, section, name) - template='' - - objects.each do |obj| - obj, *extra_attributes = obj.split(":") - - # When extra attributes do not contain = character include - # them in the previous value. Fixes adding MAC addresses. These - # contain ":" character also used as extra attributes separator. - # - # It may be needed to strip the value from start and end quotes - # as the value could be written as this: - # - # --nic 'some_net:mac="00:0A:12:34:56:78"' - # - attrs = [] - extra_attributes.each do |str| - if str.include?("=") - attrs << str - else - attrs.last << ":#{str}" - end - end + def perform_action(id, options, verbose, &block) + resource = retrieve_resource(id) - extra_attributes = attrs + rc = block.call(resource) + if OpenNebula.is_error?(rc) + return -1, rc.message + else + if options[:verbose] + puts "#{self.class.rname} #{id}: #{verbose}" + end + return 0 + end + end - res=parse_user_object(obj) - return [-1, "#{section.capitalize} \"#{obj}\" malformed"] if !res - user, object=*res + def perform_actions(ids,options,verbose,&block) + exit_code = 0 + ids.each do |id| + rc = perform_action(id,options,verbose,&block) - template<<"#{section.upcase}=[\n" - if object.downcase == "auto" + unless rc[0]==0 + puts rc[1] + exit_code=rc[0] + end + end + + exit_code + end + + ######################################################################## + # Id translation + ######################################################################## + def user_name(resource, options={}) + if options[:numeric] + resource['UID'] + else + resource['UNAME'] + end + end + + def group_name(resource, options={}) + if options[:numeric] + resource['GID'] + else + resource['GNAME'] + end + end + + ######################################################################## + # Formatters for arguments + ######################################################################## + def to_id(name) + return 0, name.to_i if name.match(/^[0123456789]+$/) + + rc = get_pool + return rc if rc.first != 0 + + pool = rc[1] + poolname = self.class.rname + + OneHelper.name_to_id(name, pool, poolname) + end + + def self.to_id_desc + "OpenNebula #{self.rname} name or id" + end + + def list_to_id(names) + rc = get_pool + return rc if rc.first != 0 + + pool = rc[1] + poolname = self.class.rname + + result = names.split(',').collect { |name| + if name.match(/^[0123456789]+$/) + name.to_i + else + rc = OneHelper.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 + "Comma-separated list of OpenNebula #{self.rname} names or ids" + end + + def self.name_to_id(name, pool, ename) + if ename=="CLUSTER" and name.upcase=="ALL" + return 0, "ALL" + end + + objects=pool.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 filterflag_to_i(str) + filter_flag = case str + when "a", "all" then OpenNebula::Pool::INFO_ALL + when "m", "mine" then OpenNebula::Pool::INFO_MINE + when "g", "group" then OpenNebula::Pool::INFO_GROUP + when "G", "primary group" then OpenNebula::Pool::INFO_PRIMARY_GROUP + else + if str.match(/^[0123456789]+$/) + str.to_i + else + rc = OpenNebulaHelper.rname_to_id(str, "USER") + if rc.first==-1 + return rc + else + rc[1] + end + end + end + + return 0, filter_flag + end + + def self.filterflag_to_i_desc + desc=<<-EOT + a, all all the known #{self.rname}s + m, mine the #{self.rname} belonging to the user in ONE_AUTH + g, group 'mine' plus the #{self.rname} belonging to the groups + the user is member of + G, primary group the #{self.rname} owned the user's primary group + uid #{self.rname} of the user identified by this uid + user #{self.rname} of the user identified by the username + EOT + end + + def self.table_conf(conf_file=self.conf_file) + path = "#{ENV["HOME"]}/.one/cli/#{conf_file}" + + if File.exists?(path) + return path + else + return "#{TABLE_CONF_PATH}/#{conf_file}" + end + end + + def retrieve_resource(id) + factory(id) + end + + private + + def pool_to_array(pool) + if !pool.instance_of?(Hash) + phash = pool.to_hash + else + phash = pool + end + + rname = self.class.rname + + if phash["#{rname}_POOL"] && + phash["#{rname}_POOL"]["#{rname}"] + if phash["#{rname}_POOL"]["#{rname}"].instance_of?(Array) + phash = phash["#{rname}_POOL"]["#{rname}"] + else + phash = [phash["#{rname}_POOL"]["#{rname}"]] + end + else + phash = Array.new + end + + phash + end + + def get_pool + user_flag = OpenNebula::Pool::INFO_ALL + pool = factory_pool(user_flag) + + rc = pool.info + if OpenNebula.is_error?(rc) + if rc.message.empty? + return -1, "OpenNebula #{self.class.rname} name not " << + "found, use the ID instead" + else + return -1,rc.message + end + end + + return 0, pool + end + end + + def OpenNebulaHelper.rname_to_id(name, poolname) + return 0, name.to_i if name.match(/^[0123456789]+$/) + + client=OneHelper.client + + pool = case poolname + when "HOST" then OpenNebula::HostPool.new(client) + when "GROUP" then OpenNebula::GroupPool.new(client) + when "USER" then OpenNebula::UserPool.new(client) + when "DATASTORE" then OpenNebula::DatastorePool.new(client) + when "CLUSTER" then OpenNebula::ClusterPool.new(client) + when "VNET" then OpenNebula::VirtualNetworkPool.new(client) + when "IMAGE" then OpenNebula::ImagePool.new(client) + when "VMTEMPLATE" then OpenNebula::TemplatePool.new(client) + when "VM" then OpenNebula::VirtualMachinePool.new(client) + when "ZONE" then OpenNebula::ZonePool.new(client) + when "MARKETPLACE" then OpenNebula::MarketPlacePool.new(client) + end + + rc = pool.info + if OpenNebula.is_error?(rc) + return -1, "OpenNebula #{poolname} name not found," << + " use the ID instead" + end + + OneHelper.name_to_id(name, pool, poolname) + end + + def OpenNebulaHelper.size_in_mb(size) + m = size.match(/^(\d+(?:\.\d+)?)(t|tb|m|mb|g|gb)?$/i) + + if !m + # return OpenNebula::Error.new('Size value malformed') + return -1, 'Size value malformed' + else + multiplier=case m[2] + when /(t|tb)/i + 1024*1024 + when /(g|gb)/i + 1024 + else + 1 + end + + value=m[1].to_f*multiplier + + # return value.ceil + return 0, value.ceil + end + end + + def OpenNebulaHelper.rname_to_id_desc(poolname) + "OpenNebula #{poolname} name or id" + end + + def OpenNebulaHelper.boolean_to_str(str) + if str.to_i == 1 + "Yes" + else + "No" + end + end + + def OpenNebulaHelper.time_to_str(time, print_seconds=true, + print_hours=true, print_years=false) + + value = time.to_i + + if value==0 + value='-' + else + if print_hours + if print_seconds + if print_years + value=Time.at(value).strftime("%m/%d/%y %H:%M:%S") + else + value=Time.at(value).strftime("%m/%d %H:%M:%S") + end + else + if print_years + value=Time.at(value).strftime("%m/%d/%y %H:%M") + else + value=Time.at(value).strftime("%m/%d %H:%M") + end + end + else + if print_years + value=Time.at(value).strftime("%m/%d/%y") + else + value=Time.at(value).strftime("%m/%d") + end + end + end + + return value + end + + def OpenNebulaHelper.period_to_str(time, print_seconds=true) + seconds=time.to_i + minutes, seconds=seconds.divmod(60) + hours, minutes=minutes.divmod(60) + days, hours=hours.divmod(24) + + if print_seconds + "%3dd %02dh%02dm%02ds" % [days, hours, minutes, seconds] + else + "%3dd %02dh%02dm" % [days, hours, minutes] + end + end + + def OpenNebulaHelper.short_period_to_str(time, print_seconds=true) + seconds=time.to_i + minutes, seconds=seconds.divmod(60) + hours, minutes=minutes.divmod(60) + + if print_seconds + "%3dh%02dm%02ds" % [hours, minutes, seconds] + else + "%3dh%02dm" % [hours, minutes] + end + end + + BinarySufix = ["K", "M", "G", "T" ] + + def OpenNebulaHelper.unit_to_str(value, options, unit="K") + if options[:kilobytes] + value + else + i=BinarySufix.index(unit).to_i + + while value > 1024 && i < 3 do + value /= 1024.0 + i+=1 + end + + value = (value * 10).round / 10.0 + + value = value.to_i if value - value.round == 0 + st = value.to_s + BinarySufix[i] + end + end + + # If the cluster name is empty, returns a '-' char. + # + # @param str [String || Hash] Cluster name, or empty Hash (when ) + # @return [String] the same Cluster name, or '-' if it is empty + def OpenNebulaHelper.cluster_str(str) + if str != nil && !str.empty? + str + else + "-" + end + end + + def OpenNebulaHelper.clusters_str(clusters) + if clusters.nil? + "-" + else + [clusters].flatten.join(',') + end + + end + + def OpenNebulaHelper.update_template(id, resource, path=nil, xpath='TEMPLATE') + return update_template_helper(false, id, resource, path, xpath) + end + + def OpenNebulaHelper.append_template(id, resource, path=nil, xpath='TEMPLATE') + return update_template_helper(true, id, resource, path, xpath) + end + + def OpenNebulaHelper.update_template_helper(append, id, resource, path, xpath, update=true) + if path + return File.read(path) + elsif append + return editor_input() + else + if update + rc = resource.info + + if OpenNebula.is_error?(rc) + puts rc.message + exit -1 + end + end + + return editor_input(resource.template_like_str(xpath)) + end + end + + def OpenNebulaHelper.editor_input(contents=nil) + require 'tempfile' + + tmp = Tempfile.new("one_cli") + + if contents + tmp << contents + tmp.flush + end + + editor_path = ENV["EDITOR"] ? ENV["EDITOR"] : EDITOR_PATH + system("#{editor_path} #{tmp.path}") + + unless $?.exitstatus == 0 + puts "Editor not defined" + exit -1 + end + + tmp.close + + str = File.read(tmp.path) + return str + end + + def self.parse_user_object(user_object) + reg=/^([^\[]+)(?:\[([^\]]+)\])?$/ + + m=user_object.match(reg) + + return nil if !m + + user=nil + if m[2] + user=m[1] + object=m[2] + else + object=m[1] + end + + [user, object] + end + + def self.create_disk_net(objects, section, name) + template='' + + objects.each do |obj| + obj, *extra_attributes = obj.split(":") + + # When extra attributes do not contain = character include + # them in the previous value. Fixes adding MAC addresses. These + # contain ":" character also used as extra attributes separator. + # + # It may be needed to strip the value from start and end quotes + # as the value could be written as this: + # + # --nic 'some_net:mac="00:0A:12:34:56:78"' + # + attrs = [] + extra_attributes.each do |str| + if str.include?("=") + attrs << str + else + attrs.last << ":#{str}" + end + end + + extra_attributes = attrs + + res=parse_user_object(obj) + return [-1, "#{section.capitalize} \"#{obj}\" malformed"] if !res + user, object=*res + + template<<"#{section.upcase}=[\n" + if object.downcase == "auto" template<<" NETWORK_MODE=\"#{object}\"\n" else template<<" #{name.upcase}_UNAME=\"#{user}\",\n" if user From 1a932c3beebbfbc2cf1c2f34debe0725fc2ecbe4 Mon Sep 17 00:00:00 2001 From: juanmont Date: Thu, 18 Oct 2018 10:55:49 +0200 Subject: [PATCH 2/2] Fixed bug with escape \n --- src/cli/one_helper.rb | 4 ++-- src/cli/onetemplate | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/one_helper.rb b/src/cli/one_helper.rb index 20d466ca28d..5b7720bc8e0 100644 --- a/src/cli/one_helper.rb +++ b/src/cli/one_helper.rb @@ -1241,8 +1241,8 @@ def self.create_disk_net(objects, section, name) return [-1, "#{section.capitalize} \"#{obj}\" malformed"] if !res user, object=*res - template<<"#{section.upcase}=[\n" - if object.downcase == "auto" + template<<"#{section.upcase}=[\n" + if object.downcase == "auto" template<<" NETWORK_MODE=\"#{object}\"\n" else template<<" #{name.upcase}_UNAME=\"#{user}\",\n" if user diff --git a/src/cli/onetemplate b/src/cli/onetemplate index 6e932a52872..64c0696397b 100755 --- a/src/cli/onetemplate +++ b/src/cli/onetemplate @@ -257,7 +257,7 @@ CommandParser::CmdParser.new(ARGV) do user_inputs = OneTemplateHelper.get_user_inputs(t.to_hash) end - extra_template << '\n' << user_inputs + extra_template << "\n#{user_inputs}" persistent = !options[:persistent].nil?