oci-logging-analytics-kubernetes-discovery 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +40 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +36 -0
  6. data/README.md +83 -0
  7. data/Rakefile +15 -0
  8. data/bin/console +17 -0
  9. data/bin/oci-loganalytics-kubernetes-discovery +184 -0
  10. data/bin/setup +12 -0
  11. data/lib/config/oci_client_retry_config.rb +34 -0
  12. data/lib/discover/infrastructure.rb +122 -0
  13. data/lib/discover/object.rb +347 -0
  14. data/lib/dto/infra/cluster_entity_payload.rb +22 -0
  15. data/lib/dto/infra/load_balancers_entity_payload.rb +22 -0
  16. data/lib/dto/infra/node_pool_entity_payload.rb +24 -0
  17. data/lib/dto/infra/subnet_entity_payload.rb +22 -0
  18. data/lib/dto/infra/vcn_entity_payload.rb +22 -0
  19. data/lib/dto/infra_objects_payload.rb +40 -0
  20. data/lib/dto/kubernetes_objects_payload.rb +58 -0
  21. data/lib/dto/payload/log_events.rb +26 -0
  22. data/lib/dto/payload/log_events_json.rb +22 -0
  23. data/lib/dto/state.rb +19 -0
  24. data/lib/enum/auth_type_enum.rb +9 -0
  25. data/lib/enum/infrastructure_resource_discovery.rb +9 -0
  26. data/lib/enum/kubernetes_objects_enum.rb +22 -0
  27. data/lib/enum/object_client_mapping_enum.rb +21 -0
  28. data/lib/infra_resources.rb +91 -0
  29. data/lib/objects_resources.rb +174 -0
  30. data/lib/oci_loganalytics_resources_discovery.rb +293 -0
  31. data/lib/util/kube_client.rb +141 -0
  32. data/lib/util/kubectl_ops.rb +229 -0
  33. data/lib/util/log_analytics.rb +154 -0
  34. data/lib/util/logging.rb +96 -0
  35. data/lib/util/oci_clients.rb +228 -0
  36. data/lib/util/state_manager.rb +61 -0
  37. data/lib/util/string_utils.rb +16 -0
  38. data/lib/version.rb +6 -0
  39. data/oci-logging-analytics-kubernetes-discovery.gemspec +45 -0
  40. metadata +324 -0
