#!/usr/bin/env ruby # Copyright (c) 2012 National ICT Australia Limited (NICTA). # This software may be used and distributed solely under the terms of the MIT license (License). # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT. # By downloading or using this software you accept the terms and the liability disclaimer in the License. puts "OMF Experiment Controller - Copyright (c) 2012-13 National ICT Australia Limited (NICTA)" abort "Please use Ruby 1.9.3 or higher" if RUBY_VERSION < "1.9.3" require 'gli' require 'omf_ec' $stdout.sync = true include GLI::App include OmfEc program_desc "Run a command on the testbed(s)" version OmfEc::VERSION desc "Debug mode (printing debug logging messages)" switch [:d, :debug] desc "URI for communication layer" arg_name "URI" default_value "xmpp://localhost" flag [:u, :uri] desc "Debug XMPP traffic mode (include XMPP debug logging messages under debug mode)." switch [:x, :xmpp] desc "Directory containing root certificates" arg_name "directory", :optional flag [:root_cert_dir] desc "Your certificate" arg_name "cert", :optional flag [:cert] desc "Your private key" arg_name "key", :optional flag [:key] desc "Log file directory" arg_name "directory" default_value "/tmp" flag [:log_file_dir] desc "Logging config file" arg_name "file" flag [:log_config] desc "Add some colours to logging" switch [:colour] desc "EC config file" arg_name "file" flag [:c, :config] $config_file = ".config/omf_ec.yml" ARGV.each_index {|a| if ARGV[a]=="-c" or ARGV[a]=="--config" $config_file = ARGV[a+1] if not ARGV[a+1].nil? end } # the path given here is relative to the user's home directory config_file($config_file) desc "Execute an experiment script" arg_name "path_to_script_file [-- --experiment_property value]" command :exec do |c| c.desc "Experiment name" c.arg_name "experiment_name" c.flag [:e, :experiment, "experiment-id"] c.desc "Slice name [Deprecated]" c.arg_name "slice_name" c.flag [:slice] c.desc "OML URI to use for collecting the experiment's measurements" c.arg_name "uri" c.flag [:oml_uri] c.desc "OML URI to use for EC Instrumentation" c.arg_name "uri" c.flag [:inst_oml_uri] c.desc "OML ID to use for EC Instrumentation" c.arg_name "id" c.flag [:inst_oml_id] c.desc "OML Domain to use for EC Instrumentation" c.arg_name "domain" c.flag [:inst_oml_domain] c.desc "Check script version (you need to define OMF_VERSIONS in your script" c.switch "version_check" c.desc "Parse graph definition to construct graph information in log output" c.switch [:g, "show-graph"] c.action do |global_options, options, args| help_now! "Missing experiment script" if args[0].nil? help_now! "Experiment script not found" unless File.exist?(File.expand_path(args[0])) # User-provided command line values for Experiment Properties cannot be # set here as the propertties have not been defined yet by the experiment. # Thus just pass them to the experiment, which will be responsible # for setting them later properties = {} if args.size > 1 exp_properties = args[1..-1] exp_properties.in_groups_of(2) do |p| unless p[0] =~ /^--(.+)/ && !p[1].nil? help_now! "Malformatted properties '#{exp_properties.join(' ')}'" else properties[$1.to_sym] = p[1].ducktype end end OmfEc.experiment.cmdline_properties = properties end OmfEc.experiment.show_graph = options['show-graph'] # FIXME this loading script is way too simple load_exp(File.expand_path(args[0]), global_options, options, properties) end end desc "Load an image onto the nodes" command :load do |c| #c.desc "use this testbed configuration in OMF 5 EC config file" #c.arg_name "AGGREGATE" #c.flag [:c, :config], :default_value => "default" c.desc "comma-separated list of nodes to image" c.arg_name "TOPOLOGY" c.flag [:t, :topology], :default_value => "system:topo:all" c.desc "disk image to load" c.arg_name "IMAGE" c.flag [:i, :image], :default_value => "baseline.ndz" c.desc "seconds to wait for the imaging process to complete" c.arg_name "TIMEOUT" c.flag [:o, :timeout], :default_value => "800" c.desc "resize the first partition to SIZE GB or to maximum size if SIZE=0 "+ "or leave x percent of free space if SIZE=x%" c.arg_name "SIZE" c.flag [:r, :resize] c.desc "Path where the resulting Topologies should be saved" c.arg_name "PATH" c.flag [:outpath], :default_value => "/tmp" c.desc "Prefix to use for naming the resulting Topologies (default is your experiment ID)" c.arg_name "PREFIX" c.flag [:outprefix] c.action do |global_options, options, args| @cmd = "USER=#{ENV['USER']} omf-5.4 load -t #{options[:t]} -i #{options[:i]} " @cmd += "-o #{options[:o]} --outpath #{options[:outpath]} " @cmd += "-r #{options[:r]} " if options[:r] @cmd += "--outprefix #{options[:outprefix]} " if options[:outprefix] load_exp(@testbed_exp_path, global_options, options) end end desc "Save an image of a node" command :save do |c| #c.desc "use this testbed configuration in OMF 5 EC config file" #c.arg_name "AGGREGATE" #c.flag [:c, :config], :default_value => "default" c.desc "node to save from" c.arg_name "NODE" c.flag [:n, :node] c.desc "resize the first partition to SIZE GB or to maximum size if SIZE=0 "+ "or leave x percent of free space if SIZE=x%" c.arg_name "SIZE" c.flag [:r, :resize] c.action do |global_options, options, args| @cmd = "USER=#{ENV['USER']} omf-5.4 save " @cmd += "-n #{options[:n]} " if options[:n] @cmd += "-r #{options[:r]} " if options[:r] load_exp(@testbed_exp_path, global_options, options) end end desc "Return the status of the nodes" command :stat do |c| c.desc "use this testbed configuration in OMF 5 EC config file" c.arg_name "AGGREGATE" c.flag [:C], :default_value => "default" c.desc "comma-separated list of nodes to image" c.arg_name "TOPOLOGY" c.flag [:t, :topology], :default_value => "system:topo:all" c.desc "print a summary of the node status for the testbed" c.switch [:s, :summary] c.action do |global_options, options, args| @cmd = "omf-5.4 stat -c #{options[:C]} -t #{options[:t]} " @cmd += "-s" if options[:s] load_exp(@testbed_exp_path, global_options, options) end end desc "Power on/off, reset or reboot the nodes" command :tell do |c| c.desc "use this testbed configuration in OMF 5 EC config file" c.arg_name "AGGREGATE" c.flag [:c, :config], :default_value => "default" c.desc "comma-separated list of nodes to image" c.arg_name "TOPOLOGY" c.flag [:t, :topology], :default_value => "system:topo:all" c.desc " 'on' turn node(s) ON - 'offs' turn node(s) OFF (soft) - 'offh' turn node(s) OFF (hard) - 'reboot' reboots node(s) (soft) - 'reset' resets node(s) (hard)" c.arg_name "ACTION" c.flag [:a, :action] c.action do |global_options, options, args| @cmd = "omf-5.4 tell -c #{options[:c]} -t #{options[:t]} " @cmd += "-a #{options[:a]} " if options[:a] load_exp(@testbed_exp_path, global_options, options) end end on_error do |exception| true end pre do |global_options, command, options, args| #opts = OmfCommon.load_yaml(config_file_name) if File.exist?(config_file_name) #opts.delete("commands") #global_options.merge!(opts) unless global_options[:uri] help_now! "Incomplete options. Need communication URI" end # Check version if options[:check] File.open(args[0], 'r') do |f| f.read.chomp.match(/OMF_VERSIONS\W*=\W*(.*)/) versions = $1 unless versions && versions.split(',').include?(OmfCommon::PROTOCOL_VERSION) raise StandardError, "Could not find compatibile protocol version number in your script" end end end include OmfEc::DSL OmfEc.experiment.name = options[:experiment] if options[:experiment] OmfEc.experiment.oml_uri = options[:oml_uri] if options[:oml_uri] @testbed_exp_path = File.join(OmfEc.lib_root, "omf_ec/backward/exp/testbed.rb") end def setup_logging(global_options = {}) if global_options[:xmpp] require 'blather' Blather.logger = logger end unless global_options[:debug] Logging.consolidate 'OmfCommon', 'OmfRc' end if global_options[:colour] Logging.logger.root.appenders.first.layout = Logging.layouts.pattern(date_pattern: '%F %T %z', color_scheme: 'default', pattern: '[%d] %-5l %c: %m\n') end # FIXME this should go to common setup if global_options[:log_file_dir] && File.exist?(File.expand_path(global_options[:log_file_dir])) Logging.logger.root.add_appenders( Logging.appenders.file( "#{File.expand_path(global_options[:log_file_dir])}/#{OmfEc.experiment.id}.log", :layout => Logging.layouts.pattern(:date_pattern => '%F %T %z', :pattern => '[%d] %-5l %c: %m\n'))) end if OmfEc.experiment.oml_uri require 'oml4r/logging/oml4r_appender' Logging.logger.root.add_appenders(Logging.appenders.oml4r('oml4r', :appName => 'omf_ec', :domain => "#{OmfEc.experiment.id}", :collect => "#{OmfEc.experiment.oml_uri}")) end OmfCommon.load_logging_config(global_options[:log_config]) end def load_exp(exp_path, global_options = {} , options = {}, properties = {}) begin if options[:inst_oml_uri] && options[:inst_oml_id] && options[:inst_oml_domain] require 'oml4r' instrument_ec = OML4R::init(nil, { collect: options[:inst_oml_uri], nodeID: options[:inst_oml_id], domain: options[:inst_oml_domain] , appName: File.basename($PROGRAM_NAME)} ) OmfCommon::Measure.enable if instrument_ec end opts = { communication: { url: global_options[:uri] }, eventloop: { type: :em }, logging: { level: { default: global_options[:debug] ? 'debug' : 'info' }, appenders: { stdout: { date_pattern: '%H:%M:%S', pattern: '%d %-5l %c{2}: %m\n' } } } } opts[:communication][:auth] = { authenticate: true } if global_options[:cert] OmfCommon.init(:development, opts) do |el| setup_logging(global_options) OmfCommon.comm.on_connected do |comm| info "OMF Experiment Controller #{OmfEc::VERSION} - Start" info "Connected using #{comm.conn_info}" info "Execute: #{exp_path}" info "Properties: #{OmfEc.experiment.cmdline_properties}" if opts[:communication][:auth] && opts[:communication][:auth][:authenticate] ec_cert = OmfCommon.load_credentials( root_cert_dir: global_options[:root_cert_dir], entity_cert: global_options[:cert], entity_key: global_options[:key] ) ec_cert.resource_id = OmfCommon.comm.local_address OmfCommon::Auth::CertificateStore.instance.register(ec_cert) end OmfEc.experiment.log_metadata("ec_version", "#{OmfEc::VERSION}") OmfEc.experiment.log_metadata("exp_path", exp_path) OmfEc.experiment.log_metadata("ec_pid", "#{Process.pid}") begin include OmfEc::Backward::DefaultEvents load exp_path OmfEc::Experiment.start rescue => e OmfEc.experiment.log_metadata("state", "error") error e.message error e.backtrace.join("\n") end comm.on_interrupted { OmfEc::Experiment.done } end end rescue => e logger.fatal e.message logger.fatal e.backtrace.join("\n") end end exit run(ARGV)