diff options
author | Rein Henrichs <rein@puppetlabs.com> | 2010-06-14 16:40:38 -0700 |
---|---|---|
committer | test branch <puppet-dev@googlegroups.com> | 2010-02-17 06:50:53 -0800 |
commit | 432db2593f15de200767ec16c14f433f718a44d9 (patch) | |
tree | 934cdcbacb66a702929e270849675a24c59a140d | |
parent | 35636e9984f1f9b2c6fae3d1410b4b08b6311693 (diff) | |
download | puppet-432db2593f15de200767ec16c14f433f718a44d9.tar.gz puppet-432db2593f15de200767ec16c14f433f718a44d9.tar.xz puppet-432db2593f15de200767ec16c14f433f718a44d9.zip |
[#4055] Add CouchDB terminus for facts
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.
* Add Puppet.features.couchdb?
* Add Puppet[:couchdb_url] setting
* Add Puppet::Node::Facts#== for testing
* Add PuppetSpec::FIXTURE_DIR for easy access to fixture files
* Add CouchDB Terminus
* Add Facts::CouchDB terminus
* Stores facts inside a "node" document
* Use key (hostname) as _id for node document
* #find returns nil if document cannot be found
* #save finds and updates existing document OR creates new doc [1]
* Store facts in "facts" attribute of node document
-rw-r--r-- | lib/puppet/defaults.rb | 4 | ||||
-rw-r--r-- | lib/puppet/feature/base.rb | 3 | ||||
-rw-r--r-- | lib/puppet/indirector/couch.rb | 68 | ||||
-rw-r--r-- | lib/puppet/indirector/facts/couch.rb | 31 | ||||
-rwxr-xr-x | lib/puppet/node/facts.rb | 5 | ||||
-rw-r--r-- | spec/fixtures/yaml/test.local.yaml | 16 | ||||
-rw-r--r-- | spec/spec_helper.rb | 1 | ||||
-rw-r--r-- | spec/unit/application/master.rb | 6 | ||||
-rw-r--r-- | spec/unit/indirector/facts/couch.rb | 40 |
9 files changed, 168 insertions, 6 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 78f4cd52e..9de73f526 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -680,6 +680,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 8a21fd30f..0ca408ce4 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -45,3 +45,6 @@ end Puppet.features.add(:win32, :libs => ["sys/admin", "win32/process", "win32/dir"]) raise Puppet::Error "Cannot determine basic system flavour" unless Puppet.features.posix? or Puppet.features.win32? + +# 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..b328f5cb7 --- /dev/null +++ b/lib/puppet/indirector/couch.rb @@ -0,0 +1,68 @@ +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 db.get(id_for(request)) + rescue RestClient::ResourceNotFound + Puppet.debug "No couchdb document with id: #{id_for(request)}" + return nil + end + + # Create or update the couchdb document with the request's data hash. + # + # RKH:TODO: Do not depend on error handling, check if the document exists + # first. (Does couchrest support this?) + # + def save(request) + raise ArgumentError, "PUT does not accept options" unless request.options.empty? + + # Try to find an existing document. + doc = db.get(id_for(request)) + doc.merge(hash_from(request)) + doc.save + rescue RestClient::ResourceNotFound + # Document does not yet exist, create it + db.save_doc hash_from(request) + end + + private + + # 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.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 2b086afea..0b9fc5879 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -49,6 +49,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 d657f8396..0ea24bb77 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/master.rb b/spec/unit/application/master.rb index f082ece16..c3f4d9e37 100644 --- a/spec/unit/application/master.rb +++ b/spec/unit/application/master.rb @@ -176,12 +176,6 @@ describe Puppet::Application::Master do @master.setup end - it "should set node facst terminus to yaml" do - Puppet::Node::Facts.expects(:terminus_class=).with(:yaml) - - @master.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..acc28b9ea --- /dev/null +++ b/spec/unit/indirector/facts/couch.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/node/facts' +require 'puppet/indirector/facts/couch' + +describe Puppet::Node::Facts::Couch do + 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('test.local').returns(mock_document) + Puppet::Node::Facts::Couch.stubs(:db).returns(@mock_db) + end + + subject { Puppet::Node::Facts::Couch } + + describe "#find" 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 "#save" do + 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 + end + + def fake_request + facts = YAML.load_file(File.join(PuppetSpec::FIXTURE_DIR, 'yaml', 'test.local.yaml')) + Struct.new(:instance, :key, :options).new(facts, facts.name, {}) + end + private :fake_request + +end + |