Skip to content

Commit 646b61f

Browse files
author
Ilja Bobkevic
committed
Merge pull request #35 from coi-gov-pl/feature/add-cache-for-remote
Feature: Add cache to significantly speedup fetching from remote proxies
2 parents ac9618c + 6468fb0 commit 646b61f

File tree

17 files changed

+412
-16
lines changed

17 files changed

+412
-16
lines changed

.rspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--format documentation
2+
--color

lib/puppet_forge_server.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,14 @@ 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'
6264
autoload :Http, 'puppet_forge_server/utils/http'
6365
autoload :MarkdownRenderer, 'puppet_forge_server/utils/md_renderer'
66+
autoload :Encoding, 'puppet_forge_server/utils/encoding'
6467
end
6568

6669
module Http

lib/puppet_forge_server/api/v3/releases.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
module PuppetForgeServer::Api::V3
1818
module Releases
19+
include PuppetForgeServer::Utils::Encoding
1920
def get_releases(metadata)
2021
metadata.map do |element|
2122
author, name = element.metadata.name.split('-')
@@ -36,7 +37,7 @@ def get_releases(metadata)
3637
:file_uri => "/v3/files#{element.path}",
3738
:file_md5 => element.checksum,
3839
:deleted_at => element.deleted_at,
39-
:readme => element.readme
40+
:readme => to_utf8(element.readme)
4041
}
4142
end.uniq{|r| r[:version]}.sort_by { |r| Gem::Version.new(r[:version]) }
4243
end

lib/puppet_forge_server/app/public/css/puppetlabs.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19+
@import url(https://fonts.googleapis.com/css?family=PT+Sans&subset=latin,latin-ext);
1920
body {
2021
background: #fff;
2122
color: #5d5e59;

lib/puppet_forge_server/backends/proxy.rb

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ 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|
43-
file.write(buffer.read)
42+
File.open(target_file, 'wb') do |file|
43+
bytes = buffer.read
44+
file.write(bytes)
45+
@log.debug("Saved #{bytes.size} bytes in filesystem cache for path: #{relative_path}, target file: #{target_file}")
4446
end
45-
path = File.join(@cache_dir, file_name[0].downcase, file_name)
47+
path = target_file
48+
else
49+
@log.info("Filesystem cache HIT for path: #{relative_path}")
4650
end
4751
File.open(path, 'rb')
4852
rescue => e
@@ -59,6 +63,27 @@ def upload(file_data)
5963
protected
6064
attr_reader :log
6165

66+
def get_non_mutable(relative_url)
67+
file_name = relative_url.split('/').last
68+
target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
69+
path = Dir["#{@cache_dir}/**/#{file_name}"].first
70+
unless File.exist?("#{path}")
71+
buffer = get(relative_url)
72+
File.open(target_file, 'wb') do |file|
73+
file.write(buffer)
74+
@log.debug("Saved #{buffer} bytes in filesystem cache for url: #{relative_url}, target file: #{target_file}")
75+
end
76+
path = target_file
77+
else
78+
@log.info("Filesystem cache HIT for url: #{relative_url}")
79+
end
80+
File.binread(path)
81+
rescue => e
82+
@log.error("#{self.class.name} failed getting non-mutable url '#{relative_url}'")
83+
@log.error("Error: #{e}")
84+
return nil
85+
end
86+
6287
def get(relative_url)
6388
@http_client.get(url(relative_url))
6489
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: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@
2323

2424
module PuppetForgeServer::Http
2525
class HttpClient
26+
include PuppetForgeServer::Utils::CacheProvider
27+
include PuppetForgeServer::Utils::FilteringInspecter
28+
29+
def initialize(cache = nil)
30+
cache = cache_instance if cache.nil?
31+
cache.extend(PuppetForgeServer::Utils::FilteringInspecter)
32+
@log = PuppetForgeServer::Logger.get
33+
@cache = cache
34+
end
2635

2736
def post_file(url, file_hash, options = {})
2837
options = { :http => {}, :headers => {}}.merge(options)
@@ -45,11 +54,29 @@ def download(url)
4554
open_uri(url)
4655
end
4756

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+
4864
private
65+
4966
def open_uri(url)
50-
::Timeout.timeout(10) do
51-
open(url, 'User-Agent' => "Puppet-Forge-Server/#{PuppetForgeServer::VERSION}", :allow_redirections => :safe)
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}"
69+
contents = @cache.fetch(url) do
70+
tmpfile = ::Timeout.timeout(10) do
71+
PuppetForgeServer::Logger.get.debug "Fetching data for url: #{url} from remote server"
72+
open(url, 'User-Agent' => "Puppet-Forge-Server/#{PuppetForgeServer::VERSION}", :allow_redirections => :safe)
73+
end
74+
contents = tmpfile.read
75+
tmpfile.close
76+
contents
5277
end
78+
@log.debug "Data for url: #{url} fetched, #{contents.size} bytes"
79+
StringIO.new(contents)
5380
end
5481
end
5582
end

lib/puppet_forge_server/logger.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717

1818
require 'logger'
19+
require 'logger/colors'
1920

2021
module PuppetForgeServer
2122
class Logger
@@ -45,7 +46,13 @@ def method_missing (method_name, *args, &block)
4546
else
4647
method_name
4748
end
48-
@loggers.each { |logger| logger.send(method_name, args.first) }
49+
if args.size > 0
50+
# setters
51+
@loggers.each { |logger| logger.send(method_name, args.first) }
52+
else
53+
# getters
54+
@loggers.collect { |logger| logger.send(method_name) }
55+
end
4956
end
5057

5158
def respond_to?(method_name, include_private = false)
@@ -70,4 +77,4 @@ def set(loggers= {})
7077
end
7178
end
7279
end
73-
end
80+
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

0 commit comments

Comments
 (0)