bindg5k.rb 23.2 KB
Newer Older
1
2
# See also: https://www.grid5000.fr/mediawiki/index.php/DNS_server

3
require 'dns/zone'
4
require 'find'
5
require 'ipaddr'
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
6

7
8
9
10
#Prettier aligned dump of records
class DNS::Zone::RR::A
  def dump
    max_pad = 30
11
    return "#{@label.ljust(max_pad)} IN A #{' ' * 6 + @address}"
12
13
14
15
16
17
18
  end
end

class DNS::Zone::RR::AAAA
  def dump
    max_pad = 30
    return "#{@label.ljust(max_pad)} IN AAAA #{' ' * 6 + @address}"
19
  end
20
21
end

22
23
24
25
26
class DNS::Zone::RR::CNAME
  def dump
    max_pad = 30
    return @label.ljust(max_pad) + " IN CNAME " + ' ' * 6 + @domainname
  end
27
28
end

29
30
31
32
33
class DNS::Zone::RR::NS
  def dump
    max_pad = 30
    return @label.ljust(max_pad) + " IN NS " + ' ' * 6 + @nameserver
  end
34
35
end

36
37
38
39
class DNS::Zone::RR::MX
  def dump
    max_pad = 30
    return @label.ljust(max_pad) + " MX " + @priority.to_s + " " * 6 + @exchange
40
  end
41
42
43
44
45
46
47
48
49
50
51
52
53
54
end

class DNS::Zone::RR::SOA
  #Keep the previous version of soa format
  def dump
    content = "@" + " " * 23 + "IN      SOA     "
    content += @nameserver + " "
    content += @email + " (\n"
    content += " " * 32 + @serial.to_s + " ; serial (YYYYMMDDSS)\n"
    content += " " * 32 + @refresh_ttl + " " * 9 + "; refresh\n"
    content += " " * 32 + @retry_ttl + " " * 9 + "; retry\n"
    content += " " * 32 + @expiry_ttl + " " * 9 + "; expire\n"
    content += " " * 32 + @minimum_ttl + ")" + " " * 8 + "; negative caching\n"
    return content
55
  end
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
56
end
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
57

58
59
60
61
62
63
64
65
66
67
68
69
class DNS::Zone

  attr_accessor :file_path
  attr_accessor :site_uid
  attr_accessor :header
  attr_accessor :soa
  attr_accessor :ns
  attr_accessor :mx
  attr_accessor :at
  attr_accessor :include

  def get_header
70
    content = "; This file was generated by reference-repository.git\n; Do not edit this file by hand. Your changes will be overwritten.\n"
71
72
73
74
75
76
77
78
79
80
81
    if header
      content += "$TTL 3h\n"
      content += soa.dump + "\n"
      content += ns.dump + "\n"
      if at
        content += at.dump + "\n"
      end
      content += mx.dump + "\n"
    end
    return content
  end
82

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  #Re-define Zone dump
  def dump
    last_type = ""
    content = []
    if @include
      content << "\n" + @include
    end
    @records.each { |record|
      if record.type != last_type
        content << "\n; #{record.type} records"
      end
      content << record.dump
      last_type = record.type
    }
    return get_header() + content.join("\n") << "\n"
98
  end
99
100
end

101
102
def get_servers_records(site)
  records = []
103
  site['servers'].sort.each { |server_uid, server|
104

105
    next if server['network_adapters'].nil?
106

107
    server['network_adapters'].each { |net_uid, net|
108
109
110
111
112
113
114
115
116
117
118
119
120
      next if net['ip'].nil? && net['ip6'].nil?
      if net['ip']
        new_record = DNS::Zone::RR::A.new
        new_record.address = net['ip']
        new_record.label = server_uid
        new_record.label += "-#{net_uid}" if net_uid != 'default'
        records << new_record
      end
      if net['ip6']
        new_record_ipv6 = DNS::Zone::RR::AAAA.new
        new_record_ipv6.address = net['ip6']
        new_record_ipv6.label = server_uid
        new_record_ipv6.label += "-#{net_uid}" if net_uid != 'default'
121
        new_record_ipv6.label += '-ipv6'
122
123
        records << new_record_ipv6
      end
124
      if server['alias']
125
        #Reject global aliases (See 7513)
126
127
128
129
130
131
132
        server['alias'].reject{ |cname| cname.include?('.') }.each{ |cname|
          cname_record = DNS::Zone::RR::CNAME.new
          cname_record.label = cname
          cname_record.label += "-#{net_uid}" if net_uid != 'default'
          cname_record.domainname = server_uid
          cname_record.domainname += "-#{net_uid}" if net_uid != 'default'
          records << cname_record
133
134
135
136
        }
      end
    }
  }
