diff options
| author | Rein Henrichs <rein@puppetlabs.com> | 2010-06-07 15:45:45 -0700 |
|---|---|---|
| committer | James Turnbull <james@lovedthanlost.net> | 2010-06-27 03:54:08 +1000 |
| commit | 97936c6d3fa3950d22266679b65d4a6877008a74 (patch) | |
| tree | 0a3ec96444635c6b7604da50cb79657aabbee448 | |
| parent | db39b7c0848f8931989cbe21ff202552c0ae78c1 (diff) | |
| download | puppet-97936c6d3fa3950d22266679b65d4a6877008a74.tar.gz puppet-97936c6d3fa3950d22266679b65d4a6877008a74.tar.xz puppet-97936c6d3fa3950d22266679b65d4a6877008a74.zip | |
[#3921] Add facts_terminus setting to Puppet settings
* defaults to "facter"
* no longer set the facts terminus class in for puppetmaster, use
setting instead
Implements an abstract CouchDB terminus and a concrete CouchDB terminus
used to store node facts. Node facts are stored in a "node" document as
the "facts" attribute. This node document may also be used by other
couchdb termini that store node-related information. It is recommended
to use a separate document (or documents) to store large data structures
like catalogs, linking them to their related node document using
embedded ids.
This implementation depends on the "couchrest" gem.
| -rw-r--r-- | lib/puppet/application/puppetd.rb | 4 | ||||
| -rw-r--r-- | lib/puppet/application/puppetmasterd.rb | 3 | ||||
| -rw-r--r-- | lib/puppet/defaults.rb | 5 | ||||
| -rw-r--r-- | lib/puppet/feature/base.rb | 3 | ||||
| -rw-r--r-- | lib/puppet/indirector/couch.rb | 76 | ||||
| -rw-r--r-- | lib/puppet/indirector/facts/couch.rb | 31 | ||||
| -rwxr-xr-x | lib/puppet/node/facts.rb | 8 | ||||
| -rw-r--r-- | spec/fixtures/yaml/test.local.yaml | 16 | ||||
| -rw-r--r-- | spec/spec_helper.rb | 1 | ||||
| -rwxr-xr-x | spec/unit/application/puppetd.rb | 5 | ||||
| -rw-r--r-- | spec/unit/application/puppetmasterd.rb | 6 | ||||
| -rw-r--r-- | spec/unit/indirector/facts/couch.rb | 98 |
12 files changed, 242 insertions, 14 deletions
diff --git a/lib/puppet/application/puppetd.rb b/lib/puppet/application/puppetd.rb index 8b07c90dd..ca5a0d47b 100644 --- a/lib/puppet/application/puppetd.rb +++ b/lib/puppet/application/puppetd.rb @@ -215,9 +215,11 @@ Puppet::Application.new(:puppetd) do # You can still override this on the command-line with, e.g., :compiler. Puppet[:catalog_terminus] = :rest + # Override the default. + Puppet[:facts_terminus] = :facter + Puppet::Resource::Catalog.cache_class = :yaml - Puppet::Node::Facts.terminus_class = :facter # We need tomake the client either way, we just don't start it # if --no-client is set. diff --git a/lib/puppet/application/puppetmasterd.rb b/lib/puppet/application/puppetmasterd.rb index 9b0bf30ce..6e694452f 100644 --- a/lib/puppet/application/puppetmasterd.rb +++ b/lib/puppet/application/puppetmasterd.rb @@ -150,9 +150,6 @@ Puppet::Application.new(:puppetmasterd) do Puppet.settings.use :main, :puppetmasterd, :ssl - # A temporary solution, to at least make the master work for now. - Puppet::Node::Facts.terminus_class = :yaml - # Cache our nodes in yaml. Currently not configurable. Puppet::Node.cache_class = :yaml diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index c81513382..0ae055351 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -151,6 +151,7 @@ module Puppet :node_terminus => ["plain", "Where to find information about nodes."], :catalog_terminus => ["compiler", "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."], + :facts_terminus => ["facter", "Where to get node facts."], :httplog => { :default => "$logdir/http.log", :owner => "root", :mode => 0640, @@ -694,6 +695,10 @@ module Puppet and other environments normally use ``debug``."] ) + setdefaults(:couchdb, + :couchdb_url => ["http://127.0.0.1:5984/puppet", "The url where the puppet couchdb database will be created"] + ) + setdefaults(:transaction, :tags => ["", "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index aac04f234..a6111d836 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -31,3 +31,6 @@ Puppet.features.add(:rrd, :libs => ["RRDtool"]) # We have OpenSSL Puppet.features.add(:openssl, :libs => ["openssl"]) + +# We have CouchDB +Puppet.features.add(:couchdb, :libs => ["couchrest"]) diff --git a/lib/puppet/indirector/couch.rb b/lib/puppet/indirector/couch.rb new file mode 100644 index 000000000..6818ee834 --- /dev/null +++ b/lib/puppet/indirector/couch.rb @@ -0,0 +1,76 @@ +raise "Couch terminus not supported without couchrest gem" unless Puppet.features.couchdb? + +require 'couchrest' +class Puppet::Indirector::Couch < Puppet::Indirector::Terminus + + # The CouchRest database instance. One database instance per Puppet runtime + # should be sufficient. + # + def self.db; @db ||= CouchRest.database! Puppet[:couchdb_url] end + def db; self.class.db end + + def find(request) + attributes_of get(request) + end + + # Create or update the couchdb document with the request's data hash. + # + def save(request) + raise ArgumentError, "PUT does not accept options" unless request.options.empty? + update(request) || create(request) + end + + private + + # RKH:TODO: Do not depend on error handling, check if the document exists + # first. (Does couchrest support this?) + # + def get(request) + db.get(id_for(request)) + rescue RestClient::ResourceNotFound + Puppet.debug "No couchdb document with id: #{id_for(request)}" + return nil + end + + def update(request) + doc = get request + return unless doc + doc.merge!(hash_from(request)) + doc.save + return true + end + + def create(request) + db.save_doc hash_from(request) + end + + # The attributes hash that is serialized to CouchDB as JSON. It includes + # metadata that is used to help aggregate data in couchdb. Add + # model-specific attributes in subclasses. + # + def hash_from(request) + { + "_id" => id_for(request), + "puppet_type" => document_type_for(request) + } + end + + # The couchdb response stripped of metadata, used to instantiate the model + # instance that is returned by save. + # + def attributes_of(response) + response && response.reject{|k,v| k =~ /^(_rev|puppet_)/ } + end + + def document_type_for(request) + request.indirection_name + end + + # The id used to store the object in couchdb. Implemented in subclasses. + # + def id_for(request) + raise NotImplementedError + end + +end + diff --git a/lib/puppet/indirector/facts/couch.rb b/lib/puppet/indirector/facts/couch.rb new file mode 100644 index 000000000..522fe3350 --- /dev/null +++ b/lib/puppet/indirector/facts/couch.rb @@ -0,0 +1,31 @@ +require 'puppet/node/facts' +require 'puppet/indirector/couch' +class Puppet::Node::Facts::Couch < Puppet::Indirector::Couch + + # Return the facts object or nil if there is no document + def find(request) + doc = super + doc ? model.new(doc['_id'], doc['facts']) : nil + end + + private + + # Facts values are stored to the document's 'facts' attribute. Hostname is + # stored to 'name' + # + def hash_from(request) + super.merge('facts' => request.instance.values) + end + + # Facts are stored to the 'node' document. + def document_type_for(request) + 'node' + end + + # The id used to store the object in couchdb. + def id_for(request) + request.key.to_s + end + +end + diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index dca435c7d..a8761f874 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -16,8 +16,7 @@ class Puppet::Node::Facts end end - # Use the node source as the indirection terminus. - indirects :facts, :terminus_class => :facter, :extend => NodeExpirer + indirects :facts, :terminus_setting => :facts_terminus, :extend => NodeExpirer attr_accessor :name, :values @@ -49,6 +48,11 @@ class Puppet::Node::Facts end end + def ==(other) + return false unless self.name == other.name + strip_internal == other.send(:strip_internal) + end + private # Add internal data to the facts for storage. diff --git a/spec/fixtures/yaml/test.local.yaml b/spec/fixtures/yaml/test.local.yaml new file mode 100644 index 000000000..194d8aa59 --- /dev/null +++ b/spec/fixtures/yaml/test.local.yaml @@ -0,0 +1,16 @@ +--- !ruby/object:Puppet::Node::Facts + expiration: 2010-06-07 19:15:36.519351 -07:00 + name: test.local + values: + sp_number_processors: "2" + kernelmajversion: "10.3" + kernelversion: 10.3.1 + sp_secure_vm: secure_vm_enabled + ps: ps auxwww + macosx_productversion_major: "10.6" + hostname: test + !ruby/sym _timestamp: Mon Jun 07 18:45:36 -0700 2010 + facterversion: 1.5.7 + sp_packages: "1" + timezone: PDT + environment: production diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e03cc9898..e1e7ea638 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,6 +23,7 @@ require 'spec/autorun' # So everyone else doesn't have to include this base constant. module PuppetSpec + FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) end # load any monkey-patches diff --git a/spec/unit/application/puppetd.rb b/spec/unit/application/puppetd.rb index 88772b2f1..338eb8e9a 100755 --- a/spec/unit/application/puppetd.rb +++ b/spec/unit/application/puppetd.rb @@ -173,6 +173,7 @@ describe "puppetd" do Puppet.stubs(:info) FileTest.stubs(:exists?).returns(true) Puppet.stubs(:[]) + Puppet.stubs(:[]=) Puppet.stubs(:[]).with(:libdir).returns("/dev/null/lib") Puppet.settings.stubs(:print_config?) Puppet.settings.stubs(:print_config) @@ -312,8 +313,8 @@ describe "puppetd" do @puppetd.run_setup end - it "should tell the facts to use facter" do - Puppet::Node::Facts.expects(:terminus_class=).with(:facter) + it "should change the facts_terminus setting to 'facter'" do + Puppet.expects(:[]=).with(:facts_terminus, :facter) @puppetd.run_setup end diff --git a/spec/unit/application/puppetmasterd.rb b/spec/unit/application/puppetmasterd.rb index f522fccc1..f7fe57119 100644 --- a/spec/unit/application/puppetmasterd.rb +++ b/spec/unit/application/puppetmasterd.rb @@ -182,12 +182,6 @@ describe "PuppetMaster" do @puppetmasterd.run_setup end - it "should set node facst terminus to yaml" do - Puppet::Node::Facts.expects(:terminus_class=).with(:yaml) - - @puppetmasterd.run_setup - end - it "should cache class in yaml" do Puppet::Node.expects(:cache_class=).with(:yaml) diff --git a/spec/unit/indirector/facts/couch.rb b/spec/unit/indirector/facts/couch.rb new file mode 100644 index 000000000..088401280 --- /dev/null +++ b/spec/unit/indirector/facts/couch.rb @@ -0,0 +1,98 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/node/facts' + +describe "Puppet::Node::Facts::Couch" do + confine "couchrest gem is missing; cannot test couch terminus" => Puppet.features.couchdb? + require 'puppet/indirector/facts/couch' if Puppet.features.couchdb? + + before do + @mock_db = mock('couch db') + mock_document = CouchRest::Document.new(:_id => fake_request.key, :facts => fake_request.values) + mock_document.stubs(:database).returns(@mock_db) + @mock_db.stubs(:get).with(fake_request.key).returns(mock_document) + Puppet::Node::Facts::Couch.stubs(:db).returns(@mock_db) + end + + subject { Puppet::Node::Facts::Couch } + + describe "#find" do + describe "when the node document exists" do + it "should find the request by key" do + @mock_db.expects(:get).with(fake_request.key).returns({'_id' => fake_request.key, 'facts' => fake_request.instance.values}) + subject.new.find(fake_request).should == fake_request.instance + end + end + + describe "when the node document does not exist" do + before do + @mock_db.expects(:get). + with(fake_request.key). + raises(RestClient::ResourceNotFound) + end + + it "should return nil" do + subject.new.find(fake_request).should be_nil + end + + it "should send Puppet a debug message" do + Puppet.expects(:debug).with("No couchdb document with id: test.local") + subject.new.find(fake_request).should be_nil + end + + end + end + + describe "#save" do + describe "with options" do + subject do + lambda { Puppet::Node::Facts::Couch.new.save(fake_request([1])) } + end + + it { should raise_error(ArgumentError, "PUT does not accept options") } + end + + it "should save the json to the CouchDB database" do + @mock_db.expects(:save_doc).at_least_once.returns({'ok' => true }) + subject.new.save(fake_request) + end + + describe "when the document exists" do + before do + @doc = CouchRest::Document.new(:_id => fake_request.key, :facts => fake_request.instance.values) + @mock_db.expects(:get).with(fake_request.key).returns(@doc) + end + + it "saves the document" do + @doc.expects(:save) + subject.new.save(fake_request) + end + + end + + describe "when the document does not exist" do + before do + @mock_db.expects(:get). + with(fake_request.key). + raises(RestClient::ResourceNotFound) + end + + it "saves the document" do + @mock_db.expects(:save_doc) + subject.new.save(fake_request) + end + + end + + end + + def fake_request(options={}) + facts = YAML.load_file(File.join(PuppetSpec::FIXTURE_DIR, 'yaml', 'test.local.yaml')) + Struct.new(:instance, :key, :options).new(facts, facts.name, options) + end + private :fake_request + +end + |
