Puppet: System Administration Automated

Testing Cached Values


I'm currently in the middle of the largest refactoring effort I've ever done, while simultaneously learning tons about how to be a better developer. I'm constantly feeling a bit overwhelmed, a bit behind the curve, and like someone's going to look at my code one day and say, "hey nimwit, you just pull this string here and suddenly 2/3 of your code just goes away."

However, one thing I've really grokked recently is that if you're fighting your tools too much, you are on the wrong track, and one way in which it seems I'm constantly fighting my tools is the combination of cached values and testing.

For instance, I have an HttpPool class that knows how to set up Net::HTTP instances with all of the SSL information they need. This class caches SSL information like the certificate and key, so that each connection doesn't hit the disk for this info, which is obviously a pretty decent use of the cache. This caching generally takes this simple form:

def ssl_host
    unless defined?(@ssl_host)
        @ssl_host = Puppet::SSL::Host.new
    end
    @ssl_host
end

Yes, you could just do @ssl_host ||= Puppet::SSL::Host.new, but I've gotten some weird behaviour out of that in the past, and it also throws a warning for undefined variables.

So anyway, this works fine in unit tests; I test the caching, and I use mocks everywhere else. When I get to integration tests, though, it starts to really hurt, especially since I try not to do much mocking in my integration tests.

For instance, say I've got two unrelated tests that do an ssl connection. They each create some certificates, start a daemon, and try to connect. In this situation, the first one caches the ssl information, and the second one uses the cached values instead of its own new certificate, and you get invalid certificates.

After talking to Rick Bradley on #nashdl on IRC (gotta represent!), I've decided on at least an initial course of action. I'm going to create a module that provides both a caching and cache-clearing interface; anyone using cached values would use this caching interface instead of caching the values themselves, and the module itself would give you a single point of entry for clearing all caches on the system.

My first instinct was to create a Cache class or struct and keep a list of them in the caching module, so they can be cleared as necessary, but my recent work with TTLs has made me realize that time-based concepts of cache dirtiness are much better than actively cleaning.

So now, I'm thinking that the caching module will just have a timestamp, and only cached values created after that time will be valid. Before the Cache struct returns any values, it will always check that time, and it will know whether the value it has is still good or should be discarded.

This keeps us from maintaining global lists of caches, and it also makes clearing caches insanely cheap -- just reset a timestamp. Given that Puppet already sometimes has onerous memory requirements, I also like that it makes the caches themselves more likely to get garbage collected, since only the caching instance ever knows about the actual cache itself.

So my new method would look something like this:

def ssl_host
    cache(:ssl_host) { Puppet::SSL::Host.new }
end

That bit with the block is something I just thought of; if we've got a value, it's not called, but if we need a value, it's there for us. Pretty sweet.

Now just to implement it.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Wed, 07 May 2008 | Tags: , ,


Jay and I converge on testing


Those four people who have been reading this blog for a while know I've been struggling to think and program like Jay Fields. In particular, he seems to have presented a few rules in the past that don't like to be used together:

Now, let's do a simple combinatorial exercise, and put these three rules together:

It's pretty clear that, like the old saw about programming ("All programs can be reduced to one line of code with a bug in it"), Jay is pointing us toward tests that can largely only be one line of code. Yeah, I know sometimes setup methods don't involve any mocking, but often they do.