137
  return records
138
139
end

140
141
def get_pdus_records(site)
  records = []
142
143
144

  site['pdus'].sort.each { |pdu_uid, pdu|

145
    next unless pdu['ip'] || pdu['ip6']
146

147
148
149
150
151
152
153
154
155
    if pdu['ip']
      new_record = DNS::Zone::RR::A.new
      new_record.address = pdu['ip']
      new_record.label = pdu_uid
      records << new_record
    end
    if pdu['ip6']
      new_record_ipv6 = DNS::Zone::RR::AAAA.new
      new_record_ipv6.address = pdu['ip6']
156
      new_record_ipv6.label = pdu_uid + '-ipv6'
157
158
      records << new_record_ipv6
    end
159
  }
160
  return records
161
162
end

163
164
def get_networks_records(site, key)
  records = []
165

166
167
  site[key].sort.each { |uid, net|
    if net['ip'].nil?
168
      puts "Warning: no IP for #{uid}"
169
170
171
      next
    end

172
    if net['ip']
173
      new_record = DNS::Zone::RR::A.new
174
      new_record.address = net['ip']
175
176
177
      new_record.label = uid
      records << new_record
    end
178
    if net['ip6']
179
      new_record_ipv6 = DNS::Zone::RR::AAAA.new
180
      new_record_ipv6.address = net['ip6']
181
182
183
      new_record_ipv6.label = uid + '-ipv6'
      records << new_record_ipv6
    end
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    if net['alias']
      net['alias'].each{ |a|
        if a.is_a?(Hash)
          new_record = DNS::Zone::RR::A.new
          new_record.address = a['ip']
          new_record.label = a['name']
          records << new_record
        end
        if a.is_a?(String) and !a.include?('.')  #Reject global aliases (See 7513)
          cname_record = DNS::Zone::RR::CNAME.new
          cname_record.label = a
          cname_record.domainname = uid
          records << cname_record
        end
      }
    end
200
  }
201

202
  return records
203
204
end

205
def get_node_records(cluster_uid, node_uid, network_adapters)
206

207
  records = []
208

