Split EdgeCache::Bust into Fastly + Nginx-specific classes (#10505) [deploy]

* Split EdgeCache::Bust into Fastly + Nginx-specific classes

* Update EdgeCache::Bust service specs

* Move Nginx TODO comment into Nginx-specific file
This commit is contained in:
Vaidehi Joshi 2020-10-01 10:07:51 -07:00 committed by GitHub
parent e1b3844dfa
commit 6a697173bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 83 deletions

View file

@ -1,36 +1,27 @@
module EdgeCache
class Bust
PROVIDERS = %w[fastly nginx].freeze
def self.call(*args)
new(*args).call
end
def initialize(path)
# TODO: (Vaidehi Joshi) - Right now, we are checking that nginx is
# available on every purge request/call to this bust service. If we are going
# to bust multiple paths, we should be able to check that nginx is
# available just once, and persist it on the class with @provider_available?.
# Then, we could allow for an array of @paths = [] to be passed in,
# and on single bust instance could bust multiple paths in order.
@path = path
@provider = determine_provider
@response = nil
end
def call
return unless PROVIDERS.include?(provider)
return unless provider
bust_method = "bust_#{provider}_cache"
if respond_to?(bust_method, true)
@response = __send__(bust_method)
provider_class = "#{self.class}::#{provider.capitalize}".constantize
if provider_class.respond_to?(:call)
@response = provider_class.call(path)
else
# We theoretically should never hit this unless someone adds a provider
# but doesn't add the implementation for it into the #call method.
@response = nil
Rails.logger.warn("EdgeCache::Bust was called with an invalid provider: #{provider}")
DatadogStatsClient.increment("edgecache_bust.invalid_provider", tags: ["provider:#{provider}"])
Rails.logger.warn("#{provider_class} cannot be used without a #call implementation!")
DatadogStatsClient.increment("edgecache_bust.invalid_provider_class",
tags: ["provider_class:#{provider_class}"])
end
self
@ -43,40 +34,11 @@ module EdgeCache
def determine_provider
if fastly_enabled?
"fastly"
elsif nginx_enabled? && nginx_available?
elsif nginx_enabled?
"nginx"
end
end
def bust_fastly_cache
# TODO: (Alex Smith) - It would be "nice to have" the ability to use the
# Fastly gem here instead of custom API calls.
# @forem/systems Fastly-enabled forems don't need "flexible" domains.
HTTParty.post(
"https://api.fastly.com/purge/https://#{URL.domain}#{path}",
headers: {
"Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"]
},
)
HTTParty.post(
"https://api.fastly.com/purge/https://#{URL.domain}#{path}?i=i",
headers: {
"Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"]
},
)
end
def bust_nginx_cache
uri = URI.parse("#{openresty_path}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
response = http.request Net::HTTP::NginxPurge.new(uri.request_uri)
raise StandardError, "NginxPurge request failed: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
response.body
end
def fastly_enabled?
ApplicationConfig["FASTLY_API_KEY"].present? && ApplicationConfig["FASTLY_SERVICE_ID"].present?
end
@ -84,32 +46,5 @@ module EdgeCache
def nginx_enabled?
ApplicationConfig["OPENRESTY_PROTOCOL"].present? && ApplicationConfig["OPENRESTY_DOMAIN"].present?
end
def openresty_path
"#{ApplicationConfig['OPENRESTY_PROTOCOL']}#{ApplicationConfig['OPENRESTY_DOMAIN']}"
end
def nginx_available?
uri = URI.parse(openresty_path)
http = Net::HTTP.new(uri.host, uri.port)
response = http.get(uri.request_uri)
return true if response.is_a?(Net::HTTPSuccess)
rescue StandardError
# If we can't connect to Openresty, alert ourselves that
# it is unavailable and return false.
Rails.logger.error("Could not connect to Openresty via #{openresty_path}!")
DatadogStatsClient.increment("edgecache_bust.service_unavailable", tags: ["path:#{openresty_path}"])
false
end
end
end
# Creates our own purge method for an HTTP request,
# which is used by Nginx to bust a cache.
# See Net::HTTPGenericRequest for attributes/methods.
class Net::HTTP::NginxPurge < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
METHOD = "PURGE".freeze
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end

View file

@ -0,0 +1,24 @@
module EdgeCache
class Bust
class Fastly
def self.call(path)
# TODO: (Alex Smith) - It would be "nice to have" the ability to use the
# Fastly gem here instead of custom API calls.
# @forem/systems Fastly-enabled forems don't need "flexible" domains.
HTTParty.post(
"https://api.fastly.com/purge/https://#{URL.domain}#{path}",
headers: {
"Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"]
},
)
HTTParty.post(
"https://api.fastly.com/purge/https://#{URL.domain}#{path}?i=i",
headers: {
"Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"]
},
)
end
end
end
end

