update dnsmasq database when node gets hostname
[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
6   MAX_SLOTS = 64
7
8   @@confdir = begin
9                 Rails.configuration.dnsmasq_conf_dir or
10                   ('/etc/dnsmasq.d' if File.exists? '/etc/dnsmasq.d/.')
11               end
12
13   def info
14     @info ||= Hash.new
15     super
16   end
17
18   def ping(o)
19     raise "must have :ip and :ping_secret" unless o[:ip] and o[:ping_secret]
20
21     if o[:ping_secret] != self.info[:ping_secret]
22       logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.info[:ping_secret]}\""
23       return nil
24     end
25     self.last_ping_at = Time.now
26
27     # Record IP address
28     if self.ip_address.nil?
29       logger.info "#{self.uuid} ip_address= #{o[:ip]}"
30       self.ip_address = o[:ip]
31       self.first_ping_at = Time.now
32     end
33
34     # Record instance ID if not already known
35     self.info[:ec2_instance_id] ||= o[:ec2_instance_id]
36
37     # Assign hostname
38     if self.slot_number.nil?
39       try_slot = 0
40       begin
41         self.slot_number = try_slot
42         try_slot += 1
43         break if self.save rescue nil
44         raise "No available node slots" if try_slot == MAX_SLOTS
45       end while true
46       self.hostname = self.class.hostname_for_slot(self.slot_number)
47       self.class.dnsmasq_update(self.hostname, self.ip_address)
48     end
49
50     save
51   end
52
53   def start!(ping_url_method)
54     ping_url = ping_url_method.call({ uuid: self.uuid, ping_secret: self.info[:ping_secret] })
55     cmd = ["ec2-run-instances",
56            "--user-data '#{ping_url}'",
57            "-t c1.xlarge -n 1 -g orvos-compute",
58            "ami-68ca6901"
59           ].join(' ')
60     self.info[:ec2_start_command] = cmd
61     logger.info "#{self.uuid} ec2_start_command= #{cmd.inspect}"
62     result = `#{cmd} 2>&1`
63     self.info[:ec2_start_result] = result
64     logger.info "#{self.uuid} ec2_start_result= #{result.inspect}"
65     result.match(/INSTANCE\s*(i-[0-9a-f]+)/) do |m|
66       self.info[:ec2_instance_id] = m[1]
67     end
68     self.save!
69   end
70
71   protected
72
73   def ensure_ping_secret
74     self.info[:ping_secret] ||= rand(2**256).to_s(36)
75   end
76
77   def self.dnsmasq_update(hostname, ip_address)
78     return unless @@confdir
79     ptr_domain = ip_address.
80       split('.').reverse.join('.').concat('.in-addr.arpa')
81     hostfile = File.join @@confdir, hostname
82     File.open hostfile, 'w' do |f|
83       f.puts "address=/#{hostname}/#{ip_address}"
84       f.puts "ptr-record=#{ptr_domain},#{hostname}"
85     end
86     File.open(File.join(@@confdir, 'restart.txt'), 'w') do |f|
87       # this should trigger a dnsmasq restart
88     end
89   end
90
91   def self.hostname_for_slot(slot_number)
92     "compute#{slot_number}"
93   end
94
95   # At startup, make sure all DNS entries exist.  Otherwise, slurmctld
96   # will refuse to start.
97   if @@confdir and
98       !File.exists? (File.join(@@confdir, hostname_for_slot(MAX_SLOTS-1)))
99     (0..MAX_SLOTS-1).each do |slot_number|
100       hostname = hostname_for_slot(slot_number)
101       hostfile = File.join @@confdir, hostname
102       if !File.exists? hostfile
103         dnsmasq_update(hostname, '127.40.4.0')
104       end
105     end
106   end
107 end