209
  network_adapters.each { |net_uid, net_hash|
210
    next unless net_hash['ip'] || net_hash['ip6']
211
212
213

    node_id = node_uid.to_s.split(/(\d+)/)[1].to_i # node number

214
215
216
217
218
219
    if net_hash['ip']
      new_record = DNS::Zone::RR::A.new
      new_record.address = net_hash['ip']
      new_record.label = "#{cluster_uid}-#{node_id}"
      new_record.label += "-#{net_uid}" unless net_hash['mounted'] && /^eth[0-9]$/.match(net_uid)
      records << new_record
220
221
222
223
224
225
      if /^eth[0-9]$/.match(net_uid)
        cname_record = DNS::Zone::RR::CNAME.new
        cname_record.label = "#{cluster_uid}-#{node_id}-#{net_hash['pname']}"
        cname_record.domainname = "#{cluster_uid}-#{node_id}-#{net_uid}"
        records << cname_record
      end
226
227
228
229
230
231
    end
    if net_hash['ip6']
      new_record_ipv6 = DNS::Zone::RR::AAAA.new
      new_record_ipv6.address = net_hash['ip6']
      new_record_ipv6.label = "#{cluster_uid}-#{node_id}"
      new_record_ipv6.label += "-#{net_uid}" unless net_hash['mounted'] && /^eth[0-9]$/.match(net_uid)
232
      new_record_ipv6.label += '-ipv6'
233
      records << new_record_ipv6
234
235
236
237
238
239
      if /^eth[0-9]$/.match(net_uid)
        cname_record_ipv6 = DNS::Zone::RR::CNAME.new
        cname_record_ipv6.label = "#{cluster_uid}-#{node_id}-#{net_hash['pname']}-ipv6"
        cname_record_ipv6.domainname = "#{cluster_uid}-#{node_id}-#{net_uid}-ipv6"
        records << cname_record_ipv6
      end
240
    end
241

242
    if net_hash['mounted'] && /^eth[0-9]$/.match(net_uid)
243
244
245
246
247
      # CNAME enabled for primary interface (node-id-iface cname node-id)
      cname_record = DNS::Zone::RR::CNAME.new
      cname_record.label = "#{cluster_uid}-#{node_id}-#{net_uid}"
      cname_record.domainname = "#{cluster_uid}-#{node_id}"
      records << cname_record
248
249
250
251
      cname_record_ipv6 = DNS::Zone::RR::CNAME.new
      cname_record_ipv6.label = "#{cluster_uid}-#{node_id}-#{net_uid}-ipv6"
      cname_record_ipv6.domainname = "#{cluster_uid}-#{node_id}-ipv6"
      records << cname_record_ipv6
252
    end
253

254
255
256
    #Handle interface aliases
    if (net_hash["alias"])
      net_hash["alias"].each { |cname|
257
258
        cname_record = DNS::Zone::RR::CNAME.new
        cname_record.label = "#{cluster_uid}-#{node_id}-#{cname}"
259
        cname_record.domainname = "#{cluster_uid}-#{node_id}-#{net_uid}"
260
        records << cname_record
261
262
      }
    end
263
264
265
266
  } #each network adapters
  return records
end

Lucas Nussbaum's avatar
Lucas Nussbaum committed
267
def get_node_kavlan_records(_cluster_uid, node_uid, network_adapters, kavlan_adapters)
268
269
270
271
  records = []

  kavlan_adapters.each { |net_uid, net_hash|

272
    next unless net_hash['ip'] || net_hash['ip6']
273
274
275
276

    net_primaries = network_adapters.select{ |u, h| h['mounted'] && /^eth[0-9]$/.match(u) } # list of primary interfaces
    net_uid_eth, net_uid_kavlan = net_uid.to_s.scan(/^([^-]*)-(.*)$/).first # split 'eth0-kavlan-1'

277
278
279
280
281
    if net_hash['ip']
      new_record = DNS::Zone::RR::A.new
      new_record.address = net_hash['ip']
      new_record.label = "#{node_uid}-#{net_uid}" #sol-23-eth0-kavlan-1
      records << new_record
282
283
284
285
      cname_record = DNS::Zone::RR::CNAME.new
      cname_record.label = "#{node_uid}-#{net_hash['pname']}"
      cname_record.domainname = "#{node_uid}-#{net_uid}" #sol-23-eno1-kavlan-1
      records << cname_record
286
287
288
289
290
291
292
    end
    if net_hash['ip6']
      new_record_ipv6 = DNS::Zone::RR::AAAA.new
      new_record_ipv6.address = net_hash['ip6']
      new_record_ipv6.label = "#{node_uid}-#{net_uid}" #sol-23-eth0-kavlan-1
      new_record_ipv6.label += '-ipv6'
      records << new_record_ipv6
293
294
295
296
      cname_record_ipv6 = DNS::Zone::RR::CNAME.new
      cname_record_ipv6.label = "#{node_uid}-#{net_hash['pname']}-ipv6"
      cname_record_ipv6.domainname = "#{node_uid}-#{net_uid}-ipv6" #sol-23-eno1-kavlan-1
      records << cname_record_ipv6
297
    end
298
299
300
301
302
303
304

    # CNAME only for primary interface kavlan
    if net_primaries.include?(net_uid_eth)
      cname_record = DNS::Zone::RR::CNAME.new
      cname_record.label = "#{node_uid}-#{net_uid_kavlan}"
      cname_record.domainname = "#{node_uid}-#{net_uid}" #sol-23-eth0-kavlan-1
      records << cname_record
305
306
307
308
      cname_record_ipv6 = DNS::Zone::RR::CNAME.new
      cname_record_ipv6.label = "#{node_uid}-#{net_uid_kavlan}-ipv6"
      cname_record_ipv6.domainname = "#{node_uid}-#{net_uid}-ipv6" #sol-23-eth0-kavlan-1
      records << cname_record_ipv6
309
310
    end
  } #each network adapters
