Skip to content

Commit 55c1ada

Browse files
author
Suszyński Krzysztof
committed
Added caching, both in memory via LRUCache and in FileSystem persistent storage
1 parent bb688d0 commit 55c1ada

File tree

8 files changed

+157
-12
lines changed

8 files changed

+157
-12
lines changed

lib/puppet_forge_server.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ module Models
5656
end
5757

5858
module Utils
59+
autoload :CacheProvider, 'puppet_forge_server/utils/cache_provider'
60+
autoload :FilteringInspecter, 'puppet_forge_server/utils/filtering_inspecter'
5961
autoload :Archiver, 'puppet_forge_server/utils/archiver'
6062
autoload :OptionParser, 'puppet_forge_server/utils/option_parser'
6163
autoload :Buffer, 'puppet_forge_server/utils/buffer'

lib/puppet_forge_server/backends/proxy.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ def initialize(url, cache_dir, http_client, file_path)
3535

3636
def get_file_buffer(relative_path)
3737
file_name = relative_path.split('/').last
38-
File.join(@cache_dir, file_name[0].downcase, file_name)
38+
target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
3939
path = Dir["#{@cache_dir}/**/#{file_name}"].first
4040
unless File.exist?("#{path}")
4141
buffer = download("#{@file_path.chomp('/')}/#{relative_path}")
42-
File.open(File.join(@cache_dir, file_name[0].downcase, file_name), 'wb') do |file|
42+
File.open(target_file, 'wb') do |file|
4343
file.write(buffer.read)
4444
end
45-
path = File.join(@cache_dir, file_name[0].downcase, file_name)
45+
path = target_file
46+
else
47+
@log.info("Filesystem cache HIT for path: #{relative_path}")
4648
end
4749
File.open(path, 'rb')
4850
rescue => e
@@ -59,6 +61,26 @@ def upload(file_data)
5961
protected
6062
attr_reader :log
6163

64+
def get_non_mutable(relative_url)
65+
file_name = relative_url.split('/').last
66+
target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
67+
path = Dir["#{@cache_dir}/**/#{file_name}"].first
68+
unless File.exist?("#{path}")
69+
buffer = get(relative_url)
70+
File.open(target_file, 'wb') do |file|
71+
file.write(buffer)
72+
end
73+
path = target_file
74+
else
75+
@log.info("Filesystem cache HIT for url: #{relative_url}")
76+
end
77+
File.binread(path)
78+
rescue => e
79+
@log.error("#{self.class.name} failed getting non-mutable url '#{relative_url}'")
80+
@log.error("Error: #{e}")
81+
return nil
82+
end
83+
6284
def get(relative_url)
6385
@http_client.get(url(relative_url))
6486
end

