From 262a37a916a32f3d2a4590f88dbbc219de67daa7 Mon Sep 17 00:00:00 2001 From: Reid Vandewiele Date: Mon, 19 Jul 2021 16:19:26 -0700 Subject: [PATCH 1/5] Add get_cluster_roles task This task can introspect an existing cluster (when run on a primary node) and return information about how the cluster is currently configured. This will aid in running plans that need to modify the cluster, by reducing the amount of information the user must supply. The task returns data about the cluster in both primary/replica form, as well as A/B form, since different forms are useful in different scenarios. --- tasks/get_peadm_config.json | 5 ++ tasks/get_peadm_config.rb | 134 ++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tasks/get_peadm_config.json create mode 100755 tasks/get_peadm_config.rb diff --git a/tasks/get_peadm_config.json b/tasks/get_peadm_config.json new file mode 100644 index 00000000..405cae85 --- /dev/null +++ b/tasks/get_peadm_config.json @@ -0,0 +1,5 @@ +{ + "description": "Return a hash of currently configured PEAdm cluster node roles", + "parameters": { }, + "input_method": "stdin" +} diff --git a/tasks/get_peadm_config.rb b/tasks/get_peadm_config.rb new file mode 100755 index 00000000..c5d4f9d0 --- /dev/null +++ b/tasks/get_peadm_config.rb @@ -0,0 +1,134 @@ +#!/opt/puppetlabs/puppet/bin/ruby +# frozen_string_literal: true + +require 'json' +require 'uri' +require 'net/http' +require 'puppet' + +# GetPEAdmConfig task class +class GetPEAdmConfig + def initialize(params); end + + # Returns a GetPEAdmConfig::NodeGroups object created from the /groups object + # returned by the classifier + def groups + return @groups unless @groups.nil? + + console_services = https(4433) + response = console_services.get('/classifier-api/v1/groups') + + groups = JSON.parse(response.body) + @groups = NodeGroup.new(groups) + end + + # Returns a list of compiler certnames, based on a PuppetDB query + def compilers + query = 'inventory[certname] { trusted.extensions.pp_auth_role = "pe_compiler" }' + pdb_query(query).map { |n| n['certname'] } + end + + def server(letter) + query = 'inventory[certname] { '\ + ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9812" = "puppet/server" and ' \ + ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9813" = "' + letter + '"}' + + server = pdb_query(query).map { |n| n['certname'] } + raise "More than one #{letter} server found!" unless server.size <= 1 + server.first + end + + def postgresql_server(letter) + query = 'inventory[certname] { '\ + ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9812" = "puppet/puppetdb-database" and ' \ + ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9813" = "' + letter + '"}' + + server = pdb_query(query).map { |n| n['certname'] } + raise "More than one #{letter} postgresql server found!" unless server.size <= 1 + server.first + end + + def config + server_conf = { + 'primary_host' => groups.pinned('PE Master'), + 'replica_host' => groups.pinned('PE HA Replica'), + 'server_a_host' => server('A'), + 'server_b_host' => server('B'), + } + + primary_letter = server_conf['primary_host'] == server_conf['server_a_host'] ? 'A' : 'B' + replica_letter = primary_letter == 'A' ? 'B' : 'A' + + remaining_conf = { + 'primary_postgresql_host' => postgresql_server(primary_letter), + 'replica_postgresql_host' => postgresql_server(replica_letter), + 'postgresql_a_host' => groups.dig('PE Primary A', 'config_data', 'puppet_enterprise::profile::puppetdb', 'database_host'), + 'postgresql_b_host' => groups.dig('PE Primary B', 'config_data', 'puppet_enterprise::profile::puppetdb', 'database_host'), + 'compilers' => compilers, + 'compiler_pool_address' => groups.dig('PE Master', 'config_data', 'pe_repo', 'compile_master_pool_address'), + 'internal_compiler_a_pool_address' => groups.dig('PE Compiler Group A', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], + 'internal_compiler_b_pool_address' => groups.dig('PE Compiler Group B', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], + } + + server_conf.merge(remaining_conf) + end + + def execute! + puts config.to_json + end + + def https(port) + https = Net::HTTP.new('localhost', port) + https.use_ssl = true + https.cert = OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert])) + https.key = OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey])) + https.verify_mode = OpenSSL::SSL::VERIFY_NONE + https + end + + def pdb_query(query) + pdb = https(8081) + pdb_request = Net::HTTP::Get.new('/pdb/query/v4') + pdb_request.set_form_data({ 'query' => query }) + JSON.parse(pdb.request(pdb_request).body) + end + + # Utility class to aid in retrieving useful information from the node group + # data + class NodeGroup + attr_reader :data + + def initialize(data) + @data = data + end + + # Aids in digging into node groups by name, rather than UUID + def dig(name, *args) + group = @data.find { |obj| obj['name'] == name } + return group if args.empty? + group.dig(*args) + end + + # Return the node pinned to the named group + # If there is more than one node, error + def pinned(name) + rule = dig(name, 'rule') + return nil if rule.nil? + raise "#{name} rule incompatible with pinning" unless rule.first == 'or' + pinned = rule.drop(1) + .select { |r| r[0] == '=' && r[1] == 'name' } + .map { |r| r[2] } + raise "#{name} contains more than one server!" unless pinned.size <= 1 + pinned.first + end + end +end + +# Run the task unless an environment flag has been set, signaling not to. The +# environment flag is used to disable auto-execution and enable Ruby unit +# testing of this task. +unless ENV['RSPEC_UNIT_TEST_MODE'] + Puppet.initialize_settings + task = GetPEAdmConfig.new(JSON.parse(STDIN.read)) + task.execute! +end From 0c42295bd4979d678180b63ea2d2e336fd36095a Mon Sep 17 00:00:00 2001 From: Reid Vandewiele Date: Mon, 19 Jul 2021 22:20:27 -0700 Subject: [PATCH 2/5] Clean up get_peadm_config task Improve the readability of the task, and the task description. --- tasks/get_peadm_config.json | 2 +- tasks/get_peadm_config.rb | 103 ++++++++++++++++++------------------ 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/tasks/get_peadm_config.json b/tasks/get_peadm_config.json index 405cae85..03ad4199 100644 --- a/tasks/get_peadm_config.json +++ b/tasks/get_peadm_config.json @@ -1,5 +1,5 @@ { - "description": "Return a hash of currently configured PEAdm cluster node roles", + "description": "Run on a PE primary node to return the currently configured PEAdm parameters", "parameters": { }, "input_method": "stdin" } diff --git a/tasks/get_peadm_config.rb b/tasks/get_peadm_config.rb index c5d4f9d0..3649e160 100755 --- a/tasks/get_peadm_config.rb +++ b/tasks/get_peadm_config.rb @@ -10,16 +10,56 @@ class GetPEAdmConfig def initialize(params); end + def execute! + puts config.to_json + end + + def config + # Compute values + primary = groups.pinned('PE Master') + replica = groups.pinned('PE HA Replica') + server_a = server('puppet/server', 'A') + server_b = server('puppet/server', 'B') + primary_letter = primary.eql?(server_a) ? 'A' : 'B' + replica_letter = primary_letter.eql?('A') ? 'B' : 'A' + postgresql = { + 'A' => server('puppet/puppetdb-database', 'A'), + 'B' => server('puppet/puppetdb-database', 'B'), + } + + # Build and return the task output + { + 'params' => { + 'primary_host' => primary, + 'replica_host' => replica, + 'primary_postgresql_host' => postgresql[primary_letter], + 'replica_postgresql_host' => postgresql[replica_letter], + 'compilers' => compilers, + 'compiler_pool_address' => groups.dig('PE Master', 'config_data', 'pe_repo', 'compile_master_pool_address'), + 'internal_compiler_a_pool_address' => groups.dig('PE Compiler Group A', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], + 'internal_compiler_b_pool_address' => groups.dig('PE Compiler Group B', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], + }, + 'role-letter' => { + 'server' => { + 'A' => server_a, + 'B' => server_b, + }, + 'postgresql' => { + 'A' => postgresql['A'], + 'B' => postgresql['B'], + }, + }, + } + end + # Returns a GetPEAdmConfig::NodeGroups object created from the /groups object # returned by the classifier def groups - return @groups unless @groups.nil? - - console_services = https(4433) - response = console_services.get('/classifier-api/v1/groups') - - groups = JSON.parse(response.body) - @groups = NodeGroup.new(groups) + @groups ||= begin + net = https(4433) + res = net.get('/classifier-api/v1/groups') + NodeGroup.new(JSON.parse(res.body)) + end end # Returns a list of compiler certnames, based on a PuppetDB query @@ -28,60 +68,21 @@ def compilers pdb_query(query).map { |n| n['certname'] } end - def server(letter) - query = 'inventory[certname] { '\ - ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9812" = "puppet/server" and ' \ - ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9813" = "' + letter + '"}' - - server = pdb_query(query).map { |n| n['certname'] } - raise "More than one #{letter} server found!" unless server.size <= 1 - server.first - end - - def postgresql_server(letter) + def server(role, letter) query = 'inventory[certname] { '\ - ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9812" = "puppet/puppetdb-database" and ' \ + ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9812" = "' + role + '" and ' \ ' trusted.extensions."1.3.6.1.4.1.34380.1.1.9813" = "' + letter + '"}' server = pdb_query(query).map { |n| n['certname'] } - raise "More than one #{letter} postgresql server found!" unless server.size <= 1 + raise "More than one #{letter} #{role} server found!" unless server.size <= 1 server.first end - def config - server_conf = { - 'primary_host' => groups.pinned('PE Master'), - 'replica_host' => groups.pinned('PE HA Replica'), - 'server_a_host' => server('A'), - 'server_b_host' => server('B'), - } - - primary_letter = server_conf['primary_host'] == server_conf['server_a_host'] ? 'A' : 'B' - replica_letter = primary_letter == 'A' ? 'B' : 'A' - - remaining_conf = { - 'primary_postgresql_host' => postgresql_server(primary_letter), - 'replica_postgresql_host' => postgresql_server(replica_letter), - 'postgresql_a_host' => groups.dig('PE Primary A', 'config_data', 'puppet_enterprise::profile::puppetdb', 'database_host'), - 'postgresql_b_host' => groups.dig('PE Primary B', 'config_data', 'puppet_enterprise::profile::puppetdb', 'database_host'), - 'compilers' => compilers, - 'compiler_pool_address' => groups.dig('PE Master', 'config_data', 'pe_repo', 'compile_master_pool_address'), - 'internal_compiler_a_pool_address' => groups.dig('PE Compiler Group A', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], - 'internal_compiler_b_pool_address' => groups.dig('PE Compiler Group B', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], - } - - server_conf.merge(remaining_conf) - end - - def execute! - puts config.to_json - end - def https(port) https = Net::HTTP.new('localhost', port) https.use_ssl = true - https.cert = OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert])) - https.key = OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey])) + https.cert = @cert ||= OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert])) + https.key = @key ||= OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey])) https.verify_mode = OpenSSL::SSL::VERIFY_NONE https end From b15639797fb7da3a9c7a979fd61e6e8b00f97aec Mon Sep 17 00:00:00 2001 From: Reid Vandewiele Date: Tue, 20 Jul 2021 11:59:49 -0700 Subject: [PATCH 3/5] Make get_peadm_config handle nil values better In the event a group doesn't exist, or a key doesn't return a value. Don't error, just return nil for those values. --- tasks/get_peadm_config.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tasks/get_peadm_config.rb b/tasks/get_peadm_config.rb index 3649e160..ef8508a0 100755 --- a/tasks/get_peadm_config.rb +++ b/tasks/get_peadm_config.rb @@ -36,8 +36,8 @@ def config 'replica_postgresql_host' => postgresql[replica_letter], 'compilers' => compilers, 'compiler_pool_address' => groups.dig('PE Master', 'config_data', 'pe_repo', 'compile_master_pool_address'), - 'internal_compiler_a_pool_address' => groups.dig('PE Compiler Group A', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], - 'internal_compiler_b_pool_address' => groups.dig('PE Compiler Group B', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host')[1], + 'internal_compiler_a_pool_address' => groups.dig('PE Compiler Group A', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host', 1), + 'internal_compiler_b_pool_address' => groups.dig('PE Compiler Group B', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host', 1), }, 'role-letter' => { 'server' => { @@ -106,8 +106,13 @@ def initialize(data) # Aids in digging into node groups by name, rather than UUID def dig(name, *args) group = @data.find { |obj| obj['name'] == name } - return group if args.empty? - group.dig(*args) + if group.nil? + nil + elsif args.empty? + group + else + group.dig(*args) + end end # Return the node pinned to the named group From 07b3126ba239e1547419bb77d90692f261636886 Mon Sep 17 00:00:00 2001 From: Reid Vandewiele Date: Tue, 20 Jul 2021 13:27:17 -0700 Subject: [PATCH 4/5] Fix lb bug in peadm::setup::node_manager class When this was refactored, the wrong defaults were given to the compiler load balancer address variables. They should default to the A and B *servers*, not the A and B postgresql hosts. --- manifests/setup/node_manager.pp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifests/setup/node_manager.pp b/manifests/setup/node_manager.pp index 686a87d4..3751ffae 100644 --- a/manifests/setup/node_manager.pp +++ b/manifests/setup/node_manager.pp @@ -31,8 +31,8 @@ Optional[String[1]] $postgresql_b_host = $server_b_host, Optional[String[1]] $compiler_pool_address = undef, - Optional[String[1]] $internal_compiler_a_pool_address = $postgresql_a_host, - Optional[String[1]] $internal_compiler_b_pool_address = $postgresql_b_host, + Optional[String[1]] $internal_compiler_a_pool_address = $server_a_host, + Optional[String[1]] $internal_compiler_b_pool_address = $server_b_host, ) { # "Not-configured" placeholder string. This will be used in places where we From 7a967b5eccfe88af6f14b8f8454e98ca54be9ce4 Mon Sep 17 00:00:00 2001 From: Reid Vandewiele Date: Tue, 20 Jul 2021 13:55:22 -0700 Subject: [PATCH 5/5] Add role-letter for compilers to get_peadm_config Realized this could be a generally useful way of understanding all letter assignments, if we returned compilers as well. --- tasks/get_peadm_config.rb | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tasks/get_peadm_config.rb b/tasks/get_peadm_config.rb index ef8508a0..7f1b5b3a 100755 --- a/tasks/get_peadm_config.rb +++ b/tasks/get_peadm_config.rb @@ -34,7 +34,7 @@ def config 'replica_host' => replica, 'primary_postgresql_host' => postgresql[primary_letter], 'replica_postgresql_host' => postgresql[replica_letter], - 'compilers' => compilers, + 'compilers' => compilers.map { |c| c['certname'] }, 'compiler_pool_address' => groups.dig('PE Master', 'config_data', 'pe_repo', 'compile_master_pool_address'), 'internal_compiler_a_pool_address' => groups.dig('PE Compiler Group A', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host', 1), 'internal_compiler_b_pool_address' => groups.dig('PE Compiler Group B', 'classes', 'puppet_enterprise::profile::master', 'puppetdb_host', 1), @@ -48,6 +48,10 @@ def config 'A' => postgresql['A'], 'B' => postgresql['B'], }, + 'compilers' => { + 'A' => compilers.select { |c| c['letter'] == 'A' }.map { |c| c['certname'] }, + 'B' => compilers.select { |c| c['letter'] == 'B' }.map { |c| c['certname'] }, + } }, } end @@ -56,16 +60,22 @@ def config # returned by the classifier def groups @groups ||= begin - net = https(4433) - res = net.get('/classifier-api/v1/groups') - NodeGroup.new(JSON.parse(res.body)) - end + net = https(4433) + res = net.get('/classifier-api/v1/groups') + NodeGroup.new(JSON.parse(res.body)) + end end - # Returns a list of compiler certnames, based on a PuppetDB query + # Returns a list of compiler certnames and letters, based on a PuppetDB query def compilers - query = 'inventory[certname] { trusted.extensions.pp_auth_role = "pe_compiler" }' - pdb_query(query).map { |n| n['certname'] } + @compilers ||= begin + pdb_query('inventory[certname,trusted.extensions] { trusted.extensions.pp_auth_role = "pe_compiler" }').map do |c| + { + 'certname' => c['certname'], + 'letter' => c.dig('trusted.extensions', '1.3.6.1.4.1.34380.1.1.9813'), + } + end + end end def server(role, letter)