311
312

  return records
313
end
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
314

315
316
def get_reverse_record(record, site_uid)

317
318
319
320
  return unless record.is_a?(DNS::Zone::RR::A) || record.is_a?(DNS::Zone::RR::AAAA)

  if record.is_a?(DNS::Zone::RR::AAAA) # check for AAAA before A because AAAA inherits from A (so an AAAA is also an A)
    nibble_array = IPAddr.new(record.address).to_string.gsub(':','').split('').reverse
321
    nibble_split = 16
322
323
324
    if /.*-kavlan-[1-9][0-9]-ipv6$/.match(record.label)
      nibble_split = 14
    end
325
326
327
328
329
330
    file_name = "reverse6-#{nibble_array[nibble_split..31].join('.')}.db"
    if /.*-kavlan-[1-3]-ipv6$/.match(record.label)
      #A filter in bind-global-site.conf.erb prevents entries in 'local' directory to be included in global configuration
      #TODO later, also add DMZ IPs check here
      file_name.prepend("local/")
    end
331
    reverse_record = DNS::Zone::RR::PTR.new
332
    reverse_record.label = nibble_array[0..(nibble_split-1)].join('.')
333
334
335
336
337
338
339
340
341
342
343
    reverse_record.name = "#{record.label}.#{site_uid}.grid5000.fr."
    return file_name, reverse_record
  elsif record.is_a?(DNS::Zone::RR::A)
    ip_array = record.address.split(".")
    file_name = "reverse-#{ip_array[0..2].reverse.join('.')}.db" # 70.16.172

    if /.*-kavlan-[1-3]$/.match(record.label)
      #A filter in bind-global-site.conf.erb prevents entries in 'local' directory to be included in global configuration
      #TODO later, also add DMZ IPs check here
      file_name.prepend("local/")
    end
344

345
346
347
    reverse_record = DNS::Zone::RR::PTR.new
    reverse_record.label = ip_array[3] #ip suffix
    reverse_record.name = "#{record.label}.#{site_uid}.grid5000.fr."
348

349
    return file_name, reverse_record
350
351
352
353
354
355
356
  end
end

def sort_records(records)
  sorted_records = []
  cnames = []
  in_a = []
357
  in_aaaa = []
358
359
360
  ptr = []

  records.each{ |record|
361
    if (record.is_a?(DNS::Zone::RR::AAAA)) # check for AAAA before A because AAAA inherits from A (so an AAAA is also an A)
362
      in_aaaa << record
363
364
    elsif (record.is_a?(DNS::Zone::RR::A))
      in_a << record
365
366
367
368
369
370
371
372
373
    elsif (record.is_a?(DNS::Zone::RR::CNAME))
      cnames << record
    elsif (record.is_a?(DNS::Zone::RR::PTR))
      ptr << record
    end
  }
  in_a.sort_by!{ |record|
    record.address.split('.').map{ |octet|
      octet.to_i
374
    }.push(record.label)
375
376
  }
  sorted_records += in_a
377
  in_aaaa.sort_by!{ |record|
378
    IPAddr.new(record.address).to_string.gsub(':','')
379
380
  }
  sorted_records += in_aaaa
