123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- #!/usr/bin/env ruby
- # Licensed to Elasticsearch under one or more contributor
- # license agreements. See the NOTICE file distributed with
- # this work for additional information regarding copyright
- # ownership. Elasticsearch licenses this file to you under
- # the Apache License, Version 2.0 (the "License"); you may
- # not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing,
- # software distributed under the License is distributed on
- # an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- # either express or implied. See the License for the specific
- # language governing permissions and limitations under the License
- #
- # NAME
- # build_randomization.rb -- Generate property file for the JDK randomization test
- #
- # SYNOPSIS
- # build_randomization.rb [-d] [-l|t]
- #
- # DESCRIPTION
- # This script takes the randomization choices described in RANDOM_CHOICE and generates apporpriate JAVA property file 'prop.txt'
- # This property file also contain the appropriate JDK selection, randomized. JDK randomization is based on what is available on the Jenkins tools
- # directory. This script is used by Jenkins test system to conduct Elasticsearch server randomization testing.
- #
- # In hash RANDOM_CHOISES, the key of randomization hash maps to key of java property. The value of the hash describes the possible value of the randomization
- #
- # For example RANDOM_CHOICES = { 'es.node.mode' => {:choices => ['local', 'network'], :method => :get_random_one} } means
- # es.node.mode will be set to either 'local' or 'network', each with 50% of probability
- #
- # OPTIONS SUMMARY
- # The options are as follows:
- #
- # -d, --debug Increase logging verbosity for debugging purpose
- # -t, --test Run in test mode. The script will execute unit tests.
- # -l, --local Run in local mode. In this mode, directory structure will be created under current directory to mimick
- # Jenkins' server directory layout. This mode is mainly used for development.
- require 'enumerator'
- require 'getoptlong'
- require 'log4r'
- require 'optparse'
- require 'rubygems'
- require 'yaml'
- include Log4r
- RANDOM_CHOICES = {
- 'tests.jvm.argline' => [
- {:choices => ['-server'], :method => 'get_random_one'},
- {:choices => ['-XX:+UseConcMarkSweepGC', '-XX:+UseParallelGC', '-XX:+UseSerialGC', '-XX:+UseG1GC'], :method => 'get_random_one'},
- {:choices => ['-XX:+UseCompressedOops', '-XX:-UseCompressedOops'], :method => 'get_random_one'},
- {:choices => ['-XX:+AggressiveOpts'], :method => 'get_50_percent'}
- ],
- 'es.node.mode' => {:choices => ['local', 'network'], :method => 'get_random_one'},
- # bug forced to be false for now :test_nightly => { :method => :true_or_false},
- 'tests.nightly' => {:selections => false},
- 'tests.heap.size' => {:choices => [512, 1024], :method => :random_heap},
- 'tests.assertion.disabled'=> {:choices => 'org.elasticsearch', :method => 'get_10_percent'},
- 'tests.security.manager' => {:choices => [true, false], :method => 'get_90_percent'},
- }
- L = Logger.new 'test_randomizer'
- L.outputters = Outputter.stdout
- L.level = INFO
- C = {:local => false, :test => false}
- OptionParser.new do |opts|
- opts.banner = "Usage: build_ranodimzatin.rb [options]"
- opts.on("-d", "--debug", "Debug mode") do |d|
- L.level = DEBUG
- end
- opts.on("-l", "--local", "Run in local mode") do |l|
- C[:local] = true
- end
- opts.on("-t", "--test", "Run unit tests") do |t|
- C[:test] = true
- end
- end.parse!
- class Randomizer
- attr_accessor :data_array
- def initialize(data_array)
- @data_array = data_array
- end
- def true_or_false
- [true, false][rand(2)]
- end
- def random_heap
- inner_data_array = [data_array[0], data_array[1], data_array[0] + rand(data_array[1] - data_array[0])]
- "%sm" % inner_data_array[rand(inner_data_array.size)]
- end
- def get_random_with_distribution(mdata_array, distribution)
- L.debug "randomized distribution data %s" % YAML.dump(mdata_array)
- L.debug "randomized distribution distribution %s" % YAML.dump(distribution)
- carry = 0
- distribution_map = distribution.enum_for(:each_with_index).map { |x,i| pre_carry = carry ; carry += x; {i => x + pre_carry} }
- random_size = distribution_map.last.values.first
- selection = rand(random_size)
- #get the index that randomize choice mapped to
- choice = distribution_map.select do |x|
- x.values.first > selection #only keep the index with distribution value that is higher than the random generated number
- end.first.keys.first #first hash's first key is the index we want
- L.debug("randomized distribution choice %s" % mdata_array[choice])
- mdata_array[choice]
- end
- def get_random_one
- data_array[rand(data_array.size)]
- end
- def method_missing(meth, *args, &block)
- # trap randomization based on percentage
- if meth.to_s =~ /^get_(\d+)_percent/
- percentage = $1.to_i
- remain = 100 - percentage
- #data = args.first
- normalized_data = if(!data_array.kind_of?(Array))
- [data_array, nil]
- else
- data_array
- end
- get_random_with_distribution(normalized_data, [percentage, remain])
- else
- super
- end
- end
- end
- class JDKSelector
- attr_reader :directory, :jdk_list
- def initialize(directory)
- @directory = directory
- end
- # get selection of available JDKs from Jenkins automatic install directory
- def get_jdk
- @jdk_list = Dir.entries(directory).select do |x|
- x.chars.first == 'J'
- end.map do |y|
- File.join(directory, y)
- end
- self
- end
- def filter_java_6(files)
- files.select{ |i| File.basename(i).split(/[^0-9]/)[-1].to_i > 6 }
- end
- # do randomized selection from a given array
- def select_one(selection_array = nil)
- selection_array = filter_java_6(selection_array || @jdk_list)
- Randomizer.new(selection_array).get_random_one
- end
- def JDKSelector.generate_jdk_hash(jdk_choice)
- file_separator = if Gem.win_platform?
- File::ALT_SEPARATOR
- else
- File::SEPARATOR
- end
- {
- :PATH => [jdk_choice, 'bin'].join(file_separator) + File::PATH_SEPARATOR + ENV['PATH'],
- :JAVA_HOME => jdk_choice
- }
- end
- end
- #
- # Fix argument JDK selector
- #
- class FixedJDKSelector < JDKSelector
- def initialize(directory)
- @directory = [*directory] #selection of directories to pick from
- end
- def get_jdk
- #since JDK selection is already specified..jdk list is the @directory
- @jdk_list = @directory
- self
- end
- def select_one(selection_array = nil)
- #bypass filtering since this is not automatic
- selection_array ||= @jdk_list
- Randomizer.new(selection_array).get_random_one
- end
- end
- #
- # Property file writer
- #
- class PropertyWriter
- attr_reader :working_directory
- def initialize(mworking_directory)
- @working_directory = mworking_directory
- end
- # # pick first element out of array of hashes, generate write java property file
- def generate_property_file(data)
- directory = working_directory
- #array transformation
- content = data.to_a.map do |x|
- x.join('=')
- end.sort
- file_name = (ENV['BUILD_ID'] + ENV['BUILD_NUMBER']) || 'prop' rescue 'prop'
- file_name = file_name.split(File::SEPARATOR).first + '.txt'
- L.debug "Property file name is %s" % file_name
- File.open(File.join(directory, file_name), 'w') do |file|
- file.write(content.join("\n"))
- end
- end
- end
- #
- # Execute randomization logics
- #
- class RandomizedRunner
- attr_reader :random_choices, :jdk, :p_writer
- def initialize(mrandom_choices, mjdk, mwriter)
- @random_choices = mrandom_choices
- @jdk = mjdk
- @p_writer = mwriter
- end
- def generate_selections
- configuration = random_choices
- L.debug "Enter %s" % __method__
- L.debug "Configuration %s" % YAML.dump(configuration)
- generated = {}
- configuration.each do |k, v|
- if(v.kind_of?(Hash))
- if(v.has_key?(:method))
- randomizer = Randomizer.new(v[:choices])
- v[:selections] = randomizer.__send__(v[:method])
- end
- else
- v.each do |x|
- if(x.has_key?(:method))
- randomizer = Randomizer.new(x[:choices])
- x[:selections] = randomizer.__send__(x[:method])
- end
- end
- end
- end.each do |k, v|
- if(v.kind_of?(Array))
- selections = v.inject([]) do |sum, current_hash|
- sum.push(current_hash[:selections])
- end
- else
- selections = [v[:selections]] unless v[:selections].nil?
- end
- generated[k] = selections unless (selections.nil? || selections.size == 0)
- end
- L.debug "Generated selections %s" % YAML.dump(generated)
- generated
- end
- def get_env_matrix(jdk_selection, selections)
- L.debug "Enter %s" % __method__
- #normalization
- s = {}
- selections.each do |k, v|
- if(v.size > 1)
- s[k] = v.compact.join(' ') #this should be dependent on class of v[0] and perform reduce operation instead... good enough for now
- else
- s[k] = v.first
- end
- end
- j = JDKSelector.generate_jdk_hash(jdk_selection)
- # create build description line
- desc = {}
- # TODO: better error handling
- desc[:BUILD_DESC] = "%s,%s,heap[%s],%s%s%s%s" % [
- File.basename(j[:JAVA_HOME]),
- s['es.node.mode'],
- s['tests.heap.size'],
- s['tests.nightly'] ? 'nightly,':'',
- s['tests.jvm.argline'].gsub(/-XX:/,''),
- s.has_key?('tests.assertion.disabled')? ',assert off' : '',
- s['tests.security.manager'] ? ',sec manager on' : ''
- ]
- result = j.merge(s).merge(desc)
- L.debug(YAML.dump(result))
- result
- end
- def run!
- p_writer.generate_property_file(get_env_matrix(jdk, generate_selections))
- end
- end
- #
- # Main
- #
- unless(C[:test])
- # Check to see if this is running locally
- unless(C[:local])
- L.debug("Normal Mode")
- working_directory = ENV.fetch('WORKSPACE', (Gem.win_platform? ? Dir.pwd : '/var/tmp'))
- else
- L.debug("Local Mode")
- test_directory = 'tools/hudson.model.JDK/'
- unless(File.exist?(test_directory))
- L.info "running local mode, setting up running environment"
- L.info "properties are written to file prop.txt"
- FileUtils.mkpath "%sJDK6" % test_directory
- FileUtils.mkpath "%sJDK7" % test_directory
- end
- working_directory = Dir.pwd
- end
- # script support both window and linux
- # TODO: refactor into platform/machine dependent class structure
- jdk = if(Gem.win_platform?)
- #window mode jdk directories are fixed
- #TODO: better logic
- L.debug("Window Mode")
- if(File.directory?('y:\jdk7\7u55')) #old window system under ec2
- FixedJDKSelector.new('y:\jdk7\7u55')
- else #new metal window system
- FixedJDKSelector.new(['c:\PROGRA~1\JAVA\jdk1.8.0_05', 'c:\PROGRA~1\JAVA\jdk1.7.0_55'])
- end
- else
- #Jenkins sets pwd prior to execution
- L.debug("Linux Mode")
- JDKSelector.new(File.join(ENV['PWD'],'tools','hudson.model.JDK'))
- end
- runner = RandomizedRunner.new(RANDOM_CHOICES,
- jdk.get_jdk.select_one,
- PropertyWriter.new(working_directory))
- environment_matrix = runner.run!
- exit 0
- else
- require "test/unit"
- end
- #
- # Test
- #
- class TestJDKSelector < Test::Unit::TestCase
- L = Logger.new 'test'
- L.outputters = Outputter.stdout
- L.level = DEBUG
- def test_hash_generator
- jdk_choice = '/dummy/jdk7'
- generated = JDKSelector.generate_jdk_hash(jdk_choice)
- L.debug "Generated %s" % generated
- assert generated[:PATH].include?(jdk_choice), "PATH doesn't included choice"
- assert generated[:JAVA_HOME].include?(jdk_choice), "JAVA home doesn't include choice"
- end
- end
- class TestFixJDKSelector < Test::Unit::TestCase
- L = Logger.new 'test'
- L.outputters = Outputter.stdout
- L.level = DEBUG
- def test_initialize
- ['/home/dummy', ['/JDK7', '/home2'], ['home/dummy']].each do |x|
- test_object = FixedJDKSelector.new(x)
- assert_kind_of Array, test_object.directory
- assert_equal [*x], test_object.directory
- end
- end
- def test_select_one
- test_array = %w(one two three)
- test_object = FixedJDKSelector.new(test_array)
- assert test_array.include?(test_object.get_jdk.select_one)
- end
- def test_hash_generator
- jdk_choice = '/dummy/jdk7'
- generated = FixedJDKSelector.generate_jdk_hash(jdk_choice)
- L.debug "Generated %s" % generated
- assert generated[:PATH].include?(jdk_choice), "PATH doesn't included choice"
- assert generated[:JAVA_HOME].include?(jdk_choice), "JAVA home doesn't include choice"
- end
- end
- class TestPropertyWriter < Test::Unit::TestCase
- L = Logger.new 'test'
- L.outputters = Outputter.stdout
- L.level = DEBUG
- def test_initialize
- ['/home/dummy','/tmp'].each do |x|
- test_object = PropertyWriter.new(x)
- assert_kind_of String, test_object.working_directory
- assert_equal x, test_object.working_directory
- end
- end
- def test_generate_property
- test_file = '/tmp/prop.txt'
- File.delete(test_file) if File.exist?(test_file)
- test_object = PropertyWriter.new(File.dirname(test_file))
- # default prop.txt
- test_object.generate_property_file({:hi => 'there'})
- assert(File.exist?(test_file))
- File.open(test_file, 'r') do |properties_file|
- properties_file.read.each_line do |line|
- line.strip!
- assert_equal 'hi=there', line, "content %s is not hi=there" % line
- end
- end
- File.delete(test_file) if File.exist?(test_file)
- end
- end
- class DummyPropertyWriter < PropertyWriter
- def generate_property_file(data)
- L.debug "generating property file for %s" % YAML.dump(data)
- L.debug "on directory %s" % working_directory
- end
- end
- class TestRandomizedRunner < Test::Unit::TestCase
- def test_initialize
- test_object = RandomizedRunner.new(RANDOM_CHOICES, '/tmp/dummy/jdk', po = PropertyWriter.new('/tmp'))
- assert_equal RANDOM_CHOICES, test_object.random_choices
- assert_equal '/tmp/dummy/jdk', test_object.jdk
- assert_equal po, test_object.p_writer
- end
- def test_generate_selection_no_method
- test_object = RandomizedRunner.new({'tests.one' => {:selections => false }}, '/tmp/dummy/jdk', po = DummyPropertyWriter.new('/tmp'))
- selection = test_object.generate_selections
- assert_equal false, selection['tests.one'].first, 'randomization without selection method fails'
- end
- def test_generate_with_method
- test_object = RandomizedRunner.new({'es.node.mode' => {:choices => ['local', 'network'], :method => 'get_random_one'}},
- '/tmp/dummy/jdk', po = DummyPropertyWriter.new('/tmp'))
- selection = test_object.generate_selections
- assert ['local', 'network'].include?(selection['es.node.mode'].first), 'selection choice is not correct'
- end
- def test_get_env_matrix
- test_object = RandomizedRunner.new(RANDOM_CHOICES,
- '/tmp/dummy/jdk', po = DummyPropertyWriter.new('/tmp'))
- selection = test_object.generate_selections
- env_matrix = test_object.get_env_matrix('/tmp/dummy/jdk', selection)
- puts YAML.dump(env_matrix)
- assert_equal '/tmp/dummy/jdk', env_matrix[:JAVA_HOME]
- end
- end
|