openstack_taster 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 847cb5a20b108d188a612e254b26c30d553efd90
4
- data.tar.gz: 36527d054338e59a9a6410541a3e532b1b096caa
3
+ metadata.gz: 2f23a80c511e0cdf522d55d4e398b7f8ba95d9d5
4
+ data.tar.gz: 4337f06484caba1510f9e0fa6b46ce1708c7e107
5
5
  SHA512:
6
- metadata.gz: 94623399b8a7d779b9db8e96410dcaffb4c144c2c41d0b2ba6ecbbdd6d5da1e100ac55b67be4efe9fb959a4b1f6ca7926928715e8bd56c1f60fbedb83b68f975
7
- data.tar.gz: ab0ae7a389592b96ae43e5a712c2ed5af1a438cd277dae8ee6126d3bd6492aaa5ade5713d1b7db3ace62aab69cb590e5ff7b22961a3366ce6f0d9c7caa361d67
6
+ metadata.gz: 970215a90be5debc307211a6733e02a016949ea7fd2165bcef7ae4c9b3d5a6a6ce9879abd9ff4100d15a2042682ac8995b08924306e73b5fb01040cd3474437d
7
+ data.tar.gz: da521ffbf76233edca3d11d94ff634ea99b1a4306aa9942b3a2288d11bd15e168e4df432ad15de6636ec8e151d7d9332175ff521ed5890ce4898d0ea0fee3a1a
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'optparse'
5
+ require 'English'
5
6
 
6
7
  suites = {
7
8
  'security' => 'Runs the security test suite',
@@ -12,11 +13,21 @@ settings = {}
12
13
  ARGV << '-h' if ARGV.empty?
13
14
 
14
15
  parser = OptionParser.new do |opts|
15
- opts.banner = "Usage: openstack_taster <image_name> {suite_name} [--create-snapshot]\n" \
16
- " or: openstack_taster <image_name> [--create-snapshot]\n" \
17
- ' or: openstack_taster <option>'
16
+ opts.banner = "Usage: openstack_taster -i <image_name> -u <ssh_user> [-s suite_name] [--create-snapshot]\n"
18
17
  opts.separator('')
19
18
  opts.separator('Available Arguments:')
19
+ opts.on('-i', '--image=<image_name>', 'Image name to use for the instance.') do |i|
20
+ settings[:image_name] = i
21
+ end
22
+ opts.on('-u', '--user=<ssh_user>', 'SSH username for logging into the instance.') do |u|
23
+ settings[:ssh_user] = u
24
+ end
25
+ opts.on('-s', '--suite=<suite_name>', 'Test suite(s) to use.') do |s|
26
+ settings[:suite] = s
27
+ end
28
+ opts.on('-f', '--flavor=<flavor>', 'Set flavor for the instance.') do |f|
29
+ settings[:flavor] = f
30
+ end
20
31
  opts.on('-c', '--create-snapshot', 'Create snapshot upon test failure.') do
21
32
  settings[:create_snapshot] = true
22
33
  end
@@ -32,7 +43,16 @@ parser = OptionParser.new do |opts|
32
43
  end
33
44
 
34
45
  begin
35
- params = parser.parse!
46
+ parser.parse!
47
+ mandatory = [:image_name, :ssh_user]
48
+ missing = mandatory.select { |p| settings[p].nil? }
49
+ raise OptionParser::MissingArgument, missing.join(', ') unless missing.empty? || settings[:exit] == true
50
+ rescue OptionParser::MissingArgument
51
+ puts
52
+ puts 'Error: ' + $ERROR_INFO.to_s
53
+ puts
54
+ puts parser unless settings[:exit] == true
55
+ exit 1
36
56
  rescue OptionParser::InvalidOption => io
37
57
  puts io.message
38
58
  puts
@@ -48,16 +68,11 @@ end
48
68
  exit if settings[:exit] # exit inside OptionParser causes problems.
49
69
 
50
70
  begin
51
- case params.length
52
- when 1
53
- image_name = params[0]
71
+ if settings[:suite].nil?
54
72
  suites.each_key { |suite| settings[suite.to_sym] = true }
55
- when 2
56
- raise "#{params[1]} is not a test suite!" unless suites.include? params[1]
57
- image_name = params[0]
58
- settings[params[1].to_sym] = true
59
73
  else
60
- raise 'Incorrect format!'
74
+ raise "#{settings[:suite]} is not a test suite!" unless suites.include? settings[:suite]
75
+ settings[settings[:suite].to_sym] = true
61
76
  end
62
77
  rescue StandardError => e
63
78
  puts e.message
@@ -66,6 +81,8 @@ rescue StandardError => e
66
81
  exit(1)
67
82
  end
68
83
 
84
+ settings[:flavor] = 'm1.tiny' if settings[:flavor].nil?
85
+
69
86
  require 'fog/openstack'
70
87
  require 'openstack_taster'
71
88
 
@@ -95,6 +112,6 @@ image = Fog::Image::OpenStack.new(OPENSTACK_CREDS)
95
112
  network = Fog::Network::OpenStack.new(OPENSTACK_CREDS)
96
113
 
97
114
  exit OpenStackTaster.new(
98
- compute, volume, image, network,
115
+ compute, volume, image, network, ENV['OS_NETWORK_REF'], settings[:flavor],
99
116
  SSH_KEYS, LOG_DIR
100
- ).taste(image_name, settings)
117
+ ).taste(settings[:image_name], settings)
@@ -9,13 +9,15 @@ require 'inspec'
9
9
 