381
382
383
384
385
386
387
388
  ptr.sort_by!{ |record|
    record.name
  }
  sorted_records += ptr
  #Sort CNAMES by node_id for node, node_id then kavlan number for node kavlan entry or finally by label
  cnames.sort_by!{ |record|
    sort_by = record.label
    label_array = record.label.split("-")
389
    if label_array.length >= 4 and (Integer(label_array[3]) rescue false)
390
      if label_array[1].to_i != 0 && label_array[3].to_i != 0
391
        sort_by = [label_array.length, label_array[3].to_i, label_array[1].to_i]
392
393
394
      end
    elsif label_array.length > 1
      if label_array[1].to_i != 0
395
        sort_by = [ label_array.length, label_array[1].to_i ]
396
397
398
399
400
401
402
403
404
405
      end
    end
    sort_by
  }
  sorted_records += cnames
  return sorted_records
end

def include_manual_file(zone)
  manual_file_path = File.join(File.dirname(zone.file_path), File.basename(zone.file_path).sub('.db', '') + '-manual.db')
Lucas Nussbaum's avatar
Lucas Nussbaum committed
406
  if (File.exist?(manual_file_path))
407
408
409
410
411
412
413
    return "$INCLUDE /etc/bind/zones/#{zone.site_uid}/#{File.basename(zone.file_path).sub('.db', '') + '-manual.db'}\n"
  end
  return ''
end

#
def load_zone(zone_file_path, site_uid, site, header)
Lucas Nussbaum's avatar
Lucas Nussbaum committed
414
  if File.exist?(zone_file_path)
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
    zone = DNS::Zone.load(File.read(zone_file_path))
  else
    zone = DNS::Zone.new
  end
  zone.site_uid = site_uid
  zone.file_path = zone_file_path
  #If the target file will have a header or not (manage included files without records duplication)
  zone.header = header
  zone.soa = zone.records[0] if zone.records.any? && zone.records[0].type == 'SOA'

  #We only want the zone to manage these records, we will manage SOA, MX, NS, @s manually
  zone.records.reject! { |rec|
    !( rec.is_a?(DNS::Zone::RR::A) || rec.is_a?(DNS::Zone::RR::CNAME) || rec.is_a?(DNS::Zone::RR::PTR)) || rec.label == "@"
  }
  if header
    set_zone_header_records(zone, site)
  end
  zone.include = include_manual_file(zone)
  return zone
end

def set_zone_header_records(zone, site)
  if zone.soa.nil?
    soa = DNS::Zone::RR::SOA.new
    soa.serial = Time.now.utc.strftime("%Y%m%d00")
    soa.refresh_ttl = "4h"
    soa.retry_ttl = "1h"
    soa.expiry_ttl = "4w"
    soa.minimum_ttl = "1h"
    soa.nameserver = "dns.grid5000.fr."
    soa.email = "nsmaster.dns.grid5000.fr."
    zone.soa = soa
  end
  zone.ns = DNS::Zone::RR::NS.new
  zone.ns.nameserver = "dns.grid5000.fr."
  zone.mx = DNS::Zone::RR::MX.new
  zone.mx.priority = 10
  zone.mx.exchange = "mail.#{zone.site_uid}.grid5000.fr."
453
  if (File.basename(zone.file_path) == "#{zone.site_uid}.db" && site['frontend_ip'])
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
    zone.at = DNS::Zone::RR::A.new
    zone.at.address = site['frontend_ip']
  end
end

def update_serial(serial)
  date_serial = DateTime.strptime(serial.to_s, "%Y%m%d%S")
  now_date = DateTime.now
  if (date_serial.strftime("%Y%m%d") == now_date.strftime("%Y%m%d"))
    date_serial = date_serial + Rational(1, 86400) #+1 second
    return date_serial.strftime("%Y%m%d%S")
  else
    return Time.now.utc.strftime("%Y%m%d00")
  end
end

