Skip to content

Commit

Permalink
Workaround #2668 - Compress facts so that the request size limit trig…
Browse files Browse the repository at this point in the history
…gers less often

This is not the right fix, but more a hackish workaround.

Since 0.25, the facts are transmitted as GET parameters when a
node asks for a catalog. Most proxies or webserver have a size limit
which is sometimes reached. In this case the request is denied
and the node can't get its catalog.

The idea is to compress facts (some non-scientific studies show a
57% fact size decrease for an average node) when transmitting
those when asking for a catalog.

Signed-off-by: Brice Figureau <brice-puppet@daysofwonder.com>
  • Loading branch information
Brice Figureau authored and jamtur01 committed Oct 25, 2009
1 parent e2ce790 commit e8bce7a
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 5 deletions.
4 changes: 2 additions & 2 deletions lib/puppet/configurer/fact_handler.rb
Expand Up @@ -29,11 +29,11 @@ def facts_for_uploading
#format = facts.class.default_format

# Hard-code yaml, because I couldn't get marshal to work.
format = :yaml
format = :b64_zlib_yaml

text = facts.render(format)

return {:facts_format => format, :facts => CGI.escape(text)}
return {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(text)}
end

# Retrieve facts from the central server.
Expand Down
50 changes: 50 additions & 0 deletions lib/puppet/network/formats.rb
Expand Up @@ -39,6 +39,56 @@ def fixup(yaml)
end
end

# This is a "special" format which is used for the moment only when sending facts
# as REST GET parameters (see Puppet::Configurer::FactHandler).
# This format combines a yaml serialization, then zlib compression and base64 encoding.
Puppet::Network::FormatHandler.create(:b64_zlib_yaml, :mime => "text/b64_zlib_yaml") do
require 'base64'
require 'zlib'

def intern(klass, text)
decode(text)
end

def intern_multiple(klass, text)
decode(text)
end

def render(instance)
yaml = instance.to_yaml

yaml = encode(fixup(yaml)) unless yaml.nil?
yaml
end

def render_multiple(instances)
yaml = instances.to_yaml

yaml = encode(fixup(yaml)) unless yaml.nil?
yaml
end

# Because of yaml issue in ruby 1.8.1...
def supported?(klass)
RUBY_VERSION != '1.8.1'
end

# fixup invalid yaml as per:
# http://redmine.ruby-lang.org/issues/show/1331
def fixup(yaml)
yaml.gsub!(/((?:&id\d+\s+)?!ruby\/object:.*?)\s*\?/) { "? #{$1}" }
yaml
end

def encode(text)
Base64.encode64(Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION))
end

def decode(yaml)
YAML.load(Zlib::Inflate.inflate(Base64.decode64(yaml)))
end
end


Puppet::Network::FormatHandler.create(:marshal, :mime => "text/marshal") do
# Marshal doesn't need the class name; it's serialized.
Expand Down
6 changes: 3 additions & 3 deletions spec/unit/configurer/fact_handler.rb
Expand Up @@ -102,7 +102,7 @@ class FactHandlerTester

@facthandler.expects(:find_facts).returns facts

@facthandler.facts_for_uploading.should == {:facts_format => :yaml, :facts => text}
@facthandler.facts_for_uploading.should == {:facts_format => :b64_zlib_yaml, :facts => text}
end

it "should properly accept facts containing a '+'" do
Expand All @@ -112,12 +112,12 @@ class FactHandlerTester

@facthandler.expects(:find_facts).returns facts

@facthandler.facts_for_uploading.should == {:facts_format => :yaml, :facts => text}
@facthandler.facts_for_uploading.should == {:facts_format => :b64_zlib_yaml, :facts => text}
end

it "should hard-code yaml as the serialization" do
facts = stub 'facts'
facts.expects(:render).with(:yaml).returns "my text"
facts.expects(:render).with(:b64_zlib_yaml).returns "my text"
text = CGI.escape("my text")

@facthandler.expects(:find_facts).returns facts
Expand Down
85 changes: 85 additions & 0 deletions spec/unit/network/formats.rb
Expand Up @@ -90,6 +90,91 @@ def to_pson(*args)
end
end

describe "base64 compressed yaml" do
before do
@yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml)
end

it "should have its mime type set to text/b64_zlib_yaml" do
@yaml.mime.should == "text/b64_zlib_yaml"
end

it "should render by calling 'to_yaml' on the instance" do
instance = mock 'instance'
instance.expects(:to_yaml).returns "foo"
@yaml.render(instance)
end

it "should fixup generated yaml on render" do
instance = mock 'instance', :to_yaml => "foo"

@yaml.expects(:fixup).with("foo").returns "bar"

@yaml.render(instance)
end

it "should encode generated yaml on render" do
instance = mock 'instance', :to_yaml => "foo"

@yaml.expects(:encode).with("foo").returns "bar"

@yaml.render(instance).should == "bar"
end

it "should render multiple instances by calling 'to_yaml' on the array" do
instances = [mock('instance')]
instances.expects(:to_yaml).returns "foo"
@yaml.render_multiple(instances)
end

it "should fixup generated yaml on render" do
instances = [mock('instance')]
instances.stubs(:to_yaml).returns "foo"

@yaml.expects(:fixup).with("foo").returns "bar"

@yaml.render(instances)
end

it "should encode generated yaml on render" do
instances = [mock('instance')]
instances.stubs(:to_yaml).returns "foo"

@yaml.expects(:encode).with("foo").returns "bar"

@yaml.render(instances).should == "bar"
end

it "should intern by calling decode" do
text = "foo"
@yaml.expects(:decode).with("foo").returns "bar"
@yaml.intern(String, text).should == "bar"
end

it "should intern multiples by calling 'decode'" do
text = "foo"
@yaml.expects(:decode).with("foo").returns "bar"
@yaml.intern_multiple(String, text).should == "bar"
end

it "should decode by base64 decoding, uncompressing and Yaml loading" do
Base64.expects(:decode64).with("zorg").returns "foo"
Zlib::Inflate.expects(:inflate).with("foo").returns "baz"
YAML.expects(:load).with("baz").returns "bar"
@yaml.decode("zorg").should == "bar"
end

it "should encode by compressing and base64 encoding" do
Zlib::Deflate.expects(:deflate).with("foo", Zlib::BEST_COMPRESSION).returns "bar"
Base64.expects(:encode64).with("bar").returns "baz"
@yaml.encode("foo").should == "baz"
end

it "should fixup incorrect yaml to correct" do
@yaml.fixup("&id004 !ruby/object:Puppet::Relationship ?").should == "? &id004 !ruby/object:Puppet::Relationship"
end
end

it "should include a marshal format" do
Puppet::Network::FormatHandler.format(:marshal).should_not be_nil
end
Expand Down

0 comments on commit e8bce7a

Please sign in to comment.