lib/puppet_forge_server/backends/proxy_v3.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ def initialize(url, cache_dir, http_client = PuppetForgeServer::Http::HttpClient
3030
def get_metadata(author, name, options = {})
3131
query ="#{author}-#{name}"
3232
begin
33-
releases = options[:version] ? [JSON.parse(get("/v3/releases/#{query}-#{options[:version]}"))] : get_all_result_pages("/v3/releases?module=#{query}")
33+
releases = get_releases(query, options)
3434
get_modules(releases)
3535
rescue => e
36-
@log.debug("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
37-
@log.debug("Error: #{e}")
36+
@log.error("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
37+
@log.error("Error: #{e}")
3838
return nil
3939
end
4040
end
@@ -44,13 +44,26 @@ def query_metadata(query, options = {})
4444
releases = get_all_result_pages("/v3/modules?query=#{query}").map {|element| element['current_release']}
4545
get_modules(releases)
4646
rescue => e
47-
@log.debug("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
48-
@log.debug("Error: #{e}")
47+
@log.error("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
48+
@log.error("Error: #{e}")
4949
return nil
5050
end
5151
end
5252

5353
private
54+
55+
def get_releases(query, options = {})
56+
version = options[:version]
57+
unless version.nil?
58+
url = "/v3/releases/#{query}-#{version}"
59+
buffer = get_non_mutable(url)
60+
release = JSON.parse(buffer)
61+
[ release ]
62+
else
63+
get_all_result_pages("/v3/releases?module=#{query}")
64+
end
65+
end
66+
5467
def get_all_result_pages(next_page)
5568
results = []
5669
begin

lib/puppet_forge_server/http/http_client.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717
require 'open-uri'
1818
require 'open_uri_redirections'
1919
require 'timeout'
20-
require 'lrucache'
2120
require 'net/http'
2221
require 'net/http/post/multipart'
2322

2423

2524
module PuppetForgeServer::Http
2625
class HttpClient
26+
include PuppetForgeServer::Utils::CacheProvider
27+
include PuppetForgeServer::Utils::FilteringInspecter
2728

2829
def initialize(cache = nil)
29-
cache = LRUCache.new(:ttl => 60 * 15, :max_size => 250) if cache.nil?
30+
cache = cache_instance if cache.nil?
31+
cache.extend(PuppetForgeServer::Utils::FilteringInspecter)
32+
@log = PuppetForgeServer::Logger.get
3033
@cache = cache
3134
end
3235

@@ -51,9 +54,18 @@ def download(url)
5154
open_uri(url)
5255
end
5356

57+
def inspect
58+
cache_inspected = @cache.inspect_without [ :@data ]
59+
cache_inspected.gsub!(/>$/, ", @size=#{@cache.size}>")
60+
inspected = inspect_without [ :@cache ]
61+
inspected.gsub(/>$/, ", @cache=#{cache_inspected}>")
62+
end
63+
5464
private
5565

5666
def open_uri(url)
67+
hit_or_miss = @cache.include?(url) ? 'HIT' : 'MISS'
68+
@log.info "Cache in RAM memory size: #{@cache.size}, #{hit_or_miss} for url: #{url}"
5769
contents = @cache.fetch(url) do
5870
tmpfile = ::Timeout.timeout(10) do
5971
PuppetForgeServer::Logger.get.debug "Fetching data for url: #{url} from remote server"
@@ -63,7 +75,7 @@ def open_uri(url)
6375
tmpfile.close
6476
contents
6577
end
66-
PuppetForgeServer::Logger.get.debug "Data for url: #{url} fetched, #{contents.size} bytes"
78+
@log.debug "Data for url: #{url} fetched, #{contents.size} bytes"
6779
StringIO.new(contents)
6880
end
6981
end

lib/puppet_forge_server/server.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
module PuppetForgeServer
2020
class Server
2121
include PuppetForgeServer::Utils::OptionParser
22+
include PuppetForgeServer::Utils::CacheProvider
2223
include PuppetForgeServer::Utils::Http
2324

2425
def go(args)
@@ -27,6 +28,7 @@ def go(args)
2728
begin
2829
options = parse_options(args)
2930
@log = logging(options)
31+
configure_cache(options[:ram_cache_ttl], options[:ram_cache_size])
3032
backends = backends(options)
3133
server = build(backends, options[:webui_root])
3234
announce(options, backends)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- encoding: utf-8 -*-
2+
#
3+
# Copyright 2015 Centralny Osroder Informatyki (gov.pl)
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require 'lrucache'
18+
19+
module PuppetForgeServer::Utils
20+
module CacheProvider
21+
22+
opts = PuppetForgeServer::Utils::OptionParser.DEFAULT_OPTIONS
23+
@@CACHE = LRUCache.new(:ttl => opts[:ram_cache_ttl], :max_size => opts[:ram_cache_size])
24+
25+
# Method for fetching application wide cache for fetching HTTP requests
26+
#
27+
# @return [LRUCache] a instance of cache for application
28+
def cache_instance
29+
@@CACHE
30+
end
31+
32+
# Configure a application wide cache using LSUCache implementation
33+
#
34+
# @param [int] ttl a time to live for elements
35+
# @param [int] size a maximum size for cache
36+
def configure_cache(ttl, size)
37+
@@CACHE = LRUCache.new(:ttl => ttl, :max_size => size)
38+
PuppetForgeServer::Logger.get.info("Using RAM memory LRUCache with time to live of #{ttl}sec and max size of #{size} elements")
39+
nil
40+
end
41+
end
42+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# -*- encoding: utf-8 -*-
2+
#
3+
# Copyright 2015 Centralny Osroder Informatyki (gov.pl)
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
module PuppetForgeServer::Utils
18+
module FilteringInspecter
19+
def self.inspect_without(object, variables)
20+
filtered = object.instance_variables.reject { |n| variables.include? n }
21+
vars = filtered.map { |n| "#{n}=#{object.instance_variable_get(n).inspect}" }
22+
oid = object.object_id << 1
23+
"#<%s:0x%x %s>" % [ object.class, oid, vars.join(', ') ]
24+
end
25+
26+
def inspect_without(variables)
27+
PuppetForgeServer::Utils::FilteringInspecter.inspect_without(self, variables)
28+
end
29+
end
30+
end

lib/puppet_forge_server/utils/option_parser.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,23 @@ module OptionParser
2828
@@DEFAULT_LOG_DIR = File.join(Dir.tmpdir.to_s, 'puppet-forge-server', 'log')
2929
@@DEFAULT_WEBUI_ROOT = File.expand_path('../app', File.dirname(__FILE__))
3030
@@DEFAULT_HOST = '0.0.0.0'
31+
@@DEFAULT_RAM_CACHE_TTL = 60 * 30 # 30min
32+
@@DEFAULT_RAM_CACHE_SIZE = 250
33+
34+
def self.DEFAULT_OPTIONS
35+
{
36+
:daemonize => @@DEFAULT_DAEMONIZE,
37+
:cache_basedir => @@DEFAULT_CACHE_DIR,
38+
:port => @@DEFAULT_PORT,
39+
:webui_root => @@DEFAULT_WEBUI_ROOT,
40+
:host => @@DEFAULT_HOST,
41+
:ram_cache_ttl => @@DEFAULT_RAM_CACHE_TTL,
42+
:ram_cache_size => @@DEFAULT_RAM_CACHE_SIZE
43+
}
44+
end
3145

3246
def parse_options(args)
33-
options = {:daemonize => @@DEFAULT_DAEMONIZE, :cache_basedir => @@DEFAULT_CACHE_DIR, :port => @@DEFAULT_PORT, :webui_root => @@DEFAULT_WEBUI_ROOT, :host => @@DEFAULT_HOST}
47+
options = PuppetForgeServer::Utils::OptionParser.DEFAULT_OPTIONS
3448
option_parser = ::OptionParser.new do |opts|
3549
opts.banner = "Usage: #{File.basename $0} [options]"
3650
opts.version = PuppetForgeServer::VERSION
@@ -63,6 +77,14 @@ def parse_options(args)
6377
options[:cache_basedir] = cache_basedir
6478
end
6579

80+
opts.on('--ram-cache-ttl SECONDS', "The time to live in seconds for remote requests RAM cache (default: #{@@DEFAULT_RAM_CACHE_TTL})") do |ram_cache_ttl|
81+
options[:ram_cache_ttl] = ram_cache_ttl
82+
end
83+
84+
opts.on('--ram-cache-size ENTRIES', "The maximum number of enties in RAM cache for remote requests (default: #{@@DEFAULT_RAM_CACHE_SIZE})") do |ram_cache_size|
85+
options[:ram_cache_size] = ram_cache_size
86+
end
87+
6688
opts.on('--log-dir DIR', "Log directory (default: #{@@DEFAULT_LOG_DIR})") do |log_dir|
6789
options[:log_dir] = log_dir
6890
end

0 commit comments

Comments
 (0)