topological_inventory-providers-common 1.0.2 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gem-push.yml +49 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +3 -0
  5. data/.rubocop_cc.yml +4 -0
  6. data/.rubocop_local.yml +2 -0
  7. data/.travis.yml +8 -0
  8. data/CHANGELOG.md +29 -1
  9. data/Gemfile +0 -3
  10. data/lib/topological_inventory/providers/common/logging.rb +8 -0
  11. data/lib/topological_inventory/providers/common/operations/endpoint_client.rb +3 -0
  12. data/lib/topological_inventory/providers/common/operations/source.rb +191 -0
  13. data/lib/topological_inventory/providers/common/operations/sources_api_client.rb +15 -6
  14. data/lib/topological_inventory/providers/common/save_inventory/saver.rb +13 -4
  15. data/lib/topological_inventory/providers/common/version.rb +1 -1
  16. data/spec/spec_helper.rb +22 -0
  17. data/spec/support/inventory_helper.rb +14 -0
  18. data/spec/support/shared/availability_check.rb +236 -0
  19. data/spec/topological_inventory/providers/common/collector_spec.rb +171 -0
  20. data/spec/topological_inventory/providers/common/collectors/inventory_collection_storage_spec.rb +44 -0
  21. data/spec/topological_inventory/providers/common/collectors/inventory_collection_wrapper_spec.rb +9 -0
  22. data/spec/topological_inventory/providers/common/collectors_pool_spec.rb +150 -0
  23. data/spec/topological_inventory/providers/common/logger_spec.rb +38 -0
  24. data/spec/topological_inventory/providers/common/operations/processor_spec.rb +102 -0
  25. data/spec/topological_inventory/providers/common/operations/source_spec.rb +5 -0
  26. data/spec/topological_inventory/providers/common/save_inventory/saver_spec.rb +65 -0
  27. data/spec/topological_inventory/providers/common_spec.rb +3 -0
  28. data/topological_inventory-providers-common.gemspec +7 -1
  29. metadata +104 -3
