-
Notifications
You must be signed in to change notification settings - Fork 3
CHEF-3961: Implement caching ability at application-level for the API calls #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ahasunos
wants to merge
32
commits into
main
Choose a base branch
from
ss/application-level-caching
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
bb1f885
FEAT: CacheManager class for basic caching mechanism
ahasunos 2ab02f3
CHORE: Include the class under RestfulClient module & lint fixes
ahasunos e3e13db
SPECS: Write tests for CacheManager
ahasunos 0a51cfa
SPECS: Extend few more test for the cache_manager spec
ahasunos ee839a7
FIX: Fix CI test failing due to missing require; works on local
ahasunos fcdd1b9
CACHE: Implement caching for /client API
ahasunos bf59e15
REFACTOR: Extract logic for calculating expiry time from cache-manage…
ahasunos 0cf2a92
FIX: Fix broken test due to caching of the calls during tests
ahasunos f234ec2
LOGS: Enable logging in cache manager & include some messages,
ahasunos bd721ab
CHORE: Disable cache during testing to avoid unnecessary caches on th…
ahasunos 6691ff6
SPEC: Test caching feature for the /client api
ahasunos 5ce7efd
CHORE: Fix lint offense
ahasunos 5544832
ENHANCE: Improve key construction for cache
ahasunos 3596640
ENHANCE: Improve methods for fetching ttl from response
ahasunos 19f6253
CHORE: Remove outdated comments
ahasunos 7ca1b46
FIX: Handle timezone correctly while calculating ttl
ahasunos b8123e8
ENHANCE: Add ability for application to set default ttl
ahasunos 3615f5a
EXTEND: Expose an endpoint from base to clear cache for given endpoints
ahasunos 8b3d245
USAGE: Implement method for client API to help clear cache
ahasunos 52b90fa
SPEC: Test for clearing cache ability
ahasunos 83ec2f1
ENHANCE: Add application-level caching for listLicenses API
ahasunos 9314df5
ENHANCE: Handle status code and multiple license server urls
ahasunos 6f06392
REFACTOR: Transfer caching related responsibilities from base to cach…
ahasunos b2d1c6c
REFACTOR: Breakdown base class into ApiGateway & FaradayConnHandler c…
ahasunos 87762d7
CHORE: Minor clean-up of comments and code
ahasunos d0b49c0
REFACTOR: Introduce a common method to handle different http method c…
ahasunos 38f2946
ENHANCE: Improve cache deletion of api response
ahasunos f342780
CHORE: Move restfulclient module tests in a folder
ahasunos 81e0fd2
SPEC: Introduce test for api gateway class
ahasunos 87eb586
SPEC: Fix incomplete test for apigateway
ahasunos 3a45115
SPEC: Introduce spec for faraday conn handler
ahasunos 6696491
CHORE: Undo v1 spec to its original location
ahasunos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
components/ruby/lib/chef-licensing/restful_client/api_gateway.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| require_relative "cache_manager" | ||
| require_relative "../config" | ||
| require_relative "../exceptions/restful_client_error" | ||
| require_relative "../exceptions/restful_client_connection_error" | ||
| require_relative "faraday_conn_handler" | ||
|
|
||
| module ChefLicensing | ||
| module RestfulClient | ||
| class ApiGateway | ||
| REQUEST_LIMIT = 5 | ||
|
|
||
| def initialize(opts = {}) | ||
| @cache_manager = opts[:cache_manager] || ChefLicensing::RestfulClient::CacheManager.new | ||
| @logger = ChefLicensing::Config.logger | ||
| @faraday_conn_handler = ChefLicensing::RestfulClient::FaradayConnHandler.new | ||
| end | ||
|
|
||
| # No application-level caching | ||
| def fetch_from_server(endpoint, params = {}) | ||
| logger.debug "Fetching data from server for #{endpoint}" | ||
| response = invoke_api(endpoint, :get, nil, params) | ||
| response.body | ||
| end | ||
|
|
||
| def post_to_server(endpoint, payload = {}, headers = {}) | ||
| response = invoke_api(endpoint, :post, payload, nil, headers) | ||
| raise RestfulClientError, format_error_from(response) unless response.success? | ||
|
|
||
| response.body | ||
| end | ||
|
|
||
| # Try to fetch data from application-cache first and if it fails, fallback to server | ||
| def fetch_from_cache_or_server(endpoint, params = {}) | ||
| cached_response = fetch_cached_response(endpoint, params) | ||
| if cached_response.nil? | ||
| logger.debug "Cache not found for #{endpoint}" | ||
| logger.debug "Fetching data from server for #{endpoint}" | ||
| response = invoke_api(endpoint, :get, nil, params) | ||
| cache_key = @cache_manager.construct_cache_key(endpoint, params) | ||
| logger.debug "Storing data in cache for #{endpoint} against key #{cache_key}" | ||
| @cache_manager.store(cache_key, response.body) if response.success? && response&.body&.status_code == 200 | ||
| response.body | ||
| else | ||
| logger.debug "Cache found for #{endpoint}" | ||
| cached_response | ||
| end | ||
| end | ||
|
|
||
| # Try to fetch data from the server first and if it fails, fallback to application-cache | ||
| def fetch_from_server_or_cache(endpoint, params = {}) | ||
| logger.debug "Fetching data from server for #{endpoint}" | ||
| response = invoke_api(endpoint, :get, nil, params) | ||
| cache_key = @cache_manager.construct_cache_key(endpoint, params) | ||
| logger.debug "Storing cache for #{endpoint} with key #{cache_key}" | ||
| # TODO: We don't receive cache info in the response body for listLicenses endpoint | ||
| # so temporarily we are hardcoding the ttl to 46108 seconds (12 hours); check with the server team | ||
| @cache_manager.store(cache_key, response.body, 46108) if response&.body&.status_code == 200 || response&.body&.status_code == 404 | ||
| response.body | ||
| rescue RestfulClientConnectionError => e | ||
| logger.debug "Restful Client Connection Error #{e.message}" | ||
| logger.debug "Falling back to cache for #{endpoint}" | ||
| cached_response = fetch_cached_response(endpoint, params) | ||
| raise_restful_client_conn_error if cached_response.nil? | ||
| cached_response | ||
| end | ||
|
|
||
| def clear_cached_response(endpoint, params = {}) | ||
| urls = ChefLicensing::Config.license_server_url.split(",").first(REQUEST_LIMIT) | ||
| urls.each do |url| | ||
| cache_key = @cache_manager.construct_cache_key(endpoint, params, url) | ||
| if @cache_manager.is_cached?(cache_key) | ||
| logger.debug "Clearing cache for #{endpoint} with key #{cache_key}" | ||
| @cache_manager.delete(cache_key) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| attr_reader :cache_manager, :logger | ||
|
|
||
| def fetch_cached_response(endpoint, params = {}) | ||
| urls = ChefLicensing::Config.license_server_url.split(",").first(REQUEST_LIMIT) | ||
| response = nil | ||
| urls.each do |url| | ||
| cache_key = @cache_manager.construct_cache_key(endpoint, params, url) | ||
| logger.debug "Checking cache for #{cache_key}" | ||
| if @cache_manager.is_cached?(cache_key) | ||
| logger.debug "Fetching data from cache for #{cache_key}" | ||
| ChefLicensing::Config.license_server_url = url | ||
| response = @cache_manager.fetch(cache_key) | ||
| break | ||
| end | ||
| end | ||
| response | ||
| end | ||
|
|
||
| def invoke_api(endpoint, http_method, payload = nil, params = {}, headers = {}) | ||
| response = nil | ||
| urls = ChefLicensing::Config.license_server_url.split(",") | ||
|
|
||
| logger.warn "Only the first #{REQUEST_LIMIT} urls will be tried." if urls.size > REQUEST_LIMIT | ||
| urls.first(REQUEST_LIMIT).each do |url| | ||
| url = url.strip | ||
|
|
||
| logger.debug "Trying to connect to #{url}" | ||
|
|
||
| response = @faraday_conn_handler.handle_connection(http_method, url) do |connection| | ||
| connection.send(http_method, endpoint) do |request| | ||
| request.body = payload.to_json if payload | ||
| request.params = params if params | ||
| request.headers = headers if headers | ||
| end | ||
| end | ||
|
|
||
| # At this point, we have a successful connection | ||
| # Update the value of license server url in config | ||
| ChefLicensing::Config.license_server_url = url | ||
| logger.debug "Connection succeeded to #{url}" | ||
| break response | ||
| rescue RestfulClientConnectionError | ||
| logger.warn "Connection failed to #{url}" | ||
| rescue URI::InvalidURIError | ||
| logger.warn "Invalid URI #{url}" | ||
| end | ||
|
|
||
| raise_restful_client_conn_error if response.nil? | ||
| response | ||
| end | ||
|
|
||
| def format_error_from(response) | ||
| error_details = response.body&.data&.error | ||
| return response.reason_phrase unless error_details | ||
|
|
||
| error_details | ||
| end | ||
|
|
||
| def raise_restful_client_conn_error | ||
| urls = ChefLicensing::Config.license_server_url.split(",").first(REQUEST_LIMIT) | ||
| error_message = <<~EOM | ||
| Unable to connect to the licensing server. #{ChefLicensing::Config.chef_product_name} requires server communication to operate. | ||
| The following URL(s) were tried:\n#{ | ||
| urls.each_with_index.map do |url, index| | ||
| "#{index + 1}. #{url}" | ||
| end.join("\n") | ||
| } | ||
| EOM | ||
|
|
||
| raise RestfulClientConnectionError, error_message | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.