beaker 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +14 -0
  5. data/DOCUMENTING.md +167 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +17 -0
  8. data/README.md +332 -0
  9. data/Rakefile +121 -0
  10. data/beaker.gemspec +42 -0
  11. data/beaker.rb +10 -0
  12. data/bin/beaker +9 -0
  13. data/lib/beaker.rb +36 -0
  14. data/lib/beaker/answers.rb +29 -0
  15. data/lib/beaker/answers/version28.rb +104 -0
  16. data/lib/beaker/answers/version30.rb +194 -0
  17. data/lib/beaker/cli.rb +113 -0
  18. data/lib/beaker/command.rb +241 -0
  19. data/lib/beaker/command_factory.rb +21 -0
  20. data/lib/beaker/dsl.rb +85 -0
  21. data/lib/beaker/dsl/assertions.rb +87 -0
  22. data/lib/beaker/dsl/helpers.rb +625 -0
  23. data/lib/beaker/dsl/install_utils.rb +299 -0
  24. data/lib/beaker/dsl/outcomes.rb +99 -0
  25. data/lib/beaker/dsl/roles.rb +97 -0
  26. data/lib/beaker/dsl/structure.rb +63 -0
  27. data/lib/beaker/dsl/wrappers.rb +100 -0
  28. data/lib/beaker/host.rb +193 -0
  29. data/lib/beaker/host/aix.rb +15 -0
  30. data/lib/beaker/host/aix/file.rb +16 -0
  31. data/lib/beaker/host/aix/group.rb +35 -0
  32. data/lib/beaker/host/aix/user.rb +32 -0
  33. data/lib/beaker/host/unix.rb +54 -0
  34. data/lib/beaker/host/unix/exec.rb +15 -0
  35. data/lib/beaker/host/unix/file.rb +16 -0
  36. data/lib/beaker/host/unix/group.rb +40 -0
  37. data/lib/beaker/host/unix/pkg.rb +22 -0
  38. data/lib/beaker/host/unix/user.rb +32 -0
  39. data/lib/beaker/host/windows.rb +44 -0
  40. data/lib/beaker/host/windows/exec.rb +18 -0
  41. data/lib/beaker/host/windows/file.rb +15 -0
  42. data/lib/beaker/host/windows/group.rb +36 -0
  43. data/lib/beaker/host/windows/pkg.rb +26 -0
  44. data/lib/beaker/host/windows/user.rb +32 -0
  45. data/lib/beaker/hypervisor.rb +37 -0
  46. data/lib/beaker/hypervisor/aixer.rb +52 -0
  47. data/lib/beaker/hypervisor/blimper.rb +123 -0
  48. data/lib/beaker/hypervisor/fusion.rb +56 -0
  49. data/lib/beaker/hypervisor/solaris.rb +65 -0
  50. data/lib/beaker/hypervisor/vagrant.rb +118 -0
  51. data/lib/beaker/hypervisor/vcloud.rb +175 -0
  52. data/lib/beaker/hypervisor/vsphere.rb +80 -0
  53. data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
  54. data/lib/beaker/logger.rb +167 -0
  55. data/lib/beaker/network_manager.rb +73 -0
  56. data/lib/beaker/options_parsing.rb +323 -0
  57. data/lib/beaker/result.rb +55 -0
  58. data/lib/beaker/shared.rb +15 -0
  59. data/lib/beaker/shared/error_handler.rb +17 -0
  60. data/lib/beaker/shared/host_handler.rb +46 -0
  61. data/lib/beaker/shared/repetition.rb +28 -0
  62. data/lib/beaker/ssh_connection.rb +198 -0
  63. data/lib/beaker/test_case.rb +225 -0
  64. data/lib/beaker/test_config.rb +148 -0
  65. data/lib/beaker/test_suite.rb +288 -0
  66. data/lib/beaker/utils.rb +7 -0
  67. data/lib/beaker/utils/ntp_control.rb +42 -0
  68. data/lib/beaker/utils/repo_control.rb +92 -0
  69. data/lib/beaker/utils/setup_helper.rb +77 -0
  70. data/lib/beaker/utils/validator.rb +27 -0
  71. data/spec/beaker/command_spec.rb +94 -0
  72. data/spec/beaker/dsl/assertions_spec.rb +104 -0
  73. data/spec/beaker/dsl/helpers_spec.rb +230 -0
  74. data/spec/beaker/dsl/install_utils_spec.rb +70 -0
  75. data/spec/beaker/dsl/outcomes_spec.rb +43 -0
  76. data/spec/beaker/dsl/roles_spec.rb +86 -0
  77. data/spec/beaker/dsl/structure_spec.rb +60 -0
  78. data/spec/beaker/dsl/wrappers_spec.rb +52 -0
  79. data/spec/beaker/host_spec.rb +95 -0
  80. data/spec/beaker/logger_spec.rb +117 -0
  81. data/spec/beaker/options_parsing_spec.rb +37 -0
  82. data/spec/beaker/puppet_command_spec.rb +128 -0
  83. data/spec/beaker/ssh_connection_spec.rb +39 -0
  84. data/spec/beaker/test_case_spec.rb +6 -0
  85. data/spec/beaker/test_suite_spec.rb +44 -0
  86. data/spec/mocks_and_helpers.rb +34 -0
  87. data/spec/spec_helper.rb +15 -0
  88. metadata +359 -0