@@ -0,0 +1,141 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require 'resolv'
5
+ require_relative '../util/logging'
6
+
7
+ module Util
8
+ module KubeClient
9
+ extend Util::Logging
10
+
11
+ module_function
12
+
13
+ attr_accessor :current_kube_context, :core_client, :apps_client, :batch_client,
14
+ :discover_v1_client, :kube_clients, :kube_config_hash,
15
+ :kubernetes_url, :api_endpoint, :api_version,
16
+ :client_cert, :client_key, :ca_file, :verify_ssl, :bearer_token_file, :secret_dir
17
+
18
+ def create_clients(kube_config_hash)
19
+ begin
20
+ initialize_config(kube_config_hash)
21
+ @core_client = create_client_by_object_type('', @api_version)
22
+ @apps_client = create_client_by_object_type('/apis/apps', @api_version)
23
+ @batch_client = create_client_by_object_type('/apis/batch', @api_version)
24
+ @discover_v1_client = create_client_by_object_type('/apis/discovery.k8s.io', @api_version)
25
+ rescue StandardError => e
26
+ logger.error("Error while creating kube clients. Error - #{e}")
27
+ raise e
28
+ end
29
+
30
+ set_clients(@core_client, @apps_client, @batch_client, @discover_v1_client)
31
+ end
32
+
33
+ def initialize_config(kube_config_hash)
34
+ @kube_config_hash = kube_config_hash
35
+
36
+ unless @kube_config_hash[:kube_config_location].nil?
37
+ begin
38
+ config = Kubeclient::Config.read @kube_config_hash[:kube_config_location]
39
+ @current_kube_context = config.context
40
+ rescue StandardError => e
41
+ logger.error("Error while initializing config params for kube client - #{e}")
42
+ raise e
43
+ end
44
+ end
45
+
46
+ @kubernetes_url = @kube_config_hash[:kubernetes_url]
47
+ @api_endpoint = @kube_config_hash[:api_endpoint]
48
+ @api_version = @kube_config_hash[:api_version]
49
+ @client_cert = @kube_config_hash[:client_cert]
50
+ @client_key = @kube_config_hash[:client_key]
51
+ @ca_file = @kube_config_hash[:ca_file]
52
+ @verify_ssl = @kube_config_hash[:verify_ssl]
53
+ @bearer_token_file = @kube_config_hash[:bearer_token_file]
54
+ @secret_dir = @kube_config_hash[:secret_dir]
55
+ end
56
+
57
+ def create_client_by_object_type(api_endpoint, api_version)
58
+ if !@current_kube_context.nil?
59
+ create_kube_client_config(api_endpoint, api_version)
60
+ else
61
+ create_kube_client_default(api_endpoint, api_version)
62
+ end
63
+ end
64
+
65
+ def create_kube_client_config(api_endpoint, api_version)
66
+ logger.debug("Endpoint: #{@current_kube_context.api_endpoint + api_endpoint} and api_version: #{api_version}")
67
+ begin
68
+ # TODO: Test with SSL options.
69
+ client = Kubeclient::Client.new(
70
+ @current_kube_context.api_endpoint + api_endpoint,
71
+ api_version,
72
+ ssl_options: @current_kube_context.ssl_options,
73
+ auth_options: @current_kube_context.auth_options
74
+ )
75
+ rescue StandardError => e
76
+ logger.error("Error while creating kube client based on config - #{e}")
77
+ raise e
78
+ end
79
+ client
80
+ end
81
+
82
+ def create_kube_client_default(api_endpoint, api_version)
83
+ # Reference: https://github.com/splunk/fluent-plugin-kubernetes-objects/blob/develop/lib/fluent/plugin/in_kubernetes_objects.rb#L139
84
+ kubernetes_url = nil
85
+ if @kubernetes_url.nil?
86
+ # Use Kubernetes default service account if we're in a pod.
87
+ env_host = ENV['KUBERNETES_SERVICE_HOST']
88
+ env_host = "[#{env_host}]" if env_host =~ Resolv::IPv6::Regex
89
+ env_port = ENV['KUBERNETES_SERVICE_PORT']
90
+ if env_host && env_port
91
+ # kubernetes_url = "https://#{env_host}:#{env_port}/#{@api_endpoint.delete_prefix('/')}#{api_endpoint}"
92
+ kubernetes_url = "https://#{env_host}:#{env_port}#{api_endpoint}"
93
+ end
94
+ else
95
+ kubernetes_url = @kubernetes_url + api_endpoint
96
+ end
97
+
98
+ # Use SSL certificate and bearer token from Kubernetes service account.
99
+ if Dir.exist?(@secret_dir)
100
+ secret_ca_file = File.join(@secret_dir, 'ca.crt')
101
+ secret_token_file = File.join(@secret_dir, 'token')
102
+
103
+ @ca_file = secret_ca_file if @ca_file.nil? && File.exist?(secret_ca_file)
104
+
105
+ @bearer_token_file = secret_token_file if @bearer_token_file.nil? && File.exist?(secret_token_file)
106
+ end
107
+
108
+ ssl_options = {
109
+ client_cert: @client_cert && OpenSSL::X509::Certificate.new(File.read(@client_cert)),
110
+ client_key: @client_key && OpenSSL::PKey::RSA.new(File.read(@client_key)),
111
+ ca_file: @ca_file,
112
+ verify_ssl: @verify_ssl ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
113
+ }
114
+
115
+ auth_options = {}
116
+ auth_options[:bearer_token] = File.read(@bearer_token_file) if @bearer_token_file
117
+ logger.debug("kubernetes_url : #{kubernetes_url} and api_version: #{api_version}")
118
+ Kubeclient::Client.new(
119
+ kubernetes_url, @api_version,
120
+ ssl_options: ssl_options,
121
+ auth_options: auth_options
122
+ )
123
+ rescue StandardError => e
124
+ logger.error("Error while creating default kube client - #{e}")
125
+ raise e
126
+ end
127
+
128
+ def set_clients(core_client, apps_client, batch_client, discover_v1_client)
129
+ @kube_clients = {
130
+ core_client: core_client,
131
+ apps_client: apps_client,
132
+ batch_client: batch_client,
133
+ discover_v1_client: discover_v1_client
134
+ }
135
+ end
136
+
137
+ def get_clients
138
+ @kube_clients
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,229 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require_relative '../util/logging'
5
+
6
+ # Enum
7
+ require_relative '../enum/kubernetes_objects_enum'
8
+
9
+ module Util
10
+ module KubectlOps
11
+ extend Util::Logging
12
+
13
+ module_function
14
+
15
+ attr_accessor :chunk_limit
16
+
17
+ CONFIG_MAP_RESOURCE_NAME = 'config_map'.freeze
18
+
19
+ # Status codes for which retry is attempted (To represent 5xx, use 5)
20
+ RETRYABLE_STATUS_CODE = [429, 5].freeze
21
+ MAX_ATTEMPTS = 5
22
+ INITIAL_DELAY = 1 # seconds
23
+ EXPONENT_CONSTANT = 2 # 'e' in exponential function (e^x) where x is number of retries
24
+
25
+ def set_chunk_limit(num)
26
+ @chunk_limit = num
27
+ end
28
+
29
+ def should_retry?(error, retry_count, object_type)
30
+ return false if error.error_code.nil? # for handling timeouts
31
+
32
+ if (RETRYABLE_STATUS_CODE.include? error.error_code) || RETRYABLE_STATUS_CODE.include?(error.error_code / 100)
33
+ if retry_count < MAX_ATTEMPTS
34
+ # If e = 2, and initial delay is 10, intervals will be (1, 2, 4, 8, 16)
35
+ sleep_interval = EXPONENT_CONSTANT.pow(retry_count) * INITIAL_DELAY
36
+ logger.warn("Error while making API Server call - '#{object_type}'. Retrying in #{sleep_interval} seconds. HTTP status code: #{error.error_code}")
37
+ sleep(sleep_interval)
38
+ return true
39
+ end
40
+ end
41
+ false
42
+ end
43
+
44
+ def kube_apiserver_request(method:, object_type:, client: nil, object_name: nil, namespace: nil, object: nil, patch: nil, opts: {})
45
+ response = nil
46
+ retry_count = 0
47
+ begin
48
+ case method
49
+ when :get_objects
50
+ if client.nil?
51
+ client = Util::KubeClient.get_clients[Enum::ObjectClientMapingEnum.const_get(object_type.upcase.to_s)]
52
+ end
53
+ logger.debug("Fetching '#{object_type}' details from all namespaces")
54
+ method_verb = 'GET'
55
+ opts.merge!({ as: :parsed_symbolized })
56
+ response = {}
57
+ loop do
58
+ entities = client.public_send("get_#{object_type}", opts)
59
+ continue = entities[:metadata][:continue]
60
+ if response.empty?
61
+ # when all objects are fetched via single API request
62
+ if continue.nil?
63
+ response = entities
64
+ break
65
+ end
66
+ response = entities.clone
67
+ else
68
+ response[:items] += entities[:items]
69
+ end
70
+ break if continue.nil?
71
+
72
+ logger.debug("remainingItemCount - #{entities[:metadata][:remainingItemCount]}")
73
+ opts.merge!({ continue: continue })
74
+ end
75
+ if response.nil? || response.empty?
76
+ logger.debug("Empty or null response recieved from Kubernetes API endpoint '#{client.rest_client.url}'.")
77
+ return { parsed_symbolized: [], raw: '' }
78
+ else
79
+ return { parsed_symbolized: response, raw: JSON.dump(response) }
80
+ end
81
+ when :get_object
82
+ method_verb = 'GET'
83
+ logger.debug("Fetching '#{object_type}/#{object_name}' details")
84
+ opts.merge!(as: :parsed_symbolized)
85
+ response = client.public_send("get_#{object_type}", object_name, namespace, opts)
86
+ when :create_object
87
+ method_verb = 'CREATE'
88
+ logger.debug("Creating '#{object_type}' - #{object}")
89
+ response = client.public_send("create_#{object_type}", object)
90
+ when :patch_object
91
+ method_verb = 'PATCH'
92
+ logger.debug("Patching '#{object_type}/#{object_name}' - #{patch}")
93
+ response = client.public_send("patch_#{object_type}", object_name, patch, namespace)
94
+ else
95
+ raise SyntaxError, "Unsupported method - #{method} passed."
96
+ end
97
+ rescue KubeException => e
98
+ if should_retry?(e, retry_count, object_type)
99
+ retry_count += 1
100
+ retry
101
+ end
102
+
103
+ if e.error_code.nil?
104
+ logger.error("Unexpected error occurred while calling #{method_verb} '#{object_type}'. Error details: #{e}")
105
+ raise e
106
+ elsif e.error_code == 302
107
+ logger.error("Max re-direct attempts while calling #{method_verb} '#{object_type}'. Error details: #{e}")
108
+ raise StandardError, "Max re-direct attempts while calling #{method_verb} '#{object_type}'."
109
+ elsif e.error_code == 401
110
+ # In such cases, client should be re-created in order to refresh token
111
+ logger.error("Not authorized to #{method_verb} '#{object_type}'. Error details: #{e}")
112
+ raise StandardError, "Not authorized to #{method_verb} '#{object_type}'."
113
+ elsif e.error_code == 403
114
+ logger.error("Forbidden to #{method_verb} '#{object_type}'. Error details: #{e}")
115
+ raise StandardError, "Forbidden to #{method_verb} '#{object_type}'."
116
+ elsif e.error_code == 404
117
+ logger.debug("Resource '#{object_type}' not found. Error details: #{e}")
118
+ raise e
119
+ elsif e.error_code == 410
120
+ # ref - https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks
121
+ logger.error("Too many '#{object_type}' objects. Consider increasing CHUNK limit.")
122
+ raise StandardError, "Too many '#{object_type}' objects. Consider increasing CHUNK limit."
123
+ elsif e.error_code == 429
124
+ logger.error("Encountered throttling while calling #{method_verb} '#{object_type}'. Error details: #{e}")
125
+ raise StandardError, "Encountered throttling while #{method_verb} '#{object_type}'."
126
+ elsif (e.error_code / 100) == 5
127
+ logger.error("Unexpected error occurred while #{method_verb} '#{object_type}'. Error details: #{e}")
128
+ raise StandardError, "Unexpected error occurred while #{method_verb} '#{object_type}'."
129
+ else
130
+ logger.error("Error '#{e}' encountered while #{method_verb} '#{object_type}' from Kubernetes API endpoint '#{client.rest_client.url}'")
131
+ raise StandardError, "Error '#{e}' encountered while #{method_verb} '#{object_type}'."
132
+ end
133
+ rescue NoMethodError => e
134
+ logger.error('Unexpected Error - Method not found.')
135
+ raise e
136
+ rescue StandardError => e
137
+ logger.error("Error while #{method_verb} '#{object_type}'. Error: #{e}")
138
+
139
+ if should_retry?(e, retry_count, object_type)
140
+ retry_count += 1
141
+ retry
142
+ end
143
+
144
+ error_msg = "Maximum retry attempts ('#{MAX_ATTEMPTS}') exceeded. Aborting fetch for '#{object_type}'."
145
+ logger.error(error_msg)
146
+ raise error error_msg
147
+ end
148
+ response
149
+ end
150
+
151
+ def get_objects(object_type, client = nil, options = {})
152
+ options.merge!({ limit: @chunk_limit, continue: nil })
153
+ kube_apiserver_request(method: :get_objects,
154
+ object_type: object_type,
155
+ client: client,
156
+ opts: options)
157
+ end
158
+
159
+ def get_object(object_type, object_name, namespace, client, options = {})
160
+ kube_apiserver_request(method: :get_object,
161
+ object_type: object_type,
162
+ client: client,
163
+ object_name: object_name,
164
+ namespace: namespace,
165
+ opts: options)
166
+ end
167
+
168
+ def create_object(object_type, kubeclient_resource_object, client)
169
+ kube_apiserver_request(method: :create_object,
170
+ object_type: object_type,
171
+ client: client,
172
+ object: kubeclient_resource_object)
173
+ end
174
+
175
+ def patch_object(object_type, object_name, patch_hash, namespace, client)
176
+ kube_apiserver_request(method: :patch_object,
177
+ object_type: object_type,
178
+ client: client,
179
+ object_name: object_name,
180
+ patch: patch_hash,
181
+ namespace: namespace)
182
+ end
183
+
184
+ def get_configmap(cm_name, namespace)
185
+ object_type = CONFIG_MAP_RESOURCE_NAME
186
+ client = Util::KubeClient.get_clients[:core_client]
187
+ begin
188
+ response = get_object(object_type, cm_name, namespace, client)
189
+ rescue StandardError => e
190
+ return nil if e.error_code == 404
191
+
192
+ raise e
193
+ end
194
+ response
195
+ end
196
+
197
+ def create_configmap(cm_name, namespace, data_hash)
198
+ # Create resource object for config map
199
+ cm = Kubeclient::Resource.new
200
+ cm.metadata = {}
201
+ cm.metadata.name = cm_name
202
+ cm.metadata.namespace = namespace
203
+ cm.data = {}
204
+ cm.data = data_hash
205
+
206
+ object_type = CONFIG_MAP_RESOURCE_NAME
207
+ client = Util::KubeClient.get_clients[:core_client]
208
+
209
+ begin
210
+ create_object(object_type, cm, client)
211
+ rescue StandardError => e
212
+ logger.error("Unable to create '#{object_type}/#{cm_name}'")
213
+ raise e
214
+ end
215
+ end
216
+
217
+ def patch_configmap(cm_name, namespace, data_hash)
218
+ patch_hash = { data: data_hash }
219
+ object_type = CONFIG_MAP_RESOURCE_NAME
220
+ client = Util::KubeClient.get_clients[:core_client]
221
+ begin
222
+ patch_object(object_type, cm_name, patch_hash, namespace, client)
223
+ rescue StandardError => e
224
+ logger.error("Unable to patch '#{object_type}/#{cm_name}'")
225
+ raise e
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,154 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require_relative '../util/logging'
5
+ require_relative '../util/oci_clients'
6
+
7
+ # 'httplog' required *after* HTTP gem of choice
8
+ # require 'httplog'
9
+
10
+ module Util
11
+ module LogAnalytics
12
+ extend Util::Logging
13
+
14
+ # Client information for raw request signing
15
+ CLIENT_INFO = 'Oracle-ORD/Raw Request'.freeze
16
+ CLIENT_AGENT = 'Oracle-ORD/Raw Request (net/http)'.freeze
17
+
18
+ LOG_ANALYTICS_ENDPOINT_TEMPLATE = 'https://loganalytics.{region}.oci.{secondLevelDomain}'.freeze
19
+
20
+ # Status codes for which retry is attempted (To represent 5xx, use 5)
21
+ RETRYABLE_STATUS_CODE = [408, 429, 5].freeze
22
+ MAX_ATTEMPTS = 5
23
+ INITIAL_DELAY = 1 # seconds
24
+ EXPONENT_CONSTANT = 2 # 'e' in exponential function (e^x) where x is number of retries
25
+
26
+ module_function
27
+
28
+ def upload_discovery_data(namespace, payload, opts)
29
+ logger.info("Uploading payload to 'Log Analytics' with Discovery API")
30
+ begin
31
+ client = Util::OCIClients.get_clients[:la_client]
32
+ response = client.upload_discovery_data(namespace_name = namespace,
33
+ upload_discovery_data_details = payload,
34
+ opts)
35
+ rescue StandardError => e
36
+ logger.error("Error while uploading payload to 'Log Analytics': #{e}")
37
+ raise StandardError, "Unable to upload data to 'Log Analytics' client."
38
+ end
39
+ response
40
+ end
41
+
42
+ def upload_discovery_data_raw_request(namespace, payload, opts, auth_config)
43
+ logger.info('Uploading payload to \'Log Analytics\' with Discovery API (raw request)')
44
+
45
+ # Prepare config and signer
46
+ signer = nil
47
+ operation_signing_strategy = :exclude_body
48
+
49
+ case auth_config[:auth_type]
50
+ when Enum::AuthTypeEnum::CONFIG
51
+ config = auth_config[:oci_config]
52
+ signer = OCI::Signer.config_file_auth_builder(config)
53
+ region = config.region
54
+ when Enum::AuthTypeEnum::INSTANCE_PRINCIPAL
55
+ signer = auth_config[:instance_principals_signer]
56
+ region = signer.region
57
+ else
58
+ logger.warn("Unknown auth_type provided for Discovery API (raw request): #{auth_object[:auth_type]}")
59
+ raise StandardError 'Unknown auth_type for Discovery API (raw request)'
60
+ end
61
+
62
+ # Construct URL protocol, subdomain, domain and port
63
+
64
+ endpoint = if !auth_config[:endpoint].nil?
65
+ auth_config[:endpoint] + '/20200601'
66
+ else
67
+ OCI::Regions.get_service_endpoint_for_template(region, LOG_ANALYTICS_ENDPOINT_TEMPLATE) + '/20200601'
68
+ end
69
+
70
+ # Construct URL path
71
+ path = '/namespaces/{namespaceName}/actions/uploadDiscoveryData'.sub('{namespaceName}', namespace.to_s)
72
+
73
+ # Construct Query Params
74
+ query_params = '?discoveryDataType={discoveryDataType}&payloadType={payloadType}'
75
+ .sub('{discoveryDataType}', opts[:discovery_data_type])
76
+ .sub('{payloadType}', opts[:payload_type])
77
+
78
+ # Prepare URI
79
+ url = endpoint + path + query_params
80
+ uri = URI(url)
81
+
82
+ # Set user agent and OPC parameters for request
83
+ http_method = :post
84
+
85
+ request = Net::HTTP::Post.new(uri)
86
+ request['opc-client-info'] = CLIENT_INFO
87
+ request['opc-request-id'] = SecureRandom.uuid.delete!('-').upcase
88
+ request['User-Agent'] = CLIENT_AGENT
89
+
90
+ # Header Params
91
+ header_params = {}
92
+ header_params = opts[:additional_headers].clone if opts[:additional_headers].is_a?(Hash)
93
+ header_params[:accept] = 'application/json'
94
+ header_params[:expect] = '100-continue'
95
+ header_params[:'content-type'] = 'application/octet-stream'
96
+ header_params[:'opc-meta-properties'] = opts[:opc_meta_properties] if opts[:opc_meta_properties]
97
+ header_params[:'opc-retry-token'] = OCI::Retry.generate_opc_retry_token
98
+
99
+ # Set the payload
100
+ if payload.respond_to?(:read) && payload.respond_to?(:write)
101
+ request.body_stream = payload
102
+ else
103
+ request.body = payload
104
+ end
105
+
106
+ # Sign the request
107
+ signer.sign(http_method, url, header_params, payload, operation_signing_strategy)
108
+
109
+ # Add header parameters
110
+ header_params.each do |key, value|
111
+ request[key.to_s] = value
112
+ end
113
+
114
+ # Set HTTP parameters
115
+ http = Net::HTTP.new(uri.hostname, uri.port)
116
+ http.use_ssl = (uri.scheme == 'https')
117
+ http.open_timeout = 10
118
+ http.read_timeout = 180 # 180 seconds = 3 minutes
119
+ http.continue_timeout = 3 # expect 100 continue timeout
120
+
121
+ # Fetch response
122
+ retry_count = 0
123
+ response = nil
124
+
125
+ begin
126
+ http.start do
127
+ http.request(request) do |resp|
128
+ response = resp
129
+ end
130
+ end
131
+ rescue StandardError => e
132
+ code_from_response = !response.nil? ? response.code.to_i : 0
133
+
134
+ if (RETRYABLE_STATUS_CODE.include? code_from_response) || RETRYABLE_STATUS_CODE.include?(code_from_response / 100) || e.instance_of?(Net::ReadTimeout)
135
+ if retry_count < MAX_ATTEMPTS
136
+ # If e = 2, and initial delay is 10, intervals will be (1, 2, 4, 8, 16)
137
+ sleep_interval = EXPONENT_CONSTANT.pow(retry_count) * INITIAL_DELAY
138
+ logger.error("Error while uploading data to discovery API (raw request). Retrying in #{sleep_interval} seconds. HTTP status code: #{code_from_response}")
139
+ sleep(sleep_interval)
140
+ retry_count += 1
141
+ retry
142
+ end
143
+ end
144
+ raise OCI::Errors::NetworkError.new(e.message, code_from_response, request_made: request, response_received: response)
145
+ end
146
+
147
+ if response.is_a?(Net::HTTPSuccess)
148
+ return OCI::Response.new(response.code.to_i, response.header, nil)
149
+ end # Success safe check
150
+
151
+ raise "Response from Discovery API (raw request) suggests failure. Response code: + #{response.code}"
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,96 @@
1
+ ## Copyright (c) 2024 Oracle and/or its affiliates.
2
+ ## The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+ require 'logger'
5
+ require 'yajl'
6
+
7
+ module Util
8
+ module Logging
9
+ module_function
10
+
11
+ TRACE = Logger::DEBUG - 1
12
+ SEV_LABEL = {
13
+ -1 => 'TRACE',
14
+ 0 => 'DEBUG',
15
+ 1 => 'INFO',
16
+ 2 => 'WARN',
17
+ 3 => 'ERROR',
18
+ 4 => 'FATAL'
19
+ }.freeze
20
+
21
+ def logger
22
+ log_level = LogConfigs.log_level
23
+ log_format = LogConfigs.log_format
24
+ datetime_format = '%Y-%m-%d %H:%M:%S %z'
25
+
26
+ case log_format
27
+ when 'text'
28
+ @logger = OCILogger.new(STDOUT,
29
+ level: log_level,
30
+ datetime_format: datetime_format,
31
+ formatter: proc do |type, time, _level, msg|
32
+ r = "#{time} [#{type.downcase}] #{self} "
33
+ r << "#{msg}\n"
34
+ r
35
+ end)
36
+ when 'json'
37
+ @logger = OCILogger.new(STDOUT,
38
+ level: log_level,
39
+ datetime_format: datetime_format,
40
+ formatter: proc do |type, time, _level, msg|
41
+ r = {
42
+ 'time' => time,
43
+ 'level' => type.downcase,
44
+ 'class' => self,
45
+ 'message' => msg
46
+ }
47
+ # JSON.pretty_generate(r, :indent => "\t") #
48
+ Yajl.dump(r).gsub('}', "}\n")
49
+ # r.to_json.gsub('}', "}\n")
50
+ end)
51
+ else
52
+ puts 'Logger type only text or json allowed'
53
+ end
54
+ end
55
+
56
+ def logger=(logger)
57
+ @logger = logger
58
+ end
59
+
60
+ class OCILogger < Logger
61
+ def format_severity(severity)
62
+ SEV_LABEL[severity] || 'ANY'
63
+ end
64
+
65
+ def trace(message)
66
+ add(TRACE, message)
67
+ end
68
+ end
69
+
70
+ class LogConfigs
71
+ @@log_format
72
+ @@log_level
73
+
74
+ def initialize(l_format, l_level)
75
+ @@log_format = l_format
76
+ @@log_level = l_level
77
+ end
78
+
79
+ def self.log_format
80
+ @@log_format
81
+ end
82
+
83
+ def log_format
84
+ @@log_format
85
+ end
86
+
87
+ def self.log_level
88
+ @@log_level
89
+ end
90
+
91
+ def log_level
92
+ @@log_level
93
+ end
94
+ end
95
+ end
96
+ end