From e24a299511f6a23019edfee16e2541c8078d3f5d Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 20 Apr 2006 05:14:13 +0000 Subject: A simple first version of an object (called "pelement") server is now in place. There is not yet a client, and the tests are pretty simple so far -- only files have been tested yet. I had to make a significant number of modifications to the file object in order to get this all to work, and one of the big changes I made is to the internals of the checksum state. git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1123 980ebf18-57e1-0310-9a29-db15c13687c0 --- lib/puppet/client/pelement.rb | 20 ++++++ lib/puppet/server/pelement.rb | 101 ++++++++++++++++++++++++++ lib/puppet/type.rb | 30 ++++++++ lib/puppet/type/pfile/checksum.rb | 145 ++++++++++++++++++++++---------------- lib/puppet/type/pfile/ensure.rb | 4 +- lib/puppet/type/pfile/source.rb | 2 + lib/puppet/type/pfile/target.rb | 65 +++++++++-------- lib/puppet/type/state.rb | 23 +++++- test/server/pelement.rb | 81 +++++++++++++++++++++ 9 files changed, 378 insertions(+), 93 deletions(-) create mode 100644 lib/puppet/client/pelement.rb create mode 100755 lib/puppet/server/pelement.rb create mode 100644 test/server/pelement.rb diff --git a/lib/puppet/client/pelement.rb b/lib/puppet/client/pelement.rb new file mode 100644 index 000000000..116624003 --- /dev/null +++ b/lib/puppet/client/pelement.rb @@ -0,0 +1,20 @@ +class Puppet::Client::FileClient < Puppet::Client::ProxyClient + @drivername = :FileServer + + # set up the appropriate interface methods + @handler = Puppet::Server::FileServer + + self.mkmethods + + def initialize(hash = {}) + if hash.include?(:FileServer) + unless hash[:FileServer].is_a?(Puppet::Server::FileServer) + raise Puppet::DevError, "Must pass an actual FS object" + end + end + + super(hash) + end +end + +# $Id$ diff --git a/lib/puppet/server/pelement.rb b/lib/puppet/server/pelement.rb new file mode 100755 index 000000000..791576666 --- /dev/null +++ b/lib/puppet/server/pelement.rb @@ -0,0 +1,101 @@ +require 'puppet' +require 'puppet/server' + +module Puppet + +class Server::PElementServer + attr_accessor :local + + @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| + iface.add_method("string describe(string, string, array, array)") + iface.add_method("string list(string, string, boolean, array)") + } + + # Describe a given object. This returns the 'is' values for every state + # available on the object type. + def describe(type, name, retrieve = nil, ignore = [], format = "yaml", client = nil, clientip = nil) + @local = true unless client + typeklass = nil + unless typeklass = Puppet.type(type) + raise Puppet::Error, "Puppet type %s is unsupported" % type + end + + obj = nil + + retrieve ||= :all + + if obj = typeklass[name] + obj[:check] = retrieve + else + begin + obj = typeklass.create(:name => name, :check => retrieve) + rescue Puppet::Error => detail + raise Puppet::Error, "%s[%s] could not be created: %s" % + [type, name, detail] + end + end + + trans = obj.to_trans + + # Now get rid of any attributes they specifically don't want + ignore.each do |st| + if trans.include? st + trans.delete(st) + end + end + + if @local + return trans + else + str = nil + case format + when "yaml": + str = YAML.dump(trans) + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + return CGI.escape(str) + end + end + + # Create a new fileserving module. + def initialize(hash = {}) + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + end + + def list(type, name, client = nil, clientip = nil) + end + + private + + def authcheck(file, mount, client, clientip) + unless mount.allowed?(client, clientip) + mount.warning "%s cannot access %s" % + [client, file] + raise Puppet::Server::AuthorizationError, "Cannot access %s" % mount + end + end + + # Deal with ignore parameters. + def handleignore(children, path, ignore) + ignore.each { |ignore| + Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| + children.delete(File.basename(match)) + } + } + return children + end + + def to_s + "pelementserver" + end +end +end + +# $Id$ diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 612c3d13f..c11d51b9a 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -9,7 +9,10 @@ require 'puppet/util' # see the bottom of the file for the rest of the inclusions module Puppet +# The type is unknown +class UnknownTypeError < Puppet::Error; end class Type < Puppet::Element + # Types (which map to elements in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an # 'attribute', but these attributes always take one of three specific @@ -1682,6 +1685,24 @@ class Type < Puppet::Element self.name end + # Convert to a transportable object + def to_trans + # Collect all of the "is" values + retrieve() + + trans = TransObject.new(self.name, self.class.name) + + states().each do |state| + trans[state.name] = state.is + end + + trans.tags = self.tags + + # FIXME I'm currently ignoring 'parent' and 'path' + + return trans + end + # instance methods dealing with actually doing work public @@ -2039,6 +2060,13 @@ class Type < Puppet::Element on all packages." munge do |args| + # If they've specified all, collect all known states + if args == :all + args = @parent.class.states.collect do |state| + state.name + end + end + unless args.is_a?(Array) args = [args] end @@ -2054,6 +2082,8 @@ class Type < Puppet::Element end next if @parent.statedefined?(state) + next unless @parent.class.validstate?(state).checkable? + @parent.newstate(state) } end diff --git a/lib/puppet/type/pfile/checksum.rb b/lib/puppet/type/pfile/checksum.rb index d25f28c52..c1f00e5b8 100755 --- a/lib/puppet/type/pfile/checksum.rb +++ b/lib/puppet/type/pfile/checksum.rb @@ -16,6 +16,44 @@ module Puppet @validtypes.include?(type) end + @validtypes.each do |ctype| + newvalue(ctype) do + handlesum() + end + end + + str = @validtypes.join("|") + + newvalue(/^\{#{str}\}/) do + handlesum() + end + + newvalue(:nosum) do + # nothing + :nochange + end + + # Convert from the sum type to the stored checksum. + munge do |value| + unless defined? @checktypes + @checktypes = [] + end + + if FileTest.directory?(@parent[:path]) + value = "time" + end + + + if value =~ /^\{(\w+)\}(.+)$/ + @checktypes << $1 + return $2 + else + value = super + @checktypes << value + return getcachedsum() + end + end + def checktype @checktypes[0] end @@ -80,15 +118,17 @@ module Puppet # Calculate the sum from disk. def getsum(checktype) sum = "" + + checktype = checktype.intern if checktype.is_a? String case checktype - when "md5", "md5lite": + when :md5, :md5lite: unless FileTest.file?(@parent[:path]) @parent.info "Cannot MD5 sum directory %s" % @parent[:path] - # because we cannot sum directories, just delete ourselves - # from the file so we won't sync - @parent.delete(self[:path]) + @should = [nil] + @is = nil + #@parent.delete(self[:path]) return else begin @@ -117,35 +157,59 @@ module Puppet @parent.delete(self.class.name) end end - when "timestamp","mtime": + when :timestamp, :mtime: sum = @parent.stat.mtime.to_s #sum = File.stat(@parent[:path]).mtime.to_s - when "time": + when :time: sum = @parent.stat.ctime.to_s #sum = File.stat(@parent[:path]).ctime.to_s else raise Puppet::Error, "Invalid sum type %s" % checktype end - return sum + return "{#{checktype}}" + sum.to_s end - # Convert from the sum type to the stored checksum. - munge do |value| - unless defined? @checktypes - @checktypes = [] - end - unless self.class.validtype?(value) - self.fail "Invalid checksum type '%s'" % value + # At this point, we don't actually modify the system, we modify + # the stored state to reflect the current state, and then kick + # off an event to mark any changes. + def handlesum + if @is.nil? + raise Puppet::Error, "Checksum state for %s is somehow nil" % + @parent.name end - if FileTest.directory?(@parent[:path]) - value = "time" - end + if @is == :absent + self.retrieve - @checktypes << value + if self.insync? + self.debug "Checksum is already in sync" + return nil + end + #@parent.debug "%s(%s): after refresh, is '%s'" % + # [self.class.name,@parent.name,@is] - return getcachedsum() + # If we still can't retrieve a checksum, it means that + # the file still doesn't exist + if @is == :absent + # if they're copying, then we won't worry about the file + # not existing yet + unless @parent.state(:source) + self.warning( + "File %s does not exist -- cannot checksum" % + @parent[:path] + ) + end + return nil + end + end + + # If the sums are different, then return an event. + if self.updatesum + return :file_changed + else + return nil + end end # Even though they can specify multiple checksums, the insync? @@ -194,49 +258,6 @@ module Puppet #@parent.debug "checksum state is %s" % self.is end - - # At this point, we don't actually modify the system, we modify - # the stored state to reflect the current state, and then kick - # off an event to mark any changes. - def sync - if @is.nil? - raise Puppet::Error, "Checksum state for %s is somehow nil" % - @parent.name - end - - if @is == :absent - self.retrieve - - if self.insync? - self.debug "Checksum is already in sync" - return nil - end - #@parent.debug "%s(%s): after refresh, is '%s'" % - # [self.class.name,@parent.name,@is] - - # If we still can't retrieve a checksum, it means that - # the file still doesn't exist - if @is == :absent - # if they're copying, then we won't worry about the file - # not existing yet - unless @parent.state(:source) - self.warning( - "File %s does not exist -- cannot checksum" % - @parent[:path] - ) - end - return nil - end - end - - # If the sums are different, then return an event. - if self.updatesum - return :file_changed - else - return nil - end - end - # Store the new sum to the state db. def updatesum result = false diff --git a/lib/puppet/type/pfile/ensure.rb b/lib/puppet/type/pfile/ensure.rb index 8a653e93b..9f44b780f 100755 --- a/lib/puppet/type/pfile/ensure.rb +++ b/lib/puppet/type/pfile/ensure.rb @@ -80,8 +80,9 @@ module Puppet if state.linkmaker self.set_directory + return :directory_created else - state.sync + return state.sync end else self.fail "Cannot create a symlink without a target" @@ -120,7 +121,6 @@ module Puppet end def retrieve - if stat = @parent.stat(false) @is = stat.ftype.intern else diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb index c7c07d1c9..8d770890b 100755 --- a/lib/puppet/type/pfile/source.rb +++ b/lib/puppet/type/pfile/source.rb @@ -28,6 +28,8 @@ module Puppet " + uncheckable + # Ask the file server to describe our file. def describe(source) sourceobj, path = @parent.uri2obj(source) diff --git a/lib/puppet/type/pfile/target.rb b/lib/puppet/type/pfile/target.rb index c136cc7ad..426c52b6d 100644 --- a/lib/puppet/type/pfile/target.rb +++ b/lib/puppet/type/pfile/target.rb @@ -5,31 +5,13 @@ module Puppet desc "The target for creating a link. Currently, symlinks are the only type supported." - munge do |value| - value + newvalue(:notlink) do + # We do nothing if the value is absent + return :nochange end - def retrieve - if @parent.state(:ensure).should == :directory - @is = self.should - @linkmaker = true - else - if stat = @parent.stat - if File.exists?(self.should) and tstat = File.lstat(self.should) and tstat.ftype == "directory" and @parent.recurse? - @parent[:ensure] = :directory - @is = self.should - @linkmaker = true - else - @is = File.readlink(@parent[:path]) - @linkmaker = false - end - else - @is = :absent - end - end - end - - def sync + # Anything else, basically + newvalue(/./) do target = self.should if stat = @parent.stat @@ -39,10 +21,9 @@ module Puppet File.unlink(@parent[:path]) end Dir.chdir(File.dirname(@parent[:path])) do - unless FileTest.exists?(target) self.debug "Not linking to non-existent '%s'" % target - return nil # Grrr, can't return + :nochange # Grrr, can't return else Puppet::Util.asuser(@parent.asuser()) do mode = @parent.should(:mode) @@ -55,9 +36,37 @@ module Puppet end end - # We can't use "return" here because we're in an anonymous - # block. - return :link_created + :link_created + end + end + end + + def retrieve + if @parent.state(:ensure).should == :directory + @is = self.should + @linkmaker = true + else + if stat = @parent.stat + # If we're just checking the value + if should = self.should and + should != :notlink + File.exists?(should) and + tstat = File.lstat(should) and + tstat.ftype == "directory" and + @parent.recurse? + @parent[:ensure] = :directory + @is = should + @linkmaker = true + else + if stat.ftype == "link" + @is = File.readlink(@parent[:path]) + @linkmaker = false + else + @is = :notlink + end + end + else + @is = :absent end end end diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index 05f876583..6d2b14cf2 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -18,6 +18,22 @@ class State < Puppet::Parameter class << self attr_accessor :unmanaged attr_reader :name + + def checkable + @checkable = true + end + + def uncheckable + @checkable = false + end + + def checkable? + if defined? @checkable + return @checkable + else + return true + end + end end # Create the value management variables. @@ -82,13 +98,18 @@ class State < Puppet::Parameter end if event and event.is_a?(Symbol) - return event + if event == :nochange + return nil + else + return event + end else # Return the appropriate event. event = case self.should when :present: (@parent.class.name.to_s + "_created").intern when :absent: (@parent.class.name.to_s + "_removed").intern else + warning self.should.inspect (@parent.class.name.to_s + "_changed").intern end diff --git a/test/server/pelement.rb b/test/server/pelement.rb new file mode 100644 index 000000000..3e55918dd --- /dev/null +++ b/test/server/pelement.rb @@ -0,0 +1,81 @@ +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '..' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/server/pelement' +require 'test/unit' +require 'puppettest.rb' +require 'base64' +require 'cgi' + +class TestPElementServer < Test::Unit::TestCase + include ServerTest + + def test_describe_file + # Make a file to describe + file = tempfile() + str = "yayness\n" + + server = nil + + assert_nothing_raised do + server = Puppet::Server::PElementServer.new() + end + + [ [nil], + [[:content, :mode], []], + [[], [:content]], + [[:content], [:mode]] + ].each do |ary| + retrieve = ary[0] || [] + ignore = ary[1] || [] + + File.open(file, "w") { |f| f.print str } + + result = nil + assert_nothing_raised do + result = server.describe("file", file, *ary) + end + + assert(result, "Could not retrieve file information") + + assert_instance_of(Puppet::TransObject, result) + + # Now we have to clear, so that the server's object gets removed + Puppet::Type.type(:file).clear + + # And remove the file, so we can verify it gets recreated + File.unlink(file) + + object = nil + assert_nothing_raised do + object = result.to_type + end + + assert(object, "Could not create type") + + retrieve.each do |state| + assert(object.should(state), "Did not retrieve %s" % state) + end + + ignore.each do |state| + assert(! object.should(state), "Incorrectly retrieved %s" % state) + end + + assert_events([:file_created], object) + + assert(FileTest.exists?(file), "File did not get recreated") + + if object.should(:content) + assert_equal(str, File.read(file), "File contents are not the same") + else + assert_equal("", File.read(file), "File content was incorrectly made") + end + end + end +end + +# $Id$ -- cgit v1.2.3