#!/usr/local/ruby-current/bin/ruby # Copyright:: Copyright (c) 2019 eGlobalTech, Inc., all rights reserved # # Licensed under the BSD-3 license (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License in the root of the project or at # # http://egt-labs.com/mu/LICENSE.html # # 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. bindir = File.realpath(File.expand_path(File.dirname(__FILE__))) dir = File.realpath(File.expand_path(bindir+"/../modules/tests")) Dir.chdir(dir) require 'colorize' require 'optimist' require bindir+"/mu-load-config.rb" require 'mu' require 'rubygems' require 'bundler/setup' require 'json' require 'erb' require 'json-schema' $opts = Optimist::options do banner <<-EOS Usage: #{$0} [-m <#>] [-f] [-v] [specific test BoK to run [...]] EOS opt :max_threads, "Environment to set on creation.", :require => false, :default => 3, :type => :integer opt :max_retries, "Number of times to retry failed tests in --dryrun mode.", :require => false, :default => 2, :type => :integer opt :full, "Actually run deploys, instead of --dryrun", :require => false, :default => false opt :verbose, "Show more information while running", :require => false, :default => false end only = ARGV files = Dir.glob("*.yaml", base: dir) files.concat(Dir.glob("*.yml", base: dir)) baseclouds = MU::Cloud.availableClouds.reject { |c| c == "CloudFormation" } commands = {} failures = [] if only and only.size > 0 files.reject! { |f| !only.include?(f) } if files.size == 0 MU.log "No files in #{dir} matched requested list", MU::WARN, details: only exit 1 end end files.each { |f| clouds = baseclouds.dup groomer_match = true File.open(dir+"/"+f).readlines.each { |l| l.chomp! if l.match(/^\s*#\s*clouds: (.*)/) clouds = [] cloudstr = Regexp.last_match[1] cloudstr.split(/\s*,\s*/).each { |c| baseclouds.each { |cloud| if cloud.match(/^#{Regexp.quote(c)}$/i) clouds << cloud end } } elsif l.match(/^\s*#\s*groomers: (.*)/) groomerstr = Regexp.last_match[1] groomerstr.split(/\s*,\s*/).each { |g| if !MU::Groomer.availableGroomers.include?(g) MU.log "#{f} requires groomer #{g}, which is not available. This test will be skipped.", MU::NOTICE groomer_match = false end } end } if !groomer_match next end clouds.each { |cloud| cmd = "mu-deploy #{f} --cloud #{cloud} #{$opts[:full] ? "" : "--dryrun"}" commands[cmd] = { "file" => f, "cloud" => cloud, } if $opts[:full] $environment = "dev" begin conf_engine = MU::Config.new(f, cloud: cloud) rescue StandardError => e MU.log e.message+" parsing "+f+" with cloud "+cloud, MU::WARN, details: e.backtrace failures << f+" ("+commands[cmd]["cloud"]+")" next end parsed = MU::Config.stripConfig(conf_engine.config) types = [] MU::Cloud.resource_types.values.each { |attrs| if parsed.has_key?(attrs[:cfg_plural]) types << attrs[:cfg_plural] end } commands[cmd]["parsed"] = parsed commands[cmd]["types"] = types end } } puts "Running #{commands.size.to_s.bold} #{$opts[:full] ? "full deploy" : "parse"} tests from #{files.size.to_s.bold} Baskets of Kittens across #{baseclouds.size.to_s.bold} clouds" @output_semaphore = Mutex.new def execCommand(cmd, results_stash) @output_semaphore.synchronize { puts cmd if $opts[:verbose] } ok = true retries = 0 begin output = %x{#{cmd} 2>&1} if $?.exitstatus != 0 ok = false retries += 1 if $opts[:verbose] and !$opts[:full] and retries <= $opts[:max_retries] puts "#{cmd} RETRY #{retries.to_s}".light_red end else ok = true end end while !ok and !$opts[:full] and retries <= $opts[:max_retries] results_stash["output"] += output @output_semaphore.synchronize { if ok if $opts[:verbose] puts "#{cmd} SUCCEEDED".green else print ".".green end else if $opts[:verbose] puts "#{cmd} FAILED:".light_red puts output else print ".".light_red end end } ok end threads = [] results = {} commands.keys.each { |cmd| if threads.size >= $opts[:max_threads] begin threads.each { |t| t.join(0.1) } threads.reject! { |t| t.nil? or !t.status } sleep 1 if threads.size >= $opts[:max_threads] end while threads.size >= $opts[:max_threads] end threads << Thread.new(cmd) { |cmd_thr| results[cmd_thr] = { "output" => "", "failed" => [] } if !execCommand(cmd_thr, results[cmd_thr]) results[cmd_thr]["failed"] << "main" end if $opts[:full] and results[cmd_thr]["output"].match(/deploy - Deployment id: .*? \((.*?)\)/) deploy_id = Regexp.last_match[1] adoptdir = Dir.mktmpdir(commands[cmd_thr]["file"].gsub(/[^a-z0-9]|yaml$/i, "")) if commands[cmd_thr]["types"] and commands[cmd_thr]["types"].size > 0 adopt = "cd #{adoptdir} && mu-adopt --appname adoptone --grouping omnibus --clouds #{commands[cmd_thr]["cloud"]} --types #{commands[cmd_thr]["types"].join(" ")} 2>&1" if !execCommand(adopt, results[cmd_thr]) results[cmd_thr]["failed"] << "adopt" end end if File.exist?(dir+"/regrooms/"+commands[cmd_thr]["file"]) regroom = "mu-deploy regrooms/#{commands[cmd_thr]["file"]} --cloud #{commands[cmd_thr]["cloud"]} --update #{deploy_id} 2>&1" if !execCommand(regroom, results[cmd_thr]) results[cmd_thr]["failed"] << "regroom" end if commands[cmd_thr]["types"] and commands[cmd_thr]["types"].size > 0 re_adopt = "cd #{adoptdir} && mu-adopt --appname adopttwo --grouping omnibus --clouds #{commands[cmd_thr]["cloud"]} --types #{commands[cmd_thr]["types"].join(" ")} 2>&1" if !execCommand(re_adopt, results[cmd_thr]) results[cmd_thr]["failed"] << "second adopt" end end # TODO big flex is to read back both adopted BoKs and .diff them, but without # all resources having implemented adoption this isn't much of a test yet end FileUtils.remove_entry(adoptdir) cleanup = %Q{mu-cleanup #{deploy_id} --skipsnapshots} if !execCommand(cleanup, results[cmd_thr]) results[cmd_thr]["failed"] << "cleanup" end end } } threads.each { |t| t.join } puts "" results.keys.sort { |a, b| results[b]["failed"].size <=> results[a]["failed"].size }.each { |cmd| if results[cmd]["failed"].size > 0 puts cmd+" failed:".light_red puts results[cmd]["output"].yellow puts "^ #{cmd}".light_red failures << commands[cmd]["file"]+" in "+commands[cmd]["cloud"]+" ("+results[cmd]["failed"].join(", ")+")" else puts cmd+" passed".green end } if failures.size > 0 puts "\n#{failures.size.to_s.bold} failure#{failures.size == 1 ? "" : "s"} in "+failures.uniq.map { |f| f.light_red }.join(", ") exit 1 end