use acts_as_api
[arvados.git] / app / models / node.rb
index fbf99aeff1e992b3467a532d07e0b23dc7b74c27..67a14384c6d770031846cce0841e3d5619d3535d 100644 (file)
@@ -2,14 +2,60 @@ class Node < ActiveRecord::Base
   include AssignUuid
   serialize :info, Hash
   before_validation :ensure_ping_secret
+  after_update :dnsmasq_update
 
   MAX_SLOTS = 64
 
+  @@confdir = if Rails.configuration.respond_to? :dnsmasq_conf_dir
+                Rails.configuration.dnsmasq_conf_dir
+              elsif File.exists? '/etc/dnsmasq.d/.'
+                '/etc/dnsmasq.d'
+              else
+                nil
+              end
+  @@domain = Rails.configuration.compute_node_domain rescue `hostname --domain`.strip
+
+  acts_as_api
+  api_accessible :superuser do |t|
+    t.add :uuid
+    t.add :created_by_client
+    t.add :created_by_user
+    t.add :created_at
+    t.add :modified_by_client
+    t.add :modified_by_user
+    t.add :modified_at
+    t.add :hostname
+    t.add :domain
+    t.add :ip_address
+    t.add :first_ping_at
+    t.add :last_ping_at
+    t.add :info
+    t.add :updated_at
+  end
+
   def info
     @info ||= Hash.new
     super
   end
 
+  def domain
+    super || @@domain
+  end
+
+  def status
+    if !self.last_ping_at
+      if Time.now - self.created_at > 5.minutes
+        'startup-fail'
+      else
+        'pending'
+      end
+    elsif Time.now - self.last_ping_at > 1.hours
+      'missing'
+    else
+      'running'
+    end
+  end
+
   def ping(o)
     raise "must have :ip and :ping_secret" unless o[:ip] and o[:ping_secret]
 
@@ -34,11 +80,15 @@ class Node < ActiveRecord::Base
       try_slot = 0
       begin
         self.slot_number = try_slot
-        try_slot += 1
-        break if self.save rescue nil
+        begin
+          self.save!
+          break
+        rescue ActiveRecord::RecordNotUnique
+          try_slot += 1
+        end
         raise "No available node slots" if try_slot == MAX_SLOTS
       end while true
-      self.hostname = "compute#{self.slot_number}"
+      self.hostname = self.class.hostname_for_slot(self.slot_number)
     end
 
     save
@@ -67,4 +117,44 @@ class Node < ActiveRecord::Base
   def ensure_ping_secret
     self.info[:ping_secret] ||= rand(2**256).to_s(36)
   end
+
+  def dnsmasq_update
+    if self.hostname_changed? or self.ip_address_changed?
+      if self.hostname and self.ip_address
+        self.class.dnsmasq_update(self.hostname, self.ip_address)
+      end
+    end
+  end
+
+  def self.dnsmasq_update(hostname, ip_address)
+    return unless @@confdir
+    ptr_domain = ip_address.
+      split('.').reverse.join('.').concat('.in-addr.arpa')
+    hostfile = File.join @@confdir, hostname
+    File.open hostfile, 'w' do |f|
+      f.puts "address=/#{hostname}/#{ip_address}"
+      f.puts "address=/#{hostname}.#{@@domain}/#{ip_address}" if @@domain
+      f.puts "ptr-record=#{ptr_domain},#{hostname}"
+    end
+    File.open(File.join(@@confdir, 'restart.txt'), 'w') do |f|
+      # this should trigger a dnsmasq restart
+    end
+  end
+
+  def self.hostname_for_slot(slot_number)
+    "compute#{slot_number}"
+  end
+
+  # At startup, make sure all DNS entries exist.  Otherwise, slurmctld
+  # will refuse to start.
+  if @@confdir and
+      !File.exists? (File.join(@@confdir, hostname_for_slot(MAX_SLOTS-1)))
+    (0..MAX_SLOTS-1).each do |slot_number|
+      hostname = hostname_for_slot(slot_number)
+      hostfile = File.join @@confdir, hostname
+      if !File.exists? hostfile
+        dnsmasq_update(hostname, '127.40.4.0')
+      end
+    end
+  end
 end