topological_inventory-providers-common 1.0.2 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
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