@@ -0,0 +1,44 @@
1
+ describe TopologicalInventory::Providers::Common::Collector::InventoryCollectionStorage do
2
+ before do
3
+ @storage = described_class.new
4
+ end
5
+
6
+ it "should add collection to data" do
7
+ @storage.add_collection(:vms)
8
+
9
+ expect(@storage.data[:vms]).not_to be_nil
10
+ end
11
+
12
+ it "should access the same collection through brackets, method name and data" do
13
+ @storage.add_collection(:vms)
14
+
15
+ vm_name = "My VM"
16
+
17
+ @storage[:vms].build(:name => vm_name)
18
+
19
+ expect(@storage.vms.data[0].name).to eq(vm_name)
20
+ expect(@storage[:vms].data[0].name).to eq(vm_name)
21
+ expect(@storage.data[:vms].data[0].name).to eq(vm_name)
22
+ end
23
+
24
+ it "should create collection automatically when api object exists" do
25
+ expect("TopologicalInventoryIngressApiClient::Vm".safe_constantize).not_to be_nil
26
+
27
+ storage = described_class.new
28
+ expect(storage.vms).to be_kind_of(TopologicalInventory::Providers::Common::Collector::InventoryCollectionWrapper)
29
+
30
+ storage = described_class.new
31
+ expect(storage[:vms]).to be_kind_of(TopologicalInventory::Providers::Common::Collector::InventoryCollectionWrapper)
32
+ end
33
+
34
+ it "should raise NameError when api object doesn't exist" do
35
+ expect("TopologicalInventoryIngressApiClient::SomethingNonexisting".safe_constantize).to be_nil
36
+
37
+ storage = described_class.new
38
+
39
+ expect { storage.add_collection(:something_nonexisting) }.to raise_error(NameError)
40
+
41
+ expect { storage.something_nonexisting.build(:name => "Vm") }.to raise_error(NameError)
42
+ end
43
+
44
+ end
@@ -0,0 +1,9 @@
1
+ describe TopologicalInventory::Providers::Common::Collector::InventoryCollectionWrapper do
2
+ it "builds only existing ingress api client's object" do
3
+ ic = described_class.new(:name => :some_undefined_class_in_api_models)
4
+ ic_existing = described_class.new(:name => :vm)
5
+
6
+ expect { ic.build({}) }.to raise_error(NameError)
7
+ expect(ic_existing.build({})).to be_kind_of(TopologicalInventoryIngressApiClient::Vm)
8
+ end
9
+ end
@@ -0,0 +1,150 @@
1
+ require "tempfile"
2
+ require "yaml"
3
+ require "json"
4
+
5
+ RSpec.describe TopologicalInventory::Providers::Common::CollectorsPool do
6
+ let(:source1) { {:source => '42b1893c-ebbd-44e9-89b1-5c29b5fe6e10', :schema => 'http', :host => 'cloud.redhat.com', :port => 80} }
7
+ let(:source2) { {:source => 'fe8bcaea-3670-42c7-bed9-71f6e0bceadd', :schema => 'https', :host => 'cloud.redhat.com', :port => 443} }
8
+ let(:source3) { {:source => '05838743-4285-404a-b4d6-294045c0d4be', :schema => 'xxx', :host => 'cloud.redhat.com', :port => 1234} }
9
+ let(:source4) { {:source => '5ed08a3c-3de4-4a90-8ce9-e0f724b2b2e6', :schema => 'xxx', :host => 'cloud.redhat.com', :port => 1234} }
10
+ let(:sources) { [source1, source2, source3] }
11
+
12
+ before do
13
+ clear_settings
14
+ end
15
+
16
+ subject { described_class.new(nil, nil, :thread_pool_size => 2) }
17
+
18
+ context "config reload" do
19
+ it "changes settings with different configs" do
20
+ settings = [{:sources => sources},
21
+ {:sources => [ source2, source4 ]}]
22
+
23
+ 2.times do |i|
24
+ config = Tempfile.new(["config#{i}", '.yml'])
25
+ begin
26
+ config.write(settings[i].to_yaml)
27
+ config.rewind
28
+
29
+ name, path = path_and_filename(config)
30
+ subject.send(:config_name=, name.split('.')[0])
31
+ allow(subject).to receive(:path_to_config).and_return(path)
32
+
33
+ subject.send(:reload_config)
34
+
35
+ expect(::Settings.sources.to_a.collect(&:to_hash)).to eq(settings[i][:sources])
36
+ ensure
37
+ config.close
38
+ config.unlink
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ context "secret reload" do
45
+ it "changes credentials with new secret" do
46
+ uuid = SecureRandom.uuid
47
+
48
+ secrets = [
49
+ {'updated_at' => Time.now.to_s, uuid => {'username' => 'admin1', 'password' => 'password1'}},
50
+ {'updated_at' => Time.now.to_s, uuid => {'username' => 'admin2', 'password' => 'password2'}},
51
+ ]
52
+
53
+ 2.times do |i|
54
+ secret = Tempfile.new(["credentials#{i}"])
55
+ begin
56
+ secret.write(secrets[i].to_json)
57
+ secret.rewind
58
+
59
+ name, path = path_and_filename(secret)
60
+
61
+ allow(subject).to receive(:path_to_secrets).and_return(path)
62
+ stub_const("#{described_class}::SECRET_FILENAME", name)
63
+
64
+ subject.send(:reload_secrets)
65
+
66
+ expect(subject.send(:secrets)).to eq(secrets[i])
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ context "add or remove collector" do
73
+ before do
74
+ ::Config.load_and_set_settings('some-value-needed.txt')
75
+ @collector = double("collector")
76
+ allow(subject).to receive(:new_collector).and_return(@collector)
77
+ end
78
+
79
+ context "without secrets check" do
80
+ before do
81
+ allow(subject).to receive(:secrets_for_source).and_return({})
82
+ end
83
+
84
+ it "adds new collectors from settings" do
85
+ allow(@collector).to receive(:collect!).and_return(nil)
86
+ expect(@collector).to receive(:collect!).exactly(sources.size).times
87
+
88
+ sources.each do |source|
89
+ stub_settings_merge(:sources => ::Settings.sources.to_a + [source])
90
+
91
+ subject.send(:queue_collectors)
92
+ end
93
+
94
+ pool = subject.send(:thread_pool)
95
+ pool.shutdown
96
+ pool.wait_for_termination
97
+ end
98
+ end
99
+
100
+ context "with secrets check" do
101
+ let(:secrets) do
102
+ { 'updated_at' => Time.now.to_s,
103
+ source1[:source] => { 'username' => 'admin1', 'password' => 'password1' },
104
+ source2[:source] => { 'username' => 'admin2', 'password' => 'password2' },
105
+ 'unknown' => { 'username' => 'admin3', 'password' => 'password3' }
106
+ }
107
+ end
108
+
109
+ before do
110
+ allow(@collector).to receive(:collect!).and_return(nil)
111
+ end
112
+
113
+ it "creates only collectors found in both secret and config" do
114
+ # 4 sources in yaml config
115
+ stub_settings_merge(:sources => sources + [source4])
116
+ # 3 sources in secret
117
+ allow(subject).to receive(:secrets).and_return(secrets)
118
+
119
+ # for each source in yaml secret is searched (4x)
120
+ expect(subject).to receive(:secrets_for_source).and_call_original.exactly(4).times
121
+ # only 2 corresponding
122
+ expect(@collector).to receive(:collect!).exactly(2).times
123
+
124
+ subject.send(:queue_collectors)
125
+
126
+ pool = subject.send(:thread_pool)
127
+ pool.shutdown
128
+ pool.wait_for_termination
129
+ end
130
+ end
131
+ end
132
+
133
+ def stub_settings_merge(hash)
134
+ if defined?(::Settings)
135
+ Settings.add_source!(hash)
136
+ Settings.reload!
137
+ end
138
+ end
139
+
140
+ def clear_settings
141
+ ::Settings.keys.dup.each { |k| ::Settings.delete_field(k) } if defined?(::Settings)
142
+ end
143
+
144
+ def path_and_filename(tempfile)
145
+ parts = tempfile.path.split('/')
146
+ name = parts[-1]
147
+ path = parts[0..-2].join('/')
148
+ [name, path]
149
+ end
150
+ end
@@ -0,0 +1,38 @@
1
+ RSpec.describe TopologicalInventory::Providers::Common::Logger do
2
+ let(:status) { :test }
3
+ let(:source) { '92844e11-17d5-4998-a33d-d886c3c7a80e' }
4
+ let(:entity_type) { 'test-entity' }
5
+ let(:refresh_state_uuid) { 'cd22ba1c-56f6-4fd4-a191-ec8eb8e993a8' }
6
+ let(:sweep_scope) { [entity_type] }
7
+ let(:total_parts) { 10 }
8
+
9
+ subject { described_class.new }
10
+
11
+ it 'receives collecting method' do
12
+ msg = "[#{status.to_s.upcase}] Collecting #{entity_type}"
13
+ msg += ", :total parts => #{total_parts}" if total_parts.present?
14
+ msg += ", :source_uid => #{source}, :refresh_state_uuid => #{refresh_state_uuid}"
15
+ expect(subject).to receive(:info).with(msg)
16
+
17
+ subject.collecting(status, source, entity_type, refresh_state_uuid, total_parts)
18
+ end
19
+
20
+ it 'receives sweeping method' do
21
+ msg = "[#{status.to_s.upcase}] Sweeping inactive records, :sweep_scope => #{sweep_scope}, :source_uid => #{source}, :refresh_state_uuid => #{refresh_state_uuid}"
22
+ expect(subject).to receive(:info).with(msg)
23
+
24
+ subject.sweeping(status, source, sweep_scope, refresh_state_uuid)
25
+ end
26
+
27
+ it 'receives collecting error method' do
28
+ begin
29
+ raise 'Test exception'
30
+ rescue => e
31
+ msg = "[ERROR] Collecting #{entity_type}, :source_uid => #{source}, :refresh_state_uuid => #{refresh_state_uuid}"
32
+ msg += ":message => #{e.message}\n#{e.backtrace.join("\n")}"
33
+ expect(subject).to receive(:error).with(msg)
34
+
35
+ subject.collecting_error(source, entity_type, refresh_state_uuid, e)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,102 @@
1
+ require "topological_inventory/providers/common/operations/processor"
2
+
3
+ RSpec.describe TopologicalInventory::Providers::Common::Operations::Processor do
4
+ let(:topology_api_client) { double }
5
+ let(:source_id) { 1 }
6
+ let(:source_ref) { 1000 }
7
+ let(:service_plan) { double("TopologicalInventoryApiClient::ServicePlan") }
8
+ let(:service_offering) { double("TopologicalInventoryApiClient::ServiceOffering") }
9
+
10
+ # Overriden in contexts
11
+ let(:payload) { {} }
12
+
13
+ before do
14
+ @processor = described_class.new(nil, nil, payload)
15
+ allow(@processor).to receive(:logger).and_return(double('null_object').as_null_object)
16
+
17
+ allow(service_plan).to receive(:service_offering_id).and_return(1)
18
+ allow(service_plan).to receive(:name).and_return(double)
19
+
20
+ allow(service_offering).to receive(:name).and_return(double)
21
+ allow(service_offering).to receive(:source_ref).and_return(source_ref)
22
+ allow(service_offering).to receive(:extra).and_return({:type => 'job_template'})
23
+ allow(service_offering).to receive(:source_id).and_return(source_id)
24
+
25
+ @endpoint_client = double
26
+ allow(@endpoint_client).to receive(:order_service)
27
+
28
+ allow(@processor).to receive(:endpoint_client).and_return(@endpoint_client)
29
+ allow(@processor).to receive(:topology_api_client).and_return(topology_api_client)
30
+ allow(topology_api_client).to receive(:update_task)
31
+ allow(topology_api_client).to receive(:show_service_plan).and_return(service_plan)
32
+ allow(topology_api_client).to receive(:show_service_offering).and_return(service_offering)
33
+ end
34
+
35
+ context "Order by ServicePlan" do
36
+ let(:payload) do
37
+ {
38
+ 'request_context' => {"x-rh-identity" => 'abcd'},
39
+ 'params' => {
40
+ 'order_params' => {
41
+ 'service_plan_id' => 1,
42
+ 'service_parameters' => { :name => "Job 1",
43
+ :param1 => "Test Topology",
44
+ :param2 => 50 },
45
+ 'provider_control_parameters' => {}
46
+ },
47
+ 'service_plan_id' => 1,
48
+ 'task_id' => 1 # in tp-inv api (Task)
49
+ }
50
+ }
51
+ end
52
+
53
+ describe "#order_service" do
54
+ it "orders job" do
55
+ allow(@processor).to receive(:poll_order_complete_thread).and_return(double)
56
+
57
+ expect(@endpoint_client).to receive(:order_service).with(service_offering, service_plan, payload['params']['order_params'])
58
+ @processor.send(:order_service, payload['params'])
59
+ end
60
+
61
+ it "updates task on error" do
62
+ err_message = "Sample error"
63
+
64
+ allow(@processor).to receive(:poll_order_complete_thread).and_return(double)
65
+ allow(@processor).to receive(:update_task).and_return(double)
66
+ allow(@endpoint_client).to receive(:order_service).and_raise(err_message)
67
+
68
+ expect(@processor).to receive(:update_task).with(payload['params']['task_id'], :state => "completed", :status => "error", :context => { :error => err_message })
69
+
70
+ @processor.send(:order_service, payload['params'])
71
+ end
72
+ end
73
+ end
74
+
75
+ context "Order by ServiceOffering" do
76
+ let(:payload) do
77
+ {
78
+ 'request_context' => {"x-rh-identity" => 'abcd'},
79
+ 'params' => {
80
+ 'order_params' => {
81
+ 'service_offering_id' => 1,
82
+ 'service_parameters' => { :name => "Job 1",
83
+ :param1 => "Test Topology",
84
+ :param2 => 50 },
85
+ 'provider_control_parameters' => {}
86
+ },
87
+ 'service_offering_id' => 1,
88
+ 'task_id' => 1 # in tp-inv api (Task)
89
+ }
90
+ }
91
+ end
92
+
93
+ describe "#order_service" do
94
+ it "orders job" do
95
+ allow(@processor).to receive(:poll_order_complete_thread).and_return(double)
96
+
97
+ expect(@endpoint_client).to receive(:order_service).with(service_offering, nil, payload['params']['order_params'])
98
+ @processor.send(:order_service, payload['params'])
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,5 @@
1
+ require "topological_inventory/providers/common/operations/source"
2
+
3
+ RSpec.describe TopologicalInventory::Providers::Common::Operations::Source do
4
+ it_behaves_like "availability_check"
5
+ end
@@ -0,0 +1,65 @@
1
+ require "topological_inventory/providers/common/save_inventory/saver"
2
+
3
+ RSpec.describe TopologicalInventory::Providers::Common::SaveInventory::Saver do
4
+ let(:client) { instance_double(TopologicalInventoryIngressApiClient::DefaultApi) }
5
+ let(:logger) { double }
6
+ let(:base_args) { {client: client, logger: logger} }
7
+
8
+ let(:small_json) { {:test => ["values"]} }
9
+ let(:big_json) { InventorySpecHelper.big_inventory(80_000, 1_000) }
10
+
11
+ describe "#save" do
12
+ subject { described_class.new(args).save(:inventory => inventory) }
13
+
14
+ context "when the data size is less than max_bytes" do
15
+ let(:args) { base_args }
16
+ let(:inventory) { small_json }
17
+
18
+ before do
19
+ allow(client).to receive(:save_inventory_with_http_info).with(small_json.to_json)
20
+ end
21
+
22
+ it "returns that it saved one chunk" do
23
+ is_expected.to eq 1
24
+ end
25
+
26
+ it "does not split the payload into batches" do
27
+ expect(client).to receive(:save_inventory_with_http_info).with(small_json.to_json).once
28
+ subject
29
+ end
30
+ end
31
+
32
+ context "when the data size is greater than specified max_bytes" do
33
+ let(:args) { base_args.merge!(:max_bytes => 19_512) }
34
+ let(:inventory) { big_json }
35
+
36
+ before do
37
+ allow(client).to receive(:save_inventory_with_http_info)
38
+ end
39
+
40
+ it "returns that it saved five chunks" do
41
+ is_expected.to eq 5
42
+ end
43
+
44
+ it "splits the payload up into chunks" do
45
+ expect(client).to receive(:save_inventory_with_http_info).exactly(5).times
46
+ subject
47
+ end
48
+ end
49
+
50
+ context "when the KAFKA_PAYLOAD_MAX_BYTES ENV var is set" do
51
+ let(:args) { base_args }
52
+ let(:inventory) { big_json }
53
+
54
+ before do
55
+ allow(ENV).to receive(:[]).with("KAFKA_PAYLOAD_MAX_BYTES").and_return("9_512")
56
+ allow(client).to receive(:save_inventory_with_http_info)
57
+ end
58
+
59
+ it "splits the payload into smaller chunks" do
60
+ expect(client).to receive(:save_inventory_with_http_info).exactly(10).times
61
+ is_expected.to eq 10
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ RSpec.describe TopologicalInventory::Providers::Common do
2
+
3
+ end
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  # Specify which files should be added to the gem when it is released.
18
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
19
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ `git ls-files -z`.split("\x0")
21
21
  end
22
22
  spec.bindir = "exe"
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -27,9 +27,15 @@ Gem::Specification.new do |spec|
27
27
  spec.add_runtime_dependency 'config', '~> 1.7', '>= 1.7.2'
28
28
  spec.add_runtime_dependency 'json', '~> 2.3'
29
29
  spec.add_runtime_dependency "manageiq-loggers", ">= 0.4.2"
30
+ spec.add_runtime_dependency "sources-api-client", "~> 3.0"
30
31
  spec.add_runtime_dependency "topological_inventory-api-client", "~> 3.0", ">= 3.0.1"
32
+ spec.add_runtime_dependency "topological_inventory-ingress_api-client", "~> 1.0"
31
33
 
32
34
  spec.add_development_dependency "bundler", "~> 2.0"
33
35
  spec.add_development_dependency "rake", ">= 12.3.3"
34
36
  spec.add_development_dependency "rspec", "~> 3.0"
37
+ spec.add_development_dependency 'rubocop', '~>0.69.0'
38
+ spec.add_development_dependency 'rubocop-performance', '~>1.3'
39
+ spec.add_development_dependency "simplecov", "~> 0.17.1"
40
+ spec.add_development_dependency 'webmock'
35
41
  end