-
Notifications
You must be signed in to change notification settings - Fork 21
(MODULES-5566) Rewrite Instance Discovery #241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(MODULES-5566) Rewrite Instance Discovery #241
Conversation
return nil if version.nil? | ||
sql_instance = get_wmi_instance_info(version, instance_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean get_wmi_instance_info
is no longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. It's removed further up in the diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. We replaced the various functionality for pulling from WMI by pulling from registry to identify and enumerate instances.
lib/puppet_x/sqlserver/features.rb
Outdated
@@ -266,7 +279,7 @@ def self.get_instances | |||
major_version = SQL_CONFIGURATION[version][:major_version] | |||
|
|||
instances = [] | |||
get_reg_instance_info(version).each do |instance| | |||
Array(get_reg_instance_info(version)).each do |instance| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious about why the need to force an array type. get_instance_names_by_reg should always be returning an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was getting errors regarding the each method not existing for nil
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh ok...so that method returns nil or an array.
lib/puppet_x/sqlserver/features.rb
Outdated
names.push(name) | ||
roots.push("Software\\Microsoft\\Microsoft SQL Server\\#{registry[name]}") | ||
} | ||
if key_exists?(instance_root) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of guarding the whole method / further indenting... should be easier to just return early like:
return [] unless key_exists?(instance_root)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I didn't even think to do this. Will update.
lib/puppet_x/sqlserver/features.rb
Outdated
} | ||
end | ||
end | ||
names.each do |name| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this a little simpler
instances = names.select do |name|
my_roots = roots.select { |path| /#{name}/.match(path) }
version = hklm.open("#{my_roots[0]}\\MSSQLServer\\CurrentVersion", KEY_READ | KEY_WOW64_64KEY) do |registry|
registry['CurrentVersion']
end
{'name' => name, 'version_friendly' => friendly_version, 'version' => version, 'reg_root' => my_roots})
end
I changed the .each
to a .select
.
You might also think about changing the hash keys from literal strings like 'name'
to symbols like :name
,
but that will require modifying the callers of this method as well. Symbols for hash keys are more idiomatic in Ruby.
I assume the .uniq
is need because there are dupes somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, though thinking about it now, the problem is that names
provides a duplicate list of names. Further, the required output is that unique list of instances, mapping a name to one or more registry roots.
A simpler solution would be to add the uniq to names
at the start of this call, like this, maybe?
names.uniq.select do |name|
my_roots = roots.select{|path| /#{name}/.match(path)}
version = hklm.open("#{my_roots[0]}\\MSSQLServer\\CurrentVersion", KEY_READ | KEY_WOW64_64KEY) do |registry|
registry['CurrentVersion']
end
{'name' => name, 'version_friendly' => friendly_version, 'version' => version, 'reg_root' => my_roots}
end
lib/puppet_x/sqlserver/features.rb
Outdated
subkeys.each do |subkey| | ||
hklm.open("#{instance_root}\\#{subkey}", KEY_READ | KEY_WOW64_64KEY) do |registry| | ||
registry.each_value{|name, _ | | ||
names.push(name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think names
and roots
are intended to be a matching set? If so, we can group them together during this iteration?
But I'm not totally certain of that based on the later line:
my_roots = roots.select{|path| /#{name}/.match(path)}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, each instance name will have one or more roots.
The problem I ran into and didn't know how to elegantly solve is this:
There are three containers inside of Instance Names
- one each for analytic services, reporting services, and the database engine. Each of those may have an instance whose name is in another container (and thus has another registry root). I needed to select out all of the roots associated with a name.
Rereading my code with some distance, there's got to be a better way to solve for this problem than what I've done in this function.
lib/puppet_x/sqlserver/features.rb
Outdated
@@ -55,6 +45,15 @@ def self.get_reg_key_val(win32_reg_key, val_name, reg_type) | |||
nil | |||
end | |||
|
|||
def self.key_exists?(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ruby trick.. this can be shortened to:
def self.key_exists?(path)
Win32::Registry::HKEY_LOCAL_MACHINE.open(path, ::Win32::Registry::KEY_READ)
true
rescue
false
end
lib/puppet_x/sqlserver/features.rb
Outdated
instances = [] | ||
# retrieve the list of subkeys - OLAP, RS, and SQL, but only if an instance exists. | ||
hklm.open("#{instance_root}", KEY_READ | KEY_WOW64_64KEY) do |registry| | ||
registry.each_key{|key, _ | subkeys.push(key)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should also be able to continue enumerating subkeys inside of this initial open
The only other thing I'll mention... at a higher level... is that we have a I should have mentioned this earlier. We typically prefer to use those over Does any of the user data specified in the registry have the potential of being Unicode? (I'm particularly wondering about instance names for Japanese users). If so, it might make sense to use Puppets registry helpers. If the reg values tend to be well-known ASCII where Unicode is unlikely, we can probably leave things as-is. |
lib/puppet_x/sqlserver/features.rb
Outdated
@@ -1,3 +1,6 @@ | |||
require 'puppet' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need to require 'puppet'
I don't think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this is a holdover from execution outside of puppet during pry testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be removed then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly. I will test! Can't believe I missed this....
lib/puppet_x/sqlserver/features.rb
Outdated
@@ -10,39 +13,25 @@ module Sqlserver | |||
class Features | |||
private | |||
|
|||
extend Puppet::Util::Windows::Registry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future reference, and since this probably isn't obvious... the only reason that this line doesn't blow up tests on non-Windows platforms is because of the line at https://github.com/puppetlabs/puppet/blob/master/lib/puppet/util/windows.rb#L8
That line creates a Puppet::Util::Windows::Registry
module when the file is included on a non-Windows platform. Otherwise, we would have to guard this line to execute only on Windows.
lib/puppet_x/sqlserver/features.rb
Outdated
begin | ||
hklm = Win32::Registry::HKEY_LOCAL_MACHINE | ||
vals = hklm.open(key_name, KEY_READ | KEY_WOW64_64KEY) do |key| | ||
vals = open(HKLM, key_name) do |key| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still might want to supply values explicitly as was done before for a few reasons:
- In case Puppet itself changes the defaults (unlikely)
- For inline documentation
You should be able to refer to them via their longer names like Puppet::Util::Windows::Registry::KEY_READ | Puppet::Util::Windows::Registry::KEY_WOW64_64KEY
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preferable to include them in their long-form in each spot or to set a single MODE
constant at the top?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only removed them because we were using the same values as the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think because you've done an extend
, the constants are in scope in their short form like KEY_READ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They were available in short form even with the include, iirc.
To clarify: Go back and add them in to each call to open()
, such that it reads like open(HKLM, 'some\path', KEY_READ | KEY64)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that sounds good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, was necessary to include in addition to extend to get the variables in short form.
lib/puppet_x/sqlserver/features.rb
Outdated
open(HKLM, "#{instance_root}\\#{instance_type}") do |instance| | ||
each_value(instance) do |short_name, _, long_name| | ||
root = "Software\\Microsoft\\Microsoft SQL Server\\#{long_name}" | ||
if discovered[short_name].nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discovered[short_name] ||= {
'name' => short_name,
'reg_root' => [],
'version' => open(HKLM, "#{root}\\MSSQLServer\\CurrentVersion") { |r| values(r)['CurrentVersion'] },
'version_friendly' => friendly_version
}
discovered[short_name]['reg_root'].push(root)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooo... forgot about that shorthand
lib/puppet_x/sqlserver/features.rb
Outdated
installed = reg_key_feat_hash.select do |subkey, feat_name| | ||
begin | ||
hklm = Win32::Registry::HKEY_LOCAL_MACHINE | ||
hklm.open("#{key_name}\\#{subkey}", KEY_READ | KEY_WOW64_64KEY) do |feat_key| | ||
open(HKLM, "#{key_name}\\#{subkey}") do |feat_key| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about the constants here...
lib/puppet_x/sqlserver/features.rb
Outdated
feats = [] | ||
sql_instance['reg_root'].each do |reg_root| | ||
feats.concat(get_instance_features(reg_root, sql_instance['name'])) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feats = sql_instance['reg_root'].map do |reg_root|
get_instance_features(reg_root, sql_instance['name'])
end
lib/puppet_x/sqlserver/features.rb
Outdated
instances = get_instance_names_by_ver(version) | ||
.map { |name| [ name, get_instance_info(version, name) ] } | ||
instances = [] | ||
Array(get_reg_instance_info(version)).each do |instance| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that get_reg_intance_info
always returns an enumerable (empty array or populated array), I think you can remove the Array
bit.
And you should be able to switch back to a .map
as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to have some unit tests on this now...but perhaps leave that for a separate PR.
@@ -1,3 +1,5 @@ | |||
require 'puppet/util/windows' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to guard this for Windows OS only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the module itself is guarded, so... no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will still attempt to load on linux :-(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g. facter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gah, well, I guess we can guard like
require 'puppet/util/windows' if Puppet::Util::Platform.windows?
?
But even so, won't all of everything explode if facter runs for SQLServer on linux?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. It's in a puppet_x ruby file so its not entirely guarded by Windows checks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's no big deal to add the guard, just concerned about a false sense of security.
Without this commit, the logic to discover instances and features relies on WMI calls and fails to discover instances unless the SQL Engine is installed; this meant that Puppet would never discover instances with only analytics services or reporting services installed, causing each puppet run to attempt to reinstall them, even though they exist. This commit refactors the logic for discovering SQL instances to use registry instead of WMI. In order to do so, we use the Puppet Util Windows Registry module and ensure that SQL instances with valid names outside the current code page can be discovered. This ensures that we are able to discover SQL instances regardless of which features they have installed. This commit also removes code supporting calls to WMI to discover SQL instances.
This commit removes logic from the features file in the acceptance tests suite which existed only to support Puppet 3. This logic is no longer needed as Puppet 3 is no longer supported. Additionally, the way the logic was written caused the tests designed for Puppet 3 to run against Puppet 5. This commit simplifies the maintenance and execution of the suite of acceptance tests.
This commit refactors the logic for discovering SQL instances to
use registry instead of WMI. In order to do so, we use the Puppet
Util Windows Registry module and ensure that SQL instances with
valid names outside the current code page can be discovered.
This commit also removes code supporting calls to WMI to discover
SQL instances.