From cf3dedb4c84d67a7d6a6954e91f7576be916b5bd Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Tue, 16 Aug 2022 14:23:31 -0400 Subject: [PATCH 1/4] add rest api manager --- lib/optimizely/odp/zaius_rest_api_manager.rb | 69 ++++++++++++++ spec/odp/zaius_rest_api_manager_spec.rb | 99 ++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 lib/optimizely/odp/zaius_rest_api_manager.rb create mode 100644 spec/odp/zaius_rest_api_manager_spec.rb diff --git a/lib/optimizely/odp/zaius_rest_api_manager.rb b/lib/optimizely/odp/zaius_rest_api_manager.rb new file mode 100644 index 00000000..40179b8a --- /dev/null +++ b/lib/optimizely/odp/zaius_rest_api_manager.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# +# Copyright 2022, Optimizely and contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'json' + +module Optimizely + class ZaiusRestApiManager + # Interface that handles sending ODP events. + + def initialize(logger: nil, proxy_config: nil) + @logger = logger || NoOpLogger.new + @proxy_config = proxy_config + end + + # Send events to the ODP Events API. + # + # @param api_key - public api key + # @param api_host - domain url of the host + # @param events - array of events to send + + def send_odp_events(api_key, api_host, events) + should_retry = false + url = "#{api_host}/v3/events" + + headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s} + + begin + response = Helpers::HttpUtils.make_request( + url, :post, events.to_json, headers, Optimizely::Helpers::Constants::ODP_REST_API_CONFIG[:REQUEST_TIMEOUT], @proxy_config + ) + rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET + log_failure('network error') + should_retry = true + return should_retry + rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e + log_failure(e) + return should_retry + end + + status = response.code.to_i + if status >= 400 + log_failure(!response.body.empty? ? response.body : "#{status}: #{response.message}") + should_retry = true if status >= 500 + end + should_retry + end + + private + + def log_failure(message, level = Logger::ERROR) + @logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], message)) + end + end +end diff --git a/spec/odp/zaius_rest_api_manager_spec.rb b/spec/odp/zaius_rest_api_manager_spec.rb new file mode 100644 index 00000000..5422c7c2 --- /dev/null +++ b/spec/odp/zaius_rest_api_manager_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +# +# Copyright 2022, Optimizely and contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +require 'spec_helper' +require 'optimizely/odp/zaius_rest_api_manager' + +describe Optimizely::ZaiusRestApiManager do + let(:user_key) { 'vuid' } + let(:user_value) { 'test-user-value' } + let(:api_key) { 'test-api-key' } + let(:api_host) { 'https://test-host.com' } + let(:spy_logger) { spy('logger') } + let(:events) do + [ + {type: 't1', action: 'a1', identifiers: {'id-key-1': 'id-value-1'}, data: {'key-1': 'value1'}}, + {type: 't2', action: 'a2', identifiers: {'id-key-2': 'id-value-2'}, data: {'key-2': 'value2'}} + ] + end + let(:failure_response_data) do + { + title: 'Bad Request', status: 400, timestamp: '2022-07-01T20:44:00.945Z', + detail: { + invalids: [{event: 0, message: "missing 'type' field"}] + } + }.to_json + end + + describe '.fetch_segments' do + it 'should send odp events successfully and return false' do + stub_request(:post, "#{api_host}/v3/events") + .with( + headers: {'content-type': 'application/json', 'x-api-key': api_key}, + body: events.to_json + ).to_return(status: 200) + + api_manager = Optimizely::ZaiusRestApiManager.new + expect(spy_logger).not_to receive(:log) + should_retry = api_manager.send_odp_events(api_key, api_host, events) + + expect(should_retry).to be false + end + + it 'should return true on network error' do + allow(Optimizely::Helpers::HttpUtils).to receive(:make_request).and_raise(SocketError) + api_manager = Optimizely::ZaiusRestApiManager.new(logger: spy_logger) + expect(spy_logger).to receive(:log).with(Logger::ERROR, 'ODP event send failed (network error).') + + should_retry = api_manager.send_odp_events(api_key, api_host, events) + + expect(should_retry).to be true + end + + it 'should return false with 400 error' do + stub_request(:post, "#{api_host}/v3/events") + .with( + body: events.to_json + ).to_return(status: [400, 'Bad Request'], body: failure_response_data) + + api_manager = Optimizely::ZaiusRestApiManager.new(logger: spy_logger) + expect(spy_logger).to receive(:log).with( + Logger::ERROR, 'ODP event send failed ({"title":"Bad Request","status":400,' \ + '"timestamp":"2022-07-01T20:44:00.945Z","detail":{"invalids":' \ + '[{"event":0,"message":"missing \'type\' field"}]}}).' + ) + + should_retry = api_manager.send_odp_events(api_key, api_host, events) + + expect(should_retry).to be false + end + + it 'should return true with 500 error' do + stub_request(:post, "#{api_host}/v3/events") + .with( + body: events.to_json + ).to_return(status: [500, 'Internal Server Error']) + + api_manager = Optimizely::ZaiusRestApiManager.new(logger: spy_logger) + expect(spy_logger).to receive(:log).with(Logger::ERROR, 'ODP event send failed (500: Internal Server Error).') + + should_retry = api_manager.send_odp_events(api_key, api_host, events) + + expect(should_retry).to be true + end + end +end From 2cc82a92b1a19af67ca38c6d4cbfae485f668d38 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 17 Aug 2022 15:11:48 -0400 Subject: [PATCH 2/4] update constants --- lib/optimizely/helpers/constants.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimizely/helpers/constants.rb b/lib/optimizely/helpers/constants.rb index 84c57dea..bce4b8a6 100644 --- a/lib/optimizely/helpers/constants.rb +++ b/lib/optimizely/helpers/constants.rb @@ -384,7 +384,7 @@ module Constants ODP_LOGS = { FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).', - ODP_EVENT_FAILED: 'ODP event send failed (invalid url).', + ODP_EVENT_FAILED: 'ODP event send failed (%s).', ODP_NOT_ENABLED: 'ODP is not enabled.' }.freeze From d8d9684dc6332bd3d10ebee1bb356880d911f00b Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Thu, 18 Aug 2022 10:13:24 -0400 Subject: [PATCH 3/4] remove unnecessary conditional --- lib/optimizely/odp/zaius_rest_api_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimizely/odp/zaius_rest_api_manager.rb b/lib/optimizely/odp/zaius_rest_api_manager.rb index 40179b8a..1f5c9c07 100644 --- a/lib/optimizely/odp/zaius_rest_api_manager.rb +++ b/lib/optimizely/odp/zaius_rest_api_manager.rb @@ -55,7 +55,7 @@ def send_odp_events(api_key, api_host, events) status = response.code.to_i if status >= 400 log_failure(!response.body.empty? ? response.body : "#{status}: #{response.message}") - should_retry = true if status >= 500 + should_retry = status >= 500 end should_retry end From 63ca9a1dd79bd25d5071321fa59e06f4d2ef5aa6 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Thu, 18 Aug 2022 14:22:31 -0400 Subject: [PATCH 4/4] fix exceptions --- lib/optimizely/odp/zaius_rest_api_manager.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/optimizely/odp/zaius_rest_api_manager.rb b/lib/optimizely/odp/zaius_rest_api_manager.rb index 1f5c9c07..8e8d2fc4 100644 --- a/lib/optimizely/odp/zaius_rest_api_manager.rb +++ b/lib/optimizely/odp/zaius_rest_api_manager.rb @@ -43,11 +43,11 @@ def send_odp_events(api_key, api_host, events) response = Helpers::HttpUtils.make_request( url, :post, events.to_json, headers, Optimizely::Helpers::Constants::ODP_REST_API_CONFIG[:REQUEST_TIMEOUT], @proxy_config ) - rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET + rescue SocketError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EFAULT, Errno::ENETUNREACH, Errno::ENETDOWN, Errno::ECONNREFUSED log_failure('network error') should_retry = true return should_retry - rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e + rescue StandardError => e log_failure(e) return should_retry end