{"id":1356,"date":"2010-03-27T17:16:38","date_gmt":"2010-03-27T16:16:38","guid":{"rendered":"http:\/\/www.devco.net\/?p=1356"},"modified":"2010-08-17T12:14:36","modified_gmt":"2010-08-17T11:14:36","slug":"infrastructure_testing_with_mcollective_and_cucumber","status":"publish","type":"post","link":"https:\/\/www.devco.net\/archives\/2010\/03\/27\/infrastructure_testing_with_mcollective_and_cucumber.php","title":{"rendered":"Infrastructure testing with MCollective and Cucumber"},"content":{"rendered":"
Some time ago I showed some sample code I had for driving MCollective with Cucumber<\/a>. Today I’ll show how I did that with SimpleRPC<\/a>.<\/p>\n Cucumber<\/a> is a testing framework, it might not be the perfect fit for systems scripting but you can achieve a lot if you bend it a bit to your will. Ultimately I am building up to using it for testing, but we need to start with how to drive MCollective first.<\/p>\n The basic idea is that you wrote a SimpleRPC Agent<\/a> for your needs like the one I showed here<\/a>. The specific agent has a number of tasks it can perform:<\/p>\n These features are all baked into a single agent, perfect for driving from a set of Cucumber features. The sample I will show here is only driving the IPTables<\/a> agent since that code is public and visible.<\/p>\n First I’ll show the feature I want to build, we’re still concerned with driving the agent here not testing so much – though the steps are tested and idempotent:<\/p>\n <\/code><\/p>\n To realize the above we’ll need some setup code that fires up our RPC client and manage options in a single place, we’ll place this in in support\/env.rb<\/em>:<\/p>\n <\/code><\/p>\n First we load up the MCollective code and install it into the Cucumber World<\/a>, this achieves more or less what include MCollective::RPC<\/em> would in a Cucumber friendly way.<\/p>\n We then set some sane default options and start our RPC client.<\/p>\n Now we can go onto writing some steps, we store these in step_definitions\/mcollective_steps.rb<\/em>, first we want to capture some data like the load balancer IP and filters:<\/p>\n <\/code><\/p>\n Here we’re just creating a table of device names to ips and we manipulate the MCollective Filters<\/a>. Finally we do a discover and we check that we are actually matching any hosts. If your filters were not matching any nodes the cucumber run would bail out.<\/p>\n Now we want to first do the work to block and unblock the load balancers:<\/p>\n <\/code><\/p>\n We do some very basic sanity checks here, simply catching nodes that did not respond and bailing out if there are any. Key is to note that to actually manipulate firewalls on any number of machines is roughly 1 line of code.<\/p>\n Now that we’re able to block and unblock IPs we also need a way to confirm those tasks were 100% done:<\/p>\n <\/code><\/p>\n This code does actual verification that the clients have the IP blocked or not. This code also highlights that perhaps my iptables agent needs some refactoring, I have two if blocks that checks for the existence of a string pattern in the result, I could make the agent return Boolean in addition to human readable results. This would make using the agent easier to use from a program like this.<\/p>\n That’s all there is to it really, MCollective RPC makes reusing code very easy and it makes addressing networks very easy. <\/p>\n <\/p>\n The above code demonstrates how using MCollective+Cucumber you can address any number of machines, perform actions and get states within a testing framework. This seems an uncomfortable fit – since Cucumber is a testing framework – but it doesn’t need to be.<\/p>\n\n
<\/p>\n
\r\nFeature: Manage the iptables firewall\r\n\r\n Background:\r\n Given the load balancer has ip address 192.168.1.1\r\n And I want to update hosts with class \/dev_server\/\r\n And I want to update hosts with fact country=de\r\n And I want to pre-discover the nodes to manage\r\n \r\n Scenario: Manage the firewall\r\n When I block the load balancer\r\n Then traffic from the load balancer should be blocked\r\n\r\n # other tasks like package management, service restarts\r\n # and monitor tasks would go here\r\n\r\n When I unblock the load balancer\r\n Then traffic from the load balancer should be unblocked\r\n<\/pre>\n
<\/p>\n
\r\nrequire 'mcollective'\r\n\r\nWorld(MCollective::RPC)\r\n\r\nBefore do\r\n @options = {:disctimeout => 2,\r\n :timeout => 5,\r\n :verbose => false,\r\n :filter => {\"identity\"=>[], \"fact\"=>[], \"agent\"=>[], \"cf_class\"=>[]},\r\n :config => \"etc\/client.cfg\"}\r\n\r\n @iptables = rpcclient(\"iptables\", :options => @options)\r\n @iptables.progress = false\r\nend\r\n<\/pre>\n
<\/p>\n
\r\nGiven \/^the (.+) has ip address (\\d+\\.\\d+\\.\\d+\\.\\d+)$\/ do |device, ip|\r\n @ips = {} unless @ips\r\n\r\n @ips[device] = ip\r\nend\r\n\r\nGiven \/I want to update hosts with fact (.+)=(.+)$\/ do |fact, value|\r\n @iptables.fact_filter fact, value\r\nend\r\n\r\nGiven \/I want to update hosts with class (.+)$\/ do |klass|\r\n @iptables.class_filter klass\r\nend\r\n\r\nGiven \/I want to pre-discover the nodes to manage\/ do\r\n @iptables.discover\r\n\r\n raise(\"Did not find any nodes to manage\") if @iptables.discovered.size == 0\r\nend\r\n<\/pre>\n
<\/p>\n
\r\nWhen \/^I block the (.+)$\/ do |device|\r\n raise(\"Unknown device #{device}\") unless @ips.include?(device)\r\n\r\n @iptables.block(:ipaddr => @ips[device]) \r\n\r\n raise(\"Not all nodes responded\") unless @iptables.stats[:noresponsefrom].size == 0\r\nend\r\n\r\nWhen \/^I unblock the (.+)$\/ do |device|\r\n raise(\"Unknown device #{device}\") unless @ips.include?(device)\r\n\r\n @iptables.unblock(:ipaddr => @ips[device])\r\n\r\n raise(\"Not all nodes responded\") unless @iptables.stats[:noresponsefrom].size == 0\r\nend\r\n<\/pre>\n
<\/p>\n
\r\nThen \/^traffic from the (.+) should be blocked$\/ do |device|\r\n raise(\"Unknown device #{device}\") unless @ips.include?(device)\r\n\r\n unblockedon = @iptables.isblocked(:ipaddr => @ips[device]).inject(0) do |c, resp|\r\n c += 1 if resp[:data][:output] =~ \/is not blocked\/ \r\n end\r\n\r\n raise(\"Not blocked on: #{unblockedon} \/ #{@iptables.discovered} hosts\") if unblockedon \r\n raise(\"Not all nodes responded\") unless @iptables.stats[:noresponsefrom].size == 0\r\nend\r\n\r\nThen \/^traffic from the (.+) should be unblocked$\/ do |device|\r\n raise(\"Unknown device #{device}\") unless @ips.include?(device)\r\n\r\n blockedon = @iptables.isblocked(:ipaddr => @ips[device]).inject(0) do |c, resp|\r\n c += 1 if resp[:data][:output] =~ \/is blocked\/ \r\n end\r\n\r\n raise(\"Still blocked on: #{blockedon} \/ #{@iptables.discovered} hosts\") if blockedon \r\n raise(\"Not all nodes responded\") unless @iptables.stats[:noresponsefrom].size == 0\r\nend\r\n<\/pre>\n
Monitoring \/ Infrastructure Testing<\/h4>\n