root/lib/proxy/dhcp/server/native_ms.rb @ de1ecf7b
1728ab70 | Paul Kelly | require 'rubygems'
|
|
require 'win32/open3'
|
|||
module Proxy::DHCP
|
|||
# Represents Microsoft DHCP Server manipulated via the netsh command
|
|||
# executed on a Microsoft server under a service account
|
|||
class NativeMS < Server
|
|||
def initialize(options = {})
|
|||
super options[:server]
|
|||
end
|
|||
def delRecord subnet, record
|
|||
validate_subnet subnet
|
|||
validate_record record
|
|||
# TODO: Refactor this into the base class
|
|||
raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable?
|
|||
mac = record.mac.gsub(/:/,"")
|
|||
msg = "Removed DHCP reservation for #{record.name} => #{record.ip} - #{record.mac}"
|
|||
cmd = "scope #{subnet.network} delete reservedip #{record.ip} #{mac}"
|
|||
execute(cmd, msg)
|
|||
subnet.delete(record)
|
|||
end
|
|||
def addRecord options={}
|
|||
f2248acc | Ohad Levy | record = super(options)
|
|
1728ab70 | Paul Kelly | ||
f2248acc | Ohad Levy | cmd = "scope #{record.subnet.network} add reservedip #{record.ip} #{record.mac.gsub(/:/,"")} #{record.name}"
|
|
execute(cmd, "Added DHCP reservation for #{record}")
|
|||
1728ab70 | Paul Kelly | ||
f2248acc | Ohad Levy | options = record.options
|
|
de1ecf7b | Ohad Levy | ignored_attributes = [:ip, :mac, :name, :subnet]
|
|
options.delete_if{|k,v| ignored_attributes.include?(k.to_sym) }
|
|||
c6ea383c | Paul kelly | return if options.empty? # This reservation is just for an IP and MAC
|
|
1728ab70 | Paul Kelly | ||
# TODO: Refactor these execs into a popen
|
|||
c6ea383c | Paul kelly | alternate_vendor_name = nil
|
|
for key, value in options
|
|||
de1ecf7b | Ohad Levy | if match = key.to_s.match(/^<([^>]+)>(.*)/)
|
|
vendor, attr = match[1,2].map(&:to_sym)
|
|||
msg = "set value for #{key}"
|
|||
c6ea383c | Paul kelly | begin
|
|
f2248acc | Ohad Levy | execute "scope #{record.subnet.network} set reservedoptionvalue #{record.ip} #{SUNW[attr][:code]} #{SUNW[attr][:kind]} vendor=#{alternate_vendor_name || vendor} #{value}", msg, true
|
|
c6ea383c | Paul kelly | rescue Proxy::DHCP::Error => e
|
|
alternate_vendor_name = find_or_create_vendor_name vendor, e
|
|||
f2248acc | Ohad Levy | execute "scope #{record.subnet.network} set reservedoptionvalue #{ip} #{SUNW[attr][:code]} #{SUNW[attr][:kind]} vendor=#{alternate_vendor_name || vendor} #{value}", msg, true
|
|
c6ea383c | Paul kelly | end
|
|
else
|
|||
de1ecf7b | Ohad Levy | logger.debug "key: " + key.inspect
|
|
k = Standard[key] || Standard[key.to_sym]
|
|||
execute "scope #{record.subnet.network} set reservedoptionvalue #{record.ip} #{k[:code]} #{k[:kind]} #{value}", msg, true
|
|||
c6ea383c | Paul kelly | end
|
|
end
|
|||
1728ab70 | Paul Kelly | ||
f2248acc | Ohad Levy | record
|
|
1728ab70 | Paul Kelly | end
|
|
c6ea383c | Paul kelly | # We did not find the vendor name we wish to use registered on the DHCP server so we attempt to find if there is a vendor class using an abbreviated name.
|
|
# E.G. We failed to find Sun-Fire-V440 so check if there is a class Fire-V440
|
|||
# If this is not available then register the supplied vendor class
|
|||
# [+vendor+] : String containing the vendor class we wish to use
|
|||
# [+exception+] : Exception detected during the previous vendor class operation
|
|||
# Returns : String containing the abbreviated vendor class OR nil to indicate that we registered the original longer vendor class
|
|||
def find_or_create_vendor_name vendor, exception
|
|||
if exception.message =~ /Vendor class not found/
|
|||
# Try a heuristic to find an alternative vendor class
|
|||
@classes = @classes || loadVendorClasses
|
|||
short_vendor = vendor.gsub(/^sun-/i, "")
|
|||
if short_vendor != vendor and !(short_vendor = @classes.grep(/#{short_vendor}/i)).empty?
|
|||
short_vendor = short_vendor[0]
|
|||
else
|
|||
# OK. There does not appear to be a class with an abbreviated vendor name so lets try
|
|||
# and add the class and hope that it does not conflict with the same entry under another name
|
|||
installVendorClass vendor
|
|||
short_vendor = nil
|
|||
end
|
|||
else
|
|||
raise exception
|
|||
end
|
|||
short_vendor
|
|||
end
|
|||
private :find_or_create_vendor_name
|
|||
1728ab70 | Paul Kelly | def loadSubnetData subnet
|
|
super
|
|||
cmd = "scope #{subnet.network} show reservedip"
|
|||
msg = "Enumerated hosts on #{subnet.network}"
|
|||
# Extract the data
|
|||
execute(cmd, msg).each do |line|
|
|||
# 172.29.216.6 - 00-a0-e7-21-41-00-
|
|||
if line =~ /^\s+([\w\.]+)\s+-\s+([-a-f\d]+)/
|
|||
ip = $1
|
|||
de1ecf7b | Ohad Levy | next unless subnet.include?(ip)
|
|
1728ab70 | Paul Kelly | mac = $2.gsub(/-/,":").match(/^(.*?).$/)[1]
|
|
begin
|
|||
de1ecf7b | Ohad Levy | opts = {:subnet => subnet, :ip => ip, :mac => mac}
|
|
opts.merge!(loadRecordOptions(opts))
|
|||
logger.debug opts.inspect
|
|||
if opts.include? :hostname
|
|||
Proxy::DHCP::Reservation.new opts.merge({:deleteable => true})
|
|||
else
|
|||
# this is not a lease, rather reservation
|
|||
# but we require option 12(hostname) to be defined for our leases
|
|||
# workaround until #1172 is resolved.
|
|||
Proxy::DHCP::Lease.new opts
|
|||
end
|
|||
1728ab70 | Paul Kelly | rescue Exception => e
|
|
logger.warn "Skipped #{line} - #{e}"
|
|||
end
|
|||
end
|
|||
end
|
|||
end
|
|||
def loadSubnetOptions subnet
|
|||
super subnet
|
|||
raise "invalid Subnet" unless subnet.is_a? Proxy::DHCP::Subnet
|
|||
cmd = "scope #{subnet.network} Show OptionValue"
|
|||
msg = "Queried #{subnet.network} options"
|
|||
subnet.options = parse_options(execute(cmd, msg))
|
|||
end
|
|||
de1ecf7b | Ohad Levy | private
|
|
def loadRecordOptions opts
|
|||
raise "unable to find subnet for #{opts[:ip]}" if opts[:subnet].nil?
|
|||
cmd = "scope #{opts[:subnet].network} Show ReservedOptionValue #{opts[:ip]}"
|
|||
msg = "Queried #{opts[:ip]} options"
|
|||
1728ab70 | Paul Kelly | ||
de1ecf7b | Ohad Levy | parse_options(execute(cmd, msg))
|
|
1728ab70 | Paul Kelly | end
|
|
c6ea383c | Paul kelly | def installVendorClass vendor_class
|
|
cmd = "show class"
|
|||
msg = "Queried vendor classes"
|
|||
classes = parse_classes(execute(cmd, msg))
|
|||
return if classes.include? vendor_class
|
|||
cls = "SUNW.#{vendor_class}"
|
|||
execute("add class #{vendor_class} \"Vendor class for #{vendor_class}\" \"#{cls}\" 1", "Added class #{vendor_class}")
|
|||
for option in ["root_server_ip", "root_server_hostname", "root_path_name", "install_server_ip", "install_server_name",
|
|||
"install_path", "sysid_server_path", "jumpstart_server_path"]
|
|||
cmd = "add optiondef #{SUNW[option][:code]} #{option} #{SUNW[option][:kind]} 0 vendor=#{vendor_class}"
|
|||
execute cmd, "Added vendor option #{option}"
|
|||
end
|
|||
end
|
|||
1728ab70 | Paul Kelly | ||
c6ea383c | Paul kelly | def loadVendorClasses
|
|
cmd = "show class"
|
|||
msg = "Queried vendor classes"
|
|||
parse_classes(execute(cmd, msg))
|
|||
end
|
|||
1728ab70 | Paul Kelly | def loadSubnets
|
|
super
|
|||
cmd = "show scope"
|
|||
msg = "Enumerated the scopes on #{@name}"
|
|||
execute(cmd, msg).each do |line|
|
|||
# 172.29.216.0 - 255.255.254.0 -Active -DC BRS -
|
|||
if match = line.match(/^\s*([\d\.]+)\s*-\s*([\d\.]+)\s*-\s*(Active|Disabled)/)
|
|||
700e96b5 | Paul kelly | next if (managed_subnets = SETTINGS.dhcp_subnets) and !managed_subnets.include? "#{match[1]}/#{match[2]}"
|
|
1728ab70 | Paul Kelly | subnet = Proxy::DHCP::Subnet.new(self, match[1], match[2])
|
|
end
|
|||
end
|
|||
end
|
|||
def execute cmd, msg=nil, error_only=false
|
|||
tsecs = 5
|
|||
response = nil
|
|||
a57ca5dc | Paul kelly | interpreter = SETTINGS.x86_64 ? 'c:\windows\sysnative\cmd.exe' : 'c:\windows\system32\cmd.exe'
|
|
1728ab70 | Paul Kelly | command = interpreter + ' /c c:\Windows\System32\netsh.exe -c dhcp ' + "server #{name} #{cmd}"
|
|
std_in = std_out = std_err = nil
|
|||
begin
|
|||
timeout(tsecs) do
|
|||
de1ecf7b | Ohad Levy | logger.debug "executing: #{command}"
|
|
1728ab70 | Paul Kelly | std_in, std_out, std_err = Open3.popen3(command)
|
|
response = std_out.readlines
|
|||
response += std_err.readlines
|
|||
end
|
|||
rescue TimeoutError
|
|||
raise Proxy::DHCP::Error.new("Netsh did not respond within #{tsecs} seconds")
|
|||
ensure
|
|||
std_in.close unless std_in.nil?
|
|||
std_out.close unless std_in.nil?
|
|||
std_err.close unless std_in.nil?
|
|||
end
|
|||
report msg, response, error_only
|
|||
response
|
|||
end
|
|||
def report msg, response, error_only
|
|||
if response.grep(/completed successfully/).empty?
|
|||
c6ea383c | Paul kelly | if response.grep /class name being used is unknown/
|
|
logger.info "Vendor class not found"
|
|||
else
|
|||
logger.error "Netsh failed:\n" + response.join("\n")
|
|||
end
|
|||
1728ab70 | Paul Kelly | msg.sub! /Removed/, "remove"
|
|
msg.sub! /Added/, "add"
|
|||
msg.sub! /Enumerated/, "enumerate"
|
|||
msg.sub! /Queried/, "query"
|
|||
6eccb4f1 | Paul kelly | match = ""
|
|
1728ab70 | Paul Kelly | msg = "Failed to #{msg}"
|
|
c6ea383c | Paul kelly | msg += "Vendor class not found" if response.grep /class name being used is unknown/
|
|
6eccb4f1 | Paul kelly | msg += ": No entry found" if response.grep(/not a reserved client/).size > 0
|
|
msg += ": #{match}" if (match = response.grep(/used by another client/)).size > 0
|
|||
1728ab70 | Paul Kelly | raise Proxy::DHCP::Error.new(msg)
|
|
else
|
|||
logger.info msg unless error_only
|
|||
end
|
|||
6eccb4f1 | Paul kelly | rescue Proxy::DHCP::Error
|
|
raise
|
|||
1728ab70 | Paul Kelly | rescue
|
|
logger.error "Netsh failed:\n" + (response.is_a?(Array) ? response.join("\n") : "Response was not an array! #{response}")
|
|||
raise Proxy::DHCP::Error.new("Unknown error while processing '#{msg}'")
|
|||
end
|
|||
def parse_options response
|
|||
optionId = nil
|
|||
c6ea383c | Paul kelly | options = {}
|
|
vendor = ""
|
|||
1728ab70 | Paul Kelly | response.each do |line|
|
|
line.chomp!
|
|||
break if line.match(/^Command completed/)
|
|||
case line
|
|||
de1ecf7b | Ohad Levy | when /For vendor class \[([^\]]+)\]:/
|
|
vendor = "<#{$1}>"
|
|||
when /OptionId : (\d+)/
|
|||
optionId = "#{vendor}#{$1}".to_i
|
|||
when /Option Element Value = (\S+)/
|
|||
title = Standard.select {|k,v| v[:code] == optionId}.flatten[0]
|
|||
logger.debug "found option #{title}"
|
|||
options[title] = $1
|
|||
1728ab70 | Paul Kelly | end
|
|
end
|
|||
de1ecf7b | Ohad Levy | logger.debug options.inspect
|
|
1728ab70 | Paul Kelly | return options
|
|
end
|
|||
c6ea383c | Paul kelly | ||
def parse_classes response
|
|||
klass = nil
|
|||
classes = []
|
|||
response.each do |line|
|
|||
line.chomp!
|
|||
break if line.match(/^Command completed/)
|
|||
case line
|
|||
de1ecf7b | Ohad Levy | when /Class \[([^\]]+)\]:/
|
|
klass = $1
|
|||
when /Isvendor= TRUE/
|
|||
classes << klass
|
|||
c6ea383c | Paul kelly | end
|
|
end
|
|||
return classes
|
|||
end
|
|||
def vendor_options_supported?
|
|||
true
|
|||
end
|
|||
1728ab70 | Paul Kelly | end
|
|
end
|