View file

@ -0,0 +1,50 @@
module EdgeCache
class Bust
class Nginx
def self.call(path)
return unless nginx_available?
uri = URI.parse("#{openresty_path}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
response = http.request Net::HTTP::Purge.new(uri.request_uri)
raise StandardError, "Nginx Purge request failed: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
response.body
end
def self.openresty_path
"#{ApplicationConfig['OPENRESTY_PROTOCOL']}#{ApplicationConfig['OPENRESTY_DOMAIN']}"
end
def self.nginx_available?
# TODO: (Vaidehi Joshi) - Right now, we are checking that nginx is
# available on every purge request/call to this bust service. If we are going
# to bust multiple paths, we should be able to check that nginx is
# available just once, and persist it on the class with @provider_available?.
# Then, we could allow for an array of @paths = [] to be passed in,
# and on single bust instance could bust multiple paths in order.
uri = URI.parse(openresty_path)
http = Net::HTTP.new(uri.host, uri.port)
response = http.get(uri.request_uri)
return true if response.is_a?(Net::HTTPSuccess)
rescue StandardError
# If we can't connect to Openresty, alert ourselves that
# it is unavailable and return false.
Rails.logger.error("Could not connect to Openresty via #{openresty_path}!")
DatadogStatsClient.increment("edgecache_bust.service_unavailable", tags: ["path:#{openresty_path}"])
false
end
end
end
end
# Creates our own purge method for an HTTP request,
# which is used by Nginx to bust a cache.
# See Net::HTTPGenericRequest for attributes/methods.
class Net::HTTP::Purge < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
METHOD = "PURGE".freeze
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end

View file

@ -5,6 +5,8 @@ RSpec.describe EdgeCache::Bust, type: :service do
let(:path) { "/#{user.username}" }
describe "#bust_fastly_cache" do
let(:fastly_provider_class) { EdgeCache::Bust::Fastly }
context "when fastly is not configured" do
before do
stub_fastly
@ -14,11 +16,11 @@ RSpec.describe EdgeCache::Bust, type: :service do
let(:cache_bust_service) { described_class.new(path) }
it "does not bust a fastly cache" do
allow(cache_bust_service).to receive(:bust_fastly_cache)
allow(fastly_provider_class).to receive(:call)
cache_bust_service.call
expect(cache_bust_service.provider).to be(nil)
expect(cache_bust_service).not_to have_received(:bust_fastly_cache)
expect(fastly_provider_class).not_to have_received(:call)
end
end
@ -31,15 +33,15 @@ RSpec.describe EdgeCache::Bust, type: :service do
let(:cache_bust_service) { described_class.new(path) }
it "can bust a fastly cache" do
allow(cache_bust_service).to receive(:bust_fastly_cache)
allow(fastly_provider_class).to receive(:call)
cache_bust_service.call
expect(cache_bust_service.provider).to eq("fastly")
expect(cache_bust_service).to have_received(:bust_fastly_cache)
expect(fastly_provider_class).to have_received(:call)
end
it "returns cache bust response" do
allow(cache_bust_service).to receive(:bust_fastly_cache).and_return("success")
allow(fastly_provider_class).to receive(:call).and_return("success")
cache_bust_service.call
expect(cache_bust_service.response).to eq("success")
@ -48,6 +50,8 @@ RSpec.describe EdgeCache::Bust, type: :service do
end
describe "#bust_nginx_cache" do
let(:nginx_provider_class) { EdgeCache::Bust::Nginx }
before do
# Explicitly stub Fastly since we check if Fastly has
# been configured before we try to use Nginx.
@ -62,11 +66,11 @@ RSpec.describe EdgeCache::Bust, type: :service do
let(:cache_bust_service) { described_class.new(path) }
it "does not bust an nginx cache" do
allow(cache_bust_service).to receive(:bust_nginx_cache)
allow(nginx_provider_class).to receive(:call)
cache_bust_service.call
expect(cache_bust_service.provider).to eq(nil)
expect(cache_bust_service).not_to have_received(:bust_nginx_cache)
expect(nginx_provider_class).not_to have_received(:call)
end
end
@ -78,11 +82,11 @@ RSpec.describe EdgeCache::Bust, type: :service do
let(:cache_bust_service) { described_class.new(path) }
it "can bust an nginx cache" do
allow(cache_bust_service).to receive(:bust_nginx_cache)
allow(nginx_provider_class).to receive(:call)
cache_bust_service.call
expect(cache_bust_service.provider).to eq("nginx")
expect(cache_bust_service).to have_received(:bust_nginx_cache)
expect(nginx_provider_class).to have_received(:call)
end
end
end