diff --git a/generators/oar-properties/lib/lib-oar-properties.rb b/generators/oar-properties/lib/lib-oar-properties.rb index 620c4847b8d3cff67310f1e793fe2dba68315f64..dec454aabdc669f24371bca36e9828951f5e2a11 100755 --- a/generators/oar-properties/lib/lib-oar-properties.rb +++ b/generators/oar-properties/lib/lib-oar-properties.rb @@ -92,7 +92,7 @@ def get_node_properties(cluster_uid, cluster, node_uid, node) end h['max_walltime'] = node['supported_job_types']['max_walltime'] || 0 - h + return h end # @@ -190,11 +190,42 @@ def oarcmd_set_node_properties(host, properties) return command end +# ' + +# Get the OAR properties from the OAR scheduler +# This is only needed for the -d option +def oarcmd_get_nodelist_properties(site_uid, filename=nil) + oarnodes_yaml = "" + + if filename and File.exist?(filename) + # Read oar properties from file + puts "Read 'oarnodes -Y' from #{filename}" + oarnodes_yaml = File.open(filename, 'rb') { |f| f.read } + else + # Download the oar properties from the oar server + puts "Downloading 'oarnodes -Y' from oar.#{site_uid}.g5kadmin ..." + + Net::SSH.start("oar.#{site_uid}.g5kadmin", 'g5kadmin', :keys => ['~/.ssh/id_rsa_g5k', '~/.ssh/id_rsa_g5kadmin.pub']) do |ssh| + # capture all stderr and stdout output from a remote process + oarnodes_yaml = ssh.exec!('oarnodes -Y') + + # puts output + end + puts "... done" -def oarcmd_get_nodelist_properties() - # Get the current OAR properties from the OAR scheduler - h = YAML.load_file('../../JG/nancy-oarnodes.yaml') + if filename + # Cache the file + puts "Save 'oarnodes -Y' as #{filename}" + File.write(filename, oarnodes_yaml) + end + end + + # Load the YAML file into an hashtable + h = YAML.load(oarnodes_yaml) + + # Format convertion: use host as keys of the hash (instead of id) h = h.map {|k, v| v['type'] == 'default' ? [v['host'].split('.').first, v] : [nil, nil] }.to_h + return h end diff --git a/generators/oar-properties/oar-properties.rb b/generators/oar-properties/oar-properties.rb index 58806c96bbdb1dac8ceff01afbc584b91720dbeb..50ace66d44e1a2b8d6d41466350e749b70664ed0 100755 --- a/generators/oar-properties/oar-properties.rb +++ b/generators/oar-properties/oar-properties.rb @@ -6,180 +6,127 @@ require 'fileutils' require 'pathname' require 'json' require 'time' +require 'yaml' +require 'hashdiff' +require 'optparse' +require 'net/ssh' +require '../oar-properties/lib/lib-oar-properties' require '../lib/input_loader' -# Output directory -refapi_path = "/tmp/data" +options = {} +options[:sites] = %w{grenoble lille luxembourg lyon nantes reims rennes sophia} +options[:diff] = false -#global_hash = JSON.parse(STDIN.read) -#global_hash = load_yaml_file_hierarchy("../../input/example/") -global_hash = load_yaml_file_hierarchy("../../input/grid5000/") +OptionParser.new do |opts| + opts.banner = "Usage: oar-properties.rb [options]" -class Hash - # sort a hash according to the position of the key in the array - def sort_by_array(array) - Hash[sort_by{|key, _| array.index(key) || length}] - end -end + opts.separator "" + opts.separator "Example: ruby oar-properties.rb -v -s nancy -d oarnodes-%s.yaml -o cmd-%s.sh" + + opts.separator "" + opts.separator "Filters:" + + ### -# Write pretty and sorted JSON files -def write_json(filepath, data) - def rec_sort(h) - case h - when Array - h.map{|v| rec_sort(v)}#.sort_by!{|v| (v.to_s rescue nil) } - when Hash - Hash[Hash[h.map{|k,v| [rec_sort(k),rec_sort(v)]}].sort_by{|k,v| [(k.to_s rescue nil), (v.to_s rescue nil)]}] - else - h - end + opts.on('-s', '--sites a,b,c', Array, 'Select site(s)', + "Default: "+options[:sites].join(", ")) do |s| + options[:sites] = s end - File.open(filepath, 'w') do |f| - f.write(JSON.pretty_generate(rec_sort(data))) + + opts.on('-c', '--clusters a,b,c', Array, 'Select clusters(s). Default: all') do |s| + options[:clusters] = s end -end -# Parse network equipment description and return switch name and port connected to given node -# In the network description, if the node interface is given (using "port" attribute), -# the interface parameter must be used. -def net_switch_port_lookup(site, node_uid, interface='') - site["net-links"].each do |switch_uid, switch| # TODO: rename net-links<->network - #pp switch_uid - switch["linecards"].each do |lc_uid,lc| - lc["ports"].each do |port_uid,port| - if port.is_a?(Hash) - switch_remote_port = port["port"] || lc["port"] || "" - switch_remote_uid = port["uid"] - else - switch_remote_port = lc["port"] || "" - switch_remote_uid = port - end - if switch_remote_uid == node_uid and switch_remote_port == interface - # Build port name from snmp_naming_pattern - # Example: '3 2 GigabitEthernet%LINECARD%/%PORT%' -> 'GigabitEthernet3/2' - port_name = lc["snmp_pattern"].sub("%LINECARD%",lc_uid.to_s).sub("%PORT%",port_uid.to_s) - return switch_uid, port_name - end - end - end + opts.on('-n', '--nodes a,b,c', Array, 'Select nodes(s). Default: all') do |n| + options[:nodes] = n end - return nil -end -global_hash["sites"].each do |site_uid, site| - pp site_uid -# pp site + ### - site["clusters"].each do |cluster_uid, cluster| - pp cluster_uid + opts.separator "" + opts.separator "Output options:" - cluster_path = Pathname.new(refapi_path).join("sites",site_uid,"clusters",cluster_uid) - cluster_path.join("nodes").mkpath() + opts.on('-o', '--output', 'Output oarnodesetting command into a file. Default: stdout') do |o| + options[:output] = o + end - # Write cluster info w/o nodes entries - cluster["type"] = "cluster" - cluster["uid"] = cluster_uid - - # On the previous version of this script, cluster["created_ad"] was generated from a Ruby Time. cluster["created_ad"] is now a Ruby Date at JSON import. - # As Date.httpdate and Time.httpdate does not behave the same with timezone, it is converted here as a Ruby time. - cluster["created_at"] = Time.parse(cluster["created_at"].to_s).httpdate - - write_json(cluster_path.join("#{cluster_uid}.json"), - cluster.reject {|k, v| k == "nodes"}) + opts.on('-e', '--exec', 'Directly apply the changes to the OAR server') do |e| + options[:exec] = e + end + + opts.on("-d", "--diff [YAML filename]", + "Only generates the minimal list of commands needed to update the site configuration", + "The optional YAML file is suppose to be the output of the 'oarnodes -Y' command.", + "If the file does not exist, the script will get the data from the OAR server and save the result on disk for future use.", + "If no filename is specified, the script will simply connect to the OAR server.", + "You can use the '%s' placeholder for 'site'. Ex: oarnodes-%s.yaml") do |d| + d = true if d == nil + options[:diff] = d + end - # Write node info - cluster["nodes"].each do |node_uid, node| - pp node_uid - -# next unless node_uid == "griffon-1" - node["uid"] = node_uid - node["type"] = "node" - if node.key?("processor") - node["processor"]["cache_l1"] = nil unless node["processor"].key?("cache_l1") - end - - # Add default keys - node["gpu"] = {} unless node.key?("gpu") - node["gpu"]["gpu"] = false unless node["gpu"].key?("gpu") - - node["main_memory"] = {} unless node.key?("main_memory") - node["main_memory"]["virtual_size"] = nil unless node["main_memory"].key?("virtual_size") - -# node["monitoring"] = {} unless node.key?("monitoring") -# node["monitoring"]["wattmeter"] = false unless node["monitoring"].key?("wattmeter") - - # Rename keys - node["storage_devices"] = node.delete("block_devices") - node["network_adapters"] = node.delete("network_interfaces") - if node.key?("chassis") - node["chassis"]["name"] = node["chassis"].delete("product_name") - node["chassis"]["serial"] = node["chassis"].delete("serial_number") - end - - # Type conversion - node["network_adapters"].each { |key, hash| hash["rate"] = hash["rate"].to_i if hash["rate"].is_a?(Float) } - - # Convert hashes to arrays - node["storage_devices"].each { |key, hash| node["storage_devices"][key]["device"] = key; } # Add "device: sdX" within the hash - node["storage_devices"] = node["storage_devices"].sort_by_array(["sda", "sdb", "sdc", "sdd", "sde"]).values - - node["network_adapters"].each { |key, hash| node["network_adapters"][key]["device"] = key; } # Add "device: ethX" within the hash - node["network_adapters"] = node["network_adapters"].sort_by_array(["eth0", "eth1", "eth2", "eth3", "ib0", "ib1", "ib2", "ib3", "bmc"]).values - - # Populate "network_address", "switch" and "switch_port" from the network equipment description for each network adapters - node["network_adapters"].each { |network_adapter| - - # ib properties - network_adapter["ib_switch_card"] = network_adapter.delete("line_card") if network_adapter.key?("line_card") - network_adapter["ib_switch_card_pos"] = network_adapter.delete("position") if network_adapter.key?("position") - - next unless network_adapter["enabled"] - - # Management network_adapter (bmc) - if network_adapter["management"] - network_adapter["network_address"] = "#{node_uid}-bmc.#{site_uid}.grid5000.fr" - next - end - - # if network_adapter["network_address"] or network_adapter["switch"] or network_adapter["switch_port"] - # pp "Warning: network_address, switch or switch_port defined manually for (#{node_uid}, #{network_adapter["device"]})" - # pp "#{network_adapter["network_address"]}, #{network_adapter["switch"]} or #{network_adapter["switch_port"]}" - # end - - if network_adapter["mounted"] and /^eth[0-9]$/.match(network_adapter["device"]) - # Primary network_adapter - network_adapter["network_address"] = "#{node_uid}.#{site_uid}.grid5000.fr" - - # Interface may not be specified in Network Reference for primary network_adapter - network_adapter["switch"], network_adapter["switch_port"] = net_switch_port_lookup(site, node_uid, network_adapter["device"]) || net_switch_port_lookup(site, node_uid) - - # network_adapter["bridged"] = true # TODO? - next - end -# if network_adapter["bridged"] - # Secondary network_adapter(s) - network_adapter["network_address"] = "#{node_uid}-#{network_adapter["device"]}.#{site_uid}.grid5000.fr" - switch, port = net_switch_port_lookup(site, node_uid, network_adapter["device"]) - network_adapter["switch"] = switch if switch - network_adapter["switch_port"] = port if port -# end - } - - if node.key?("sensors") and node.key?("pdu") and node["pdu"].key?("pdu_name") - node["sensors"]["power"] = {} unless node["sensors"].key?("power") - node["sensors"]["power"]["via"] = {} unless node["sensors"]["power"].key?("via") - node["sensors"]["power"]["via"]["pdu"] = [] unless node["sensors"]["power"]["via"].key?("pdu") - node["sensors"]["power"]["via"]["pdu"][0] = {} unless node["sensors"]["power"]["via"]["pdu"].size > 0 - - node["sensors"]["power"]["via"]["pdu"][0]["uid"] = node["pdu"]["pdu_name"] - node["sensors"]["power"]["via"]["pdu"][0]["port"] = node["pdu"]["pdu_position"] if node["pdu"].key?("pdu_position") - - node.delete("pdu") - end - - #pp cluster_path.join("nodes","#{node_uid}.json") - write_json(cluster_path.join("nodes","#{node_uid}.json"), node) - end +# opts.on("-n", "--dry-run", "Perform a trial run with no changes made") do |n| +# options[:dryrun] = n +# end + + ### + + opts.separator "" + opts.separator "Common options:" + + opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| + options[:verbose] = v end + + # Print an options summary. + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end +end.parse! + +pp options + +# Option +site_uid = 'nancy' + +nodelist_properties = {} + +# +# Get the OAR properties from the reference-repo +# + +global_hash = load_yaml_file_hierarchy('../../input/grid5000/') +nodelist_properties["ref"] = get_nodelist_properties(site_uid, global_hash["sites"][site_uid]) + +# +# Get the current OAR properties from the OAR scheduler +# This is only needed for the -d option +# + +nodelist_properties["oar"] = {} + +if options[:diff] + filename = options[:diff].is_a?(String) ? options[:diff].gsub("%s", site_uid) : nil + nodelist_properties["oar"] = oarcmd_get_nodelist_properties(site_uid, filename) end + + +# +# Diff +# + +node_properties = {} +node_properties["ref"] = nodelist_properties["ref"]["graphene-1"] +node_properties["oar"] = nodelist_properties["oar"]["graphene-1"] + +diff = diff_node_properties(node_properties["ref"], node_properties["oar"]) +diff_keys = diff.map{ |hashdiff_array| hashdiff_array[1] } + +node_properties["to_be_updated"] = node_properties["ref"].select { |key, value| diff_keys.include?(key) } + +# +# Example +# +puts oarcmd_set_node_properties("graphene-1", node_properties["oar"]) +puts oarcmd_set_node_properties("graphene-1", node_properties["to_be_updated"])