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