10
10
  # @author Andrew Tolvstad, Samarendra Hedaoo, Cody Holliday
11
11
  class OpenStackTaster
12
- INSTANCE_FLAVOR_NAME = 'm1.tiny'
13
- INSTANCE_NETWORK_NAME = 'public'
14
12
  INSTANCE_NAME_PREFIX = 'taster'
15
13
  INSTANCE_VOLUME_MOUNT_POINT = '/mnt/taster_volume'
16
14
 
15
+ VOLUME_NAME_PREFIX = 'test_volume'
16
+ VOLUME_DESCRIPTION = 'Test volume for OpenStack Taster.'
17
+ VOLUME_SIZE = 1
18
+ VOLUME_FILESYSTEM = 'ext4'
17
19
  VOLUME_TEST_FILE_NAME = 'info'
18
- VOLUME_TEST_FILE_CONTENTS = nil # Contents would be something like 'test-vol-1 on openpower8.osuosl.bak'
20
+ VOLUME_TEST_FILE_CONTENTS = 'test-volume'
19
21
  TIMEOUT_INSTANCE_CREATE = 20
20
22
  TIMEOUT_VOLUME_ATTACH = 10
21
23
  TIMEOUT_VOLUME_PERSIST = 20
@@ -32,6 +34,8 @@ class OpenStackTaster
32
34
  volume_service,
33
35
  image_service,
34
36
  network_service,
37
+ network_name,
38
+ instance_flavor,
35
39
  ssh_keys,
36
40
  log_dir
37
41
  )
@@ -40,6 +44,7 @@ class OpenStackTaster
40
44
  @image_service = image_service
41
45
  @network_service = network_service
42
46
 
47
+ @network_name = network_name || 'public'
43
48
  @volumes = @volume_service.volumes
44
49
 
45
50
  @ssh_keypair = ssh_keys[:keypair]
@@ -50,9 +55,9 @@ class OpenStackTaster
50
55
  @log_dir = log_dir + "/#{@session_id}"
51
56
 
52
57
  @instance_flavor = @compute_service.flavors
53
- .select { |flavor| flavor.name == INSTANCE_FLAVOR_NAME }.first
58
+ .select { |flavor| flavor.name == instance_flavor }.first
54
59
  @instance_network = @network_service.networks
55
- .select { |network| network.name == INSTANCE_NETWORK_NAME }.first
60
+ .select { |network| network.name == @network_name }.first
56
61
  end
57
62
 
58
63
  # Taste a specified image
@@ -74,14 +79,12 @@ class OpenStackTaster
74
79
 