And, since your tests can only be one line of code, they can't test very much, which means that all of your methods need to also be one line of code, else they aren't testable. (Yes, I'm being a touch extreme here, but that is where the arrow is pointed, anyway.)

You can see how this would kinda drive me bonkers. Some local dev friends have been trying to help me see the error of my ways, mostly so my code would stop looking like such crap; I've learned a helluva lot in the last 8 months or so, and most of it has actually made my code look more like Jay would recommend. I will say it's blindingly obvious Jay is doing internal development at enterprises, rather than developing as part of a consistent team producing software that is distributed to the wider world.

But one can only go so far, and the three rules above, in combination, are just way too far. I've often kind of sputtered expasperatedly at Jay's posts, especially his announcement of his new testing tool, expectations. Again, I can kind of see where he's going with that, but you've got another thing coming if you think I'm using it, especially given how happy (at least, relative to test/unit) I am with RSpec.

Also, I think it's just stupid having all setup code inline. DRY ("don't repeat yourself") is just as true in your test code as anywhere else, and having a maintainable test code base is, IMO, more important than having your normal code base be maintainable, because tests are kind of unnecessary. If you have good, readable, maintainable tests, then people who contribute will also contribute tests. If your tests are all 50 lines long and have lots of repetition, then 1) you've got 5x the amount of code you should, which is wicked expensive, and 2) you've got so much code no one will look at it. Yay, never getting patches with tests in them. My favorite example of this is Steve Yegge's rant Code's Worst Enemy; he describes his 500k line Java project with no tests, which is a lot of code but much less code than if it had tests. I've experienced in Puppet that test code seems to be much harder to maintain that normal code (although maybe it's just own crap test code, not normal test code), and having 5x test code than normal code would make me just quit writing unit tests entirely.

So, I am absolutely overjoyed to announce that Jay has changed one of his rules: He now recommends stubs over mocks. This is clearly just for setup code and such, but it's a big step. He even goes into using stub_everything, which I find is the only way to build tests that aren't fragile. For instance, say you start with this code:

class MyClass
    def go
        start()
        finish()
    end
end

describe MyClass do
    before
        @me = MyClass.new
        @me.stubs(:start)
        @me.stubs(:finish)
    end

    it "should start when going" do
        @me.expects(:start)
        @me.go
    end

    it "should finish when going" do
        @me.expects(:finish)
        @me.go
    end
end

Now you find you need a validation method, so you add this test:

it "should validate when going" do
    @me.expects(:validate)
    @me.go
end

Update: Fixed code to actually call @me.go in the validate test.

Oops. Now your single test passes, but your two old tests break, because you were only stubbing start and finish, instead of using stub_everything. Your setup code needs to be modified to take this new call into account (or, if you're Jay, you need to modify every test in your suite; yay). This comes up constantly. If you specifically mock or stub methods during setup, then you are almost guaranteed to have cascading failures when you expand your code.

Anyway, the point is, if I tried to follow Jay's rules, then the above trivial change -- I add one line of code to a very simple method -- would result in me adding a test for that line, plus at least one line of code in every other test in that suite. Instead, if I use stub_everything, then I add my new test and I'm done. (Well, kind of; notice I'm not actually testing the order of the method calls, which is actually pretty tough.)

My recommendation is to read Jay, since he's clearly thinking and talking about aspects of testing that not many others are, but read him with a skeptical eye, and be willing to say "That's just nuts!" and write your own relatively abstracted test code. And if you're working with people who can't think to look at their setup code when a test fails, then you need to find a different job.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Tue, 06 May 2008 | Tags: , , , , ,


Ruby has a distribution problem


I've been doing a better job of reading development books recently (e.g., Domain Driven Design), and something has really begun to stick out at me. There seems to be a split between those developers who write software that is expected to run in one place and those who write software that is expected to run in many places.

If you, as a developer, know that your software will really only be installed at a single customer (whether that customer is your employer, a consulting client, or whatever), then your life is drastically easier -- you don't usually have to worry about cross-platform issues, and you don't have to worry about different users having different needs, because you only have one user.

Obviously there's no inherent problem with having the simpler life of a developer with only one user, but it seems to me that the Ruby community is, as a group, largely adopting that perspective as the default. This is worrying to me, because I'm building an application that I expect to be installed in thousands of locations (in fact, it's probably already installed in thousands of locations). I'd like to take as much advantage of existing Ruby code as possible, but it's not exactly easy.

For example, rubygems (probably my least favorite ruby software of all time) basically require that you always try to load them, because their design stupidly requires that you know whether a given piece of software is installed via rubygems or some other mechanism. For instance, if you've installed the Facter gem, then this code doesn't work:

ruby -rfacter -e 'Facter.to_hash'

Instead, you have to do this:

ruby -rubygems -rfacter -e 'Facter.to_hash'

The reason is that rubygems installs in a location that Ruby doesn't search by default. The reason for that is that apparently this one guy, somewhere, wanted to have multiple versions of a given package installed at once. Who wants this? Let's just say it's not the guys who are distributing hundreds and thousands of copies of their software.

The truth is, most Rubyists don't even seem to use gems this way -- they tend to create a vendor subdirectory in their project, and then install their gems there. This is a clear example of how little they expect to have their projects distributed. These gems might be compiled, they might conflict with installed software, they might require installed software -- you have no idea, because it's an entirely separate repository of packages.

This is basically anathema to how I think about management, yet it's the standard, recommended practice in the Rails community, because it makes it easy to "guarantee" behaviour in a given environment. Of course, your guarantee is only good if no one ever tries to run the software anywhere except an exact duplicate of where you run it.

I tried to have a conversation about this at the Ruby Hoedown last year -- my claim was it was difficult to turn a Rails project into a native package, especially with the tendency toward requiring all kinds of random gems. Quite a few people kind of stared blankly at me and said, multiple times, "I just put it in vendor." Since then, this has become my go-to phrase for describing the Ruby way of solving distribution problems: "I just put it in vendor." I keep waiting for someone to try to put their kernel or web browser in vendor: "We only support the Firefox copy in vendor, sorry."

I don't know if other communities are any better at this. From what I can tell, this is basically how the Java community behaves, too. They have pathologically bad distribution systems, and as a language it seems to be most influenced by consulting shops developing huge, worthless software projects for large enterprises, rather than developing distributed applications that will be installed in thousands of locations.

I'd like to think that Puppet would have some counter-affect to this. It's one of the largest and most sophisticated publicly available Ruby projects, it's already installed in at least hundreds and probably thousands of places, and it does a pretty good job of working nearly everywere. However, I keep getting blank stares when I talk about this with other Rubyists, half the time I'm called a troll for even bringing it up, and when I explain why Puppet exists to most Rubyists, they just say, "I just put it in vendor", or, maybe, "Why not just use Capistrano?" To that I ask, how do you install Capistrano, but you know what they say to that.

I think Rails is a big part of the problem. Rails is clearly created by a company that will never distribute its software, and the Rails philosophy is again almost pathologically opposed to the idea of turning your software into a package. Imagine trying to make a Rails project LSB compliant -- your database.yaml file would need to be in /etc, your log directory would need to be in /var, and your actual code would need to be in /usr. There went all of your fancy Rails "convention over configuration", and you're suddenly fighting Rails instead of using it, and everyone you ask for help just tells you to "put it in vendor".

I'm looking at creating a new application that I'm planning on distributing, and one of my big goals is to be able to distribute the core in one package and various additional pieces of functionality as separate packages. I'll need to simultaneously support as many of my customer platforms as I can and provide a consistent operating environment for my packages. The only way to do this is to have supported operating environments with well-defined dependencies, such as you can almost trivially build in Debian or Red Hat.

For those of you who are thinking, "you could just put it in vendor", or "you could at least use gems", No, I couldn't. Take a trivial example: Say I want to use RRD support in my application (which is likely, in this case). There is Ruby support for RRD, but not in Gem form. Even if there were a gem, though, it would require a native RRDTool package, and, of course, Gems can't specify dependencies on native packages, so I'd be telling my customers, "well, install X gems and Y packages".

Instead, if I use native packages (say, those for Debian and Red Hat, to cover most cases), I can define clear dependencies for all cases. I know Debian provides everything I need, and in the rare case it doesn't, I can provide my own apt repository (and the same for yum). Gems, on the other hand, can really only do Ruby stuff. No, I don't actually want to put glibc in vendor, thanks.

I don't see a solution to this, other than getting more Rubyists distributing their software, but I'd really like to see this issue begin to be approached by the community. I feel like a wolf howling in the wilderness at this point, and if often feels like I'm fighting against my community in order to produce software that hundreds or thousands of people will install, as opposed to just use over the web.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Mon, 05 May 2008 | Tags: , , ,


Git branch in your bash prompt


Kevin Barnes has posted his mechanism for getting the current branch of the git repository into his bash prompt.

He mentions color in his article, and it turns out I'm the person who added the color, so I figured I'd post my version.

Here are the functions I have:

git_current_branch()
{
  git branch 2>/dev/null | sed -n '/^\*/ s/^\* //p'
}

git_display()
{
  br=$(git_current_branch)
  if [ -n br ]; then
      echo $br | BRANCH="$br" GIT_COLOR=$(git_color) awk '{if ($1) { print ENVIRON["GIT_COLOR"] ENVIRON["BRANCH"] " " } }'
  fi
}

git_color()
{
    git status 2>/dev/null | grep -c : | awk '{if ($1 > 0) { print ENVIRON["ORANGE"] } else { print ENVIRON["PINK"] } }'
}

And then here's my prompt:

title="033]0;h:W007" PS1="$title[$(git_display)$GREENw$NOCOLOR]nu@h("'$?'") $ "

First, the git bits. As Kevin mentions, I color the branch name; I use orange if I've got modified files, and pink if I don't (these are names that I map elsewhere to terminal codes). The three functions provide the three, um, functions: See what branch I'm on, see whether there are uncommitted files, and colorize the branch name.

Now, the bash bits.

First, you'll notice I have a multi-line prompt. I first started this when I switched to a color prompt, because for a while there bash didn't like the hidden characters that add color. I got to choose between a multi-line prompt, or a prompt that wrapped in broken ways. Since I really only wanted color in the path, I put that on the upper line, avoiding most of the wrapping problems. It was worth it, because (especially when I was still a sysadmin by trade) having color, almost any color, in the prompt makes it easy to pick out my commands from command output.

Now that I've had my prompt this way for about 4 years, I'm pretty fond of it. I think the bash wrapping problems are mostly fixed now, but I'm keepin git.

The $title sets the terminal title (which is slightly useful).

So, that's how I add the git branch, colored by whether I have uncommitted files, to my prompt, with a bit more prompt info thrown in for kicks.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Mon, 05 May 2008 | Tags: , ,


Reductive Labs should fill out the Little 4


Michael Cote and John Willis have been talking for a while about the Little 4 in management software, and it looks like Qlusters is no longer on the list.

I'd love to be able to comfortably say that Reductive Labs deserves to fill that fourth slot (you can have your say too). The truth is, of course, that all of those companies are far larger than Reductive Labs, and they've all successfully gotten investment while we have not (although we haven't tried all that hard). The products of the companies are pretty dissimilar, too -- they're mostly more focused on monitoring rather than what I would call management, as far as I can tell.

On the other hand, Puppet has a lot of traction, and is a clear leader in its space. We've been profitable since almost the beginning (which is to say, profitable enough to pay my meagre wages), and we've got a great and growing community. Now that Andrew Shafer has joined the company full-time as a partner, I do think we're going to start growing, and it's well-timed in terms of how the community is developing.

I do hope we grow this year, but I don't really know how we will. I'm still considering how hard we should be seeking investment, but it seems that VCs are pretty uninterested in infrastructure (or maybe they're just uninterested in me). Really, I'm hoping I can just get a big enough customer base that Andrew and I can build a bigger development team and start doing some of the almost-obvious but really interesting projects to enhance the Puppet ecosystem, like change control applications.

But the summary is, I want to deserve to be in the Little 4, and I think Puppet is popular enough that we just might, but there's still lots more to do.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Mon, 21 Apr 2008 | Tags: ,


Closing the Whisky Experiment


It's been a bit more than a month since I started my Double Blind Whisky Taste Test, and I've decided it's time to call it. I've drunk a scarily large amount of the whisky, but science is a harsh mistress and she will not be denied. Those of you who are sitting at home complaining that I haven't fixed your bug, it's because I was drunk from too much experimentation.

I've posting my reviews to Twitter, which in some ways adds a third blindness: I didn't know how I reviewed a given Scotch previously.

I'm writing this having not yet looked at the key that will tell me which Scotch is which. Here are my reviews so far (using my arbitrary decanter names). I'm actually taking a small drink of each as I write, just to remind myself and do one last review. These reviews are in order of perceived strength, so that I'm going from weakest to strongest.

My comments will also try to mention what I would do with these whiskies; this is because I've still got decanters with a lot of whisky in them, and really, I probably won't drink them all straight, which is all I would normally do with a single malt.

My primary goal in this review was to find that single peaty Scotch that I could put in my liquor cabinet (currently virtual, since the liquor is kind of scattered wherever there is storage).

Okay, so now we know what I think about each of the whiskies, it's time to get the key out and see what is what. First, though, I will give my guesses as to what is what. Here's my estimate:

And now, without further ado, here's the real key:

So, I got two correct but mixed two of them. The clear winner in the "best peaty Islay malt" category is Lagavulin, which is unfortunate, since it's about $20 more per bottle than any of the others (assuming you can find Caol Ila, which I had to buy at the Duty Free in Melbourne). The Caol Ila gets an honorable mention as the easiest to drink.

Looks like I've been harshing on the Ardbeg this whole time, and it's the Laphroaig that has the sourness I didn't particularly like.

And with that, the experiment closes. Now I can finally drink some of the Caol Ila 18yr I have -- I couldn't drink it while the experiment was on, since it would likely have clued me into which decanter had the 12yr.

In the name of Science (and great whisky), Luke

[1]This decanter has a square bottom and a kind of oval top, so it looks kind of like a D sitting on its back. Really, though, it gets its name from the movie 8 Mile -- I have a friend from Detroit who said it was an accurate portrayal of Detroit, and ever since I've harassed him about how Eminem calls the city "The D".

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Sun, 13 Apr 2008 | Tags: ,


ArsTechnica Launches Forum on Large-Scale IT


(by Luke) ArsTechnica, my favorite tech news site, has just announced the launch of a forum dedicated to the discussion of large-scale IT:

The forum has been in beta for a few weeks, and already there are some great discussions happening. We envision The Server Room as a place to discuss IT topics that don't already have a dedicated forum, and are of an IT nature. Users are already talking about virtualization, storage, disaster recovery, and systems design. IT hardware discussion is appropriate as a function of the plan.

So go join the Server Room and tell 'em how great Puppet is.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Wed, 09 Apr 2008 | Tags: , , ,


Mountain West Ruby Conference


(By Drew)

I've been meaning to post this for a while, but we didn't have a good way to make this a multi-author blog. (We still don't, which is only slightly less lame than the fact that I haven't put anything up about the conference)

http://www.reductivelabs.com/images/mtnwestrubyconf-attendee-badge-170.png

I spent the weekend before last at the Mountain West Ruby Conference (March 28-29).

This was the first language centric conference I have attended and I would recommend getting involved to anyone who wants to expand their understanding of Ruby and programming.

These conferences aren't going to help someone find all the answers, but they will help you start asking the right questions, and you couldn't beat the price of MWRC. $100 for a t-shirt, 2 lunches, 2 full days of talks, plus 2 party/hackfests with a fridge packed full of redbull and a room packed with passionate programmers.

Just doesn't get much better than that. . .

The speakers topics ranged from whimsical to deep magic. Some of my favorites were Giles Bowkett on metprogramming/code generation(who had everyone monkey patching sombreros into the presentations), the keynote by Jim Weirich, and Patrick Farley on Ruby internals. The most practical presentation, at least for me and Puppet, was Philippe Hanrigou's talk on using GDB and DTrace to troubleshoot running processes.

There are some excellent resources on Phillipe's website. Anyone interested in getting insight into running Ruby processes should take advantage of what he has available.

All the speakers are online. (Thanks Confreaks and the sponsors)

These aren't the $10 dollar burritos you are looking for
http://www.reductivelabs.com/images/sombrero.jpg

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Tue, 08 Apr 2008 | Tags: , , ,


Checking whether classes have been evaluated


Another snippet I used for testing.

Bug #1165 requests the ability to check for whether a class exists, but we've already got that:

class yay {
    notify { "This is a test": }
}

class other {
    if defined(Class[yay]) {
        notify { "Defined works just fine": }
    }
}

include yay, other

Prints:

notice: This is a test
notice: Defined works just fine

Works for me. :)

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Thu, 03 Apr 2008 | Tags: , , ,


Caching and REST


(by Luke)

One of the things I was supposed to write about last week was how I'm rethinking some of Puppet's internal caching. This rethinking is a direct result of listening to ThoughtWork's IT Matters Podcast on REST (I've only listened to part 1 so far). I actually listened to the episode three times, because it's only about 20 minutes and I listened to it on a 60 minute bike ride, which worked well because it was so windy that day that I didn't hear the whole thing any of those listenings.

I'll hopefully write later about how this podcast made me rethink how environments are used in fileserving, but for now, I'm going to focus on caching.

Indirection

For a couple of months now, Puppet has had an Indirector module that is basically useful for connecting classes with collections of instances of those classes. The only reason you'd really even bother to use it is if you had multiple collections, and needed to interact with different collections at different times, but you wanted those differences to be transparent.

For instance, when retrieving node information, you just call this code:

Puppet::Node.find("mynode")

Somewhere else, you'll have configured which collection (the word I'm currently using is terminus) this uses, and the Indirector just delegates the find call to the right collection. For nodes, you might be using the exec collection, which calls an external script, turns the resulting YAML into a Node instance, and returns it (or returns nil if nothing was found).

I think the Indirector is pretty cool, and it's certainly simplified a lot of my modeling of interacting with different sources of information. Those who are familiar with REST, at least how it's usually done in the Ruby world, will recognize the find as one of the methods usually used for REST interfaces -- it's mapped to the HTTP verb get. One of the primary design goals of the Indirector was to facilitate REST interfaces, so the methods we're indirecting are, not coincidentally, exactly the methods you'd implement for REST support.

Caching

One of the later additions to the indirection code was support for cache collections. That is, you might have a canonical collection, and then a cache collection for speed or proximity purposes. Following our Node example above, if you were using the exec collection, you'd probably want to have the results cached in the yaml collection, so they were inexpensive to retrieve.

The critical question with any caching system is how to know when the cache is dirty. How do you know if you should use the cached node information or go back to the source?

I expect there are as many answers to this question as there are caching implementations, just about. I had never implemented a caching solution before, and I probably misinterpreted my discussions with Rick Bradley, because I ended choosing a not-very-good system. The current cache invalidation mechanism is based on relative versions: If the version of the cached object is older than the version of the object in the other collection, then your cache is dirty.

What is a version? Well, normally it's just the timestamp of when the instance was created. This might work okay for some systems, but in general, the timestamp ends up being pretty useless. Look at our Node example -- the timestamp of the exec collection is always later, because we retrieve the cache version, then generate a new node using the exec collection, and compare. Duh. The answer's always the same.

Even worse, in most situations the cache doesn't save you any work, because you're pulling fresh data from the original source. If we have to re-execute the external node script to get the latest node version, we haven't saved any effort at all, we've just added a bunch of useless work, which is stupid.

Puppet 0.24.4 "fixed" this problem by saying that the cached node's version was the timestamp of the node's Facts cache. If the facts are updated, then the cache needs to be updated. This seems to mostly work, but it feels like a hack for something that should be easy.

TTL

So, on to the podcast. It was a good podcast in general, and they focused a good bit on caching. At first I found this pretty strange -- why is caching an important design criterion? As they talked, though, I realized that a generalized, simple caching model is useful a lot more places than I would expect, including in Puppet.

There didn't seem to be any disagreement over the best way to handle knowing when a cache is dirty -- they apparently just use time-to-live (TTL) or expiration headers. I think it was the second time listening through that I realized that the vast majority of my caching problems could be fixed with this.

Puppet has a natural TTL for most of its information -- every host runs every half an hour, so if you set a TTL of half an hour (or whatever you're run interval is), then you'll get fresh data once a run, and cached data the rest of the time. In the above Node scenario, the exec collection would set the TTL of the node (so that your external node app could pick its own TTL), or Puppet would have a default TTL equal to the run interval. Then, when Puppet goes to check whether its cache was dirty, it could just compare the TTL against the current time -- no need to hit both collections, and no arbitrary definition of "version".

This actually makes even more sense with the current problem I'm trying to solve. I'm trying to remodel the SSL certificate signing process, and it's gotten pretty messy. With this, though, you just set the TTL of the certificate to its own internal TTL, and you use the local system as the cache the CA server as the ultimate source. If there is a local cert and it's still valid, use it; if there's a local cert but we're past its TTL, then discard it and get a fresh cert; if there's no cert, then get one from the server and cache it locally.

Next Steps

I don't have the whole thing figured out mentally yet, but I'm pretty close. At the least, the next step is to replace the current broken version-based cache with ttl-based caching. The two things I most need to resolve are:

Obviously, these two things are linked -- the user needs a complete configuration path from the command line or configuration file to the bit that actually sets the ttl.

For now, fortunately, I don't need to worry about it, because I can just stick with the run interval as the TTL for essentially everything I'm doing. As things get more interesting, though, we're going to want to configure these values, because....

TTL Can Help Provide Change Control

One of my primary goals in moving the catalog compiling process to REST is to enable a decoupling between compiling and applying. In other words, I want people to be able to apply a configuration without recompiling.

Imagine a configuration TTL of a week -- every host recompiles its configuration during some specific maintenance window, like Sunday morning between 2 and 6 am. They still apply their configurations every half an hour, but that's normally just validating that nothing has drifted.

Obviously, this wouldn't be used by most shops -- most people would still want all hosts to recompile every time. But for those shops that are highly worried about change control, or those who want to do rolling upgrades, where they upgrade 10% of a pool of servers at a time, this would help a lot. You take your pool of servers, trigger a recompile on 10%, and once you're confident they're working, you trigger a recompile on another 10%, and so on.

Once you can do that with Puppet, it'll feel almost enterprisey. :)

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Wed, 02 Apr 2008 | Tags: , , , , , , , ,


[1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26  >>