@@ -0,0 +1,80 @@
1
+ module Beaker
2
+ class Vsphere < Beaker::Hypervisor
3
+
4
+ def initialize(vsphere_hosts, options, config)
5
+ @options = options
6
+ @@config = config['CONFIG'].dup
7
+ @logger = options[:logger]
8
+ @vsphere_hosts = vsphere_hosts
9
+ require 'yaml' unless defined?(YAML)
10
+ vsphere_credentials = VsphereHelper.load_config
11
+
12
+ @logger.notify "Connecting to vSphere at #{vsphere_credentials[:server]}" +
13
+ " with credentials for #{vsphere_credentials[:user]}"
14
+
15
+ vsphere_helper = VsphereHelper.new( vsphere_credentials )
16
+
17
+ vsphere_vms = {}
18
+ @vsphere_hosts.each do |h|
19
+ name = h["vmname"] || h.name
20
+ vsphere_vms[name] = h["snapshot"]
21
+ end
22
+ vms = vsphere_helper.find_vms(vsphere_vms.keys)
23
+ vsphere_vms.each_pair do |name, snap|
24
+ unless vm = vms[name]
25
+ raise "Couldn't find VM #{name} in vSphere!"
26
+ end
27
+
28
+ snapshot = vsphere_helper.find_snapshot(vm, snap) or
29
+ raise "Could not find snapshot '#{snap}' for VM #{vm.name}!"
30
+
31
+ @logger.notify "Reverting #{vm.name} to snapshot '#{snap}'"
32
+ start = Time.now
33
+ # This will block for each snapshot...
34
+ # The code to issue them all and then wait until they are all done sucks
35
+ snapshot.RevertToSnapshot_Task.wait_for_completion
36
+
37
+ time = Time.now - start
38
+ @logger.notify "Spent %.2f seconds reverting" % time
39
+
40
+ unless vm.runtime.powerState == "poweredOn"
41
+ @logger.notify "Booting #{vm.name}"
42
+ start = Time.now
43
+ vm.PowerOnVM_Task.wait_for_completion
44
+ @logger.notify "Spent %.2f seconds booting #{vm.name}" % (Time.now - start)
45
+ end
46
+ end
47
+
48
+ vsphere_helper.close
49
+ end
50
+
51
+ def cleanup
52
+ @logger.notify "Destroying vsphere boxes"
53
+ vsphere_credentials = VsphereHelper.load_config
54
+
55
+ @logger.notify "Connecting to vSphere at #{vsphere_credentials[:server]}" +
56
+ " with credentials for #{vsphere_credentials[:user]}"
57
+
58
+ vsphere_helper = VsphereHelper.new( vsphere_credentials )
59
+
60
+ vm_names = @vsphere_hosts.map {|h| h['vmname'] || h.name }
61
+ vms = vsphere_helper.find_vms vm_names
62
+ vm_names.each do |name|
63
+ unless vm = vms[name]
64
+ raise "Couldn't find VM #{name} in vSphere!"
65
+ end
66
+
67
+ if vm.runtime.powerState == "poweredOn"
68
+ @logger.notify "Shutting down #{vm.name}"
69
+ start = Time.now
70
+ vm.PowerOffVM_Task.wait_for_completion
71
+ @logger.notify(
72
+ "Spent %.2f seconds halting #{vm.name}" % (Time.now - start) )
73
+ end
74
+ end
75
+
76
+ vsphere_helper.close
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,200 @@
1
+ require 'yaml' unless defined?(YAML)
2
+ require 'rubygems' unless defined?(Gem)
3
+ begin
4
+ require 'beaker/logger'
5
+ rescue LoadError
6
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'logger.rb'))
7
+ end
8
+
9
+ class VsphereHelper
10
+ def initialize vInfo = {}
11
+ @logger = vInfo[:logger] || Beaker::Logger.new
12
+ begin
13
+ require 'rbvmomi'
14
+ rescue LoadError
15
+ raise "Unable to load RbVmomi, please ensure its installed"
16
+ end
17
+ @connection = RbVmomi::VIM.connect :host => vInfo[:server],
18
+ :user => vInfo[:user],
19
+ :password => vInfo[:pass],
20
+ :insecure => true
21
+ end
22
+
23
+ def self.load_config
24
+ # support Fog/Cloud Provisioner layout
25
+ # (ie, someplace besides my made up conf)
26
+ vsphere_credentials = nil
27
+ if File.exists? '/etc/plharness/vsphere'
28
+ vsphere_credentials = load_legacy_credentials
29
+
30
+ elsif File.exists?( File.join(ENV['HOME'], '.fog') )
31
+ vsphere_credentials = load_fog_credentials
32
+ end
33
+
34
+ return vsphere_credentials
35
+ end
36
+
37
+ def self.load_fog_credentials
38
+ vInfo = YAML.load_file( File.join(ENV['HOME'], '.fog') )
39
+
40
+ vsphere_credentials = {}
41
+ vsphere_credentials[:server] = vInfo[:default][:vsphere_server]
42
+ vsphere_credentials[:user] = vInfo[:default][:vsphere_username]
43
+ vsphere_credentials[:pass] = vInfo[:default][:vsphere_password]
44
+
45
+ return vsphere_credentials
46
+ end
47
+
48
+ def self.load_legacy_credentials
49
+ vInfo = YAML.load_file '/etc/plharness/vsphere'
50
+
51
+ puts(
52
+ "Use of /etc/plharness/vsphere as a config file is deprecated.\n" +
53
+ "Please use ~/.fog instead\n" +
54
+ "See http://docs.puppetlabs.com/pe/2.0/" +
55
+ "cloudprovisioner_configuring.html for format"
56
+ )
57
+
58
+ vsphere_credentials = {}
59
+ vsphere_credentials[:server] = vInfo['location']
60
+ vsphere_credentials[:user] = vInfo['user']
61
+ vsphere_credentials[:pass] = vInfo['pass']
62
+
63
+ return vsphere_credentials
64
+ end
65
+
66
+ def find_snapshot vm, snapname
67
+ search_child_snaps vm.snapshot.rootSnapshotList, snapname
68
+ end
69
+
70
+ def search_child_snaps tree, snapname
71
+ snapshot = nil
72
+ tree.each do |child|
73
+ if child.name == snapname
74
+ snapshot ||= child.snapshot
75
+ else
76
+ snapshot ||= search_child_snaps child.childSnapshotList, snapname
77
+ end
78
+ end
79
+ snapshot
80
+ end
81
+
82
+ def find_customization name
83
+ csm = @connection.serviceContent.customizationSpecManager
84
+
85
+ begin
86
+ customizationSpec = csm.GetCustomizationSpec({:name => name}).spec
87
+ rescue
88
+ customizationSpec = nil
89
+ end
90
+
91
+ return customizationSpec
92
+ end
93
+
94
+ # an easier wrapper around the horrid PropertyCollector interface,
95
+ # necessary for searching VMs in all Datacenters that may be nested
96
+ # within folders of arbitrary depth
97
+ # returns a hash array of <name> => <VirtualMachine ManagedObjects>
98
+ def find_vms names, connection = @connection
99
+ names = names.is_a?(Array) ? names : [ names ]
100
+ containerView = get_base_vm_container_from connection
101
+ propertyCollector = connection.propertyCollector
102
+
103
+ objectSet = [{
104
+ :obj => containerView,
105
+ :skip => true,
106
+ :selectSet => [ RbVmomi::VIM::TraversalSpec.new({
107
+ :name => 'gettingTheVMs',
108
+ :path => 'view',
109
+ :skip => false,
110
+ :type => 'ContainerView'
111
+ }) ]
112
+ }]
113
+
114
+ propSet = [{
115
+ :pathSet => [ 'name' ],
116
+ :type => 'VirtualMachine'
117
+ }]
118
+
119
+ results = propertyCollector.RetrievePropertiesEx({
120
+ :specSet => [{
121
+ :objectSet => objectSet,
122
+ :propSet => propSet
123
+ }],
124
+ :options => { :maxObjects => nil }
125
+ })
126
+
127
+ vms = {}
128
+ results.objects.each do |result|
129
+ name = result.propSet.first.val
130
+ next unless names.include? name
131
+ vms[name] = result.obj
132
+ end
133
+
134
+ while results.token do
135
+ results = propertyCollector.ContinueRetrievePropertiesEx({:token => results.token})
136
+ results.objects.each do |result|
137
+ name = result.propSet.first.val
138
+ next unless names.include? name
139
+ vms[name] = result.obj
140
+ end
141
+ end
142
+ vms
143
+ end
144
+
145
+ def find_datastore datastorename
146
+ datacenter = @connection.serviceInstance.find_datacenter
147
+ datacenter.find_datastore(datastorename)
148
+ end
149
+
150
+ def find_folder foldername
151
+ datacenter = @connection.serviceInstance.find_datacenter
152
+ base = datacenter.vmFolder
153
+ folders = foldername.split('/')
154
+ folders.each do |folder|
155
+ case base
156
+ when RbVmomi::VIM::Folder
157
+ base = base.childEntity.find { |f| f.name == folder }
158
+ else
159
+ abort "Unexpected object type encountered (#{base.class}) while finding folder"
160
+ end
161
+ end
162
+
163
+ base
164
+ end
165
+
166
+ def find_pool poolname
167
+ datacenter = @connection.serviceInstance.find_datacenter
168
+ base = datacenter.hostFolder
169
+ pools = poolname.split('/')
170
+ pools.each do |pool|
171
+ case base
172
+ when RbVmomi::VIM::Folder
173
+ base = base.childEntity.find { |f| f.name == pool }
174
+ when RbVmomi::VIM::ClusterComputeResource
175
+ base = base.resourcePool.resourcePool.find { |f| f.name == pool }
176
+ when RbVmomi::VIM::ResourcePool
177
+ base = base.resourcePool.find { |f| f.name == pool }
178
+ else
179
+ abort "Unexpected object type encountered (#{base.class}) while finding resource pool"
180
+ end
181
+ end
182
+
183
+ base = base.resourcePool unless base.is_a?(RbVmomi::VIM::ResourcePool) and base.respond_to?(:resourcePool)
184
+ base
185
+ end
186
+
187
+ def get_base_vm_container_from connection
188
+ viewManager = connection.serviceContent.viewManager
189
+ viewManager.CreateContainerView({
190
+ :container => connection.serviceContent.rootFolder,
191
+ :recursive => true,
192
+ :type => [ 'VirtualMachine' ]
193
+ })
194
+ end
195
+
196
+ def close
197
+ @connection.close
198
+ end
199
+ end
200
+
@@ -0,0 +1,167 @@
1
+ module Beaker
2
+ class Logger
3
+ NORMAL = "\e[00;00m"
4
+ BRIGHT_NORMAL = "\e[00;01m"
5
+ BLACK = "\e[00;30m"
6
+ RED = "\e[00;31m"
7
+ GREEN = "\e[00;32m"
8
+ YELLOW = "\e[00;33m"
9
+ BLUE = "\e[00;34m"
10
+ MAGENTA = "\e[00;35m"
11
+ CYAN = "\e[00;36m"
12
+ WHITE = "\e[00;37m"
13
+ GREY = "\e[01;30m"
14
+ BRIGHT_RED = "\e[01;31m"
15
+ BRIGHT_GREEN = "\e[01;32m"
16
+ BRIGHT_YELLOW = "\e[01;33m"
17
+ BRIGHT_BLUE = "\e[01;34m"
18
+ BRIGHT_MAGENTA = "\e[01;35m"
19
+ BRIGHT_CYAN = "\e[01;36m"
20
+ BRIGHT_WHITE = "\e[01;37m"
21
+
22
+ LOG_LEVELS = {
23
+ :debug => 1,
24
+ :warn => 2,
25
+ :normal => 3,
26
+ :info => 4
27
+ }
28
+
29
+ attr_accessor :color, :log_level, :destinations
30
+
31
+ def initialize(*args)
32
+ options = args.last.is_a?(Hash) ? args.pop : {}
33
+ @color = options[:color]
34
+ @log_level = options[:debug] ? :debug : :normal
35
+ @destinations = []
36
+
37
+ dests = args
38
+ dests << STDOUT unless options[:quiet]
39
+ dests.uniq!
40
+ dests.each {|dest| add_destination(dest)}
41
+ end
42
+
43
+ def add_destination(dest)
44
+ case dest
45
+ when IO
46
+ @destinations << dest
47
+ when String
48
+ @destinations << File.open(dest, 'w')
49
+ else
50
+ raise "Unsuitable log destination #{dest.inspect}"
51
+ end
52
+ end
53
+
54
+ def remove_destination(dest)
55
+ case dest
56
+ when IO
57
+ @destinations.delete(dest)
58
+ when String
59
+ @destinations.delete_if {|d| d.respond_to?(:path) and d.path == dest}
60
+ else
61
+ raise "Unsuitable log destination #{dest.inspect}"
62
+ end
63
+ end
64
+
65
+ def is_debug?
66
+ LOG_LEVELS[@log_level] <= LOG_LEVELS[:debug]
67
+ end
68
+
69
+ def is_warn?
70
+ LOG_LEVELS[@log_level] <= LOG_LEVELS[:warn]
71
+ end
72
+
73
+ def host_output *args
74
+ return unless is_debug?
75
+ strings = strip_colors_from args
76
+ string = strings.join
77
+ optionally_color GREY, string, false
78
+ end
79
+
80
+ def debug *args
81
+ return unless is_debug?
82
+ optionally_color WHITE, args
83
+ end
84
+
85
+ def warn *args
86
+ return unless is_warn?
87
+ strings = args.map {|msg| "Warning: #{msg}" }
88
+ optionally_color YELLOW, strings
89
+ end
90
+
91
+ def success *args
92
+ optionally_color GREEN, args
93
+ end
94
+
95
+ def notify *args
96
+ optionally_color BRIGHT_WHITE, args
97
+ end
98
+
99
+ def error *args
100
+ optionally_color BRIGHT_RED, args
101
+ end
102
+
103
+ def strip_colors_from lines
104
+ Array(lines).map do |line|
105
+ line.gsub /\e\[(\d+;)?\d+m/, ''
106
+ end
107
+ end
108
+
109
+ def optionally_color color_code, msg, add_newline = true
110
+ print_statement = add_newline ? :puts : :print
111
+ @destinations.each do |to|
112
+ to.print color_code if @color
113
+ to.send print_statement, msg
114
+ to.print NORMAL if @color
115
+ end
116
+ end
117
+
118
+ # utility method to get the current call stack and format it
119
+ # to a human-readable string (which some IDEs/editors
120
+ # will recognize as links to the line numbers in the trace)
121
+ def pretty_backtrace backtrace = caller(1)
122
+ trace = purge_harness_files_from( Array( backtrace ) )
123
+ expand_symlinks( trace ).join "\n"
124
+ end
125
+
126
+ private
127
+ def expand_symlinks backtrace
128
+ backtrace.collect do |line|
129
+ file_path, line_num = line.split( ":" )
130
+ expanded_path = expand_symlink File.expand_path( file_path )
131
+ expanded_path.to_s + ":" + line_num.to_s
132
+ end
133
+ end
134
+
135
+ def purge_harness_files_from backtrace
136
+ mostly_purged = backtrace.reject do |line|
137
+ # LOADED_FEATURES is an array of anything `require`d, i.e. everything
138
+ # but the test in question
139
+ $LOADED_FEATURES.any? do |require_path|
140
+ line.include? require_path
141
+ end
142
+ end
143
+
144
+ # And remove lines that contain our program name in them
145
+ completely_purged = mostly_purged.reject {|line| line.include? $0 }
146
+ end
147
+
148
+ # utility method that takes a path as input, checks each component
149
+ # of the path to see if it is a symlink, and expands
150
+ # it if it is. returns the expanded path.
151
+ def expand_symlink file_path
152
+ file_path.split( "/" ).inject do |full_path, next_dir|
153
+ next_path = full_path + "/" + next_dir
154
+ if File.symlink? next_path
155
+ link = File.readlink next_path
156
+ next_path =
157
+ case link
158
+ when /^\// then link
159
+ else
160
+ File.expand_path( full_path + "/" + link )
161
+ end
162
+ end
163
+ next_path
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,73 @@
1
+ %w(hypervisor).each do |lib|
2
+ begin
3
+ require "beaker/#{lib}"
4
+ rescue LoadError
5
+ require File.expand_path(File.join(File.dirname(__FILE__), lib))
6
+ end
7
+ end
8
+
9
+ module Beaker
10
+ class NetworkManager
11
+ HYPERVISOR_TYPES = ['solaris', 'blimpy', 'vsphere', 'fusion', 'aix', 'vcloud', 'vagrant']
12
+
13
+ def initialize(config, options, logger)
14
+ @logger = logger
15
+ @options = options
16
+ @hosts = []
17
+ @config = config
18
+ @virtual_machines = {}
19
+ @noprovision_machines = []
20
+ @config['HOSTS'].each_key do |name|
21
+ host_info = @config['HOSTS'][name]
22
+ #check to see if this host has a hypervisor
23
+ hypervisor = host_info['hypervisor']
24
+ #provision this box
25
+ # - only if we are running with --provision
26
+ # - only if we have a hypervisor
27
+ # - only if either the specific hosts has no specification or has 'provision' in its config
28
+ if @options[:provision] && hypervisor && (host_info.has_key?('provision') ? host_info['provision'] : true) #obey config file provision, defaults to provisioning vms
29
+ raise "Invalid hypervisor: #{hypervisor} (#{name})" unless HYPERVISOR_TYPES.include? hypervisor
30
+ @logger.debug "Hypervisor for #{name} is #{host_info['hypervisor'] || 'default' }, and I'm going to use #{hypervisor}"
31
+ @virtual_machines[hypervisor] = [] unless @virtual_machines[hypervisor]
32
+ @virtual_machines[hypervisor] << name
33
+ else #this is a non-provisioned machine, deal with it without hypervisors
34
+ @logger.debug "No hypervisor for #{name}, connecting to host without provisioning"
35
+ @noprovision_machines << name
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ def provision
42
+ @provisioned_set = {}
43
+ @virtual_machines.each do |type, names|
44
+ hosts_for_type = []
45
+ #set up host objects for provisioned provisioned_set
46
+ names.each do |name|
47
+ host = Beaker::Host.create(name, @options, @config)
48
+ hosts_for_type << host
49
+ end
50
+ @provisioned_set[type] = Beaker::Hypervisor.create(type, hosts_for_type, @options, @config)
51
+ @hosts << hosts_for_type
52
+ end
53
+ @noprovision_machines.each do |name|
54
+ @hosts << Beaker::Host.create(name, @options, @config)
55
+ end
56
+ @hosts = @hosts.flatten
57
+ @hosts
58
+ end
59
+
60
+ def cleanup
61
+ #only cleanup if we aren't preserving hosts
62
+ #shut down connections
63
+ @hosts.each {|host| host.close }
64
+
65
+ if not @options[:preserve_hosts]
66
+ @provisioned_set.each_key do |type|
67
+ @provisioned_set[type].cleanup
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end