75
80
  abort("#{image_name} is not an available image.") if image.nil?
76
81
 
77
- distro_user_name = image.name.downcase.gsub(/[^a-z].*$/, '') # truncate downcased name at first non-alpha char
78
- distro_arch = image.name.downcase.slice(-2, 2)
82
+ distro = image.name.downcase[/^[a-z]*/]
79
83
  instance_name = format(
80
- '%s-%s-%s-%s',
84
+ '%s-%s-%s',
81
85
  INSTANCE_NAME_PREFIX,
82
86
  Time.new.strftime(TIME_SLUG_FORMAT),
83
- distro_user_name,
84
- distro_arch
87
+ distro
85
88
  )
86
89
 
87
90
  FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
@@ -91,7 +94,8 @@ class OpenStackTaster
91
94
  error_log(
92
95
  instance_logger,
93
96
  'info',
94
- "Tasting #{image.name} as '#{instance_name}' with username '#{distro_user_name}'.\nBuilding...",
97
+ "Tasting #{image.name} as '#{instance_name}' with username '#{settings[:ssh_user]}' and " \
98
+ "flavor '#{settings[:flavor]}'.\nBuilding...",
95
99
  true
96
100
  )
97
101
 
@@ -121,8 +125,8 @@ class OpenStackTaster
121
125
 
122
126
  # Run tests
123
127
  return_values = []
124
- return_values.push taste_security(instance, distro_user_name) if settings[:security]
125
- return_values.push taste_volumes(instance, distro_user_name) if settings[:volumes]
128
+ return_values.push taste_security(instance, settings[:ssh_user]) if settings[:security]
129
+ return_values.push taste_volumes(instance, settings[:ssh_user]) if settings[:volumes]
126
130
 
127
131
  if settings[:create_snapshot] && !return_values.all?
128
132
  error_log(instance.logger, 'info', "Tests failed for instance '#{instance.id}'. Creating image...", true)
@@ -152,7 +156,7 @@ class OpenStackTaster
152
156
  def taste_security(instance, username)
