Quick Functional Tests in Saltstack

SaltStack is pretty awesome. If you don’t know what it is and are looking for a configuration manangement tool, I highly recommend it.

All of the major configuration management tools (Puppet, Chef, Ansible, Salt) are mostly the same, but the saltstack community is great. The IRC channel on freenode is an excellent resource, and their github issue tracker is actively monitored if you submit a bug report (or even a question, although you shouldn’t use the issue tracker for questions).

Anyway, a project I’m working on has become complex enough that I really need to start writing functional tests. Salt has some built-in testing classes that you can use and extend but they are mostly for unit and integration testing to my knowledge. I’m mostly interested in simple functional testing right now — ie: After I run my “highstate”, that does a ton of complicated things to a cluster of 10 servers, each with unique roles, does the website respond correctly?

That is to say, I need to go above and beyond the result output / return status of my highstate runs and orchestrations and actually test if I seem to have achieved the state I desire on the remote minions.

I don’t need fancy testing, or continuous integration right now. What I need are just some simple functional tests. When I started thinking about this tonight I realized I would definitely need access to salt’s internals to get pillars, grains, minions, etc. After researching for only 10 minutes (which is pretty typical for me before I just start playing) I gave up and just started writing code. The best option seemed to be writing my own ‘salt-runner’.

My initial goal was just to write a simple test for http response on the resulting cluster for each website. Here is the salt-runner code /srv/salt/runners/test_websites.py:

# Import salt modules
import salt.client
import requests

def responding(tgt, outputter=None):
    local = salt.client.LocalClient()
    pillar = local.cmd(tgt, 'pillar.items')
    websites = pillar[tgt]['websites']    
    lb_ip = pillar[tgt]['lb']
    results = {}
    for website, data in websites.iteritems():
        if website != 'default':           
            headers = {
                'Host': website,
                'User-Agent': 'Salt Testing Agent'
            }
            r = requests.get('https://{0}'.format(lb_ip), headers=headers, verify=False)
            _results = {
                'status_code': r.status_code
            }
            results[website] = _results
    return {'outputter': outputter, 'data': results}

This goes in your runners_dir (for me that’s /srv/salt/runners] which is defined on your salt-master in your /etc/salt/master (or wherever your salt’s master configuration file is).

The example usage/output looks like:

# salt-run test_websites.responding web1.foo.fqdn.com
somesite1.com:
    ----------
    status_code:
        200
somesite2.com:
    ----------
    status_code:
        200
somesite3.com:
    ----------
    status_code:
        200

The script gets the pillar data from the ‘tgt’ specified on the command line, then makes an http request to the loadbalancer ip, passing in the host header for each site that should be responding.

This is specific to my case, but you can see how it’s a quick way for me to do “poor man’s functional testing” and ensure that specific aspect of the resulting state are actually being achieved.

I know this isn’t real functional testing in the true sense of the term. I’m doing this for now as a way to do regression testing against future bugs.

I hope that helps someone out there. If anyone has a better way to do this kind of thing I would love to hear it.

Bound Method Python

This is a mini post that only exists to hopefully help out people like me who are dumb sometimes.

If you are pulling your hair out trying to figure out why you are getting something like this:

<bound method MyClass.my_method of <__main__.MyClass instance at 0x7f8f2cac1710>>

Then you are probably trying to do something like this:

MyClass = MyClass()
print MyClass.my_method

You should be doing:

MyClass = MyClass()
print MyClass.my_method()

In the former code block, python was printing exactly what you told it to. It’s a lot like Ruby, in that everything is an object. It will happily “print” a class method for you and even give you the location in memory of it! But that’s not what you wanted. You wanted it to execute the class method.

I hope this saves someone out there some unnecessary troubleshooting.