add /nodes status page
[arvados.git] / app / models / node.rb
1 class Node < ActiveRecord::Base
2   include AssignUuid
3   serialize :info, Hash
4   before_validation :ensure_ping_secret
5   after_update :dnsmasq_update
6
7   MAX_SLOTS = 64
8
9   @@confdir = if Rails.configuration.respond_to? :dnsmasq_conf_dir
10                 Rails.configuration.dnsmasq_conf_dir
11               elsif File.exists? '/etc/dnsmasq.d/.'
12                 '/etc/dnsmasq.d'
13               else
14                 nil
15               end
16
17   def info
18     @info ||= Hash.new
19     super
20   end
21
22   def status
23     if !self.last_ping_at
24       if Time.now - self.created_at > 5.minutes
25         'startup-fail'
26       else
27         'pending'
28       end
29     elsif Time.now - self.last_ping_at > 1.hours
30       'missing'
31     else
32       'alive'
33     end
34   end
35
36   def ping(o)
37     raise "must have :ip and :ping_secret" unless o[:ip] and o[:ping_secret]
38
39     if o[:ping_secret] != self.info[:ping_secret]
40       logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.info[:ping_secret]}\""
41       return nil
42     end
43     self.last_ping_at = Time.now
44
45     # Record IP address
46     if self.ip_address.nil?
47       logger.info "#{self.uuid} ip_address= #{o[:ip]}"
48       self.ip_address = o[:ip]
49       self.first_ping_at = Time.now
50     end
51
52     # Record instance ID if not already known
53     self.info[:ec2_instance_id] ||= o[:ec2_instance_id]
54
55     # Assign hostname
56     if self.slot_number.nil?
57       try_slot = 0
58       begin
59         self.slot_number = try_slot
60         begin
61           self.save!
62           break
63         rescue ActiveRecord::RecordNotUnique
64           try_slot += 1
65         end
66         raise "No available node slots" if try_slot == MAX_SLOTS
67       end while true
68       self.hostname = self.class.hostname_for_slot(self.slot_number)
69     end
70
71     save
72   end
73
74   def start!(ping_url_method)
75     ping_url = ping_url_method.call({ uuid: self.uuid, ping_secret: self.info[:ping_secret] })
76     cmd = ["ec2-run-instances",
77            "--user-data '#{ping_url}'",
78            "-t c1.xlarge -n 1 -g orvos-compute",
79            "ami-68ca6901"
80           ].join(' ')
81     self.info[:ec2_start_command] = cmd
82     logger.info "#{self.uuid} ec2_start_command= #{cmd.inspect}"
83     result = `#{cmd} 2>&1`
84     self.info[:ec2_start_result] = result
85     logger.info "#{self.uuid} ec2_start_result= #{result.inspect}"
86     result.match(/INSTANCE\s*(i-[0-9a-f]+)/) do |m|
87       self.info[:ec2_instance_id] = m[1]
88     end
89     self.save!
90   end
91
92   protected
93
94   def ensure_ping_secret
95     self.info[:ping_secret] ||= rand(2**256).to_s(36)
96   end
97
98   def dnsmasq_update
99     if self.hostname_changed? or self.ip_address_changed?
100       if self.hostname and self.ip_address
101         self.class.dnsmasq_update(self.hostname, self.ip_address)
102       end
103     end
104   end
105
106   def self.dnsmasq_update(hostname, ip_address)
107     return unless @@confdir
108     ptr_domain = ip_address.
109       split('.').reverse.join('.').concat('.in-addr.arpa')
110     hostfile = File.join @@confdir, hostname
111     File.open hostfile, 'w' do |f|
112       f.puts "address=/#{hostname}/#{ip_address}"
113       f.puts "ptr-record=#{ptr_domain},#{hostname}"
114     end
115     File.open(File.join(@@confdir, 'restart.txt'), 'w') do |f|
116       # this should trigger a dnsmasq restart
117     end
118   end
119
120   def self.hostname_for_slot(slot_number)
121     "compute#{slot_number}"
122   end
123
124   # At startup, make sure all DNS entries exist.  Otherwise, slurmctld
125   # will refuse to start.
126   if @@confdir and
127       !File.exists? (File.join(@@confdir, hostname_for_slot(MAX_SLOTS-1)))
128     (0..MAX_SLOTS-1).each do |slot_number|
129       hostname = hostname_for_slot(slot_number)
130       hostfile = File.join @@confdir, hostname
131       if !File.exists? hostfile
132         dnsmasq_update(hostname, '127.40.4.0')
133       end
134     end
135   end
136 end