Class: Vmth
- Inherits:
-
Object
- Object
- Vmth
- Defined in:
- lib/vmth.rb
Overview
This class provides a VM test harness to allow testing of operational code (puppet policies, chef configs, etc..) against a environment similar to your production environment.
The VM test harness uses features of the VM monitor (qemu) to freeze and re-use system memory/disk state so that a series of test scenarios can be rapidly tested.
This class provides all the logic to implement the VM test harness. It manages the VM, loads and runs tests for each scenario, and produces a ‘results’ hash with the results of the test.
Constant Summary collapse
- DEFAULT_OPTIONS =
new takes no arguments.
{ :source_path => ".", :vmm_enabled => true, :config_file => nil, :scenarios_file => nil, :image_file => nil, :debug => false, :action => 'all', :outfile => self.class.to_s.downcase+"_out.yaml", :out_format => 'text', :services => [] }
Instance Attribute Summary collapse
-
#image_file ⇒ Object
The system/disk image file booted by QEMU.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#results ⇒ Object
readonly
Contains the hash of all test output and results.
-
#scenarios_file ⇒ Object
The machdb services.yaml location.
-
#source_dir ⇒ Object
Set the directory where your puppet code directory is.
-
#vmm_enabled ⇒ Object
A boolean, should we use QEMU or not? Should almost always be true.
-
#vmm_r ⇒ Object
So we can flush this if there’s an error.
Instance Method Summary collapse
-
#cleanup ⇒ Object
Cleanup state file, but only if everything is done!.
- #cleanup_private_disk ⇒ Object
-
#console ⇒ Object
Set up a vm, and drop it off for a developer to use.
- #create_private_disk ⇒ Object
-
#eb(string) ⇒ Object
Expand a string with ERB.
-
#initialize(options = {}) ⇒ Vmth
constructor
A new instance of Vmth.
-
#loglevel=(level) ⇒ Object
Change the loglevel of the logger.
-
#test_all ⇒ Object
(also: #test)
Test all testable services - this is indicated by if a service in machdb has the ‘testable’ field set to true.
-
#test_services(services) ⇒ Object
Test a bunch of services.
-
#test_without_vm ⇒ Object
Really only for development/testing of this class.
-
#vmcl ⇒ Object
Return the command-line that would have been used to start QEMU.
Constructor Details
#initialize(options = {}) ⇒ Vmth
Returns a new instance of Vmth.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/vmth.rb', line 76 def initialize(={}) @options=DEFAULT_OPTIONS.merge() @config = YAML.load_file(File.dirname(__FILE__)+'/defaults.yaml') if @options[:config_file] @config.merge!(YAML.load_file(@options[:config_file])) end @log = Logger.new(STDERR) if @options[:debug] @log.level = Logger::DEBUG else @log.level = Logger::WARN end @tmp_state = Tempfile.new("pth").path @results = { 'tests' => {} } ssh_port_range=@config['vmm']['ssh_port_start']..@config['vmm']['ssh_port_end'] @vm_ssh_port = Vmth.allocate_tcp_port(ssh_port_range) vnc_port_range=@config['vmm']['vnc_port_start']..@config['vmm']['vnc_port_end'] @vm_vnc_port = Vmth.allocate_tcp_port(vnc_port_range) - 5900 @vm_mac_addr = @config['vmm']['mca_start'] + "%02x" % (rand()*256).round @vmm_prompt = eb(@config['vmm']['prompt']) @vmm_timeout = @config['vmm']['timeout'] @image_file=@options[:image_file] @source_path=@options[:source_path] # Try to cleanly shutdown the vmm. trap("INT") do if @vm_running stop_vm() end raise end end |
Instance Attribute Details
#image_file ⇒ Object
The system/disk image file booted by QEMU
53 54 55 |
# File 'lib/vmth.rb', line 53 def image_file @image_file end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
59 60 61 |
# File 'lib/vmth.rb', line 59 def @options end |
#results ⇒ Object (readonly)
Contains the hash of all test output and results. Read this after you’ve completed your test run.
56 57 58 |
# File 'lib/vmth.rb', line 56 def results @results end |
#scenarios_file ⇒ Object
The machdb services.yaml location. Describes which services should be tested.
51 52 53 |
# File 'lib/vmth.rb', line 51 def scenarios_file @scenarios_file end |
#source_dir ⇒ Object
Set the directory where your puppet code directory is.
47 48 49 |
# File 'lib/vmth.rb', line 47 def source_dir @source_dir end |
#vmm_enabled ⇒ Object
A boolean, should we use QEMU or not? Should almost always be true.
49 50 51 |
# File 'lib/vmth.rb', line 49 def vmm_enabled @vmm_enabled end |
#vmm_r ⇒ Object
So we can flush this if there’s an error.
58 59 60 |
# File 'lib/vmth.rb', line 58 def vmm_r @vmm_r end |
Instance Method Details
#cleanup ⇒ Object
Cleanup state file, but only if everything is done!
199 200 201 |
# File 'lib/vmth.rb', line 199 def cleanup File.delete(@tmp_state) rescue nil end |
#cleanup_private_disk ⇒ Object
191 192 193 194 195 196 |
# File 'lib/vmth.rb', line 191 def cleanup_private_disk @log.debug "Removing tmp imagefile #{@image_file}" if defined?(@orig_image_file) and @orig_image_file != @image_file File.delete(@image_file) end end |
#console ⇒ Object
Set up a vm, and drop it off for a developer to use.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/vmth.rb', line 158 def console create_private_disk @results['test_start'] = Time.now() passed = [] boot_vm() if @options[:vmm_enabled] prep freeze_vm() if @options[:vmm_enabled] # Print out ssh & vnc port, and freeze name. @log.info "Handing off VM to you.. Type #{@config['vmm']['quitvmm']} to end session." @log.info "Ports - SSH: #{@vm_ssh_port} VNC: #{@vm_vnc_port}" # hand off console. print @config['vmm']['prompt'] begin system('stty raw -echo') Thread.new{ loop { @vmm_w.print $stdin.getc.chr } } loop { $stdout.print @vmm_r.readpartial(512); STDOUT.flush } rescue nil # User probably caused the VMM to exit. ensure system "stty -raw echo" end # Done via the user? # stop_vm() cleanup_private_disk return end |
#create_private_disk ⇒ Object
185 186 187 188 189 190 |
# File 'lib/vmth.rb', line 185 def create_private_disk @orig_image_file = @image_file @image_file = "#{@orig_image_file}.#{$$}" @log.debug "Copying #{@orig_image_file} to #{@image_file}" FileUtils.cp(@orig_image_file,@image_file) end |
#eb(string) ⇒ Object
Expand a string with ERB.
110 111 112 113 |
# File 'lib/vmth.rb', line 110 def eb(string) renderer = ERB.new(string) return renderer.result(binding) end |
#loglevel=(level) ⇒ Object
Change the loglevel of the logger. Argument should be a loglevel constant, i.e. Logger::INFO
116 117 118 |
# File 'lib/vmth.rb', line 116 def loglevel=(level) @log.level=level end |
#test_all ⇒ Object Also known as: test
Test all testable services - this is indicated by if a service in machdb has the ‘testable’ field set to true. It takes no arguments and returns an array of booleans, indicating the success or failure of tests. You should query results() for your results.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/vmth.rb', line 123 def test_all @results['test_start'] = Time.now() passed = [] boot_vm() if @options[:vmm_enabled] prep freeze_vm() if @options[:vmm_enabled] @log.info "RUNNING NO-SERVICE TEST" passed << one_test(@config['init_scenario']) # Stop testing if our initial test fails. unless passed.first == true @log.error "Initial setup failed.. sleeping 60 seconds for debugging." sleep 60 stop_vm() if @options[:vmm_enabled] return passed end freeze_vm() if @options[:vmm_enabled] @log.info "RUNNING TESTS" scenarios = get_scenarios test_counter = 0 scenarios.each do |scenario| test_counter += 1 @log.info "Running test for #{scenario} - #{test_counter} of #{scenarios.size}" passed << one_test(scenario) end stop_vm() if @config[:vmm_enabled] all_passed = passed.select{|p| p == false}.size == 0 @log.info "Number of tests run : #{passed.size}" @log.info "Result of ALL tests: Passed? #{all_passed}" @results['test_stop'] = Time.now() @results['elapsed_time'] = @results['test_stop'] - @results['test_start'] return all_passed end |
#test_services(services) ⇒ Object
Test a bunch of services. Pass in an array containing the names of services to test. Returns an array of booleans, indicating the success or failure of the tests. You should read detailed results from results()
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/vmth.rb', line 213 def test_services(services) @results['test_start'] = Time.now() boot_vm() if @options[:vmm_enabled] prep freeze_vm() if @options[:vmm_enabled] passed = [] @log.info "RUNNING NO-SERVICE TEST" passed << one_test(eb(@config["init_scenario"])) # Stop testing if our initial test fails. unless passed.first == true stop_vm() if @options[:vmm_enabled] return passed end freeze_vm() if @options[:vmm_enabled] @log.info "RUNNING TESTS" test_counter = 0 services.each do |service| test_counter += 1 @log.info "Running test for #{service} - #{test_counter} of #{services.size}" passed << one_test(service) end stop_vm() if @options[:vmm_enabled] @results['test_stop'] = Time.now() @results['elapsed_time']= @results['test_stop'] - @results['test_start'] return passed end |
#test_without_vm ⇒ Object
Really only for development/testing of this class. Will run tests against an already running VM (presumably the developer is running it in another window)
205 206 207 208 |
# File 'lib/vmth.rb', line 205 def test_without_vm prep test_services end |
#vmcl ⇒ Object
Return the command-line that would have been used to start QEMU. This can be used for developing this library, or to get a new disk image prepped to be used with the test harness.
242 243 244 |
# File 'lib/vmth.rb', line 242 def vmcl return vmm_command_line end |