#Check if there are differences between existing and newly created records
#We check only A and CNAME records
def diff_zone_file(zone, records)
  #Compare dumped strings directly instead of RR objects
  zone_records = zone.records.map{ |rec|
    rec.dump
  }
  recs = records.map{ |rec|
    rec.dump
  }
  removed_records = zone_records - recs
  added_records = recs - zone_records
  if $options[:verbose]
    if removed_records.any?
      puts "Removed records in zone file: #{zone.file_path}"
      removed_records.each{ |rec|
        puts rec
      }
    end
    if added_records.any?
      puts "Added records in zone file: #{zone.file_path}"
      added_records.each{ |rec|
        puts rec
      }
    end
  end
  return added_records.any? || removed_records.any?
end

499
500
def write_site_conf(site_uid, dest_dir, zones_dir)
    conf_file = File.join(dest_dir, "#{site_uid}-zones.conf")
501
    FileUtils.mkdir_p(File.dirname(conf_file))
502
503
504
505
506
507
508
509
    conf_content = ERB.new(File.read(File.expand_path('templates/bind-site.conf.erb', File.dirname(__FILE__)))).result(binding)
    File.write(conf_file, conf_content)
end

def write_site_local_conf(site_uid, dest_dir, zones_dir)
    conf_file = File.join(dest_dir, "#{site_uid}-localzones.conf")
    FileUtils.mkdir_p(File.dirname(conf_file))
    conf_content = ERB.new(File.read(File.expand_path('templates/bind-site-local.conf.erb', File.dirname(__FILE__)))).result(binding)
510
511
512
513
514
515
516
517
    File.write(conf_file, conf_content)
end

def write_zone(zone)
  FileUtils.mkdir_p(File.dirname(zone.file_path))
  File.write(zone.file_path, zone.dump)
end

518
519
CLEAN_OLD_ZONE_FILES = false

Lucas Nussbaum's avatar
Lucas Nussbaum committed
520
521
522
523
524
# main method
def generate_puppet_bindg5k(options)
  $options = options
  puts "Writing DNS configuration files to: #{$options[:output_dir]}"
  puts "For site(s): #{$options[:sites].join(', ')}"
525

Lucas Nussbaum's avatar
Lucas Nussbaum committed
526
  puts "Note: if you modify *-manual.db files you will have to manually update the serial in managed db file for changes to be applied"
527

Lucas Nussbaum's avatar
Lucas Nussbaum committed
528
  $written_files = []
529

530
  refapi = load_data_hierarchy
531

