From 5f994c4e49eb28d0c4272564bb0f5a71d56d2975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gaidamour?= <jeremie.gaidamour@inria.fr> Date: Tue, 1 Mar 2016 13:23:37 +0100 Subject: [PATCH] [dev] A first version of the new generator for oar properties --- .../oar-properties/lib/lib-oar-properties.rb | 200 ++++++++++++++++++ generators/oar-properties/oar-properties.rb | 185 ++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100755 generators/oar-properties/lib/lib-oar-properties.rb create mode 100755 generators/oar-properties/oar-properties.rb diff --git a/generators/oar-properties/lib/lib-oar-properties.rb b/generators/oar-properties/lib/lib-oar-properties.rb new file mode 100755 index 00000000000..620c4847b8d --- /dev/null +++ b/generators/oar-properties/lib/lib-oar-properties.rb @@ -0,0 +1,200 @@ +#!/usr/bin/ruby + +require 'pp' +require 'erb' +require 'fileutils' +require 'pathname' +require 'json' +require 'time' +require 'yaml' +require 'hashdiff' + +class MissingProperty < StandardError; end + +MiB = 1024**2 + +# Get node properties from the reference repo hash +def get_node_properties(cluster_uid, cluster, node_uid, node) + h = {} # ouput + + main_network_adapter = node['network_interfaces'].values.find{ |na| na['enabled'] && na['mounted'] && na['interface'] =~ /ethernet/i && !na['management'] } + main_network_adapter = node['network_interfaces'].values.find{ |na| na['enabled'] && na['mounted'] } + raise MissingProperty, "Node #{node_uid} does not have a main network_adapter" unless main_network_adapter + +# h['host'] = main_network_adapter['network_address'] +#TODO raise MissingProperty, "Node #{node_uid} has no network_address" unless h['host'] + + h['ip'] = main_network_adapter['ip'] + raise MissingProperty, "Node #{node_uid} has no IP" unless h['ip'] + h['cluster'] = cluster_uid + h['nodemodel'] = cluster['model'] + h['switch'] = main_network_adapter['switch'] + h['besteffort'] = node['supported_job_types']['besteffort'] + h['deploy'] = node['supported_job_types']['deploy'] + h['ip_virtual'] = node['supported_job_types']['virtual'] == 'ivt' + h['virtual'] = node['supported_job_types']['virtual'] + h['cpuarch'] = node['architecture']['platform_type'] + h['cpucore'] = node['architecture']['smt_size']/node['architecture']['smp_size'] + h['cputype'] = [node['processor']['model'], node['processor']['version']].join(' ') + h['cpufreq'] = node['processor']['clock_speed']/1_000_000_000.0 + h['disktype'] = (node['block_devices'].first[1] || {})['interface'] + h['ethnb'] = node['network_interfaces'].values.select{|na| na['interface'] =~ /ethernet/i}.select{|nb| nb['mountable'] == true}.length + + eth10g = node['network_interfaces'].values.select{|na| na['interface'] =~ /ethernet/i}.select{|nb| nb['mountable'] == true} + h['eth10g'] = eth10g.detect{|na| na['rate'] == 10_000_000_000}.nil? ? false : true + + ib10g = node['network_interfaces'].values.detect{|na| na['interface'] =~ /infiniband/i && na['rate'] == 10_000_000_000} + h['ib10g'] = ib10g ? true : false + h['ib10gmodel'] = ib10g ? ib10g['version'] : 'none' + + ib20g = node['network_interfaces'].values.detect{|na| na['interface'] =~ /infiniband/i && na['rate'] == 20_000_000_000} + h['ib20g'] = ib20g ? true : false + h['ib20gmodel'] = ib20g ? ib20g['version'] : 'none' + + ib40g = node['network_interfaces'].values.detect{|na| na['interface'] =~ /infiniband/i && na['rate'] == 40_000_000_000} + h['ib40g'] = ib40g ? true : false + h['ib40gmodel'] = ib40g ? ib40g['version'] : 'none' + + ib56g = node['network_interfaces'].values.detect{|na| na['interface'] =~ /infiniband/i && na['rate'] == 56_000_000_000} + h['ib56g'] = ib56g ? true : false + h['ib56gmodel'] = ib56g ? ib56g['version'] : 'none' + + myri10g = node['network_interfaces'].values.detect{|na| na['interface'] =~ /myri/i && na['rate'] == 10_000_000_000} + h['myri10g'] = myri10g ? true : false + h['myri10gmodel'] = myri10g ? myri10g['version'] : 'none' + + myri2g = node['network_interfaces'].values.detect{|na| na['interface'] =~ /myri/i && na['rate'] == 2_000_000_000} + h['myri2g'] = myri2g ? true : false + h['myri2gmodel'] = myri2g ? myri2g['version'] : 'none' + + h['memcore'] = node['main_memory']['ram_size']/node['architecture']['smt_size']/MiB + h['memcpu'] = node['main_memory']['ram_size']/node['architecture']['smp_size']/MiB + h['memnode'] = node['main_memory']['ram_size']/MiB + + node['gpu'] ||= {} + h['gpu'] = case node['gpu']['gpu'] when true; true; when false; false when nil; false; else node['gpu']['gpu'].upcase end + h['gpu_count'] = node['gpu']['gpu_count'] + h['gpu_model'] = node['gpu']['gpu_model'] + + node['monitoring'] ||= {} + + h['wattmeter'] = case node['monitoring']['wattmeter'] when true; 'YES'; when false; 'NO' when nil; 'NO'; else node['monitoring']['wattmeter'].upcase end + + # h['rconsole'] = node['monitoring']['rconsole'] + + h['cluster_priority'] = (cluster['priority'] || Time.parse(cluster['created_at'].to_s).strftime('%Y%m')).to_i + + begin + h['production'] = node['supported_job_types']['queues'].include?('production') ? true : false + rescue + # Set as NO by default + h['production'] = 'NO' + end + h['max_walltime'] = node['supported_job_types']['max_walltime'] || 0 + + h +end + +# +# +# +def get_nodelist_properties(site_uid, site) + properties = {} # output + + site['clusters'].each do |cluster_uid, cluster| + + cluster['nodes'].each do |node_uid, node| + + begin + properties[node_uid] = get_node_properties(cluster_uid, cluster, node_uid, node) + + rescue MissingProperty => e + # puts "Error while processing node #{node_uid}: #{e}" + end + + end + end + + return properties +end + +def diff_node_properties(a, b) + + ignore_keys = [ + "slash_16", + "slash_17", + "slash_18", + "slash_19", + "slash_20", + "slash_21", + "slash_22", + "available_upto", + "chunks", + "comment", # TODO + "core", + "cpu", + "cpuset", + "desktop_computing", + "drain", + "expiry_date", + "finaud_decision", + "grub", + "host", # TODO + "last_available_upto", + "last_job_date", + "maintenance", + "mic", # TODO + "network_address", # TODO + "next_finaud_decision", + "next_state", + "rconsole", # TODO + "resource_id", + "scheduler_priority", + "state", + "state_num", + "subnet_address", + "subnet_prefix", + "suspended_jobs", + "thread", + "type", # TODO + "vlan", + "ib56g", # TODO + "ib56gmodel", # TODO + "wattmeter" # TODO + ] + + ignore_keys.each { |key| a.delete(key) } + ignore_keys.each { |key| b.delete(key) } + + return HashDiff.diff(a, b) + +end + +#def cmd_set_oarnodesetting(properties) +# properties.each +#end + +def oarcmd_set_node_properties(host, properties) + return "# #{host}: OK" if properties.size == 0 + + command = "# #{host}:\n" + command += "#{ENV["SUDO"]} oarnodesetting -h #{host} -p " + + command += + properties.to_a.map{ |(k,v)| + v = "YES" if v == true + v = "NO" if v == false + + ! v.nil? ? "#{k}=#{v.inspect.gsub("'", "\\'").gsub("\"", "'")}" : nil + }.compact.join(' -p ') + + return command +end + +def oarcmd_get_nodelist_properties() + # Get the current OAR properties from the OAR scheduler + h = YAML.load_file('../../JG/nancy-oarnodes.yaml') + 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 new file mode 100755 index 00000000000..58806c96bbd --- /dev/null +++ b/generators/oar-properties/oar-properties.rb @@ -0,0 +1,185 @@ +#!/usr/bin/ruby + +require 'pp' +require 'erb' +require 'fileutils' +require 'pathname' +require 'json' +require 'time' + +require '../lib/input_loader' + +# Output directory +refapi_path = "/tmp/data" + +#global_hash = JSON.parse(STDIN.read) +#global_hash = load_yaml_file_hierarchy("../../input/example/") +global_hash = load_yaml_file_hierarchy("../../input/grid5000/") + +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 + +# 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 + end + File.open(filepath, 'w') do |f| + f.write(JSON.pretty_generate(rec_sort(data))) + 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 + 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 + + cluster_path = Pathname.new(refapi_path).join("sites",site_uid,"clusters",cluster_uid) + cluster_path.join("nodes").mkpath() + + # 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"}) + + # 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 + end +end -- GitLab