153
157
  opts = {
154
158
  'backend' => 'ssh',
155
- 'host' => instance.addresses['public'].first['addr'],
159
+ 'host' => instance.addresses[@network_name].first['addr'],
156
160
  'port' => 22,
157
161
  'user' => username,
158
162
  'sudo' => true,
@@ -251,37 +255,68 @@ class OpenStackTaster
251
255
  # @param username [String] the username to use when logging into the instance
252
256
  # @return [Boolean] Whether or not the tests succeeded
253
257
  def taste_volumes(instance, username)
254
- mount_failures = @volumes.reject do |volume|
255
- if volume.attachments.any?
256
- error_log(instance.logger, 'info', "Volume '#{volume.name}' is already in an attached state; skipping.", true)
257
- next
258
- end
259
-
260
- unless volume_attach?(instance, volume)
261
- error_log(instance.logger, 'error', "Volume '#{volume.name}' failed to attach.", true)
262
- next
263
- end
258
+ volume_name = format(
259
+ '%s-%s-%s',
260
+ VOLUME_NAME_PREFIX,
261
+ Time.new.strftime(TIME_SLUG_FORMAT),
262
+ instance.id
263
+ )
264
+ begin
265
+ volume = @compute_service.volumes.create name: volume_name, size: VOLUME_SIZE, description: VOLUME_DESCRIPTION
266
+ rescue Excon::Error => e
267
+ puts 'Failed to create volume. check log for details.'
268
+ error_log(instance.logger, 'error', e.message)
269
+ false
270
+ end
264
271
 
265
- volume_mount_unmount?(instance, username, volume)
272
+ loop do
273
+ volume.reload
274
+ sleep 2
275
+ break if volume.ready?
276
+ error_log(instance.logger, 'info', "volume #{volume.name} not ready, waiting...", true)
266
277
  end
267
278
 
268
- detach_failures = @volumes.reject do |volume|
269
- volume_detach?(instance, volume)
279
+ if volume_attach?(instance, volume)
280
+ vdev = @volume_service.volumes.find_by_id(volume.id).attachments.first['device']
281
+ mkfs_command = [
282
+ ["sudo parted --script #{vdev} mklabel gpt mkpart primary 1 100%", ''],
283
+ ["sudo mkfs.#{VOLUME_FILESYSTEM} -Fq #{vdev}1", '']
284
+ ]
285
+ with_ssh(instance, username) do |ssh|
286
+ mkfs_command.each do |command, expected|
287
+ result = ssh.exec!(command)
288
+ next unless result != expected
289
+ error_log(
290
+ instance.logger,
291
+ 'error',
292
+ "Failure while running '#{command}':\n\texpected '#{expected}'\n\tgot '#{result}'",
293
+ true
294
+ )
295
+ return false
296
+ end
297
+ end
298
+ mount = volume_mount_unmount?(instance, username, volume)
299
+ detach = volume_detach?(instance, volume)
300
+ else
301
+ error_log(instance.logger, 'error', "Volume '#{volume.id}' failed to attach.", true)
302
+ return false
270
303
  end
271
304
 
272
- if mount_failures.empty? && detach_failures.empty?
273
- error_log(instance.logger, 'info', "\nEncountered 0 failures.", true)
305
+ if mount && detach
306
+ error_log(instance.logger, 'info', "\nVolume testing passed!.", true)
274
307
  true
275
308
  else
276
309
  error_log(
277
310
  instance.logger,
278
311
  'error',
279
- "\nEncountered #{mount_failures.count} mount failures and #{detach_failures.count} detach failures.",
312
+ "\nVolume mounted: #{mount} detached: #{detach}.",
280
313
  true
281
314
  )
282
315
  error_log(instance.logger, 'error', "\nEncountered failures.", true)
283
316
  false
284
317
  end
318
+ error_log(instance.logger, 'info', "Deleting volume #{volume.id}.", true)
319
+ volume.destroy if volume.ready?
285
320
  end
286
321
 
287
322
  # A helper method to execute a series of commands remotely on an instance. This helper
@@ -294,7 +329,7 @@ class OpenStackTaster
294
329
  instance.logger.progname = 'SSH'
295
330
  begin
296
331
  Net::SSH.start(
297
- instance.addresses['public'].first['addr'],
332
+ instance.addresses[@network_name].first['addr'],
298
333
  username,
299
334
  verbose: :info,
300
335
  paranoid: false,
@@ -372,6 +407,7 @@ class OpenStackTaster
372
407
  ['sudo partprobe -s', nil],
373
408
  ["[ -d '#{mount}' ] || sudo mkdir #{mount}", ''],
374
409
  ["sudo mount #{vdev} #{mount}", ''],
410
+ ["sudo sh -c 'echo -n #{file_contents} > #{mount}/#{file_name}'", ''],
375
411
  ["sudo cat #{mount}/#{file_name}", file_contents],
376
412
  ["sudo umount #{mount}", '']
377
413
  ]
@@ -405,7 +441,8 @@ class OpenStackTaster
405
441
  puts 'Logging partition list and dmesg...'
406
442
 
407
443
  record_info_commands = [
408
- 'cat /proc/partitions',
444
+ 'lsblk -l',
445
+ 'lsblk -fl',
409
446
  'dmesg | tail -n 20'
410
447
  ]
411
448
 
@@ -60,6 +60,15 @@ control 'security-1.0' do
60
60
  its('stdout') { should cmp(/\(ALL\) ((NO)*PASSWD)*: ALL/) }
61
61
  end
62
62
  end
63
+ end
64
+
65
+ control 'ports-1.0' do
66
+ impact 1.0
67
+ title 'Openstack Image Ports Test'
68
+ desc 'Tests the open ports of images used for Openstack.'
69
+
70
+ # Skip these tests if we detect openstack is installed
71
+ only_if { !file('/etc/keystone').exist? }
63
72
 
64
73
  # ssh should be the only thing listening
65
74
  describe port.where { protocol =~ /tcp/ && port != 22 } do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstack_taster
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OSU Open Source Lab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-19 00:00:00.000000000 Z
11
+ date: 2018-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inspec