Lucas Nussbaum's avatar
Lucas Nussbaum committed
532
533
  # Loop over Grid'5000 sites
  refapi["sites"].each { |site_uid, site|
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
534

Lucas Nussbaum's avatar
Lucas Nussbaum committed
535
    next unless $options[:sites].include?(site_uid)
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
536

Lucas Nussbaum's avatar
Lucas Nussbaum committed
537
538
    dest_dir = "#{$options[:output_dir]}/platforms/production/modules/generated/files/bind/"
    zones_dir = File.join(dest_dir, "zones/#{site_uid}")
539

540
    if CLEAN_OLD_ZONE_FILES and File::exist?(zones_dir)
541
542
543
544
545
546
547
548
      # Cleanup of old zone files
      Find.find(zones_dir) do |path|
        next if not File::file?(path)
        next if path =~ /manual/ # skip *manual* files
        # FIXME those files are not named *manual*, but should not be removed
        next if ['nancy-laptops.db', 'toulouse-servers.db', 'toulouse.db'].include?(File::basename(path))
        FileUtils::rm(path)
      end
549
550
    end

Lucas Nussbaum's avatar
Lucas Nussbaum committed
551
    site_records = {}
552

Lucas Nussbaum's avatar
Lucas Nussbaum committed
553
554
    # Servers
    site_records['servers'] = get_servers_records(site) unless site['servers'].nil?
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
555

Lucas Nussbaum's avatar
Lucas Nussbaum committed
556
557
    # PDUs
    site_records['pdus'] = get_pdus_records(site) unless site['pdus'].nil?
558

Lucas Nussbaum's avatar
Lucas Nussbaum committed
559
    # Networks and laptops (same input format)
560
    site_records['networks'] = get_networks_records(site, 'network_equipments') unless site['network_equipments'].nil?
Lucas Nussbaum's avatar
Lucas Nussbaum committed
561
    site_records['laptops'] = get_networks_records(site, 'laptops') unless site['laptops'].nil?
562

Lucas Nussbaum's avatar
Lucas Nussbaum committed
563
    site.fetch("clusters", []).sort.each { |cluster_uid, cluster|
564

Lucas Nussbaum's avatar
Lucas Nussbaum committed
565
566
567
      cluster.fetch('nodes').select { |node_uid, node|
        node != nil && node["status"] != "retired" && node.has_key?('network_adapters')
      }.each_sort_by_node_uid { |node_uid, node|
568

Lucas Nussbaum's avatar
Lucas Nussbaum committed
569
        network_adapters = {}
570

Lucas Nussbaum's avatar
Lucas Nussbaum committed
571
        # Nodes
572
        node.fetch('network_adapters').each { |net|
573
574
575
576
577
578
579
          network_adapters[net['device']] = {
            "ip"      => net["ip"],
            "ip6"     => net["ip6"],
            "mounted" => net["mounted"],
            'alias'   => net['alias'],
            'pname'   => net['name'],
          }
580
        }
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
581

Lucas Nussbaum's avatar
Lucas Nussbaum committed
582
        # Mic
583
584
        if node['mic'] && (node['mic']['ip'] || node['mic']['ip6'])
          network_adapters['mic0'] = {"ip" => node['mic']['ip'], "ip6" => node['mic']['ip6']}
Lucas Nussbaum's avatar
Lucas Nussbaum committed
585
586
587
588
589
590
        end

        site_records[cluster_uid] ||= []
        site_records[cluster_uid] += get_node_records(cluster_uid, node_uid, network_adapters)

        # Kavlan
591
592
593
594
595
596
597
        kavlan_adapters = {}
        ['kavlan', 'kavlan6'].each { |kavlan_kind|
          if node[kavlan_kind]
            node.fetch(kavlan_kind).each { |net_uid, net_hash|
              net_hash.each { |kavlan_net_uid, ip|
                kavlan_adapters["#{net_uid}-#{kavlan_net_uid}"] ||= {}
                kavlan_adapters["#{net_uid}-#{kavlan_net_uid}"]['mounted'] = node['network_adapters'].select { |n|
IMBERT Matthieu's avatar
IMBERT Matthieu committed
598
599
                  n['device'] == net_uid
                }[0]['mounted']
600
601
602
                kavlan_adapters["#{net_uid}-#{kavlan_net_uid}"]['pname'] = node['network_adapters'].select { |n|
                  n['device'] == net_uid
                }.first['name'] + '-' + kavlan_net_uid
603
604
605
606
607
                if kavlan_kind == 'kavlan6'
                  kavlan_adapters["#{net_uid}-#{kavlan_net_uid}"]['ip6'] = ip
                else
                  kavlan_adapters["#{net_uid}-#{kavlan_net_uid}"]['ip'] = ip
                end
608
              }
Lucas Nussbaum's avatar
Lucas Nussbaum committed
609
            }
610
611
612
613
614
615
          end
        }
        if kavlan_adapters.length > 0
          key_sr = "#{cluster_uid}-kavlan"
          site_records[key_sr] ||= []
          site_records[key_sr] += get_node_kavlan_records(cluster_uid, node_uid, network_adapters, kavlan_adapters)
Lucas Nussbaum's avatar
Lucas Nussbaum committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
        end
      } # each nodes
    } # each cluster

    reverse_records = {} # one hash entry per reverse dns file

    site_records.each { |zone, records|

      #Sort records
      site_records[zone] = sort_records(records)

      records.each{ |record|
        #get Reverse records
        reverse_file_name, reverse_record = get_reverse_record(record, site_uid)
        if reverse_file_name != nil
          reverse_records[reverse_file_name] ||= []
          reverse_records[reverse_file_name].each {|r|
            if r.label == reverse_record.label
634
              puts "Warning: reverse entry with address #{reverse_record.label} already exists in #{reverse_file_name}, #{reverse_record.name} is duplicate"
Lucas Nussbaum's avatar
Lucas Nussbaum committed
635
636
637
638
639
640
            end
          }
          reverse_records[reverse_file_name] << reverse_record
        end
      }
    }
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
641

Lucas Nussbaum's avatar
Lucas Nussbaum committed
642
    zones = []
643

Lucas Nussbaum's avatar
Lucas Nussbaum committed
644
645
    #Sort reverse records and create reverse zone from files
    reverse_records.each{ |file_name, records|
646
647
648
649
650
      if file_name.start_with?('reverse6')
        records.sort!{ |a, b|
          a.label.gsub('.','').reverse <=> b.label.gsub('.','').reverse
        }
      else
651
652
        records.sort_by!{ |r|
          [r.label.to_i, r.name]
653
654
        }
      end
655

Lucas Nussbaum's avatar
Lucas Nussbaum committed
656
657
658
659
      reverse_file_path = File.join(zones_dir, file_name)
      zone = load_zone(reverse_file_path, site_uid, site, true)
      if diff_zone_file(zone, records)
        zone.soa.serial = update_serial(zone.soa.serial)
660
      end
Lucas Nussbaum's avatar
Lucas Nussbaum committed
661
662
      zone.records = records;
      zones << zone
663
    }
664

Lucas Nussbaum's avatar
Lucas Nussbaum committed
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
    #Manage site zone (SITE.db file)
    #It only contains header and inclusion of other db files
    #Check modification in included files and update serial accordingly
    site_zone_path = File.join(zones_dir, site_uid + ".db")
    site_zone = load_zone(site_zone_path, site_uid, site, true)
    site_zone_changed = false

    site_records.each{ |type, records|
      next if records.empty?
      zone_file_path = File.join(zones_dir, site_uid + "-" + type + ".db")
      zone = load_zone(zone_file_path, site_uid, site, false)
      if diff_zone_file(zone, records)
        puts "Zone file changed: #{zone.file_path}" if $options[:verbose]
        site_zone_changed = true
      end
      zone.records = records
      site_zone.include += "$INCLUDE /etc/bind/zones/#{site_uid}/#{File.basename(zone_file_path)}\n"
      zones << zone
683
    }
684

Lucas Nussbaum's avatar
Lucas Nussbaum committed
685
686
    if (site_zone_changed)
      site_zone.soa.serial = update_serial(site_zone.soa.serial)
687
    end
688

Lucas Nussbaum's avatar
Lucas Nussbaum committed
689
    zones << site_zone
690

691
692
693
694
695
    # zones that are already known and going to be written
    future_zones = zones.map { |z| File::basename(z.file_path) }

    # Create reverse-*.db files for each reverse-*-manual.db that do not have (yet) a corresponding file.
    Dir.glob(File.join(zones_dir, "reverse-*-manual.db")).each { |reverse_manual_file|
696
      #FIXME: need to be adapted for IPv6 at some point
697
698
699
700
701
702
703
      output_file = reverse_manual_file.sub("-manual.db", ".db")
      next if future_zones.include?(File::basename(output_file)) # the zone is already going to be written
      puts "Creating file for orphan reverse manual file: #{output_file}" if $options[:verbose]
      #Creating the zone will include automatically the manual file
      zone = load_zone(output_file, site_uid, site, true)
      zones << zone
    }
704

Lucas Nussbaum's avatar
Lucas Nussbaum committed
705
706
707
    zones.each{ |zone|
      write_zone(zone)
    }
708

Lucas Nussbaum's avatar
Lucas Nussbaum committed
709
710
    write_site_conf(site_uid, dest_dir, zones_dir)
    write_site_local_conf(site_uid, dest_dir, zones_dir)
Jérémie Gaidamour's avatar
Jérémie Gaidamour committed
711

Lucas Nussbaum's avatar
Lucas Nussbaum committed
712
713
  } # each sites
end