diff --git a/TESTING.asciidoc b/TESTING.asciidoc index 37f7962ff0c96..3bf5ccf582363 100644 --- a/TESTING.asciidoc +++ b/TESTING.asciidoc @@ -363,9 +363,17 @@ quality boxes available in vagrant atlas: * sles-11 -We're missing the following because our tests are very linux/bash centric: +Windows boxes currently supported by the Vagrantfile (but not used in packaging +tests yet) are -* Windows Server 2012 +* windows-2012r2 aka Windows Server 2012 R2 +* windows-2016 aka Windows Server 2016 + +Unfortunately licensing issues prevent us from providing Windows boxes for +public use. You can provide your own by setting the environment variables +`VAGRANT_WINDOWS_2012R2_BOX` and/or `VAGRANT_WINDOWS_2016_BOX` with IDs of +images that support remote connections through WinRM and remote Powershell +sessions. It's important to think of VMs like cattle. If they become lame you just shoot them and let vagrant reprovision them. Say you've hosed your precise VM: diff --git a/Vagrantfile b/Vagrantfile index f123a572f97ea..f6cd16eec5cd6 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -21,167 +21,200 @@ # specific language governing permissions and limitations # under the License. +define_opts = { + autostart: false +}.freeze + Vagrant.configure(2) do |config| - config.vm.define "ubuntu-1404" do |config| - config.vm.box = "elastic/ubuntu-14.04-x86_64" - ubuntu_common config + + config.vm.provider 'virtualbox' do |vbox| + # Give the box more memory and cpu because our tests are beasts! + vbox.memory = Integer(ENV['VAGRANT_MEMORY'] || 8192) + vbox.cpus = Integer(ENV['VAGRANT_CPUS'] || 4) end - config.vm.define "ubuntu-1604" do |config| - config.vm.box = "elastic/ubuntu-16.04-x86_64" - ubuntu_common config, extra: <<-SHELL - # Install Jayatana so we can work around it being present. - [ -f /usr/share/java/jayatanaag.jar ] || install jayatana - SHELL + + # Vagrant and ruby stdlib expand '.' differently, so look for the build + # in the directory containing the Vagrantfile + vagrantfile_dir = File.expand_path('..', __FILE__) + + # Switch the default share for the project root from /vagrant to + # /elasticsearch because /vagrant is confusing when there is a project inside + # the elasticsearch project called vagrant.... + config.vm.synced_folder vagrantfile_dir, '/vagrant', disabled: true + config.vm.synced_folder vagrantfile_dir, '/elasticsearch' + + # Expose project directory. Note that VAGRANT_CWD may not be the same as Dir.pwd + PROJECT_DIR = ENV['VAGRANT_PROJECT_DIR'] || Dir.pwd + config.vm.synced_folder PROJECT_DIR, '/project' + + 'ubuntu-1404'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/ubuntu-14.04-x86_64' + deb_common config, box + end + end + 'ubuntu-1604'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/ubuntu-16.04-x86_64' + deb_common config, box, extra: <<-SHELL + # Install Jayatana so we can work around it being present. + [ -f /usr/share/java/jayatanaag.jar ] || install jayatana + SHELL + end end # Wheezy's backports don't contain Openjdk 8 and the backflips # required to get the sun jdk on there just aren't worth it. We have # jessie and stretch for testing debian and it works fine. - config.vm.define "debian-8" do |config| - config.vm.box = "elastic/debian-8-x86_64" - deb_common config - end - config.vm.define "debian-9" do |config| - config.vm.box = "elastic/debian-9-x86_64" - deb_common config - end - config.vm.define "centos-6" do |config| - config.vm.box = "elastic/centos-6-x86_64" - rpm_common config + 'debian-8'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/debian-8-x86_64' + deb_common config, box + end end - config.vm.define "centos-7" do |config| - config.vm.box = "elastic/centos-7-x86_64" - rpm_common config + 'debian-9'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/debian-9-x86_64' + deb_common config, box + end end - config.vm.define "oel-6" do |config| - config.vm.box = "elastic/oraclelinux-6-x86_64" - rpm_common config + 'centos-6'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/centos-6-x86_64' + rpm_common config, box + end end - config.vm.define "oel-7" do |config| - config.vm.box = "elastic/oraclelinux-7-x86_64" - rpm_common config + 'centos-7'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/centos-7-x86_64' + rpm_common config, box + end end - config.vm.define "fedora-26" do |config| - config.vm.box = "elastic/fedora-26-x86_64" - dnf_common config + 'oel-6'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/oraclelinux-6-x86_64' + rpm_common config, box + end end - config.vm.define "fedora-27" do |config| - config.vm.box = "elastic/fedora-27-x86_64" - dnf_common config + 'oel-7'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/oraclelinux-7-x86_64' + rpm_common config, box + end end - config.vm.define "opensuse-42" do |config| - config.vm.box = "elastic/opensuse-42-x86_64" - opensuse_common config + 'fedora-26'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/fedora-26-x86_64' + dnf_common config, box + end end - config.vm.define "sles-12" do |config| - config.vm.box = "elastic/sles-12-x86_64" - sles_common config + 'fedora-27'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/fedora-27-x86_64' + dnf_common config, box + end end - # Switch the default share for the project root from /vagrant to - # /elasticsearch because /vagrant is confusing when there is a project inside - # the elasticsearch project called vagrant.... - config.vm.synced_folder ".", "/vagrant", disabled: true - config.vm.synced_folder ".", "/elasticsearch" - # Expose project directory - PROJECT_DIR = ENV['VAGRANT_PROJECT_DIR'] || Dir.pwd - config.vm.synced_folder PROJECT_DIR, "/project" - config.vm.provider "virtualbox" do |v| - # Give the boxes 3GB because Elasticsearch defaults to using 2GB - v.memory = 3072 + 'opensuse-42'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/opensuse-42-x86_64' + suse_common config, box + end end - if Vagrant.has_plugin?("vagrant-cachier") - config.cache.scope = :box + 'sles-12'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = 'elastic/sles-12-x86_64' + sles_common config, box + end end - config.vm.defined_vms.each do |name, config| - config.options[:autostart] = false - set_prompt = lambda do |config| - # Sets up a consistent prompt for all users. Or tries to. The VM might - # contain overrides for root and vagrant but this attempts to work around - # them by re-source-ing the standard prompt file. - config.vm.provision "prompt", type: "shell", inline: <<-SHELL - cat \<\ /etc/profile.d/elasticsearch_prompt.sh -export PS1='#{name}:\\w$ ' -PROMPT - grep 'source /etc/profile.d/elasticsearch_prompt.sh' ~/.bashrc | - cat \<\> ~/.bashrc -# Replace the standard prompt with a consistent one -source /etc/profile.d/elasticsearch_prompt.sh -SOURCE_PROMPT - grep 'source /etc/profile.d/elasticsearch_prompt.sh' ~vagrant/.bashrc | - cat \<\> ~vagrant/.bashrc -# Replace the standard prompt with a consistent one -source /etc/profile.d/elasticsearch_prompt.sh -SOURCE_PROMPT - SHELL - # Creates a file to mark the machine as created by vagrant. Tests check - # for this file and refuse to run if it is not present so that they can't - # be run unexpectedly. - config.vm.provision "markerfile", type: "shell", inline: <<-SHELL - touch /etc/is_vagrant_vm - SHELL + + windows_2012r2_box = ENV['VAGRANT_WINDOWS_2012R2_BOX'] + if windows_2012r2_box && windows_2012r2_box.empty? == false + 'windows-2012r2'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = windows_2012r2_box + windows_common config, box + end end - config.config_procs.push ['2', set_prompt] end -end -def ubuntu_common(config, extra: '') - deb_common config, extra: extra + windows_2016_box = ENV['VAGRANT_WINDOWS_2016_BOX'] + if windows_2016_box && windows_2016_box.empty? == false + 'windows-2016'.tap do |box| + config.vm.define box, define_opts do |config| + config.vm.box = windows_2016_box + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + config.vm.provision 'enable long paths', type: 'shell', inline: <<-SHELL + Set-ItemProperty -Path "HKLM:/SYSTEM/CurrentControlSet/Control/Filesystem/" -Name "LongPathsEnabled" -Value 1 + SHELL + windows_common config, box + end + end + end end -def deb_common(config, extra: '') +def deb_common(config, name, extra: '') # http://foo-o-rama.com/vagrant--stdin-is-not-a-tty--fix.html - config.vm.provision "fix-no-tty", type: "shell" do |s| + config.vm.provision 'fix-no-tty', type: 'shell' do |s| s.privileged = false s.inline = "sudo sed -i '/tty/!s/mesg n/tty -s \\&\\& mesg n/' /root/.profile" end - provision(config, - update_command: "apt-get update", - update_tracking_file: "/var/cache/apt/archives/last_update", - install_command: "apt-get install -y", - extra: extra) + linux_common( + config, + name, + update_command: 'apt-get update', + update_tracking_file: '/var/cache/apt/archives/last_update', + install_command: 'apt-get install -y', + extra: extra + ) end -def rpm_common(config) - provision(config, - update_command: "yum check-update", - update_tracking_file: "/var/cache/yum/last_update", - install_command: "yum install -y") +def rpm_common(config, name) + linux_common( + config, + name, + update_command: 'yum check-update', + update_tracking_file: '/var/cache/yum/last_update', + install_command: 'yum install -y' + ) end -def dnf_common(config) - provision(config, - update_command: "dnf check-update", - update_tracking_file: "/var/cache/dnf/last_update", - install_command: "dnf install -y", - install_command_retries: 5) - if Vagrant.has_plugin?("vagrant-cachier") - # Autodetect doesn't work.... +def dnf_common(config, name) + # Autodetect doesn't work.... + if Vagrant.has_plugin?('vagrant-cachier') config.cache.auto_detect = false - config.cache.enable :generic, { :cache_dir => "/var/cache/dnf" } + config.cache.enable :generic, { :cache_dir => '/var/cache/dnf' } end + linux_common( + config, + name, + update_command: 'dnf check-update', + update_tracking_file: '/var/cache/dnf/last_update', + install_command: 'dnf install -y', + install_command_retries: 5 + ) end -def opensuse_common(config) - suse_common config, '' -end - -def suse_common(config, extra) - provision(config, - update_command: "zypper --non-interactive list-updates", - update_tracking_file: "/var/cache/zypp/packages/last_update", - install_command: "zypper --non-interactive --quiet install --no-recommends", - extra: extra) +def suse_common(config, name, extra: '') + linux_common( + config, + name, + update_command: 'zypper --non-interactive list-updates', + update_tracking_file: '/var/cache/zypp/packages/last_update', + install_command: 'zypper --non-interactive --quiet install --no-recommends', + extra: extra + ) end -def sles_common(config) +def sles_common(config, name) extra = <<-SHELL zypper rr systemsmanagement_puppet puppetlabs-pc1 zypper --non-interactive install git-core -SHELL - suse_common config, extra + SHELL + suse_common config, name, extra: extra end -# Register the main box provisioning script. +# Configuration needed for all linux boxes # @param config Vagrant's config object. Required. +# @param name [String] The box name. Required. # @param update_command [String] The command used to update the package # manager. Required. Think `apt-get update`. # @param update_tracking_file [String] The location of the file tracking the @@ -189,24 +222,76 @@ end # is cached by vagrant-cachier. # @param install_command [String] The command used to install a package. # Required. Think `apt-get install #{package}`. -# @param extra [String] Extra provisioning commands run before anything else. -# Optional. Used for things like setting up the ppa for Java 8. -def provision(config, - update_command: 'required', - update_tracking_file: 'required', - install_command: 'required', - install_command_retries: 0, - extra: '') - # Vagrant run ruby 2.0.0 which doesn't have required named parameters.... - raise ArgumentError.new('update_command is required') if update_command == 'required' - raise ArgumentError.new('update_tracking_file is required') if update_tracking_file == 'required' - raise ArgumentError.new('install_command is required') if install_command == 'required' - config.vm.provider "virtualbox" do |v| - # Give the box more memory and cpu because our tests are beasts! - v.memory = Integer(ENV['VAGRANT_MEMORY'] || 8192) - v.cpus = Integer(ENV['VAGRANT_CPUS'] || 4) +# @param install_command_retries [Integer] Number of times to retry +# a failed install command +# @param extra [String] Additional script to run before installing +# dependencies +# +def linux_common(config, + name, + update_command: 'required', + update_tracking_file: 'required', + install_command: 'required', + install_command_retries: 0, + extra: '') + + raise ArgumentError, 'update_command is required' if update_command == 'required' + raise ArgumentError, 'update_tracking_file is required' if update_tracking_file == 'required' + raise ArgumentError, 'install_command is required' if install_command == 'required' + + if Vagrant.has_plugin?('vagrant-cachier') + config.cache.scope = :box end - config.vm.provision "dependencies", type: "shell", inline: <<-SHELL + + config.vm.provision 'markerfile', type: 'shell', inline: <<-SHELL + touch /etc/is_vagrant_vm + SHELL + + # This prevents leftovers from previous tests using the + # same VM from messing up the current test + config.vm.provision 'clean es installs in tmp', run: 'always', type: 'shell', inline: <<-SHELL + rm -rf /tmp/elasticsearch* + SHELL + + sh_set_prompt config, name + sh_install_deps( + config, + update_command, + update_tracking_file, + install_command, + install_command_retries, + extra + ) +end + +# Sets up a consistent prompt for all users. Or tries to. The VM might +# contain overrides for root and vagrant but this attempts to work around +# them by re-source-ing the standard prompt file. +def sh_set_prompt(config, name) + config.vm.provision 'set prompt', type: 'shell', inline: <<-SHELL + cat \<\ /etc/profile.d/elasticsearch_prompt.sh +export PS1='#{name}:\\w$ ' +PROMPT + grep 'source /etc/profile.d/elasticsearch_prompt.sh' ~/.bashrc | + cat \<\> ~/.bashrc +# Replace the standard prompt with a consistent one +source /etc/profile.d/elasticsearch_prompt.sh +SOURCE_PROMPT + grep 'source /etc/profile.d/elasticsearch_prompt.sh' ~vagrant/.bashrc | + cat \<\> ~vagrant/.bashrc +# Replace the standard prompt with a consistent one +source /etc/profile.d/elasticsearch_prompt.sh +SOURCE_PROMPT + SHELL +end + +def sh_install_deps(config, + update_command, + update_tracking_file, + install_command, + install_command_retries, + extra) + config.vm.provision 'install dependencies', type: 'shell', inline: <<-SHELL set -e set -o pipefail @@ -240,9 +325,9 @@ def provision(config, echo "==> Installing $1" if [ #{install_command_retries} -eq 0 ] then - #{install_command} $1 + #{install_command} $1 else - retry_installcommand $1 #{install_command_retries} + retry_installcommand $1 #{install_command_retries} fi } @@ -253,12 +338,13 @@ def provision(config, #{extra} installed java || { - echo "==> Java is not installed on vagrant box ${config.vm.box}" + echo "==> Java is not installed" return 1 } ensure tar ensure curl ensure unzip + ensure rsync installed bats || { # Bats lives in a git repository.... @@ -292,9 +378,122 @@ Defaults env_keep += "BATS_ARCHIVES" SUDOERS_VARS chmod 0440 /etc/sudoers.d/elasticsearch_vars SHELL - # This prevents leftovers from previous tests using the - # same VM from messing up the current test - config.vm.provision "clean_tmp", run: "always", type: "shell", inline: <<-SHELL - rm -rf /tmp/elasticsearch* +end + +def windows_common(config, name) + config.vm.provision 'markerfile', type: 'shell', inline: <<-SHELL + $ErrorActionPreference = "Stop" + New-Item C:/is_vagrant_vm -ItemType file -Force | Out-Null + SHELL + + config.vm.provision 'clean es installs in tmp', run: 'always', type: 'shell', inline: <<-SHELL + Remove-Item -Recurse -Force C:\\tmp\\elasticsearch* + SHELL + + config.vm.provision 'set prompt', type: 'shell', inline: <<-SHELL + $ErrorActionPreference = "Stop" + $ps_prompt = 'function Prompt { "#{name}:$($ExecutionContext.SessionState.Path.CurrentLocation)>" }' + $ps_prompt | Out-File $PsHome/Microsoft.PowerShell_profile.ps1 + SHELL + + # Windows' system APIs limit paths to 260 characters. In server 2016 we can raise this limit, + # (see LongPathsEnabled above) but not in server 2012r2. This adds a powershell module that has basic + # copy and delete command that can handle long paths + config.vm.provision 'long path shim module', type: 'shell' do |s| + s.privileged = false + s.inline = <<-SHELL + $ErrorActionPreference = "Stop" + $longPathScript = @' +#{powershell_long_path_module} +'@ + $ModuleDir = "C:/Users/vagrant/Documents/WindowsPowerShell/Modules/LongPathShims" + $ModuleFile = "$ModuleDir/LongPathShims.psm1" + if (-Not (Test-Path "$ModuleDir")) { + New-Item "$ModuleDir" -ItemType Directory | Out-Null + } + $longPathScript | Out-File "$ModuleFile" + SHELL + end + + powershell_install_deps config +end + +# Powershell's filesystem commands cannot handle long paths by default. This module implements +# the required commands by calling Robocopy [1], a Windows system utility for moving files around +# that can handle long paths by default. Robocopy returns unusual exit codes [2] so we call it in +# a wrapper that converts them to what we'd normally expect. +# +# The Remove-Long-Path function works by syncing the contents of an empty directory to the +# delete target, which has the effect of recursively deleting it. +# +# [1] https://ss64.com/nt/robocopy.html +# [2] https://ss64.com/nt/robocopy-exit.html +def powershell_long_path_module + <<-SHELL + function Copy-Long-Path { + param( + [string]$Source, + [string]$Destination + ) + + robocopy "$Source" "$Destination" /E /COPY:DT /DCOPY:T /NFL /NDL /NJH /NJS /NC /NS /NP + Handle-Robocopy-Exit-Code $LASTEXITCODE + } + + function Remove-Long-Path { + param( + [string]$Target + ) + + $EmptyDir = "C:\\intentionally_empty" + + try { + New-Item "$EmptyDir" -ItemType Directory | Out-Null + # There doesn't appear to be a way to silence output about files that are purged, + # so just sent it to null as writing it to the terminal is very slow + robocopy "$EmptyDir" "$Target" /PURGE /NFL /NDL /NJH /NJS /NC /NS /NP | Out-Null + $RobocopyExitCode = $LASTEXITCODE + Remove-Item "$Target" -Recurse + Handle-Robocopy-Exit-Code $RobocopyExitCode + } finally { + Remove-Item "$EmptyDir" -Recurse + } + } + + function Handle-Robocopy-Exit-Code { + param( + [int]$ExitCode + ) + + Write-Verbose "robocopy returned exit code $ExitCode" + if ($ExitCode -ge 7) { + Write-Error "robocopy encountered an error and returned exit code $ExitCode" + } + } + SHELL +end + +# Just check that java is installed - when other dependencies are needed +# in the future, this is where they'll go +def powershell_install_deps(config) + config.vm.provision 'install deps', type: 'shell', inline: <<-SHELL + $ErrorActionPreference = "Stop" + + function Installed { + Param( + [string]$command + ) + + try { + Get-Command $command + return $true + } catch { + return $false + } + } + + if (-Not (Installed java)) { + Write-Error "java is not installed" + } SHELL end