From ca24d60d8f0dceabd6f20c8234801f77c291ad36 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 15:29:16 -0800 Subject: [PATCH 01/40] providers/hyperv: initial commit Initial work done by MS Open Tech --- plugins/providers/hyperv/action.rb | 128 +++++++++++++++ plugins/providers/hyperv/action/import.rb | 49 ++++++ plugins/providers/hyperv/action/is_created.rb | 22 +++ plugins/providers/hyperv/action/is_stopped.rb | 22 +++ .../hyperv/action/message_already_created.rb | 22 +++ .../hyperv/action/message_not_created.rb | 22 +++ .../hyperv/action/message_not_running.rb | 22 +++ .../providers/hyperv/action/read_guest_ip.rb | 46 ++++++ plugins/providers/hyperv/action/read_state.rb | 36 +++++ .../providers/hyperv/action/share_folders.rb | 123 ++++++++++++++ .../providers/hyperv/action/start_instance.rb | 29 ++++ .../providers/hyperv/action/stop_instance.rb | 24 +++ .../providers/hyperv/action/sync_folders.rb | 74 +++++++++ .../providers/hyperv/action/wait_for_state.rb | 40 +++++ plugins/providers/hyperv/communicator.rb | 13 ++ plugins/providers/hyperv/config.rb | 50 ++++++ plugins/providers/hyperv/driver.rb | 13 ++ plugins/providers/hyperv/driver/base.rb | 110 +++++++++++++ plugins/providers/hyperv/error.rb | 13 ++ .../hyperv/error/subprocess_error.rb | 24 +++ .../providers/hyperv/guest_config/config.rb | 33 ++++ plugins/providers/hyperv/host_share/config.rb | 33 ++++ plugins/providers/hyperv/plugin.rb | 76 +++++++++ plugins/providers/hyperv/provider.rb | 61 +++++++ .../providers/hyperv/scripts/file_sync.ps1 | 123 ++++++++++++++ .../hyperv/scripts/get_network_config.ps1 | 28 ++++ .../hyperv/scripts/get_vm_status.ps1 | 30 ++++ .../providers/hyperv/scripts/host_info.ps1 | 25 +++ .../providers/hyperv/scripts/import_vm.ps1 | 150 ++++++++++++++++++ .../providers/hyperv/scripts/mount_share.ps1 | 62 ++++++++ .../hyperv/scripts/set_smb_share.ps1 | 46 ++++++ plugins/providers/hyperv/scripts/start_vm.ps1 | 32 ++++ plugins/providers/hyperv/scripts/stop_vm.ps1 | 31 ++++ .../hyperv/scripts/utils/create_session.ps1 | 34 ++++ .../hyperv/scripts/utils/write_messages.ps1 | 20 +++ plugins/providers/hyperv/version.rb | 10 ++ 36 files changed, 1676 insertions(+) create mode 100644 plugins/providers/hyperv/action.rb create mode 100644 plugins/providers/hyperv/action/import.rb create mode 100644 plugins/providers/hyperv/action/is_created.rb create mode 100644 plugins/providers/hyperv/action/is_stopped.rb create mode 100644 plugins/providers/hyperv/action/message_already_created.rb create mode 100644 plugins/providers/hyperv/action/message_not_created.rb create mode 100644 plugins/providers/hyperv/action/message_not_running.rb create mode 100644 plugins/providers/hyperv/action/read_guest_ip.rb create mode 100644 plugins/providers/hyperv/action/read_state.rb create mode 100644 plugins/providers/hyperv/action/share_folders.rb create mode 100644 plugins/providers/hyperv/action/start_instance.rb create mode 100644 plugins/providers/hyperv/action/stop_instance.rb create mode 100644 plugins/providers/hyperv/action/sync_folders.rb create mode 100644 plugins/providers/hyperv/action/wait_for_state.rb create mode 100644 plugins/providers/hyperv/communicator.rb create mode 100644 plugins/providers/hyperv/config.rb create mode 100644 plugins/providers/hyperv/driver.rb create mode 100644 plugins/providers/hyperv/driver/base.rb create mode 100644 plugins/providers/hyperv/error.rb create mode 100644 plugins/providers/hyperv/error/subprocess_error.rb create mode 100644 plugins/providers/hyperv/guest_config/config.rb create mode 100644 plugins/providers/hyperv/host_share/config.rb create mode 100644 plugins/providers/hyperv/plugin.rb create mode 100644 plugins/providers/hyperv/provider.rb create mode 100644 plugins/providers/hyperv/scripts/file_sync.ps1 create mode 100644 plugins/providers/hyperv/scripts/get_network_config.ps1 create mode 100644 plugins/providers/hyperv/scripts/get_vm_status.ps1 create mode 100644 plugins/providers/hyperv/scripts/host_info.ps1 create mode 100644 plugins/providers/hyperv/scripts/import_vm.ps1 create mode 100644 plugins/providers/hyperv/scripts/mount_share.ps1 create mode 100644 plugins/providers/hyperv/scripts/set_smb_share.ps1 create mode 100644 plugins/providers/hyperv/scripts/start_vm.ps1 create mode 100644 plugins/providers/hyperv/scripts/stop_vm.ps1 create mode 100644 plugins/providers/hyperv/scripts/utils/create_session.ps1 create mode 100644 plugins/providers/hyperv/scripts/utils/write_messages.ps1 create mode 100644 plugins/providers/hyperv/version.rb diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb new file mode 100644 index 000000000..c2ca42694 --- /dev/null +++ b/plugins/providers/hyperv/action.rb @@ -0,0 +1,128 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "pathname" +require "vagrant/action/builder" + +module VagrantPlugins + module HyperV + module Action + # Include the built-in modules so we can use them as top-level things. + include Vagrant::Action::Builtin + + def self.action_reload + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + b2.use action_halt + b2.use Call, WaitForState, :off, 120 do |env2, b3| + if env2[:result] + b3.use action_up + else + env2[:ui].info("Machine did not reload, Check machine's status") + end + end + end + end + end + + def self.action_halt + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + b2.use StopInstance + end + end + end + + def self.action_start + Vagrant::Action::Builder.new.tap do |b| + b.use StartInstance + b.use ShareFolders + b.use SyncFolders + end + end + + def self.action_up + Vagrant::Action::Builder.new.tap do |b| + b.use HandleBoxUrl + b.use ConfigValidate + b.use Call, IsCreated do |env1, b1| + if env1[:result] + b1.use Call, IsStopped do |env2, b2| + if env2[:result] + b2.use action_start + else + b2.use MessageAlreadyCreated + end + end + else + b1.use Import + b1.use action_start + end + end + end + end + + def self.action_read_state + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ReadState + end + end + + def self.action_ssh + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + b2.use Call, IsStopped do |env1, b3| + if env1[:result] + b3.use MessageNotRunning + else + b3.use SSHExec + end + end + end + end + end + + def self.action_read_guest_ip + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ReadGuestIP + end + end + + + # The autoload farm + action_root = Pathname.new(File.expand_path("../action", __FILE__)) + autoload :IsCreated, action_root.join("is_created") + autoload :IsStopped, action_root.join("is_stopped") + autoload :ReadState, action_root.join("read_state") + autoload :Import, action_root.join("import") + autoload :StartInstance, action_root.join('start_instance') + autoload :StopInstance, action_root.join('stop_instance') + autoload :MessageNotCreated, action_root.join('message_not_created') + autoload :MessageAlreadyCreated, action_root.join('message_already_created') + autoload :MessageNotRunning, action_root.join('message_not_running') + autoload :SyncFolders, action_root.join('sync_folders') + autoload :WaitForState, action_root.join('wait_for_state') + autoload :ReadGuestIP, action_root.join('read_guest_ip') + autoload :ShareFolders, action_root.join('share_folders') + end + end +end diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb new file mode 100644 index 000000000..25e834935 --- /dev/null +++ b/plugins/providers/hyperv/action/import.rb @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class Import + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::connection") + end + + def call(env) + box_directory = env[:machine].box.directory.to_s + path = Pathname.new(box_directory.to_s + '/Virtual Machines') + config_path = "" + path.each_child do |f| + config_path = f.to_s if f.extname.downcase == ".xml" + end + + path = Pathname.new(box_directory.to_s + '/Virtual Hard Disks') + vhdx_path = "" + path.each_child do |f| + vhdx_path = f.to_s if f.extname.downcase == ".vhdx" + end + + options = { + vm_xml_config: config_path.gsub("/", "\\"), + vhdx_path: vhdx_path.gsub("/", "\\") + } + + env[:ui].info "Importing a Hyper-V instance" + begin + server = env[:machine].provider.driver.execute('import_vm.ps1', options) + rescue Error::SubprocessError => e + env[:ui].info e.message + return + end + env[:ui].info "Successfully imported a VM with name #{server['name']}" + env[:machine].id = server["id"] + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/is_created.rb b/plugins/providers/hyperv/action/is_created.rb new file mode 100644 index 000000000..1d9934f99 --- /dev/null +++ b/plugins/providers/hyperv/action/is_created.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class IsCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].state.id != :not_created + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/is_stopped.rb b/plugins/providers/hyperv/action/is_stopped.rb new file mode 100644 index 000000000..83e87bd8c --- /dev/null +++ b/plugins/providers/hyperv/action/is_stopped.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class IsStopped + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].state.id == :off + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/message_already_created.rb b/plugins/providers/hyperv/action/message_already_created.rb new file mode 100644 index 000000000..72e4b052d --- /dev/null +++ b/plugins/providers/hyperv/action/message_already_created.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class MessageAlreadyCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Machine already created") + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/message_not_created.rb b/plugins/providers/hyperv/action/message_not_created.rb new file mode 100644 index 000000000..5af00f180 --- /dev/null +++ b/plugins/providers/hyperv/action/message_not_created.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class MessageNotCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Machine not created") + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/message_not_running.rb b/plugins/providers/hyperv/action/message_not_running.rb new file mode 100644 index 000000000..1a5ca2502 --- /dev/null +++ b/plugins/providers/hyperv/action/message_not_running.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class MessageNotRunning + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Machine is not running, Please turn it on.") + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/read_guest_ip.rb b/plugins/providers/hyperv/action/read_guest_ip.rb new file mode 100644 index 000000000..209d02c58 --- /dev/null +++ b/plugins/providers/hyperv/action/read_guest_ip.rb @@ -0,0 +1,46 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "log4r" +require "timeout" + +module VagrantPlugins + module HyperV + module Action + # This action reads the SSH info for the machine and puts it into the + # `:machine_ssh_info` key in the environment. + class ReadGuestIP + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::connection") + end + + def call(env) + env[:machine_ssh_info] = read_host_ip(env) + @app.call(env) + end + + def read_host_ip(env) + return nil if env[:machine].id.nil? + # Get Network details from WMI Provider + # Wait for 120 sec By then the machine should be ready + host_ip = nil + begin + Timeout.timeout(120) do + begin + options = { vm_id: env[:machine].id } + network_info = env[:machine].provider.driver.execute('get_network_config.ps1', options) + host_ip = network_info["ip"] + sleep 10 if host_ip.empty? + end while host_ip.empty? + end + rescue Timeout::Error + @logger.info("Cannot find the IP address of the virtual machine") + end + return { host: host_ip } unless host_ip.nil? + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/read_state.rb b/plugins/providers/hyperv/action/read_state.rb new file mode 100644 index 000000000..b0bc82242 --- /dev/null +++ b/plugins/providers/hyperv/action/read_state.rb @@ -0,0 +1,36 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "log4r" +module VagrantPlugins + module HyperV + module Action + class ReadState + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::connection") + end + + def call(env) + if env[:machine].id + begin + options = { vm_id: env[:machine].id } + response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) + env[:machine_state_id] = response["state"].downcase.to_sym + rescue Error::SubprocessError => e + env[:machine].id = nil + env[:ui].info "Could not find a machine, assuming it to be deleted or terminated." + env[:machine_state_id] = :not_created + end + else + env[:machine_state_id] = :not_created + end + @app.call(env) + end + + end + end + end +end diff --git a/plugins/providers/hyperv/action/share_folders.rb b/plugins/providers/hyperv/action/share_folders.rb new file mode 100644 index 000000000..c46f89162 --- /dev/null +++ b/plugins/providers/hyperv/action/share_folders.rb @@ -0,0 +1,123 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "vagrant/util/subprocess" +module VagrantPlugins + module HyperV + module Action + class ShareFolders + + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + smb_shared_folders + prepare_smb_share + # A BIG Clean UP + # There should be a communicator class which branches between windows + # and Linux + if @smb_shared_folders.length > 0 + env[:ui].info('Mounting shared folders with VM, This process may take few minutes.') + end + if env[:machine].config.vm.guest == :windows + env[:ui].info "Mounting shared folders to windows is under development." + # mount_shared_folders_to_windows + elsif env[:machine].config.vm.guest == :linux + mount_shared_folders_to_linux + end + @app.call(env) + end + + def smb_shared_folders + @smb_shared_folders = {} + @env[:machine].config.vm.synced_folders.each do |id, data| + # Ignore disabled shared folders + next if data[:disabled] + + # Collect all SMB shares + next unless data[:smb] + # This to prevent overwriting the actual shared folders data + @smb_shared_folders[id] = data.dup + end + end + + def prepare_smb_share + @smb_shared_folders.each do |id, data| + begin + hostpath = File.expand_path(data[:hostpath], @env[:root_path]) + host_share_username = @env[:machine].provider_config.host_share.username + options = {:path => hostpath, + :share_name => data[:share_name], + :host_share_username => host_share_username} + response = @env[:machine].provider.driver.execute('set_smb_share.ps1', options) + if response["message"] == "OK" + @env[:ui].info "Successfully created SMB share for #{hostpath} with name #{data[:share_name]}" + end + rescue Error::SubprocessError => e + @env[:ui].info e.message + end + end + end + + def ssh_info + @ssh_info || @env[:machine].ssh_info + end + + def mount_shared_folders_to_windows + result = @env[:machine].provider.driver.execute('host_info.ps1', {}) + @smb_shared_folders.each do |id, data| + begin + options = { :share_name => data[:share_name], + :guest_path => data[:guestpath].gsub("/", "\\"), + :guest_ip => ssh_info[:host], + :username => ssh_info[:username], + :host_ip => result["host_ip"], + :password => @env[:machine].provider_config.guest.password, + :host_share_username => @env[:machine].provider_config.host_share.username, + :host_share_password => @env[:machine].provider_config.host_share.password} + @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") + @env[:machine].provider.driver.execute('mount_share.ps1', options) + rescue Error::SubprocessError => e + @env[:ui].info "Failed to link #{data[:share_name]} to Guest" + @env[:ui].info e.message + end + end + end + + def mount_shared_folders_to_linux + # Find Host Machine's credentials + result = @env[:machine].provider.driver.execute('host_info.ps1', {}) + host_share_username = @env[:machine].provider_config.host_share.username + host_share_password = @env[:machine].provider_config.host_share.password + @smb_shared_folders.each do |id, data| + begin + # Mount the Network drive to Guest VM + @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") + + # Create a location in guest to guestpath + @env[:machine].communicate.sudo("mkdir -p #{data[:guestpath]}") + owner = data[:owner] || ssh_info[:username] + group = data[:group] || ssh_info[:username] + + mount_options = "-o rw,username=#{host_share_username},pass=#{host_share_password}," + mount_options += "sec=ntlm,file_mode=0777,dir_mode=0777," + mount_options += "uid=`id -u #{owner}`,gid=`id -g #{group}`,rw #{data[:guestpath]}" + + command = "mount -t cifs //#{result["host_ip"]}/#{data[:share_name]} #{mount_options}" + + @env[:machine].communicate.sudo(command) + + rescue RuntimeError => e + @env[:ui].error(e.message) + end + end + + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/start_instance.rb b/plugins/providers/hyperv/action/start_instance.rb new file mode 100644 index 000000000..66bdad6ee --- /dev/null +++ b/plugins/providers/hyperv/action/start_instance.rb @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Action + class StartInstance + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info('Starting the Machine') + options = { vm_id: env[:machine].id } + begin + response = env[:machine].provider.driver.execute('start_vm.ps1', options) + env[:ui].info "Machine #{response["name"]} started" + rescue Error::SubprocessError => e + env[:ui].info e.message + return + end + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/stop_instance.rb b/plugins/providers/hyperv/action/stop_instance.rb new file mode 100644 index 000000000..7dc2244ec --- /dev/null +++ b/plugins/providers/hyperv/action/stop_instance.rb @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class StopInstance + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info('Stopping the Machine') + options = { vm_id: env[:machine].id } + response = env[:machine].provider.driver.execute('stop_vm.ps1', options) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/sync_folders.rb b/plugins/providers/hyperv/action/sync_folders.rb new file mode 100644 index 000000000..baf38d76e --- /dev/null +++ b/plugins/providers/hyperv/action/sync_folders.rb @@ -0,0 +1,74 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "log4r" +require "vagrant/util/subprocess" +require "vagrant/util/which" + +module VagrantPlugins + module HyperV + module Action + class SyncFolders + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant_hyperv::action::sync_folders") + end + + def call(env) + @env = env + @app.call(env) + if env[:machine].config.vm.guest == :windows + sync_folders_to_windows + elsif env[:machine].config.vm.guest == :linux + sync_folders_to_linux + end + end + + def ssh_info + @ssh_info ||= @env[:machine].ssh_info + end + + def sync_folders_to_windows + @env[:machine].config.vm.synced_folders.each do |id, data| + # Ignore disabled shared folders + next if data[:disabled] || data[:smb] + hostpath = File.expand_path(data[:hostpath], @env[:root_path]).gsub("/", "\\") + guestpath = data[:guestpath].gsub("/", "\\") + options = { :guest_ip => ssh_info[:host], + :username => ssh_info[:username], + :host_path => hostpath, + :guest_path => guestpath, + :vm_id => @env[:machine].id, + :password => @env[:machine].provider_config.guest.password } + response = @env[:machine].provider.driver.execute('file_sync.ps1', options) + end + end + + def sync_folders_to_linux + if ssh_info.nil? + @env[:ui].info('SSH Info not available, Aborting Sync folder') + return + end + + @env[:machine].config.vm.synced_folders.each do |id, data| + # Ignore disabled shared folders + next if data[:disabled] || data[:smb] + hostpath = File.expand_path(data[:hostpath], @env[:root_path]) + guestpath = data[:guestpath] + @env[:ui].info('Starting Sync folders') + begin + @env[:machine].communicate.upload(hostpath, guestpath) + rescue RuntimeError => e + @env[:ui].error(e.message) + end + + end + end + + end + end + end +end diff --git a/plugins/providers/hyperv/action/wait_for_state.rb b/plugins/providers/hyperv/action/wait_for_state.rb new file mode 100644 index 000000000..6ed873410 --- /dev/null +++ b/plugins/providers/hyperv/action/wait_for_state.rb @@ -0,0 +1,40 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "log4r" +require "timeout" +require "debugger" + +module VagrantPlugins + module HyperV + module Action + class WaitForState + def initialize(app, env, state, timeout) + @app = app + @state = state + @timeout = timeout + end + + def call(env) + env[:result] = true + # Wait until the Machine's state is disabled (ie State of Halt) + unless env[:machine].state.id == @state + env[:ui].info("Waiting for machine to #{@state}") + begin + Timeout.timeout(@timeout) do + until env[:machine].state.id == @state + sleep 2 + end + end + rescue Timeout::Error + env[:result] = false # couldn't reach state in time + end + end + @app.call(env) + end + + end + end + end +end diff --git a/plugins/providers/hyperv/communicator.rb b/plugins/providers/hyperv/communicator.rb new file mode 100644 index 000000000..883992bb8 --- /dev/null +++ b/plugins/providers/hyperv/communicator.rb @@ -0,0 +1,13 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Communicator + lib_path = Pathname.new(File.expand_path("../communicator", __FILE__)) + autoload :SSH, lib_path.join("ssh") + end + end +end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb new file mode 100644 index 000000000..5d16c04b4 --- /dev/null +++ b/plugins/providers/hyperv/config.rb @@ -0,0 +1,50 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "vagrant" +require_relative "guest_config/config" +require_relative "host_share/config" +module VagrantPlugins + module HyperV + class Config < Vagrant.plugin("2", :config) + # If set to `true`, then VirtualBox will be launched with a GUI. + # + # @return [Boolean] + attr_accessor :gui + attr_reader :host_share, :guest + + def host_config(&block) + block.call(@host_share) + end + + def guest_config(&block) + block.call(@guest) + end + + def finalize! + @gui = nil if @gui == UNSET_VALUE + end + + def initialize(region_specific=false) + @gui = UNSET_VALUE + @host_share = HostShare::Config.new + @guest = GuestConfig::Config.new + end + + def validate(machine) + errors = _detected_errors + unless host_share.valid_config? + errors << host_share.errors.flatten.join(" ") + end + + unless guest.valid_config? + errors << guest.errors.flatten.join(" ") + end + { "HyperV" => errors } + end + + end + end +end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb new file mode 100644 index 000000000..be47fb5ce --- /dev/null +++ b/plugins/providers/hyperv/driver.rb @@ -0,0 +1,13 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Driver + lib_path = Pathname.new(File.expand_path("../driver", __FILE__)) + autoload :Base, lib_path.join("base") + end + end +end diff --git a/plugins/providers/hyperv/driver/base.rb b/plugins/providers/hyperv/driver/base.rb new file mode 100644 index 000000000..d9dbb636f --- /dev/null +++ b/plugins/providers/hyperv/driver/base.rb @@ -0,0 +1,110 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "json" +require "vagrant/util/which" +require "vagrant/util/subprocess" + +module VagrantPlugins + module HyperV + module Driver + class Base + attr_reader :vmid + + def initialize(id=nil) + @vmid = id + check_power_shell + @output = nil + end + + def execute(path, options) + r = execute_powershell(path, options) do |type, data| + process_output(type, data) + end + if success? + JSON.parse(json_output[:success].join) unless json_output[:success].empty? + else + message = json_output[:error].join unless json_output[:error].empty? + raise Error::SubprocessError, message if message + end + end + + def raw_execute(command) + command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten + clear_output_buffer + Vagrant::Util::Subprocess.execute(*command) do |type, data| + process_output(type, data) + end + end + + protected + + def json_output + return @json_output if @json_output + json_success_begin = false + json_error_begin = false + success = [] + error = [] + @output.split("\n").each do |line| + json_error_begin = false if line.include?("===End-Error===") + json_success_begin = false if line.include?("===End-Output===") + message = "" + if json_error_begin || json_success_begin + message = line.gsub("\\'","\"") + end + success << message if json_success_begin + error << message if json_error_begin + json_success_begin = true if line.include?("===Begin-Output===") + json_error_begin = true if line.include?("===Begin-Error===") + end + @json_output = { :success => success, :error => error } + end + + def success? + @error_messages.empty? && json_output[:error].empty? + end + + def process_output(type, data) + if type == :stdout + @output = data.gsub("\r\n", "\n") + end + if type == :stdin + # $stdin.gets.chomp || "" + end + if type == :stderr + @error_messages = data.gsub("\r\n", "\n") + end + end + + def clear_output_buffer + @output = "" + @error_messages = "" + @json_output = nil + end + + def check_power_shell + unless Vagrant::Util::Which.which('powershell') + raise "Power Shell not found" + end + end + + def execute_powershell(path, options, &block) + lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) + path = lib_path.join(path).to_s.gsub("/", "\\") + options = options || {} + ps_options = [] + options.each do |key, value| + ps_options << "-#{key}" + ps_options << "'#{value}'" + end + clear_output_buffer + command = ["powershell", "-NoProfile", "-ExecutionPolicy", + "Bypass", path, ps_options, {notify: [:stdout, :stderr, :stdin]}].flatten + Vagrant::Util::Subprocess.execute(*command, &block) + end + end + end + end +end diff --git a/plugins/providers/hyperv/error.rb b/plugins/providers/hyperv/error.rb new file mode 100644 index 000000000..1b6cb1cc1 --- /dev/null +++ b/plugins/providers/hyperv/error.rb @@ -0,0 +1,13 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Error + lib_path = Pathname.new(File.expand_path("../error", __FILE__)) + autoload :SubprocessError, lib_path.join("subprocess_error") + end + end +end diff --git a/plugins/providers/hyperv/error/subprocess_error.rb b/plugins/providers/hyperv/error/subprocess_error.rb new file mode 100644 index 000000000..06773cdc6 --- /dev/null +++ b/plugins/providers/hyperv/error/subprocess_error.rb @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "json" +require "vagrant/util/which" +require "vagrant/util/subprocess" + +module VagrantPlugins + module HyperV + module Error + class SubprocessError < RuntimeError + def initialize(message) + @message = JSON.parse(message) if message + end + + def message + @message["error"] + end + end + end + end +end diff --git a/plugins/providers/hyperv/guest_config/config.rb b/plugins/providers/hyperv/guest_config/config.rb new file mode 100644 index 000000000..5b02d5a21 --- /dev/null +++ b/plugins/providers/hyperv/guest_config/config.rb @@ -0,0 +1,33 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module HyperV + module GuestConfig + class Config < Vagrant.plugin("2", :config) + attr_accessor :username, :password + + def errors + @errors + end + + def validate + @errors = [] + if username.nil? + @errors << "Please configure a Guest VM's username" + end + if password.nil? + @errors << "Please configure a Guest VM's password" + end + end + + def valid_config? + validate + errors.empty? + end + + end + end + end +end diff --git a/plugins/providers/hyperv/host_share/config.rb b/plugins/providers/hyperv/host_share/config.rb new file mode 100644 index 000000000..93a4e9cda --- /dev/null +++ b/plugins/providers/hyperv/host_share/config.rb @@ -0,0 +1,33 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module HyperV + module HostShare + class Config < Vagrant.plugin("2", :config) + attr_accessor :username, :password + + def errors + @errors + end + + def validate + @errors = [] + if username.nil? + @errors << "Please configure a Windows user account to share folders" + end + if password.nil? + @errors << "Please configure a Windows user account password to share folders" + end + end + + def valid_config? + validate + errors.empty? + end + + end + end + end +end diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb new file mode 100644 index 000000000..5fdff60af --- /dev/null +++ b/plugins/providers/hyperv/plugin.rb @@ -0,0 +1,76 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +begin + require "vagrant" +rescue LoadError + raise "The Vagrant Hyper-V plugin must be run within Vagrant." +end + +# This is a sanity check to make sure no one is attempting to install +# this into an early Vagrant version. +if Vagrant::VERSION < "1.3.5" + raise "The Vagrant Hyper-V plugin is only compatible with Vagrant 1.3+" +end + +module VagrantPlugins + module HyperV + class Plugin < Vagrant.plugin("2") + name "HyperV" + description <<-DESC + This plugin installs a provider that allows Vagrant to manage + machines in Hyper-V. + DESC + + config(:hyperv, :provider) do + require_relative "config" + Config + end + + provider(:hyperv, parallel: true) do + # Setup logging and i18n + # setup_logging + # setup_i18n + + # Return the provider + require_relative "provider" + Provider + end + + # This initializes the internationalization strings. + def self.setup_i18n + + end + + # This sets up our log level to be whatever VAGRANT_LOG is. + def self.setup_logging + require "log4r" + level = nil + begin + level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) + rescue NameError + # This means that the logging constant wasn't found, + # which is fine. We just keep `level` as `nil`. But + # we tell the user. + level = nil + end + + # Some constants, such as "true" resolve to booleans, so the + # above error checking doesn't catch it. This will check to make + # sure that the log level is an integer, as Log4r requires. + level = nil if !level.is_a?(Integer) + + # Set the logging level on all "vagrant" namespaced + # logs as long as we have a valid level. + if level + logger = Log4r::Logger.new("vagrant_hyperv") + logger.outputters = Log4r::Outputter.stderr + logger.level = level + logger = nil + end + end + end + end +end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb new file mode 100644 index 000000000..f1bfcd4a2 --- /dev/null +++ b/plugins/providers/hyperv/provider.rb @@ -0,0 +1,61 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "log4r" +require "vagrant" + +module VagrantPlugins + module HyperV + class Provider < Vagrant.plugin("2", :provider) + + def initialize(machine) + @machine = machine + end + + def action(name) + # Attempt to get the action method from the Action class if it + # exists, otherwise return nil to show that we don't support the + # given action. + action_method = "action_#{name}" + return Action.send(action_method) if Action.respond_to?(action_method) + nil + end + + def state + # Run a custom action we define called "read_state" which does + # what it says. It puts the state in the `:machine_state_id` + # key in the environment. + env = @machine.action("read_state") + state_id = env[:machine_state_id] + + # Get the short and long description + # TODO + short = "Machine's current state is #{state_id}" + long = "" + + # Return the MachineState object + Vagrant::MachineState.new(state_id, short, long) + end + + def to_s + id = @machine.id.nil? ? "new" : @machine.id + "Hyper-V (#{id})" + end + + def ssh_info + # Run a custom action called "read_guest_ip" which does what it + # says and puts the resulting SSH info into the `:machine_ssh_info` + # key in the environment. + env = @machine.action("read_guest_ip") + if env[:machine_ssh_info] + env[:machine_ssh_info].merge!(:port => 22) + end + end + + def driver + @driver ||= Driver::Base.new() + end + end + end +end diff --git a/plugins/providers/hyperv/scripts/file_sync.ps1 b/plugins/providers/hyperv/scripts/file_sync.ps1 new file mode 100644 index 000000000..4caad2bde --- /dev/null +++ b/plugins/providers/hyperv/scripts/file_sync.ps1 @@ -0,0 +1,123 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required."), + [string]$guest_ip = $(throw "-guest_ip is required."), + [string]$username = $(throw "-guest_username is required."), + [string]$password = $(throw "-guest_password is required."), + [string]$host_path = $(throw "-host_path is required."), + [string]$guest_path = $(throw "-guest_path is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +function Get-file-hash($source_path, $delimiter) { + $source_files = @() + (Get-ChildItem $source_path -rec | ForEach-Object -Process { + Get-FileHash -Path $_.FullName -Algorithm MD5 } ) | + ForEach-Object -Process { + $source_files += $_.Path.Replace($source_path, "") + $delimiter + $_.Hash + } + $source_files +} + +function Get-Remote-Session($guest_ip, $username, $password) { + $secstr = convertto-securestring -AsPlainText -Force -String $password + $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr + New-PSSession -ComputerName $guest_ip -Credential $cred +} + +function Get-remote-file-hash($source_path, $delimiter, $session) { + Invoke-Command -Session $session -ScriptBlock ${function:Get-file-hash} -ArgumentList $source_path, $delimiter + # TODO: + # Check if remote PS Scripting errors +} + +function Sync-Remote-Machine($machine, $remove_files, $copy_files, $host_path, $guest_path) { + ForEach ($item in $copy_files) { + $from = $host_path + $item + $to = $guest_path + $item + # Copy VM can also take a VM object + Copy-VMFile -VM $machine -SourcePath $from -DestinationPath $to -CreateFullPath -FileSource Host -Force + } +} + +function Create-Remote-Folders($empty_source_folders, $guest_path) { + ForEach ($item in $empty_source_folders) { + $new_name = $guest_path + $item + New-Item "$new_name" -type directory -Force + } +} + +function Get-Empty-folders-From-Source($host_path) { + Get-ChildItem $host_path -recurse | + Where-Object {$_.PSIsContainer -eq $True} | + Where-Object {$_.GetFiles().Count -eq 0} | + Select-Object FullName | ForEach-Object -Process { + $empty_source_folders += ($_.FullName.Replace($host_path, "")) + } +} + +$delimiter = " || " + +$machine = Get-VM -Id $vm_id + +# FIXME: PowerShell guys please fix this. +# The below script checks for all VMIntegrationService which are not enabled +# and will enable this. +# When when all the services are enabled this throws an error. +# Enable VMIntegrationService to true +try { + Get-VM -Id $vm_id | Get-VMIntegrationService -Name "Guest Service Interface" | Enable-VMIntegrationService -Passthru + } + catch { } + +$session = Get-Remote-Session $guest_ip $username $password + +$source_files = Get-file-hash $host_path $delimiter +$destination_files = Get-remote-file-hash $guest_path $delimiter $session + +if (!$destination_files) { + $destination_files = @() +} +if (!$source_files) { + $source_files = @() +} + +# Compare source and destination files +$remove_files = @() +$copy_files = @() + + +Compare-Object -ReferenceObject $source_files -DifferenceObject $destination_files | ForEach-Object { + if ($_.SideIndicator -eq '=>') { + $remove_files += $_.InputObject.Split($delimiter)[0] + } else { + $copy_files += $_.InputObject.Split($delimiter)[0] + } +} + +# Update the files to remote machine +Sync-Remote-Machine $machine $remove_files $copy_files $host_path $guest_path + +# Create any empty folders which missed to sync to remote machine +$empty_source_folders = @() +$directories = Get-Empty-folders-From-Source $host_path + +$result = Invoke-Command -Session $session -ScriptBlock ${function:Create-Remote-Folders} -ArgumentList $empty_source_folders, $guest_path +# Always remove the connection after Use +Remove-PSSession -Id $session.Id + +$resultHash = @{ + message = "OK" +} +$result = ConvertTo-Json $resultHash +Write-Output-Message $result + diff --git a/plugins/providers/hyperv/scripts/get_network_config.ps1 b/plugins/providers/hyperv/scripts/get_network_config.ps1 new file mode 100644 index 000000000..bc48163d8 --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_network_config.ps1 @@ -0,0 +1,28 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction "stop" + $network = Get-VMNetworkAdapter -VM $vm + $ip_address = $network.IpAddresses[0] + $resultHash = @{ + ip = "$ip_address" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message "Failed to obtain network info of VM $_" +} diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 new file mode 100644 index 000000000..d7bfcc0ba --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -0,0 +1,30 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction "stop" + $state = $vm.state + $status = $vm.status + $resultHash = @{ + state = "$state" + status = "$status" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result + +} +catch { + Write-Error-Message $_ +} diff --git a/plugins/providers/hyperv/scripts/host_info.ps1 b/plugins/providers/hyperv/scripts/host_info.ps1 new file mode 100644 index 000000000..6b655f9dd --- /dev/null +++ b/plugins/providers/hyperv/scripts/host_info.ps1 @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { +$hostname = $(whoami) +$ip = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0] + $resultHash = @{ + host_name = "$username" + host_ip = "$ip" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message $_ +} + diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 new file mode 100644 index 000000000..587d0394b --- /dev/null +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -0,0 +1,150 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + + +param ( + [string]$vm_xml_config = $(throw "-vm_xml_config is required."), + [string]$vhdx_path = $(throw "-vhdx_path is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + [xml]$vmconfig = Get-Content -Path $vm_xml_config + + $vm_name = $vmconfig.configuration.properties.name.'#text' + $processors = $vmconfig.configuration.settings.processors.count.'#text' + + function Get_unique_name($name) { + Get-VM | ForEach-Object -Process { + if ($name -eq $_.Name) { + $name = $name + "_1" + } + } + return $name + } + + do { + $name = $vm_name + $vm_name = Get_unique_name $name + } while ($vm_name -ne $name) + + $memory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank + if ($memory.dynamic_memory_enabled."#text" -eq "True") { + $dynamicmemory = $True + } + else { + $dynamicmemory = $False + } + + # Memory values need to be in bytes + $MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB + $MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB + $MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB + + # Get the name of the virtual switch + $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" + + # Determine boot device + Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { + "Floppy" { $bootdevice = "floppy" } + "HardDrive" { $bootdevice = "IDE" } + "Optical" { $bootdevice = "CD" } + "Network" { $bootdevice = "LegacyNetworkAdapter" } + "Default" { $bootdevice = "IDE" } + } #switch + + # Define a hash map of parameter values for New-VM + + $vm_params = @{ + Name = $vm_name + NoVHD = $True + MemoryStartupBytes = $MemoryStartupBytes + SwitchName = $switchname + BootDevice = $bootdevice + ErrorAction = "Stop" + } + + # Create the VM using the values in the hash map + + $vm = New-VM @vm_params + + $notes = (Select-Xml -xml $vmconfig -XPath "//notes").node.'#text' + + # Set-VM parameters to configure new VM with old values + + $more_vm_params = @{ + ProcessorCount = $processors + MemoryStartupBytes = $MemoryStartupBytes + } + + If ($dynamicmemory) { + $more_vm_params.Add("DynamicMemory",$True) + $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) + $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) + } + else { + $more_vm_params.Add("StaticMemory",$True) + } + + if ($notes) { + $more_vm_params.Add("Notes",$notes) + } + + # Set the values on the VM + $vm | Set-VM @more_vm_params -Passthru + + # Add drives to the virtual machine + $controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" + # A regular expression pattern to pull the number from controllers + [regex]$rx="\d" + + foreach ($controller in $controllers) { + $node = $controller.Node + # Check for SCSI + if ($node.ParentNode.ChannelInstanceGuid) { + $ControllerType = "SCSI" + } + else { + $ControllerType = "IDE" + } + + $drives = $node.ChildNodes | where {$_.pathname."#text"} + foreach ($drive in $drives) { + #if drive type is ISO then set DVD Drive accordingly + $driveType = $drive.type."#text" + + $addDriveParam = @{ + ControllerNumber = $rx.Match($controller.node.name).value + Path = $vhdx_path + } + if ($drive.pool_id."#text") { + $ResourcePoolName = $drive.pool_id."#text" + $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) + } + + if ($drivetype -eq 'VHD') { + $addDriveParam.add("ControllerType",$ControllerType) + $vm | Add-VMHardDiskDrive @AddDriveparam + } + } + } + + $vm_id = (Get-VM $vm_name).id.guid + $resultHash = @{ + name = $vm_name + id = $vm_id + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message $_ + return +} diff --git a/plugins/providers/hyperv/scripts/mount_share.ps1 b/plugins/providers/hyperv/scripts/mount_share.ps1 new file mode 100644 index 000000000..de099e0a9 --- /dev/null +++ b/plugins/providers/hyperv/scripts/mount_share.ps1 @@ -0,0 +1,62 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + + +param ( + [string]$share_name = $(throw "-share_name is required."), + [string]$guest_path = $(throw "-guest_path is required."), + [string]$guest_ip = $(throw "-guest_ip is required."), + [string]$username = $(throw "-username is required."), + [string]$password = $(throw "-password is required."), + [string]$host_ip = $(throw "-host_ip is required."), + [string]$host_share_username = $(throw "-host_share_username is required."), + [string]$host_share_password = $(throw "-host_share_password is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\create_session.ps1" +$modules += $presentDir + "\utils\write_messages.ps1" + +forEach ($module in $modules) { . $module } + +try { + function Mount-File($share_name, $guest_path, $host_path, $host_share_username, $host_share_password) { + try { + # TODO: Check for folder exist. + # Use net use and prompt for password + $guest_path = $guest_path.replace("/", "\") + # Map a network drive to the guest machine + $result = net use * $host_path /user:$host_share_username $host_share_password /persistent:yes + $mapped_drive = (($result -match "\w:") -split (" "))[1] + Write-Host cmd /c mklink /d $guest_path $mapped_drive + # If a folder exist remove it. + if (Test-Path $guest_path) { + $junction = Get-Item $guest_path + $junction.Delete() + } + cmd /c mklink /d $guest_path $mapped_drive + } catch { + return $_ + } + } + + $response = Create-Remote-Session $guest_ip $username $password + + if (!$response["session"] -and $response["error"]) { + Write-Error-Message $response["error"] + return + } + $host_path = "\\$host_ip\$share_name" + $host_share_username = "$host_ip\$host_share_username" + $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Mount-File} -ArgumentList $share_name, $guest_path, $host_path, $host_share_username, $host_share_password -ErrorAction "stop" + Remove-PSSession -Id $response["session"].Id + Write-Error-Message $result +} +catch { + Write-Error-Message "Failed to mount files VM $_" + return +} diff --git a/plugins/providers/hyperv/scripts/set_smb_share.ps1 b/plugins/providers/hyperv/scripts/set_smb_share.ps1 new file mode 100644 index 000000000..6f64183c8 --- /dev/null +++ b/plugins/providers/hyperv/scripts/set_smb_share.ps1 @@ -0,0 +1,46 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$path = $(throw "-path is required."), + [string]$share_name = $(throw "-share_name is required."), + [string]$host_share_username = $(throw "-host_share_username is required") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + # See all available shares and check alert user for existing / conflicting share name + $shared_folders = net share + $reg = "$share_name(\s+)$path(\s)" + $existing_share = $shared_folders -Match $reg + if ($existing_share) { + # Always clear the existing share name and create a new one + net share $share_name /delete /y + } + + $computer_name = $(Get-WmiObject Win32_Computersystem).name + $grant_permission = "$computer_name\$host_share_username,Full" + $result = net share $share_name=$path /unlimited /GRANT:$grant_permission + if ($result -Match "$share_name was shared successfully.") { + $resultHash = @{ + message = "OK" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result + } else { + $reg = "^$share_name(\s+)" + $existing_share = $shared_folders -Match $reg + Write-Error-Message "IGNORING Conflicting share name, A share name already exist $existing_share" + } +} catch { + Write-Error-Message $_ + return +} + diff --git a/plugins/providers/hyperv/scripts/start_vm.ps1 b/plugins/providers/hyperv/scripts/start_vm.ps1 new file mode 100644 index 000000000..6ccf45111 --- /dev/null +++ b/plugins/providers/hyperv/scripts/start_vm.ps1 @@ -0,0 +1,32 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction "stop" + Start-VM $vm + $state = $vm.state + $status = $vm.status + $name = $vm.name + $resultHash = @{ + state = "$state" + status = "$status" + name = "$name" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message "Failed to start a VM $_" +} diff --git a/plugins/providers/hyperv/scripts/stop_vm.ps1 b/plugins/providers/hyperv/scripts/stop_vm.ps1 new file mode 100644 index 000000000..759930b68 --- /dev/null +++ b/plugins/providers/hyperv/scripts/stop_vm.ps1 @@ -0,0 +1,31 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction stop + # Shuts down virtual machine regardless of any unsaved application data + Stop-VM $vm -Force + $state = $vm.state + $status = $vm.status + $resultHash = @{ + state = "$state" + status = "$status" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message "Failed to stop a VM $_" +} diff --git a/plugins/providers/hyperv/scripts/utils/create_session.ps1 b/plugins/providers/hyperv/scripts/utils/create_session.ps1 new file mode 100644 index 000000000..fcf0f7f10 --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/create_session.ps1 @@ -0,0 +1,34 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +function Get-Remote-Session($guest_ip, $username, $password) { + $secstr = convertto-securestring -AsPlainText -Force -String $password + $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr + New-PSSession -ComputerName $guest_ip -Credential $cred -ErrorAction "stop" +} + +function Create-Remote-Session($guest_ip, $username, $password) { + $count = 0 + $session_error = "" + $session = "" + do { + $count++ + try { + $session = Get-Remote-Session $guest_ip $username $password + $session_error = "" + } + catch { + Start-Sleep -s 1 + $session_error = $_ + $session = "" + } + } + while (!$session -and $count -lt 20) + + return @{ + session = $session + error = $session_error + } +} diff --git a/plugins/providers/hyperv/scripts/utils/write_messages.ps1 b/plugins/providers/hyperv/scripts/utils/write_messages.ps1 new file mode 100644 index 000000000..ce0d8d8cf --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/write_messages.ps1 @@ -0,0 +1,20 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +function Write-Error-Message($message) { + $error_message = @{ + error = "$message" + } + Write-Host "===Begin-Error===" + $result = ConvertTo-json $error_message + Write-Host $result + Write-Host "===End-Error===" +} + +function Write-Output-Message($message) { + Write-Host "===Begin-Output===" + Write-Host $message + Write-Host "===End-Output===" +} diff --git a/plugins/providers/hyperv/version.rb b/plugins/providers/hyperv/version.rb new file mode 100644 index 000000000..6278a48c7 --- /dev/null +++ b/plugins/providers/hyperv/version.rb @@ -0,0 +1,10 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + VERSION = "0.0.1" + end +end From 3d8971b15f99bc50a8f0ec7ee3ca94d58ce54d97 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 15:38:11 -0800 Subject: [PATCH 02/40] providers/hyper-v: cleanup --- plugins/providers/hyperv/action.rb | 6 +- plugins/providers/hyperv/action/import.rb | 6 +- plugins/providers/hyperv/communicator.rb | 5 -- plugins/providers/hyperv/config.rb | 8 +-- plugins/providers/hyperv/driver/base.rb | 5 -- plugins/providers/hyperv/plugin.rb | 63 ++----------------- plugins/providers/hyperv/provider.rb | 7 --- .../providers/hyperv/scripts/import_vm.ps1 | 6 -- plugins/providers/hyperv/version.rb | 10 --- 9 files changed, 8 insertions(+), 108 deletions(-) delete mode 100644 plugins/providers/hyperv/version.rb diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index c2ca42694..a801ad346 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -1,9 +1,5 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - require "pathname" + require "vagrant/action/builder" module VagrantPlugins diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 25e834935..e7def6156 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -1,9 +1,5 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - require "log4r" + module VagrantPlugins module HyperV module Action diff --git a/plugins/providers/hyperv/communicator.rb b/plugins/providers/hyperv/communicator.rb index 883992bb8..3fca58482 100644 --- a/plugins/providers/hyperv/communicator.rb +++ b/plugins/providers/hyperv/communicator.rb @@ -1,8 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - module VagrantPlugins module HyperV module Communicator diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 5d16c04b4..8c8c5a0a6 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -1,11 +1,7 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - require "vagrant" require_relative "guest_config/config" require_relative "host_share/config" + module VagrantPlugins module HyperV class Config < Vagrant.plugin("2", :config) @@ -13,6 +9,7 @@ module VagrantPlugins # # @return [Boolean] attr_accessor :gui + attr_reader :host_share, :guest def host_config(&block) @@ -44,7 +41,6 @@ module VagrantPlugins end { "HyperV" => errors } end - end end end diff --git a/plugins/providers/hyperv/driver/base.rb b/plugins/providers/hyperv/driver/base.rb index d9dbb636f..70d337a40 100644 --- a/plugins/providers/hyperv/driver/base.rb +++ b/plugins/providers/hyperv/driver/base.rb @@ -1,8 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -require "debugger" require "json" require "vagrant/util/which" require "vagrant/util/subprocess" diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb index 5fdff60af..666400c6c 100644 --- a/plugins/providers/hyperv/plugin.rb +++ b/plugins/providers/hyperv/plugin.rb @@ -1,75 +1,20 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -begin - require "vagrant" -rescue LoadError - raise "The Vagrant Hyper-V plugin must be run within Vagrant." -end - -# This is a sanity check to make sure no one is attempting to install -# this into an early Vagrant version. -if Vagrant::VERSION < "1.3.5" - raise "The Vagrant Hyper-V plugin is only compatible with Vagrant 1.3+" -end - module VagrantPlugins module HyperV class Plugin < Vagrant.plugin("2") - name "HyperV" + name "Hyper-V provider" description <<-DESC This plugin installs a provider that allows Vagrant to manage machines in Hyper-V. DESC - config(:hyperv, :provider) do - require_relative "config" - Config - end - provider(:hyperv, parallel: true) do - # Setup logging and i18n - # setup_logging - # setup_i18n - - # Return the provider require_relative "provider" Provider end - # This initializes the internationalization strings. - def self.setup_i18n - - end - - # This sets up our log level to be whatever VAGRANT_LOG is. - def self.setup_logging - require "log4r" - level = nil - begin - level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) - rescue NameError - # This means that the logging constant wasn't found, - # which is fine. We just keep `level` as `nil`. But - # we tell the user. - level = nil - end - - # Some constants, such as "true" resolve to booleans, so the - # above error checking doesn't catch it. This will check to make - # sure that the log level is an integer, as Log4r requires. - level = nil if !level.is_a?(Integer) - - # Set the logging level on all "vagrant" namespaced - # logs as long as we have a valid level. - if level - logger = Log4r::Logger.new("vagrant_hyperv") - logger.outputters = Log4r::Outputter.stderr - logger.level = level - logger = nil - end + config(:hyperv, :provider) do + require_relative "config" + Config end end end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index f1bfcd4a2..4e60f5869 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -1,14 +1,8 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- require "log4r" -require "vagrant" module VagrantPlugins module HyperV class Provider < Vagrant.plugin("2", :provider) - def initialize(machine) @machine = machine end @@ -30,7 +24,6 @@ module VagrantPlugins state_id = env[:machine_state_id] # Get the short and long description - # TODO short = "Machine's current state is #{state_id}" long = "" diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index 587d0394b..42de437ee 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -1,9 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - - param ( [string]$vm_xml_config = $(throw "-vm_xml_config is required."), [string]$vhdx_path = $(throw "-vhdx_path is required.") diff --git a/plugins/providers/hyperv/version.rb b/plugins/providers/hyperv/version.rb deleted file mode 100644 index 6278a48c7..000000000 --- a/plugins/providers/hyperv/version.rb +++ /dev/null @@ -1,10 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -module VagrantPlugins - module HyperV - VERSION = "0.0.1" - end -end From a1958ee12f677f811dd357ec66f820402309d0e5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 16:28:11 -0800 Subject: [PATCH 03/40] providers/hyperv: shuffle things around --- lib/vagrant/util/powershell.rb | 36 ++++++ plugins/providers/hyperv/action.rb | 4 +- plugins/providers/hyperv/action/read_state.rb | 9 +- plugins/providers/hyperv/config.rb | 2 + plugins/providers/hyperv/driver.rb | 96 ++++++++++++++-- plugins/providers/hyperv/driver/base.rb | 105 ------------------ plugins/providers/hyperv/error.rb | 13 --- .../hyperv/error/subprocess_error.rb | 24 ---- plugins/providers/hyperv/errors.rb | 14 +++ plugins/providers/hyperv/plugin.rb | 15 +++ plugins/providers/hyperv/provider.rb | 16 ++- templates/locales/providers_hyperv.yml | 6 + .../plugins/providers/hyperv/provider_test.rb | 24 ++++ 13 files changed, 202 insertions(+), 162 deletions(-) create mode 100644 lib/vagrant/util/powershell.rb delete mode 100644 plugins/providers/hyperv/driver/base.rb delete mode 100644 plugins/providers/hyperv/error.rb delete mode 100644 plugins/providers/hyperv/error/subprocess_error.rb create mode 100644 plugins/providers/hyperv/errors.rb create mode 100644 templates/locales/providers_hyperv.yml create mode 100644 test/unit/plugins/providers/hyperv/provider_test.rb diff --git a/lib/vagrant/util/powershell.rb b/lib/vagrant/util/powershell.rb new file mode 100644 index 000000000..3e2206c3e --- /dev/null +++ b/lib/vagrant/util/powershell.rb @@ -0,0 +1,36 @@ +require_relative "subprocess" +require_relative "which" + +module Vagrant + module Util + # Executes PowerShell scripts. + # + # This is primarily a convenience wrapper around Subprocess that + # properly sets powershell flags for you. + class PowerShell + def self.available? + !!Which.which("powershell") + end + + # Execute a powershell script. + # + # @param [String] path Path to the PowerShell script to execute. + # @return [Subprocess::Result] + def self.execute(path, *args, **opts, &block) + command = [ + "powershell", + "-NoProfile", + "-ExecutionPolicy", "Bypass", + path, + args + ].flatten + + # Append on the options hash since Subprocess doesn't use + # Ruby 2.0 style options yet. + command << opts + + Subprocess.execute(*command, &block) + end + end + end +end diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index a801ad346..532c5b353 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -44,8 +44,8 @@ module VagrantPlugins def self.action_start Vagrant::Action::Builder.new.tap do |b| b.use StartInstance - b.use ShareFolders - b.use SyncFolders + #b.use ShareFolders + #b.use SyncFolders end end diff --git a/plugins/providers/hyperv/action/read_state.rb b/plugins/providers/hyperv/action/read_state.rb index b0bc82242..f0ad3456e 100644 --- a/plugins/providers/hyperv/action/read_state.rb +++ b/plugins/providers/hyperv/action/read_state.rb @@ -1,9 +1,5 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -require "debugger" require "log4r" + module VagrantPlugins module HyperV module Action @@ -19,7 +15,7 @@ module VagrantPlugins options = { vm_id: env[:machine].id } response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) env[:machine_state_id] = response["state"].downcase.to_sym - rescue Error::SubprocessError => e + rescue Error::SubprocessError env[:machine].id = nil env[:ui].info "Could not find a machine, assuming it to be deleted or terminated." env[:machine_state_id] = :not_created @@ -29,7 +25,6 @@ module VagrantPlugins end @app.call(env) end - end end end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 8c8c5a0a6..8513e8c7e 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -32,6 +32,7 @@ module VagrantPlugins def validate(machine) errors = _detected_errors +=begin unless host_share.valid_config? errors << host_share.errors.flatten.join(" ") end @@ -39,6 +40,7 @@ module VagrantPlugins unless guest.valid_config? errors << guest.errors.flatten.join(" ") end +=end { "HyperV" => errors } end end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index be47fb5ce..9c0f2645e 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -1,13 +1,95 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- +require "json" + +require "vagrant/util/powershell" module VagrantPlugins module HyperV - module Driver - lib_path = Pathname.new(File.expand_path("../driver", __FILE__)) - autoload :Base, lib_path.join("base") + class Driver + attr_reader :vmid + + def initialize(id=nil) + @vmid = id + @output = nil + end + + def execute(path, options) + execute_powershell(path, options) do |type, data| + process_output(type, data) + end + if success? + JSON.parse(json_output[:success].join) unless json_output[:success].empty? + else + message = json_output[:error].join unless json_output[:error].empty? + raise Error::SubprocessError, message if message + end + end + + def raw_execute(command) + command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten + clear_output_buffer + Vagrant::Util::Subprocess.execute(*command) do |type, data| + process_output(type, data) + end + end + + protected + + def json_output + return @json_output if @json_output + json_success_begin = false + json_error_begin = false + success = [] + error = [] + @output.split("\n").each do |line| + json_error_begin = false if line.include?("===End-Error===") + json_success_begin = false if line.include?("===End-Output===") + message = "" + if json_error_begin || json_success_begin + message = line.gsub("\\'","\"") + end + success << message if json_success_begin + error << message if json_error_begin + json_success_begin = true if line.include?("===Begin-Output===") + json_error_begin = true if line.include?("===Begin-Error===") + end + @json_output = { :success => success, :error => error } + end + + def success? + @error_messages.empty? && json_output[:error].empty? + end + + def process_output(type, data) + if type == :stdout + @output = data.gsub("\r\n", "\n") + end + if type == :stdin + # $stdin.gets.chomp || "" + end + if type == :stderr + @error_messages = data.gsub("\r\n", "\n") + end + end + + def clear_output_buffer + @output = "" + @error_messages = "" + @json_output = nil + end + + def execute_powershell(path, options, &block) + lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) + path = lib_path.join(path).to_s.gsub("/", "\\") + options = options || {} + ps_options = [] + options.each do |key, value| + ps_options << "-#{key}" + ps_options << "'#{value}'" + end + clear_output_buffer + opts = { notify: [:stdout, :stderr, :stdin] } + Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block) + end end end end diff --git a/plugins/providers/hyperv/driver/base.rb b/plugins/providers/hyperv/driver/base.rb deleted file mode 100644 index 70d337a40..000000000 --- a/plugins/providers/hyperv/driver/base.rb +++ /dev/null @@ -1,105 +0,0 @@ -require "json" -require "vagrant/util/which" -require "vagrant/util/subprocess" - -module VagrantPlugins - module HyperV - module Driver - class Base - attr_reader :vmid - - def initialize(id=nil) - @vmid = id - check_power_shell - @output = nil - end - - def execute(path, options) - r = execute_powershell(path, options) do |type, data| - process_output(type, data) - end - if success? - JSON.parse(json_output[:success].join) unless json_output[:success].empty? - else - message = json_output[:error].join unless json_output[:error].empty? - raise Error::SubprocessError, message if message - end - end - - def raw_execute(command) - command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten - clear_output_buffer - Vagrant::Util::Subprocess.execute(*command) do |type, data| - process_output(type, data) - end - end - - protected - - def json_output - return @json_output if @json_output - json_success_begin = false - json_error_begin = false - success = [] - error = [] - @output.split("\n").each do |line| - json_error_begin = false if line.include?("===End-Error===") - json_success_begin = false if line.include?("===End-Output===") - message = "" - if json_error_begin || json_success_begin - message = line.gsub("\\'","\"") - end - success << message if json_success_begin - error << message if json_error_begin - json_success_begin = true if line.include?("===Begin-Output===") - json_error_begin = true if line.include?("===Begin-Error===") - end - @json_output = { :success => success, :error => error } - end - - def success? - @error_messages.empty? && json_output[:error].empty? - end - - def process_output(type, data) - if type == :stdout - @output = data.gsub("\r\n", "\n") - end - if type == :stdin - # $stdin.gets.chomp || "" - end - if type == :stderr - @error_messages = data.gsub("\r\n", "\n") - end - end - - def clear_output_buffer - @output = "" - @error_messages = "" - @json_output = nil - end - - def check_power_shell - unless Vagrant::Util::Which.which('powershell') - raise "Power Shell not found" - end - end - - def execute_powershell(path, options, &block) - lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) - path = lib_path.join(path).to_s.gsub("/", "\\") - options = options || {} - ps_options = [] - options.each do |key, value| - ps_options << "-#{key}" - ps_options << "'#{value}'" - end - clear_output_buffer - command = ["powershell", "-NoProfile", "-ExecutionPolicy", - "Bypass", path, ps_options, {notify: [:stdout, :stderr, :stdin]}].flatten - Vagrant::Util::Subprocess.execute(*command, &block) - end - end - end - end -end diff --git a/plugins/providers/hyperv/error.rb b/plugins/providers/hyperv/error.rb deleted file mode 100644 index 1b6cb1cc1..000000000 --- a/plugins/providers/hyperv/error.rb +++ /dev/null @@ -1,13 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -module VagrantPlugins - module HyperV - module Error - lib_path = Pathname.new(File.expand_path("../error", __FILE__)) - autoload :SubprocessError, lib_path.join("subprocess_error") - end - end -end diff --git a/plugins/providers/hyperv/error/subprocess_error.rb b/plugins/providers/hyperv/error/subprocess_error.rb deleted file mode 100644 index 06773cdc6..000000000 --- a/plugins/providers/hyperv/error/subprocess_error.rb +++ /dev/null @@ -1,24 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "json" -require "vagrant/util/which" -require "vagrant/util/subprocess" - -module VagrantPlugins - module HyperV - module Error - class SubprocessError < RuntimeError - def initialize(message) - @message = JSON.parse(message) if message - end - - def message - @message["error"] - end - end - end - end -end diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb new file mode 100644 index 000000000..e4f647e5c --- /dev/null +++ b/plugins/providers/hyperv/errors.rb @@ -0,0 +1,14 @@ +module VagrantPlugins + module HyperV + module Errors + # A convenient superclass for all our errors. + class HyperVError < Vagrant::Errors::VagrantError + error_namespace("vagrant_hyperv.errors") + end + + class PowerShellRequired < HyperVError + error_key(:powershell_required) + end + end + end +end diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb index 666400c6c..5498a0051 100644 --- a/plugins/providers/hyperv/plugin.rb +++ b/plugins/providers/hyperv/plugin.rb @@ -1,5 +1,8 @@ module VagrantPlugins module HyperV + autoload :Action, File.expand_path("../action", __FILE__) + autoload :Errors, File.expand_path("../errors", __FILE__) + class Plugin < Vagrant.plugin("2") name "Hyper-V provider" description <<-DESC @@ -9,13 +12,25 @@ module VagrantPlugins provider(:hyperv, parallel: true) do require_relative "provider" + init! Provider end config(:hyperv, :provider) do require_relative "config" + init! Config end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path( + "templates/locales/providers_hyperv.yml", Vagrant.source_root) + I18n.reload! + @_init = true + end end end end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index 4e60f5869..882b002a4 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -1,10 +1,22 @@ require "log4r" +require_relative "driver" +require_relative "plugin" + +require "vagrant/util/powershell" + module VagrantPlugins module HyperV class Provider < Vagrant.plugin("2", :provider) + attr_reader :driver + def initialize(machine) + @driver = Driver.new @machine = machine + + if !Vagrant::Util::PowerShell.available? + raise Errors::PowerShellRequired + end end def action(name) @@ -45,10 +57,6 @@ module VagrantPlugins env[:machine_ssh_info].merge!(:port => 22) end end - - def driver - @driver ||= Driver::Base.new() - end end end end diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml new file mode 100644 index 000000000..fb84baa3a --- /dev/null +++ b/templates/locales/providers_hyperv.yml @@ -0,0 +1,6 @@ +en: + vagrant_hyperv: + errors: + powershell_required: |- + The Vagrant Hyper-V provider requires PowerShell to be available. + Please make sure "powershell.exe" is available on your PATH. diff --git a/test/unit/plugins/providers/hyperv/provider_test.rb b/test/unit/plugins/providers/hyperv/provider_test.rb new file mode 100644 index 000000000..258087028 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/provider_test.rb @@ -0,0 +1,24 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/provider") + +describe VagrantPlugins::HyperV::Provider do + let(:machine) { double("machine") } + let(:powershell) { double("powershell") } + + subject { described_class.new(machine) } + + before do + stub_const("Vagrant::Util::PowerShell", powershell) + powershell.stub(available?: true) + end + + describe "#initialize" do + it "raises an exception if powershell is not available" do + powershell.stub(available?: false) + + expect { subject }. + to raise_error(VagrantPlugins::HyperV::Errors::PowerShellRequired) + end + end +end From fb7dd73d95c205dd6bd0552b21c4e10311623ddd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 16:35:04 -0800 Subject: [PATCH 04/40] providers/hyperv: better errors --- plugins/providers/hyperv/action/import.rb | 7 +------ plugins/providers/hyperv/driver.rb | 10 +++++++++- plugins/providers/hyperv/errors.rb | 4 ++++ templates/locales/providers_hyperv.yml | 10 ++++++++++ 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index e7def6156..6cc0b0b42 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -29,12 +29,7 @@ module VagrantPlugins } env[:ui].info "Importing a Hyper-V instance" - begin - server = env[:machine].provider.driver.execute('import_vm.ps1', options) - rescue Error::SubprocessError => e - env[:ui].info e.message - return - end + server = env[:machine].provider.driver.execute('import_vm.ps1', options) env[:ui].info "Successfully imported a VM with name #{server['name']}" env[:machine].id = server["id"] @app.call(env) diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 9c0f2645e..a4cfcafa2 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -2,6 +2,8 @@ require "json" require "vagrant/util/powershell" +require_relative "plugin" + module VagrantPlugins module HyperV class Driver @@ -13,9 +15,15 @@ module VagrantPlugins end def execute(path, options) - execute_powershell(path, options) do |type, data| + r = execute_powershell(path, options) do |type, data| process_output(type, data) end + if r.exit_code != 0 + raise Errors::PowerShellError, + script: path, + stderr: r.stderr + end + if success? JSON.parse(json_output[:success].join) unless json_output[:success].empty? else diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb index e4f647e5c..d820198ab 100644 --- a/plugins/providers/hyperv/errors.rb +++ b/plugins/providers/hyperv/errors.rb @@ -6,6 +6,10 @@ module VagrantPlugins error_namespace("vagrant_hyperv.errors") end + class PowerShellError < HyperVError + error_key(:powershell_error) + end + class PowerShellRequired < HyperVError error_key(:powershell_required) end diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index fb84baa3a..c3a47969c 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -1,6 +1,16 @@ en: vagrant_hyperv: errors: + powershell_error: |- + An error occurred while executing a PowerShell script. This error + is shown below. Please read the error message and see if this is + a configuration error with your system. If it is not, then please + report a bug. + + Script: %{script} + Error: + + %{stderr} powershell_required: |- The Vagrant Hyper-V provider requires PowerShell to be available. Please make sure "powershell.exe" is available on your PATH. From ab4390eb67378f91ef063029a773706340205dc3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 16:51:25 -0800 Subject: [PATCH 05/40] providers/hyperv: clean up driver error handling quite a bit --- plugins/providers/hyperv/driver.rb | 38 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index a4cfcafa2..7676b1849 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -7,6 +7,9 @@ require_relative "plugin" module VagrantPlugins module HyperV class Driver + ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m + OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m + attr_reader :vmid def initialize(id=nil) @@ -15,29 +18,32 @@ module VagrantPlugins end def execute(path, options) - r = execute_powershell(path, options) do |type, data| - process_output(type, data) - end + r = execute_powershell(path, options) if r.exit_code != 0 raise Errors::PowerShellError, script: path, stderr: r.stderr end - if success? - JSON.parse(json_output[:success].join) unless json_output[:success].empty? - else - message = json_output[:error].join unless json_output[:error].empty? - raise Error::SubprocessError, message if message - end - end + # We only want unix-style line endings within Vagrant + r.stdout.gsub!("\r\n", "\n") + r.stderr.gsub!("\r\n", "\n") - def raw_execute(command) - command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten - clear_output_buffer - Vagrant::Util::Subprocess.execute(*command) do |type, data| - process_output(type, data) + error_match = ERROR_REGEXP.match(r.stdout) + output_match = OUTPUT_REGEXP.match(r.stdout) + + if error_match + data = JSON.parse(error_match[1]) + + # We have some error data. + raise Errors::PowerShellError, + script: path, + stderr: data["error"] end + + # Nothing + return nil if !output_match + return JSON.parse(output_match[1]) end protected @@ -86,7 +92,7 @@ module VagrantPlugins end def execute_powershell(path, options, &block) - lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) + lib_path = Pathname.new(File.expand_path("../scripts", __FILE__)) path = lib_path.join(path).to_s.gsub("/", "\\") options = options || {} ps_options = [] From 784a5b2e32e24fd84cf94a1fc7d5bdf3f14926bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 17:06:26 -0800 Subject: [PATCH 06/40] providers/hyperv: all sorts of tests to verify Windows/admins --- lib/vagrant/util/platform.rb | 21 +++++++++++++++++++ plugins/providers/hyperv/errors.rb | 8 +++++++ plugins/providers/hyperv/provider.rb | 9 ++++++++ templates/locales/providers_hyperv.yml | 9 ++++++++ .../plugins/providers/hyperv/provider_test.rb | 18 ++++++++++++++++ 5 files changed, 65 insertions(+) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index bb9181648..bdee0f7f4 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -29,6 +29,27 @@ module Vagrant false end + # Checks if the user running Vagrant on Windows has administrative + # privileges. + # + # @return [Boolean] + def windows_admin? + # We lazily-load this because it is only available on Windows + require 'win32/registry' + + # Verify that we have administrative privileges. The odd method of + # detecting this is based on this StackOverflow question: + # + # http://stackoverflow.com/questions/560366/ + # detect-if-running-with-administrator-privileges-under-windows-xp + begin + Win32::Registry::HKEY_USERS.open("S-1-5-19") {} + return true + rescue Win32::Registry::Error + return false + end + end + # This takes any path and converts it to a full-length Windows # path on Windows machines in Cygwin. # diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb index d820198ab..fa48b67d6 100644 --- a/plugins/providers/hyperv/errors.rb +++ b/plugins/providers/hyperv/errors.rb @@ -6,6 +6,10 @@ module VagrantPlugins error_namespace("vagrant_hyperv.errors") end + class AdminRequired < HyperVError + error_key(:admin_required) + end + class PowerShellError < HyperVError error_key(:powershell_error) end @@ -13,6 +17,10 @@ module VagrantPlugins class PowerShellRequired < HyperVError error_key(:powershell_required) end + + class WindowsRequired < HyperVError + error_key(:windows_required) + end end end end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index 882b002a4..d7d985f80 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -3,6 +3,7 @@ require "log4r" require_relative "driver" require_relative "plugin" +require "vagrant/util/platform" require "vagrant/util/powershell" module VagrantPlugins @@ -14,6 +15,14 @@ module VagrantPlugins @driver = Driver.new @machine = machine + if !Vagrant::Util::Platform.windows? + raise Errors::WindowsRequired + end + + if !Vagrant::Util::Platform.windows_admin? + raise Errors::AdminRequired + end + if !Vagrant::Util::PowerShell.available? raise Errors::PowerShellRequired end diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index c3a47969c..26322a4c9 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -1,6 +1,12 @@ en: vagrant_hyperv: errors: + admin_required: |- + The Hyper-V provider requires that Vagrant be run with + administrative privileges. This is a limitation of Hyper-V itself. + Hyper-V requires administrative privileges for management + commands. Please restart your console with administrative + privileges and try again. powershell_error: |- An error occurred while executing a PowerShell script. This error is shown below. Please read the error message and see if this is @@ -14,3 +20,6 @@ en: powershell_required: |- The Vagrant Hyper-V provider requires PowerShell to be available. Please make sure "powershell.exe" is available on your PATH. + windows_required: |- + The Hyper-V provider only works on Windows. Please try to + use another provider. diff --git a/test/unit/plugins/providers/hyperv/provider_test.rb b/test/unit/plugins/providers/hyperv/provider_test.rb index 258087028..c69301a22 100644 --- a/test/unit/plugins/providers/hyperv/provider_test.rb +++ b/test/unit/plugins/providers/hyperv/provider_test.rb @@ -4,16 +4,34 @@ require Vagrant.source_root.join("plugins/providers/hyperv/provider") describe VagrantPlugins::HyperV::Provider do let(:machine) { double("machine") } + let(:platform) { double("platform") } let(:powershell) { double("powershell") } subject { described_class.new(machine) } before do + stub_const("Vagrant::Util::Platform", platform) stub_const("Vagrant::Util::PowerShell", powershell) + platform.stub(windows?: true) + platform.stub(windows_admin?: true) powershell.stub(available?: true) end describe "#initialize" do + it "raises an exception if not windows" do + platform.stub(windows?: false) + + expect { subject }. + to raise_error(VagrantPlugins::HyperV::Errors::WindowsRequired) + end + + it "raises an exception if not an admin" do + platform.stub(windows_admin?: false) + + expect { subject }. + to raise_error(VagrantPlugins::HyperV::Errors::AdminRequired) + end + it "raises an exception if powershell is not available" do powershell.stub(available?: false) From fe93b0d2a53c09b32b7918b7e8138f8b0f7e4906 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 18:13:39 -0800 Subject: [PATCH 07/40] providers/hyperv: more checks on machine import --- plugins/providers/hyperv/action/import.rb | 45 +++++++++++++------ plugins/providers/hyperv/errors.rb | 4 ++ templates/locales/providers_hyperv.yml | 13 ++++++ .../plugins/providers/hyperv/provider_test.rb | 6 +++ 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 6cc0b0b42..fad717ccc 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -6,31 +6,48 @@ module VagrantPlugins class Import def initialize(app, env) @app = app - @logger = Log4r::Logger.new("vagrant::hyperv::connection") + @logger = Log4r::Logger.new("vagrant::hyperv::import") end def call(env) - box_directory = env[:machine].box.directory.to_s - path = Pathname.new(box_directory.to_s + '/Virtual Machines') - config_path = "" - path.each_child do |f| - config_path = f.to_s if f.extname.downcase == ".xml" + vm_dir = env[:machine].box.directory.join("Virtual Machines") + hd_dir = env[:machine].box.directory.join("Virtual Hard Disks") + + if !vm_dir.directory? || !hd_dir.directory? + raise Errors::BoxInvalid end - path = Pathname.new(box_directory.to_s + '/Virtual Hard Disks') - vhdx_path = "" - path.each_child do |f| - vhdx_path = f.to_s if f.extname.downcase == ".vhdx" + config_path = nil + vm_dir.each_child do |f| + if f.extname.downcase == ".xml" + config_path = f + break + end end + vhdx_path = nil + hd_dir.each_child do |f| + if f.extname.downcase == ".vhdx" + vhdx_path = f + break + end + end + + if !config_path || !vhdx_path + raise Errors::BoxInvalid + end + + # We have to normalize the paths to be Windows paths since + # we're executing PowerShell. options = { - vm_xml_config: config_path.gsub("/", "\\"), - vhdx_path: vhdx_path.gsub("/", "\\") + vm_xml_config: config_path.to_s.gsub("/", "\\"), + vhdx_path: vhdx_path.to_s.gsub("/", "\\") } env[:ui].info "Importing a Hyper-V instance" - server = env[:machine].provider.driver.execute('import_vm.ps1', options) - env[:ui].info "Successfully imported a VM with name #{server['name']}" + server = env[:machine].provider.driver.execute( + 'import_vm.ps1', options) + env[:ui].info "Successfully imported a VM with name: #{server['name']}" env[:machine].id = server["id"] @app.call(env) end diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb index fa48b67d6..67e26b3ad 100644 --- a/plugins/providers/hyperv/errors.rb +++ b/plugins/providers/hyperv/errors.rb @@ -10,6 +10,10 @@ module VagrantPlugins error_key(:admin_required) end + class BoxInvalid < HyperVError + error_key(:box_invalid) + end + class PowerShellError < HyperVError error_key(:powershell_error) end diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index 26322a4c9..edd2e21ae 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -7,6 +7,19 @@ en: Hyper-V requires administrative privileges for management commands. Please restart your console with administrative privileges and try again. + box_invalid: |- + The box you're using with the Hyper-V provider ('%{name}') + is invalid. A Hyper-V box should contain both a + "Virtual Machines" and a "Virtual Hard Disks" folder that are + created as part of exporting a Hyper-V machine. + + Within these directories, Vagrant expects to find the + virtual machine configuration as well as the root hard disk. + + The box you're attempting to use is missing one or both of + these directories or does not contain the files expected. Verify + that you added the correct box. If this problem persists, + please contact the creator of the box for assistance. powershell_error: |- An error occurred while executing a PowerShell script. This error is shown below. Please read the error message and see if this is diff --git a/test/unit/plugins/providers/hyperv/provider_test.rb b/test/unit/plugins/providers/hyperv/provider_test.rb index c69301a22..d431a2d2f 100644 --- a/test/unit/plugins/providers/hyperv/provider_test.rb +++ b/test/unit/plugins/providers/hyperv/provider_test.rb @@ -39,4 +39,10 @@ describe VagrantPlugins::HyperV::Provider do to raise_error(VagrantPlugins::HyperV::Errors::PowerShellRequired) end end + + describe "#driver" do + it "is initialized" do + expect(subject.driver).to be_kind_of(VagrantPlugins::HyperV::Driver) + end + end end From 88247797d436a01133abb9674600f1fd82b8530f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Feb 2014 18:18:46 -0800 Subject: [PATCH 08/40] providers/hyperv: more cleanup --- plugins/providers/hyperv/action/start_instance.rb | 5 ----- plugins/providers/hyperv/communicator.rb | 8 -------- plugins/providers/hyperv/scripts/start_vm.ps1 | 5 ----- 3 files changed, 18 deletions(-) delete mode 100644 plugins/providers/hyperv/communicator.rb diff --git a/plugins/providers/hyperv/action/start_instance.rb b/plugins/providers/hyperv/action/start_instance.rb index 66bdad6ee..db5906ae4 100644 --- a/plugins/providers/hyperv/action/start_instance.rb +++ b/plugins/providers/hyperv/action/start_instance.rb @@ -1,8 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - module VagrantPlugins module HyperV module Action diff --git a/plugins/providers/hyperv/communicator.rb b/plugins/providers/hyperv/communicator.rb deleted file mode 100644 index 3fca58482..000000000 --- a/plugins/providers/hyperv/communicator.rb +++ /dev/null @@ -1,8 +0,0 @@ -module VagrantPlugins - module HyperV - module Communicator - lib_path = Pathname.new(File.expand_path("../communicator", __FILE__)) - autoload :SSH, lib_path.join("ssh") - end - end -end diff --git a/plugins/providers/hyperv/scripts/start_vm.ps1 b/plugins/providers/hyperv/scripts/start_vm.ps1 index 6ccf45111..65971fc84 100644 --- a/plugins/providers/hyperv/scripts/start_vm.ps1 +++ b/plugins/providers/hyperv/scripts/start_vm.ps1 @@ -1,8 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - param ( [string]$vm_id = $(throw "-vm_id is required.") ) From 7c0948c81d84c7f35e014ac5d4e608a1edce9e69 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Feb 2014 11:31:33 -0800 Subject: [PATCH 09/40] providers/hyperv: clean up status script --- plugins/providers/hyperv/action.rb | 22 ++ plugins/providers/hyperv/action/read_state.rb | 12 +- .../providers/hyperv/action/stop_instance.rb | 28 +-- plugins/providers/hyperv/provider.rb | 15 +- .../hyperv/scripts/get_vm_status.ps1 | 43 ++-- .../providers/hyperv/scripts/import_vm.ps1 | 190 +++++++++--------- plugins/providers/hyperv/scripts/stop_vm.ps1 | 5 - .../plugins/providers/hyperv/provider_test.rb | 16 ++ 8 files changed, 173 insertions(+), 158 deletions(-) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 532c5b353..c5da842b6 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -28,6 +28,28 @@ module VagrantPlugins end end + def self.action_destroy + Vagrant::Action::Builder.new.tap do |b| + b.use Call, IsCreated do |env1, b1| + if !env1[:result] + b2.use MessageNotCreated + next + end + + b2.use Call, DestroyConfirm do |env2, b3| + if !env2[:result] + b3.use MessageWillNotDestroy + next + end + + b3.use ConfigValidate + b3.use StopInstance + b3.use DeleteVM + end + end + end + end + def self.action_halt Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate diff --git a/plugins/providers/hyperv/action/read_state.rb b/plugins/providers/hyperv/action/read_state.rb index f0ad3456e..31f5f2371 100644 --- a/plugins/providers/hyperv/action/read_state.rb +++ b/plugins/providers/hyperv/action/read_state.rb @@ -11,15 +11,9 @@ module VagrantPlugins def call(env) if env[:machine].id - begin - options = { vm_id: env[:machine].id } - response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) - env[:machine_state_id] = response["state"].downcase.to_sym - rescue Error::SubprocessError - env[:machine].id = nil - env[:ui].info "Could not find a machine, assuming it to be deleted or terminated." - env[:machine_state_id] = :not_created - end + options = { VmId: env[:machine].id } + response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) + env[:machine_state_id] = response["state"].downcase.to_sym else env[:machine_state_id] = :not_created end diff --git a/plugins/providers/hyperv/action/stop_instance.rb b/plugins/providers/hyperv/action/stop_instance.rb index 7dc2244ec..d6693cdc2 100644 --- a/plugins/providers/hyperv/action/stop_instance.rb +++ b/plugins/providers/hyperv/action/stop_instance.rb @@ -1,24 +1,18 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "log4r" module VagrantPlugins module HyperV module Action - class StopInstance - def initialize(app, env) - @app = app - end - - def call(env) - env[:ui].info('Stopping the Machine') - options = { vm_id: env[:machine].id } - response = env[:machine].provider.driver.execute('stop_vm.ps1', options) - @app.call(env) - end + class StopInstance + def initialize(app, env) + @app = app end + + def call(env) + env[:ui].info('Stopping the Machine') + options = { vm_id: env[:machine].id } + env[:machine].provider.driver.execute('stop_vm.ps1', options) + @app.call(env) + end + end end end end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index d7d985f80..818227735 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -38,11 +38,16 @@ module VagrantPlugins end def state - # Run a custom action we define called "read_state" which does - # what it says. It puts the state in the `:machine_state_id` - # key in the environment. - env = @machine.action("read_state") - state_id = env[:machine_state_id] + state_id = nil + state_id = :not_created if !@machine.id + + if !state_id + # Run a custom action we define called "read_state" which does + # what it says. It puts the state in the `:machine_state_id` + # key in the environment. + env = @machine.action(:read_state) + state_id = env[:machine_state_id] + end # Get the short and long description short = "Machine's current state is #{state_id}" diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 index d7bfcc0ba..739560190 100644 --- a/plugins/providers/hyperv/scripts/get_vm_status.ps1 +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -1,30 +1,25 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -param ( - [string]$vm_id = $(throw "-vm_id is required.") - ) +Param( + [Parameter(Mandatory=$true)] + [string]$VmId +) # Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +$Dir = Split-Path $script:MyInvocation.MyCommand.Path +. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +# Get the VM with the given name try { - $vm = Get-VM -Id $vm_id -ErrorAction "stop" - $state = $vm.state - $status = $vm.status - $resultHash = @{ - state = "$state" - status = "$status" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result + $VM = Get-VM -Id $VmId -ErrorAction "Stop" + $State = $VM.state + $Status = $VM.status +} catch [Microsoft.HyperV.PowerShell.VirtualizationOperationFailedException] { + $State = "not_created" + $Status = $State +} +$resultHash = @{ + state = "$State" + status = "$Status" } -catch { - Write-Error-Message $_ -} +$result = ConvertTo-Json $resultHash +Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index 42de437ee..a24351b6f 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -1,144 +1,138 @@ -param ( +Param( [string]$vm_xml_config = $(throw "-vm_xml_config is required."), [string]$vhdx_path = $(throw "-vhdx_path is required.") - ) +) + +$ErrorActionPreference = "Stop" # Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +$Dir = Split-Path $script:MyInvocation.MyCommand.Path +. [System.IO.Path]::Combine($Dir, "utils\write_messages.ps1") -try { - [xml]$vmconfig = Get-Content -Path $vm_xml_config +[xml]$vmconfig = Get-Content -Path $vm_xml_config - $vm_name = $vmconfig.configuration.properties.name.'#text' - $processors = $vmconfig.configuration.settings.processors.count.'#text' +$vm_name = $vmconfig.configuration.properties.name.'#text' +$processors = $vmconfig.configuration.settings.processors.count.'#text' - function Get_unique_name($name) { +function GetUniqueName($name) { Get-VM | ForEach-Object -Process { - if ($name -eq $_.Name) { - $name = $name + "_1" - } + if ($name -eq $_.Name) { + $name = $name + "_1" + } } return $name - } +} - do { +do { $name = $vm_name - $vm_name = Get_unique_name $name - } while ($vm_name -ne $name) + $vm_name = GetUniqueName $name +} while ($vm_name -ne $name) - $memory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank - if ($memory.dynamic_memory_enabled."#text" -eq "True") { - $dynamicmemory = $True - } - else { - $dynamicmemory = $False - } +$memory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank +if ($memory.dynamic_memory_enabled."#text" -eq "True") { + $dynamicmemory = $True +} +else { + $dynamicmemory = $False +} - # Memory values need to be in bytes - $MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB - $MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB - $MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB +# Memory values need to be in bytes +$MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB +$MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB +$MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB - # Get the name of the virtual switch - $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" +# Get the name of the virtual switch +$switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" - # Determine boot device - Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { - "Floppy" { $bootdevice = "floppy" } - "HardDrive" { $bootdevice = "IDE" } - "Optical" { $bootdevice = "CD" } - "Network" { $bootdevice = "LegacyNetworkAdapter" } - "Default" { $bootdevice = "IDE" } - } #switch +# Determine boot device +Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { + "Floppy" { $bootdevice = "floppy" } + "HardDrive" { $bootdevice = "IDE" } + "Optical" { $bootdevice = "CD" } + "Network" { $bootdevice = "LegacyNetworkAdapter" } + "Default" { $bootdevice = "IDE" } +} #switch - # Define a hash map of parameter values for New-VM +# Define a hash map of parameter values for New-VM - $vm_params = @{ - Name = $vm_name - NoVHD = $True - MemoryStartupBytes = $MemoryStartupBytes - SwitchName = $switchname - BootDevice = $bootdevice - ErrorAction = "Stop" - } +$vm_params = @{ + Name = $vm_name + NoVHD = $True + MemoryStartupBytes = $MemoryStartupBytes + SwitchName = $switchname + BootDevice = $bootdevice + ErrorAction = "Stop" +} - # Create the VM using the values in the hash map +# Create the VM using the values in the hash map - $vm = New-VM @vm_params +$vm = New-VM @vm_params - $notes = (Select-Xml -xml $vmconfig -XPath "//notes").node.'#text' +$notes = (Select-Xml -xml $vmconfig -XPath "//notes").node.'#text' - # Set-VM parameters to configure new VM with old values +# Set-VM parameters to configure new VM with old values - $more_vm_params = @{ - ProcessorCount = $processors - MemoryStartupBytes = $MemoryStartupBytes - } +$more_vm_params = @{ + ProcessorCount = $processors + MemoryStartupBytes = $MemoryStartupBytes +} - If ($dynamicmemory) { - $more_vm_params.Add("DynamicMemory",$True) - $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) - $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) - } - else { +If ($dynamicmemory) { + $more_vm_params.Add("DynamicMemory",$True) + $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) + $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) +} +else { $more_vm_params.Add("StaticMemory",$True) - } +} - if ($notes) { +if ($notes) { $more_vm_params.Add("Notes",$notes) - } +} - # Set the values on the VM - $vm | Set-VM @more_vm_params -Passthru +# Set the values on the VM +$vm | Set-VM @more_vm_params -Passthru - # Add drives to the virtual machine - $controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" - # A regular expression pattern to pull the number from controllers - [regex]$rx="\d" +# Add drives to the virtual machine +$controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" +# A regular expression pattern to pull the number from controllers +[regex]$rx="\d" - foreach ($controller in $controllers) { +foreach ($controller in $controllers) { $node = $controller.Node - # Check for SCSI - if ($node.ParentNode.ChannelInstanceGuid) { - $ControllerType = "SCSI" - } - else { - $ControllerType = "IDE" - } +# Check for SCSI + if ($node.ParentNode.ChannelInstanceGuid) { + $ControllerType = "SCSI" + } + else { + $ControllerType = "IDE" + } $drives = $node.ChildNodes | where {$_.pathname."#text"} foreach ($drive in $drives) { - #if drive type is ISO then set DVD Drive accordingly +#if drive type is ISO then set DVD Drive accordingly $driveType = $drive.type."#text" - $addDriveParam = @{ - ControllerNumber = $rx.Match($controller.node.name).value - Path = $vhdx_path - } + $addDriveParam = @{ + ControllerNumber = $rx.Match($controller.node.name).value + Path = $vhdx_path + } if ($drive.pool_id."#text") { - $ResourcePoolName = $drive.pool_id."#text" - $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) + $ResourcePoolName = $drive.pool_id."#text" + $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) } if ($drivetype -eq 'VHD') { $addDriveParam.add("ControllerType",$ControllerType) - $vm | Add-VMHardDiskDrive @AddDriveparam + $vm | Add-VMHardDiskDrive @AddDriveparam } } - } +} - $vm_id = (Get-VM $vm_name).id.guid - $resultHash = @{ +$vm_id = (Get-VM $vm_name).id.guid +$resultHash = @{ name = $vm_name - id = $vm_id - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result -} -catch { - Write-Error-Message $_ - return + id = $vm_id } +$result = ConvertTo-Json $resultHash +Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/stop_vm.ps1 b/plugins/providers/hyperv/scripts/stop_vm.ps1 index 759930b68..4ac442ab1 100644 --- a/plugins/providers/hyperv/scripts/stop_vm.ps1 +++ b/plugins/providers/hyperv/scripts/stop_vm.ps1 @@ -1,8 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - param ( [string]$vm_id = $(throw "-vm_id is required.") ) diff --git a/test/unit/plugins/providers/hyperv/provider_test.rb b/test/unit/plugins/providers/hyperv/provider_test.rb index d431a2d2f..cbe77dcb0 100644 --- a/test/unit/plugins/providers/hyperv/provider_test.rb +++ b/test/unit/plugins/providers/hyperv/provider_test.rb @@ -45,4 +45,20 @@ describe VagrantPlugins::HyperV::Provider do expect(subject.driver).to be_kind_of(VagrantPlugins::HyperV::Driver) end end + + describe "#state" do + it "returns not_created if no ID" do + machine.stub(id: nil) + + expect(subject.state.id).to eq(:not_created) + end + + it "calls an action to determine the ID" do + machine.stub(id: "foo") + machine.should_receive(:action).with(:read_state). + and_return({ machine_state_id: :bar }) + + expect(subject.state.id).to eq(:bar) + end + end end From 27688a183bd65c5c83a19c35b93aa7fc77649451 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Feb 2014 11:42:20 -0800 Subject: [PATCH 10/40] providers/hyperv: destroy --- plugins/providers/hyperv/action.rb | 13 +++-- plugins/providers/hyperv/action/delete_vm.rb | 18 ++++++ .../providers/hyperv/action/stop_instance.rb | 2 +- plugins/providers/hyperv/driver.rb | 55 ++----------------- .../providers/hyperv/scripts/delete_vm.ps1 | 7 +++ .../providers/hyperv/scripts/import_vm.ps1 | 10 ++-- plugins/providers/hyperv/scripts/stop_vm.ps1 | 32 +++-------- 7 files changed, 49 insertions(+), 88 deletions(-) create mode 100644 plugins/providers/hyperv/action/delete_vm.rb create mode 100644 plugins/providers/hyperv/scripts/delete_vm.ps1 diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index c5da842b6..d13dc7103 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -32,19 +32,19 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use Call, IsCreated do |env1, b1| if !env1[:result] - b2.use MessageNotCreated + b1.use MessageNotCreated next end - b2.use Call, DestroyConfirm do |env2, b3| + b1.use Call, DestroyConfirm do |env2, b2| if !env2[:result] - b3.use MessageWillNotDestroy + b2.use MessageWillNotDestroy next end - b3.use ConfigValidate - b3.use StopInstance - b3.use DeleteVM + b2.use ConfigValidate + b2.use StopInstance + b2.use DeleteVM end end end @@ -128,6 +128,7 @@ module VagrantPlugins # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) + autoload :DeleteVM, action_root.join("delete_vm") autoload :IsCreated, action_root.join("is_created") autoload :IsStopped, action_root.join("is_stopped") autoload :ReadState, action_root.join("read_state") diff --git a/plugins/providers/hyperv/action/delete_vm.rb b/plugins/providers/hyperv/action/delete_vm.rb new file mode 100644 index 000000000..afc47dd8f --- /dev/null +++ b/plugins/providers/hyperv/action/delete_vm.rb @@ -0,0 +1,18 @@ +module VagrantPlugins + module HyperV + module Action + class DeleteVM + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info('Deleting the Machine') + options = { VmId: env[:machine].id } + env[:machine].provider.driver.execute('delete_vm.ps1', options) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/stop_instance.rb b/plugins/providers/hyperv/action/stop_instance.rb index d6693cdc2..fe62693f8 100644 --- a/plugins/providers/hyperv/action/stop_instance.rb +++ b/plugins/providers/hyperv/action/stop_instance.rb @@ -8,7 +8,7 @@ module VagrantPlugins def call(env) env[:ui].info('Stopping the Machine') - options = { vm_id: env[:machine].id } + options = { VmId: env[:machine].id } env[:machine].provider.driver.execute('stop_vm.ps1', options) @app.call(env) end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 7676b1849..0ff8bad4e 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -10,13 +10,6 @@ module VagrantPlugins ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m - attr_reader :vmid - - def initialize(id=nil) - @vmid = id - @output = nil - end - def execute(path, options) r = execute_powershell(path, options) if r.exit_code != 0 @@ -48,49 +41,6 @@ module VagrantPlugins protected - def json_output - return @json_output if @json_output - json_success_begin = false - json_error_begin = false - success = [] - error = [] - @output.split("\n").each do |line| - json_error_begin = false if line.include?("===End-Error===") - json_success_begin = false if line.include?("===End-Output===") - message = "" - if json_error_begin || json_success_begin - message = line.gsub("\\'","\"") - end - success << message if json_success_begin - error << message if json_error_begin - json_success_begin = true if line.include?("===Begin-Output===") - json_error_begin = true if line.include?("===Begin-Error===") - end - @json_output = { :success => success, :error => error } - end - - def success? - @error_messages.empty? && json_output[:error].empty? - end - - def process_output(type, data) - if type == :stdout - @output = data.gsub("\r\n", "\n") - end - if type == :stdin - # $stdin.gets.chomp || "" - end - if type == :stderr - @error_messages = data.gsub("\r\n", "\n") - end - end - - def clear_output_buffer - @output = "" - @error_messages = "" - @json_output = nil - end - def execute_powershell(path, options, &block) lib_path = Pathname.new(File.expand_path("../scripts", __FILE__)) path = lib_path.join(path).to_s.gsub("/", "\\") @@ -100,7 +50,10 @@ module VagrantPlugins ps_options << "-#{key}" ps_options << "'#{value}'" end - clear_output_buffer + + # Always have a stop error action for failures + ps_options << "-ErrorAction" << "Stop" + opts = { notify: [:stdout, :stderr, :stdin] } Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block) end diff --git a/plugins/providers/hyperv/scripts/delete_vm.ps1 b/plugins/providers/hyperv/scripts/delete_vm.ps1 new file mode 100644 index 000000000..f54d23dc5 --- /dev/null +++ b/plugins/providers/hyperv/scripts/delete_vm.ps1 @@ -0,0 +1,7 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$VmId +) + +$VM = Get-VM -Id $VmId -ErrorAction "Stop" +Remove-VM $VM -Force diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index a24351b6f..5d673456f 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -1,13 +1,13 @@ Param( - [string]$vm_xml_config = $(throw "-vm_xml_config is required."), - [string]$vhdx_path = $(throw "-vhdx_path is required.") + [Parameter(Mandatory=$true)] + [string]$vm_xml_config, + [Parameter(Mandatory=$true)] + [string]$vhdx_path ) -$ErrorActionPreference = "Stop" - # Include the following modules $Dir = Split-Path $script:MyInvocation.MyCommand.Path -. [System.IO.Path]::Combine($Dir, "utils\write_messages.ps1") +. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) [xml]$vmconfig = Get-Content -Path $vm_xml_config diff --git a/plugins/providers/hyperv/scripts/stop_vm.ps1 b/plugins/providers/hyperv/scripts/stop_vm.ps1 index 4ac442ab1..5bf4a441d 100644 --- a/plugins/providers/hyperv/scripts/stop_vm.ps1 +++ b/plugins/providers/hyperv/scripts/stop_vm.ps1 @@ -1,26 +1,8 @@ -param ( - [string]$vm_id = $(throw "-vm_id is required.") - ) +Param( + [Parameter(Mandatory=$true)] + [string]$VmId +) -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } - -try { - $vm = Get-VM -Id $vm_id -ErrorAction stop - # Shuts down virtual machine regardless of any unsaved application data - Stop-VM $vm -Force - $state = $vm.state - $status = $vm.status - $resultHash = @{ - state = "$state" - status = "$status" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result -} -catch { - Write-Error-Message "Failed to stop a VM $_" -} +# Shuts down virtual machine regardless of any unsaved application data +$VM = Get-VM -Id $VmId -ErrorAction "Stop" +Stop-VM $VM -Force From 64abd95c6f7f6b62d447eb87b2449c7adc25c2ce Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 10:46:30 -0800 Subject: [PATCH 11/40] providers/hyperv: use HandleBox middleware --- plugins/providers/hyperv/action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index d13dc7103..e69ae130a 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -73,7 +73,7 @@ module VagrantPlugins def self.action_up Vagrant::Action::Builder.new.tap do |b| - b.use HandleBoxUrl + b.use HandleBox b.use ConfigValidate b.use Call, IsCreated do |env1, b1| if env1[:result] From 5d192857740091ac1d8783fd2f104bbdc0a9c192 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:12:24 -0800 Subject: [PATCH 12/40] providers/hyperv: wait for IP on boot --- plugins/providers/hyperv/action.rb | 4 +- plugins/providers/hyperv/action/import.rb | 4 +- .../providers/hyperv/action/read_guest_ip.rb | 7 +-- .../providers/hyperv/action/start_instance.rb | 11 ++--- .../hyperv/action/wait_for_ip_address.rb | 45 +++++++++++++++++++ plugins/providers/hyperv/config.rb | 32 ++++++------- plugins/providers/hyperv/errors.rb | 4 ++ .../providers/hyperv/guest_config/config.rb | 33 -------------- .../hyperv/scripts/get_network_config.ps1 | 33 +++++--------- templates/locales/providers_hyperv.yml | 10 +++++ .../plugins/providers/hyperv/config_test.rb | 18 ++++++++ 11 files changed, 111 insertions(+), 90 deletions(-) create mode 100644 plugins/providers/hyperv/action/wait_for_ip_address.rb delete mode 100644 plugins/providers/hyperv/guest_config/config.rb create mode 100644 test/unit/plugins/providers/hyperv/config_test.rb diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index e69ae130a..1761afa61 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -66,6 +66,7 @@ module VagrantPlugins def self.action_start Vagrant::Action::Builder.new.tap do |b| b.use StartInstance + b.use WaitForIPAddress #b.use ShareFolders #b.use SyncFolders end @@ -139,9 +140,10 @@ module VagrantPlugins autoload :MessageAlreadyCreated, action_root.join('message_already_created') autoload :MessageNotRunning, action_root.join('message_not_running') autoload :SyncFolders, action_root.join('sync_folders') - autoload :WaitForState, action_root.join('wait_for_state') autoload :ReadGuestIP, action_root.join('read_guest_ip') autoload :ShareFolders, action_root.join('share_folders') + autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") + autoload :WaitForState, action_root.join('wait_for_state') end end end diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index fad717ccc..2cf7240c7 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -44,10 +44,10 @@ module VagrantPlugins vhdx_path: vhdx_path.to_s.gsub("/", "\\") } - env[:ui].info "Importing a Hyper-V instance" + env[:ui].output("Importing a Hyper-V instance") server = env[:machine].provider.driver.execute( 'import_vm.ps1', options) - env[:ui].info "Successfully imported a VM with name: #{server['name']}" + env[:ui].detail("Successfully imported a VM with name: #{server['name']}") env[:machine].id = server["id"] @app.call(env) end diff --git a/plugins/providers/hyperv/action/read_guest_ip.rb b/plugins/providers/hyperv/action/read_guest_ip.rb index 209d02c58..92bfbdc8d 100644 --- a/plugins/providers/hyperv/action/read_guest_ip.rb +++ b/plugins/providers/hyperv/action/read_guest_ip.rb @@ -1,7 +1,3 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- require "log4r" require "timeout" @@ -23,13 +19,14 @@ module VagrantPlugins def read_host_ip(env) return nil if env[:machine].id.nil? + # Get Network details from WMI Provider # Wait for 120 sec By then the machine should be ready host_ip = nil begin Timeout.timeout(120) do begin - options = { vm_id: env[:machine].id } + options = { VmId: env[:machine].id } network_info = env[:machine].provider.driver.execute('get_network_config.ps1', options) host_ip = network_info["ip"] sleep 10 if host_ip.empty? diff --git a/plugins/providers/hyperv/action/start_instance.rb b/plugins/providers/hyperv/action/start_instance.rb index db5906ae4..d97924c04 100644 --- a/plugins/providers/hyperv/action/start_instance.rb +++ b/plugins/providers/hyperv/action/start_instance.rb @@ -7,15 +7,10 @@ module VagrantPlugins end def call(env) - env[:ui].info('Starting the Machine') + env[:ui].output('Starting the machine...') options = { vm_id: env[:machine].id } - begin - response = env[:machine].provider.driver.execute('start_vm.ps1', options) - env[:ui].info "Machine #{response["name"]} started" - rescue Error::SubprocessError => e - env[:ui].info e.message - return - end + response = env[:machine].provider.driver.execute('start_vm.ps1', options) + @app.call(env) end end diff --git a/plugins/providers/hyperv/action/wait_for_ip_address.rb b/plugins/providers/hyperv/action/wait_for_ip_address.rb new file mode 100644 index 000000000..a6a14b8d6 --- /dev/null +++ b/plugins/providers/hyperv/action/wait_for_ip_address.rb @@ -0,0 +1,45 @@ +require "timeout" + +module VagrantPlugins + module HyperV + module Action + class WaitForIPAddress + def initialize(app, env) + @app = app + end + + def call(env) + timeout = env[:machine].provider_config.ip_address_timeout + + env[:ui].output("Waiting for the machine to report its IP address...") + env[:ui].detail("Timeout: #{timeout} seconds") + + guest_ip = nil + Timeout.timeout(timeout) do + while true + # If a ctrl-c came through, break out + return if env[:interrupted] + + # Try to get the IP + network_info = env[:machine].provider.driver.execute( + "get_network_config.ps1", VmId: env[:machine].id) + guest_ip = network_info["ip"] + break if guest_ip && guest_ip != "" + + sleep 1 + end + end + + # If we were interrupted then return now + return if env[:interrupted] + + env[:ui].detail("IP: #{guest_ip}") + + @app.call(env) + rescue Timeout::Error + raise Errors::IPAddrTimeout + end + end + end + end +end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 8513e8c7e..3a512a945 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -1,34 +1,32 @@ require "vagrant" -require_relative "guest_config/config" require_relative "host_share/config" module VagrantPlugins module HyperV class Config < Vagrant.plugin("2", :config) - # If set to `true`, then VirtualBox will be launched with a GUI. + # The timeout to wait for an IP address when booting the machine, + # in seconds. # - # @return [Boolean] - attr_accessor :gui + # @return [Integer] + attr_accessor :ip_address_timeout - attr_reader :host_share, :guest + attr_reader :host_share + + def initialize + @ip_address_timeout = UNSET_VALUE + @host_share = HostShare::Config.new + end def host_config(&block) block.call(@host_share) end - def guest_config(&block) - block.call(@guest) - end - def finalize! - @gui = nil if @gui == UNSET_VALUE + if @ip_address_timeout == UNSET_VALUE + @ip_address_timeout = 120 + end end - def initialize(region_specific=false) - @gui = UNSET_VALUE - @host_share = HostShare::Config.new - @guest = GuestConfig::Config.new - end def validate(machine) errors = _detected_errors @@ -36,10 +34,6 @@ module VagrantPlugins unless host_share.valid_config? errors << host_share.errors.flatten.join(" ") end - - unless guest.valid_config? - errors << guest.errors.flatten.join(" ") - end =end { "HyperV" => errors } end diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb index 67e26b3ad..cd2519813 100644 --- a/plugins/providers/hyperv/errors.rb +++ b/plugins/providers/hyperv/errors.rb @@ -14,6 +14,10 @@ module VagrantPlugins error_key(:box_invalid) end + class IPAddrTimeout < HyperVError + error_key(:ip_addr_timeout) + end + class PowerShellError < HyperVError error_key(:powershell_error) end diff --git a/plugins/providers/hyperv/guest_config/config.rb b/plugins/providers/hyperv/guest_config/config.rb deleted file mode 100644 index 5b02d5a21..000000000 --- a/plugins/providers/hyperv/guest_config/config.rb +++ /dev/null @@ -1,33 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -module VagrantPlugins - module HyperV - module GuestConfig - class Config < Vagrant.plugin("2", :config) - attr_accessor :username, :password - - def errors - @errors - end - - def validate - @errors = [] - if username.nil? - @errors << "Please configure a Guest VM's username" - end - if password.nil? - @errors << "Please configure a Guest VM's password" - end - end - - def valid_config? - validate - errors.empty? - end - - end - end - end -end diff --git a/plugins/providers/hyperv/scripts/get_network_config.ps1 b/plugins/providers/hyperv/scripts/get_network_config.ps1 index bc48163d8..f8554297c 100644 --- a/plugins/providers/hyperv/scripts/get_network_config.ps1 +++ b/plugins/providers/hyperv/scripts/get_network_config.ps1 @@ -1,28 +1,17 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -param ( - [string]$vm_id = $(throw "-vm_id is required.") +Param( + [Parameter(Mandatory=$true)] + [string]$VmId ) # Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +$Dir = Split-Path $script:MyInvocation.MyCommand.Path +. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) -try { - $vm = Get-VM -Id $vm_id -ErrorAction "stop" - $network = Get-VMNetworkAdapter -VM $vm - $ip_address = $network.IpAddresses[0] - $resultHash = @{ +$vm = Get-VM -Id $VmId -ErrorAction "Stop" +$network = Get-VMNetworkAdapter -VM $vm +$ip_address = $network.IpAddresses[0] +$resultHash = @{ ip = "$ip_address" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result -} -catch { - Write-Error-Message "Failed to obtain network info of VM $_" } +$result = ConvertTo-Json $resultHash +Write-Output-Message $result diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index edd2e21ae..e18fa4136 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -20,6 +20,16 @@ en: these directories or does not contain the files expected. Verify that you added the correct box. If this problem persists, please contact the creator of the box for assistance. + ip_addr_timeout: |- + Hyper-V failed to determine your machine's IP address within the + configured timeout. Please verify the machine properly booted and + the network works. To do this, open the Hyper-V manager, find your + virtual machine, and connect to it. + + The most common cause for this error is that the running virtual + machine doesn't have the latest Hyper-V integration drivers. Please + research for your operating system how to install these in order + for the VM to properly communicate its IP address to Hyper-V. powershell_error: |- An error occurred while executing a PowerShell script. This error is shown below. Please read the error message and see if this is diff --git a/test/unit/plugins/providers/hyperv/config_test.rb b/test/unit/plugins/providers/hyperv/config_test.rb new file mode 100644 index 000000000..0aac992ab --- /dev/null +++ b/test/unit/plugins/providers/hyperv/config_test.rb @@ -0,0 +1,18 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/config") + +describe VagrantPlugins::HyperV::Config do + describe "#ip_address_timeout" do + it "can be set" do + subject.ip_address_timeout = 180 + subject.finalize! + expect(subject.ip_address_timeout).to eq(180) + end + + it "defaults to a number" do + subject.finalize! + expect(subject.ip_address_timeout).to eq(120) + end + end +end From 9fa4549105cfe2a422bbf0c908774721676da476 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:12:42 -0800 Subject: [PATCH 13/40] providers/hyperv: remove unused var --- plugins/providers/hyperv/action/start_instance.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/action/start_instance.rb b/plugins/providers/hyperv/action/start_instance.rb index d97924c04..17c5b9d57 100644 --- a/plugins/providers/hyperv/action/start_instance.rb +++ b/plugins/providers/hyperv/action/start_instance.rb @@ -9,7 +9,7 @@ module VagrantPlugins def call(env) env[:ui].output('Starting the machine...') options = { vm_id: env[:machine].id } - response = env[:machine].provider.driver.execute('start_vm.ps1', options) + env[:machine].provider.driver.execute('start_vm.ps1', options) @app.call(env) end From faf906c05788240a22383c75dca307f9f8804b1a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:19:11 -0800 Subject: [PATCH 14/40] providers/hyperv: wait for communicator on boot --- plugins/providers/hyperv/action.rb | 1 + plugins/providers/hyperv/provider.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 1761afa61..aa663cacf 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -67,6 +67,7 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use StartInstance b.use WaitForIPAddress + b.use WaitForCommunicator, [:running] #b.use ShareFolders #b.use SyncFolders end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index 818227735..d9a615f87 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -50,7 +50,7 @@ module VagrantPlugins end # Get the short and long description - short = "Machine's current state is #{state_id}" + short = state_id.to_s long = "" # Return the MachineState object From d4ec3b5dfc7fded539b22b431f380df87491caa7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:20:53 -0800 Subject: [PATCH 15/40] providers/hyperv: reset machine ID if machine deleted outside --- plugins/providers/hyperv/action/read_state.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/action/read_state.rb b/plugins/providers/hyperv/action/read_state.rb index 31f5f2371..c56bd44c4 100644 --- a/plugins/providers/hyperv/action/read_state.rb +++ b/plugins/providers/hyperv/action/read_state.rb @@ -12,8 +12,15 @@ module VagrantPlugins def call(env) if env[:machine].id options = { VmId: env[:machine].id } - response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) + response = env[:machine].provider.driver.execute( + "get_vm_status.ps1", options) env[:machine_state_id] = response["state"].downcase.to_sym + + # If the machine isn't created, then our ID is stale, so just + # mark it as not created. + if env[:machine_state_id] == :not_created + env[:machine].id = nil + end else env[:machine_state_id] = :not_created end From d8e4482f2c0fabf59b315df81c92256eb7fed7c5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:26:29 -0800 Subject: [PATCH 16/40] providers/hyperv: enable provisioning --- plugins/providers/hyperv/action.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index aa663cacf..a36e66e9b 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -65,6 +65,7 @@ module VagrantPlugins def self.action_start Vagrant::Action::Builder.new.tap do |b| + b.use Provision b.use StartInstance b.use WaitForIPAddress b.use WaitForCommunicator, [:running] From 4c810a879a68bef07fa30dead076a4129d2a627f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:56:37 -0800 Subject: [PATCH 17/40] providers/hyperv: clone the disk --- plugins/providers/hyperv/action/import.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 2cf7240c7..0808e679b 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -1,3 +1,5 @@ +require "fileutils" + require "log4r" module VagrantPlugins @@ -37,6 +39,13 @@ module VagrantPlugins raise Errors::BoxInvalid end + env[:ui].output("Importing a Hyper-V instance") + env[:ui].detail("Cloning virtual hard drive...") + source_path = vhdx_path.to_s + dest_path = env[:machine].data_dir.join("disk.vhdx").to_s + FileUtils.cp(source_path, dest_path) + vhdx_path = dest_path + # We have to normalize the paths to be Windows paths since # we're executing PowerShell. options = { @@ -44,7 +53,7 @@ module VagrantPlugins vhdx_path: vhdx_path.to_s.gsub("/", "\\") } - env[:ui].output("Importing a Hyper-V instance") + env[:ui].detail("Creating and registering the VM...") server = env[:machine].provider.driver.execute( 'import_vm.ps1', options) env[:ui].detail("Successfully imported a VM with name: #{server['name']}") From ea871ec9efdbc30a17e0d1c34a403a3a43b0c052 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 11:57:17 -0800 Subject: [PATCH 18/40] providers/hyperv: change wording to look a bit better --- plugins/providers/hyperv/action/delete_vm.rb | 2 +- plugins/providers/hyperv/action/stop_instance.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/providers/hyperv/action/delete_vm.rb b/plugins/providers/hyperv/action/delete_vm.rb index afc47dd8f..6f8608b98 100644 --- a/plugins/providers/hyperv/action/delete_vm.rb +++ b/plugins/providers/hyperv/action/delete_vm.rb @@ -7,7 +7,7 @@ module VagrantPlugins end def call(env) - env[:ui].info('Deleting the Machine') + env[:ui].info("Deleting the machine...") options = { VmId: env[:machine].id } env[:machine].provider.driver.execute('delete_vm.ps1', options) @app.call(env) diff --git a/plugins/providers/hyperv/action/stop_instance.rb b/plugins/providers/hyperv/action/stop_instance.rb index fe62693f8..f73e0cefe 100644 --- a/plugins/providers/hyperv/action/stop_instance.rb +++ b/plugins/providers/hyperv/action/stop_instance.rb @@ -7,7 +7,7 @@ module VagrantPlugins end def call(env) - env[:ui].info('Stopping the Machine') + env[:ui].info("Stopping the machine...")) options = { VmId: env[:machine].id } env[:machine].provider.driver.execute('stop_vm.ps1', options) @app.call(env) From 31abc3f4a30aafdb0f36d0d3ce2732145e5ccdbd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 12:07:18 -0800 Subject: [PATCH 19/40] providers/hyperv: fix file format to unix --- .../hyperv/action/wait_for_ip_address.rb | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/plugins/providers/hyperv/action/wait_for_ip_address.rb b/plugins/providers/hyperv/action/wait_for_ip_address.rb index a6a14b8d6..635d832f2 100644 --- a/plugins/providers/hyperv/action/wait_for_ip_address.rb +++ b/plugins/providers/hyperv/action/wait_for_ip_address.rb @@ -1,45 +1,45 @@ -require "timeout" - -module VagrantPlugins - module HyperV - module Action - class WaitForIPAddress - def initialize(app, env) - @app = app - end - - def call(env) - timeout = env[:machine].provider_config.ip_address_timeout - - env[:ui].output("Waiting for the machine to report its IP address...") - env[:ui].detail("Timeout: #{timeout} seconds") - - guest_ip = nil - Timeout.timeout(timeout) do - while true - # If a ctrl-c came through, break out - return if env[:interrupted] - - # Try to get the IP - network_info = env[:machine].provider.driver.execute( - "get_network_config.ps1", VmId: env[:machine].id) - guest_ip = network_info["ip"] - break if guest_ip && guest_ip != "" - - sleep 1 - end - end - - # If we were interrupted then return now - return if env[:interrupted] - - env[:ui].detail("IP: #{guest_ip}") - - @app.call(env) - rescue Timeout::Error - raise Errors::IPAddrTimeout - end - end - end - end -end +require "timeout" + +module VagrantPlugins + module HyperV + module Action + class WaitForIPAddress + def initialize(app, env) + @app = app + end + + def call(env) + timeout = env[:machine].provider_config.ip_address_timeout + + env[:ui].output("Waiting for the machine to report its IP address...") + env[:ui].detail("Timeout: #{timeout} seconds") + + guest_ip = nil + Timeout.timeout(timeout) do + while true + # If a ctrl-c came through, break out + return if env[:interrupted] + + # Try to get the IP + network_info = env[:machine].provider.driver.execute( + "get_network_config.ps1", VmId: env[:machine].id) + guest_ip = network_info["ip"] + break if guest_ip && guest_ip != "" + + sleep 1 + end + end + + # If we were interrupted then return now + return if env[:interrupted] + + env[:ui].detail("IP: #{guest_ip}") + + @app.call(env) + rescue Timeout::Error + raise Errors::IPAddrTimeout + end + end + end + end +end From 0fe4a4af263b58589ccaad20beae4305993a081f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 15:54:53 -0800 Subject: [PATCH 20/40] synced_folders/smb: basically working --- .../linux/cap/choose_addressable_ip_addr.rb | 20 +++ .../linux/cap/mount_smb_shared_folder.rb | 80 ++++++++++ plugins/guests/linux/plugin.rb | 10 ++ plugins/providers/hyperv/action.rb | 32 ++-- .../providers/hyperv/action/stop_instance.rb | 2 +- .../providers/hyperv/action/wait_for_state.rb | 40 ----- .../hyperv/scripts/set_smb_share.ps1 | 64 +++----- plugins/synced_folders/smb/errors.rb | 22 +++ plugins/synced_folders/smb/plugin.rb | 32 ++++ .../synced_folders/smb/scripts/host_info.ps1 | 8 + .../synced_folders/smb/scripts/set_share.ps1 | 34 +++++ plugins/synced_folders/smb/synced_folder.rb | 138 ++++++++++++++++++ templates/locales/synced_folder_smb.yml | 39 +++++ 13 files changed, 421 insertions(+), 100 deletions(-) create mode 100644 plugins/guests/linux/cap/choose_addressable_ip_addr.rb create mode 100644 plugins/guests/linux/cap/mount_smb_shared_folder.rb delete mode 100644 plugins/providers/hyperv/action/wait_for_state.rb create mode 100644 plugins/synced_folders/smb/errors.rb create mode 100644 plugins/synced_folders/smb/plugin.rb create mode 100644 plugins/synced_folders/smb/scripts/host_info.ps1 create mode 100644 plugins/synced_folders/smb/scripts/set_share.ps1 create mode 100644 plugins/synced_folders/smb/synced_folder.rb create mode 100644 templates/locales/synced_folder_smb.yml diff --git a/plugins/guests/linux/cap/choose_addressable_ip_addr.rb b/plugins/guests/linux/cap/choose_addressable_ip_addr.rb new file mode 100644 index 000000000..7584bd77a --- /dev/null +++ b/plugins/guests/linux/cap/choose_addressable_ip_addr.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module GuestLinux + module Cap + module ChooseAddressableIPAddr + def self.choose_addressable_ip_addr(machine, possible) + machine.communicate.tap do |comm| + possible.each do |ip| + command = "ping -c1 -w1 -W1 #{ip}" + if comm.test(command) + return ip + end + end + end + + nil + end + end + end + end +end diff --git a/plugins/guests/linux/cap/mount_smb_shared_folder.rb b/plugins/guests/linux/cap/mount_smb_shared_folder.rb new file mode 100644 index 000000000..1c0824468 --- /dev/null +++ b/plugins/guests/linux/cap/mount_smb_shared_folder.rb @@ -0,0 +1,80 @@ +module VagrantPlugins + module GuestLinux + module Cap + class MountSMBSharedFolder + def self.mount_smb_shared_folder(machine, name, guestpath, options) + expanded_guest_path = machine.guest.capability( + :shell_expand_guest_path, guestpath) + + mount_commands = [] + mount_device = "//#{options[:smb_host]}/#{name}" + + if options[:owner].is_a? Integer + mount_uid = options[:owner] + else + mount_uid = "`id -u #{options[:owner]}`" + end + + if options[:group].is_a? Integer + mount_gid = options[:group] + mount_gid_old = options[:group] + else + mount_gid = "`getent group #{options[:group]} | cut -d: -f3`" + mount_gid_old = "`id -g #{options[:group]}`" + end + + options[:mount_options] ||= [] + options[:mount_options] << "sec=ntlm" + options[:mount_options] << "username=#{options[:smb_username]}" + options[:mount_options] << "pass=#{options[:smb_password]}" + + # First mount command uses getent to get the group + mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}" + mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options] + mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}" + + # Second mount command uses the old style `id -g` + mount_options = "-o uid=#{mount_uid},gid=#{mount_gid_old}" + mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options] + mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}" + + # Create the guest path if it doesn't exist + machine.communicate.sudo("mkdir -p #{expanded_guest_path}") + + # Attempt to mount the folder. We retry here a few times because + # it can fail early on. + attempts = 0 + while true + success = true + + mount_commands.each do |command| + no_such_device = false + status = machine.communicate.sudo(command, error_check: false) do |type, data| + no_such_device = true if type == :stderr && data =~ /No such device/i + end + + success = status == 0 && !no_such_device + break if success + end + + break if success + + attempts += 1 + if attempts > 10 + raise Vagrant::Errors::LinuxMountFailed, + command: mount_commands.join("\n") + end + + sleep 2 + end + + # Emit an upstart event if we can + if machine.communicate.test("test -x /sbin/initctl") + machine.communicate.sudo( + "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}") + end + end + end + end + end +end diff --git a/plugins/guests/linux/plugin.rb b/plugins/guests/linux/plugin.rb index 9a93ab5b9..45c6e2bce 100644 --- a/plugins/guests/linux/plugin.rb +++ b/plugins/guests/linux/plugin.rb @@ -11,6 +11,11 @@ module VagrantPlugins Guest end + guest_capability("linux", "choose_addressable_ip_addr") do + require_relative "cap/choose_addressable_ip_addr" + Cap::ChooseAddressableIPAddr + end + guest_capability("linux", "halt") do require_relative "cap/halt" Cap::Halt @@ -31,6 +36,11 @@ module VagrantPlugins Cap::MountNFS end + guest_capability("linux", "mount_smb_shared_folder") do + require_relative "cap/mount_smb_shared_folder" + Cap::MountSMBSharedFolder + end + guest_capability("linux", "mount_virtualbox_shared_folder") do require_relative "cap/mount_virtualbox_shared_folder" Cap::MountVirtualBoxSharedFolder diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index a36e66e9b..b1cda402f 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -16,14 +16,9 @@ module VagrantPlugins b2.use MessageNotCreated next end + b2.use action_halt - b2.use Call, WaitForState, :off, 120 do |env2, b3| - if env2[:result] - b3.use action_up - else - env2[:ui].info("Machine did not reload, Check machine's status") - end - end + b2.use action_start end end end @@ -58,7 +53,12 @@ module VagrantPlugins b2.use MessageNotCreated next end - b2.use StopInstance + + b2.use Call, GracefulHalt, :off, :running do |env2, b3| + if !env2[:result] + b3.use StopInstance + end + end end end end @@ -69,6 +69,8 @@ module VagrantPlugins b.use StartInstance b.use WaitForIPAddress b.use WaitForCommunicator, [:running] + b.use SyncedFolders + #b.use ShareFolders #b.use SyncFolders end @@ -79,18 +81,11 @@ module VagrantPlugins b.use HandleBox b.use ConfigValidate b.use Call, IsCreated do |env1, b1| - if env1[:result] - b1.use Call, IsStopped do |env2, b2| - if env2[:result] - b2.use action_start - else - b2.use MessageAlreadyCreated - end - end - else + if !env1[:result] b1.use Import - b1.use action_start end + + b1.use action_start end end end @@ -145,7 +140,6 @@ module VagrantPlugins autoload :ReadGuestIP, action_root.join('read_guest_ip') autoload :ShareFolders, action_root.join('share_folders') autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") - autoload :WaitForState, action_root.join('wait_for_state') end end end diff --git a/plugins/providers/hyperv/action/stop_instance.rb b/plugins/providers/hyperv/action/stop_instance.rb index f73e0cefe..132ea8624 100644 --- a/plugins/providers/hyperv/action/stop_instance.rb +++ b/plugins/providers/hyperv/action/stop_instance.rb @@ -7,7 +7,7 @@ module VagrantPlugins end def call(env) - env[:ui].info("Stopping the machine...")) + env[:ui].info("Stopping the machine...") options = { VmId: env[:machine].id } env[:machine].provider.driver.execute('stop_vm.ps1', options) @app.call(env) diff --git a/plugins/providers/hyperv/action/wait_for_state.rb b/plugins/providers/hyperv/action/wait_for_state.rb deleted file mode 100644 index 6ed873410..000000000 --- a/plugins/providers/hyperv/action/wait_for_state.rb +++ /dev/null @@ -1,40 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -require "log4r" -require "timeout" -require "debugger" - -module VagrantPlugins - module HyperV - module Action - class WaitForState - def initialize(app, env, state, timeout) - @app = app - @state = state - @timeout = timeout - end - - def call(env) - env[:result] = true - # Wait until the Machine's state is disabled (ie State of Halt) - unless env[:machine].state.id == @state - env[:ui].info("Waiting for machine to #{@state}") - begin - Timeout.timeout(@timeout) do - until env[:machine].state.id == @state - sleep 2 - end - end - rescue Timeout::Error - env[:result] = false # couldn't reach state in time - end - end - @app.call(env) - end - - end - end - end -end diff --git a/plugins/providers/hyperv/scripts/set_smb_share.ps1 b/plugins/providers/hyperv/scripts/set_smb_share.ps1 index 6f64183c8..fd8c4e795 100644 --- a/plugins/providers/hyperv/scripts/set_smb_share.ps1 +++ b/plugins/providers/hyperv/scripts/set_smb_share.ps1 @@ -1,46 +1,30 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- +Param( + [Parameter(Mandatory=$true)] + [string]$path, + [Parameter(Mandatory=$true)] + [string]$share_name, + [Parameter(Mandatory=$true)] + [string]$host_share_username +) -param ( - [string]$path = $(throw "-path is required."), - [string]$share_name = $(throw "-share_name is required."), - [string]$host_share_username = $(throw "-host_share_username is required") - ) +$ErrorAction = "Stop" -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } - -try { - # See all available shares and check alert user for existing / conflicting share name - $shared_folders = net share - $reg = "$share_name(\s+)$path(\s)" - $existing_share = $shared_folders -Match $reg - if ($existing_share) { +# See all available shares and check alert user for +# existing/conflicting share name +$shared_folders = net share +$reg = "$share_name(\s+)$path(\s)" +$existing_share = $shared_folders -Match $reg +if ($existing_share) { # Always clear the existing share name and create a new one net share $share_name /delete /y - } - - $computer_name = $(Get-WmiObject Win32_Computersystem).name - $grant_permission = "$computer_name\$host_share_username,Full" - $result = net share $share_name=$path /unlimited /GRANT:$grant_permission - if ($result -Match "$share_name was shared successfully.") { - $resultHash = @{ - message = "OK" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result - } else { - $reg = "^$share_name(\s+)" - $existing_share = $shared_folders -Match $reg - Write-Error-Message "IGNORING Conflicting share name, A share name already exist $existing_share" - } -} catch { - Write-Error-Message $_ - return } +$computer_name = $(Get-WmiObject Win32_Computersystem).name +$grant_permission = "$computer_name\$host_share_username,Full" +$result = net share $share_name=$path /unlimited /GRANT:$grant_permission +if ($result -Match "$share_name was shared successfully.") { + exit 0 +} + +$host.ui.WriteErrorLine("Error: $result") +exit 1 diff --git a/plugins/synced_folders/smb/errors.rb b/plugins/synced_folders/smb/errors.rb new file mode 100644 index 000000000..d1279563a --- /dev/null +++ b/plugins/synced_folders/smb/errors.rb @@ -0,0 +1,22 @@ +module VagrantPlugins + module SyncedFolderSMB + module Errors + # A convenient superclass for all our errors. + class SMBError < Vagrant::Errors::VagrantError + error_namespace("vagrant_sf_smb.errors") + end + + class DefineShareFailed < SMBError + error_key(:define_share_failed) + end + + class NoHostIPAddr < SMBError + error_key(:no_routable_host_addr) + end + + class PowershellError < SMBError + error_key(:powershell_error) + end + end + end +end diff --git a/plugins/synced_folders/smb/plugin.rb b/plugins/synced_folders/smb/plugin.rb new file mode 100644 index 000000000..82b029a0b --- /dev/null +++ b/plugins/synced_folders/smb/plugin.rb @@ -0,0 +1,32 @@ +require "vagrant" + +module VagrantPlugins + module SyncedFolderSMB + autoload :Errors, File.expand_path("../errors", __FILE__) + + # This plugin implements SMB synced folders. + class Plugin < Vagrant.plugin("2") + name "SMB synced folders" + description <<-EOF + The SMB synced folders plugin enables you to use SMB folders on + Windows and share them to guest machines. + EOF + + synced_folder("smb", 5) do + require_relative "synced_folder" + init! + SyncedFolder + end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path( + "templates/locales/synced_folder_smb.yml", Vagrant.source_root) + I18n.reload! + @_init = true + end + end + end +end diff --git a/plugins/synced_folders/smb/scripts/host_info.ps1 b/plugins/synced_folders/smb/scripts/host_info.ps1 new file mode 100644 index 000000000..7d283a7f4 --- /dev/null +++ b/plugins/synced_folders/smb/scripts/host_info.ps1 @@ -0,0 +1,8 @@ +$ErrorAction = "Stop" + +$net = Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"' +$result = @{ + ip_addresses = $net.ipaddress +} + +Write-Output $(ConvertTo-Json $result) diff --git a/plugins/synced_folders/smb/scripts/set_share.ps1 b/plugins/synced_folders/smb/scripts/set_share.ps1 new file mode 100644 index 000000000..ea24995fd --- /dev/null +++ b/plugins/synced_folders/smb/scripts/set_share.ps1 @@ -0,0 +1,34 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$path, + [Parameter(Mandatory=$true)] + [string]$share_name, + [string]$host_share_username = $null +) + +$ErrorAction = "Stop" + +# See all available shares and check alert user for existing/conflicting +# share names. +$path_regexp = [System.Text.RegularExpressions.Regex]::Escape($path) +$name_regexp = [System.Text.RegularExpressions.Regex]::Escape($share_name) +$reg = "(?m)$name_regexp\s+$path_regexp\s" +$existing_share = $($(net share) -join "`n") -Match $reg +if ($existing_share) { + # Always clear the existing share name and create a new one + net share $share_name /delete /y +} + +$grant = "Everyone,Full" +if (![string]::IsNullOrEmpty($host_share_username)) { + $computer_name = $(Get-WmiObject Win32_Computersystem).name + $grant = "$computer_name\$host_share_username,Full" +} + +$result = net share $share_name=$path /unlimited /GRANT:$grant +if ($result -Match "$share_name was shared successfully.") { + exit 0 +} + +$host.ui.WriteErrorLine("Error: $result") +exit 1 diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb new file mode 100644 index 000000000..b5817038c --- /dev/null +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -0,0 +1,138 @@ +require "json" + +require "log4r" + +require "vagrant/util/platform" +require "vagrant/util/powershell" + +module VagrantPlugins + module SyncedFolderSMB + class SyncedFolder < Vagrant.plugin("2", :synced_folder) + def initialize(*args) + super + + @logger = Log4r::Logger.new("vagrant::synced_folders::smb") + end + + def usable?(machine, raise_error=false) + if !Vagrant::Util::Platform.windows? + # TODO: raise error if specified + return false + end + + true + end + + def prepare(machine, folders, opts) + script_path = File.expand_path("../scripts/set_share.ps1", __FILE__) + + folders.each do |id, data| + hostpath = data[:hostpath] + + data[:smb_id] ||= "#{machine.id}-#{id.gsub("/", "-")}" + + args = [] + args << "-path" << hostpath.gsub("/", "\\") + args << "-share_name" << data[:smb_id] + #args << "-host_share_username" << "mitchellh" + + r = Vagrant::Util::PowerShell.execute(script_path, *args) + if r.exit_code != 0 + raise Errors::DefineShareFailed, + host: hostpath.to_s, + stderr: r.stderr, + stdout: r.stdout + end + end + end + + def enable(machine, folders, nfsopts) + machine.ui.output(I18n.t("vagrant_sf_smb.mounting")) + + # Make sure that this machine knows this dance + if !machine.guest.capability?(:mount_smb_shared_folder) + raise Vagrant::Errors::GuestCapabilityNotFound, + cap: "mount_smb_shared_folder", + guest: machine.guest.name.to_s + end + + # Detect the host IP for this guest if one wasn't specified + # for every folder. + host_ip = nil + need_host_ip = false + folders.each do |id, data| + if !data[:smb_host] + need_host_ip = true + break + end + end + + if need_host_ip + candidate_ips = load_host_ips + @logger.debug("Potential host IPs: #{candidate_ips.inspect}") + host_ip = machine.guest.capability( + :choose_addressable_ip_addr, candidate_ips) + if !host_ip + raise Errors::NoHostIPAddr + end + end + + # If we need auth information, then ask the user + username = nil + password = nil + need_auth = false + folders.each do |id, data| + if !data[:smb_username] || !data[:smb_password] + need_auth = true + break + end + end + + if need_auth + machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ") + username = machine.ui.ask("Username: ") + password = machine.ui.ask("Password (will be hidden): ", echo: false) + end + + # This is used for defaulting the owner/group + ssh_info = machine.ssh_info + + folders.each do |id, data| + data = data.dup + data[:smb_host] ||= host_ip + data[:smb_username] ||= username + data[:smb_password] ||= password + + # Default the owner/group of the folder to the SSH user + data[:owner] ||= ssh_info[:username] + data[:group] ||= ssh_info[:username] + + machine.ui.detail(I18n.t( + "vagrant_sf_smb.mounting_single", + host: data[:hostpath].to_s, + guest: data[:guestpath].to_s)) + machine.guest.capability( + :mount_smb_shared_folder, data[:smb_id], data[:guestpath], data) + end + end + + def cleanup(machine, opts) + + end + + protected + + def load_host_ips + script_path = File.expand_path("../scripts/host_info.ps1", __FILE__) + r = Vagrant::Util::PowerShell.execute(script_path) + if r.exit_code != 0 + raise Errors::PowershellError, + script: script_path, + stderr: r.stderr + end + + JSON.parse(r.stdout)["ip_addresses"] + end + end + end +end diff --git a/templates/locales/synced_folder_smb.yml b/templates/locales/synced_folder_smb.yml new file mode 100644 index 000000000..451cb80af --- /dev/null +++ b/templates/locales/synced_folder_smb.yml @@ -0,0 +1,39 @@ +en: + vagrant_sf_smb: + mounting: |- + Mounting SMB shared folders... + mounting_single: |- + %{host} => %{guest} + warning_password: |- + You will be asked for the username and password to use to mount the + folders shortly. Please use the proper username/password of your + Windows account. + + errors: + define_share_failed: |- + Exporting an SMB share failed! Details about the failure are shown + below. Please inspect the error message and correct any problems. + + Host path: %{host} + + Stderr: %{stderr} + + Stdout: %{stdout} + no_routable_host_addr: |- + We couldn't detect an IP address that was routable to this + machine from the guest machine! Please verify networking is properly + setup in the guest machine and that it is able to access this + host. + + As another option, you can manually specify an IP for the machine + to mount from using the `smb_host` option to the synced folder. + powershell_error: |- + An error occurred while executing a PowerShell script. This error + is shown below. Please read the error message and see if this is + a configuration error with your system. If it is not, then please + report a bug. + + Script: %{script} + Error: + + %{stderr} From a50421c54c6b7af336c5cda45e3f34d8f4159275 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 15:58:43 -0800 Subject: [PATCH 21/40] synced_folders/smb: raise error if not windows --- plugins/synced_folders/smb/errors.rb | 4 ++++ plugins/synced_folders/smb/synced_folder.rb | 2 +- templates/locales/synced_folder_smb.yml | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/synced_folders/smb/errors.rb b/plugins/synced_folders/smb/errors.rb index d1279563a..b9923774c 100644 --- a/plugins/synced_folders/smb/errors.rb +++ b/plugins/synced_folders/smb/errors.rb @@ -17,6 +17,10 @@ module VagrantPlugins class PowershellError < SMBError error_key(:powershell_error) end + + class WindowsHostRequired < SMBError + error_key(:windows_host_required) + end end end end diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index b5817038c..c843c6b30 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -16,7 +16,7 @@ module VagrantPlugins def usable?(machine, raise_error=false) if !Vagrant::Util::Platform.windows? - # TODO: raise error if specified + raise Errors::WindowsHostRequired if raise_error return false end diff --git a/templates/locales/synced_folder_smb.yml b/templates/locales/synced_folder_smb.yml index 451cb80af..509772a57 100644 --- a/templates/locales/synced_folder_smb.yml +++ b/templates/locales/synced_folder_smb.yml @@ -37,3 +37,7 @@ en: Error: %{stderr} + windows_host_required: |- + SMB shared folders are only available when Vagrant is running + on Windows. The guest machine can be running non-Windows. Please use + another synced folder type. From dd6f1083b07afc89d2566aeb1a57c2ecf8a4c7d0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 16:02:33 -0800 Subject: [PATCH 22/40] synced_folders/smb: test for admin privileges --- plugins/synced_folders/smb/errors.rb | 4 ++++ plugins/synced_folders/smb/synced_folder.rb | 5 +++++ templates/locales/synced_folder_smb.yml | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/plugins/synced_folders/smb/errors.rb b/plugins/synced_folders/smb/errors.rb index b9923774c..697829833 100644 --- a/plugins/synced_folders/smb/errors.rb +++ b/plugins/synced_folders/smb/errors.rb @@ -21,6 +21,10 @@ module VagrantPlugins class WindowsHostRequired < SMBError error_key(:windows_host_required) end + + class WindowsAdminRequired < SMBError + error_key(:windows_admin_required) + end end end end diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index c843c6b30..df3bab2a2 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -20,6 +20,11 @@ module VagrantPlugins return false end + if !Vagrant::Util::Platform.windows_admin? + raise Errors::WindowsAdminRequired if raise_error + return false + end + true end diff --git a/templates/locales/synced_folder_smb.yml b/templates/locales/synced_folder_smb.yml index 509772a57..c3ce974fa 100644 --- a/templates/locales/synced_folder_smb.yml +++ b/templates/locales/synced_folder_smb.yml @@ -37,6 +37,11 @@ en: Error: %{stderr} + windows_admin_required: |- + SMB shared folders require running Vagrant with administrative + privileges. This is a limitation of Windows, since creating new + network shares requires admin privileges. Please try again in a + console with proper permissions or use another synced folder type. windows_host_required: |- SMB shared folders are only available when Vagrant is running on Windows. The guest machine can be running non-Windows. Please use From 41bc86c4905f80d65665170a2cfff611da884260 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 16:05:37 -0800 Subject: [PATCH 23/40] providers/hyperv: clean up provider since we moved out SMB --- plugins/providers/hyperv/action.rb | 2 - .../providers/hyperv/action/share_folders.rb | 123 ------------------ .../providers/hyperv/scripts/host_info.ps1 | 25 ---- .../hyperv/scripts/set_smb_share.ps1 | 30 ----- .../smb}/scripts/mount_share.ps1 | 118 ++++++++--------- plugins/synced_folders/smb/synced_folder.rb | 23 ++++ 6 files changed, 79 insertions(+), 242 deletions(-) delete mode 100644 plugins/providers/hyperv/action/share_folders.rb delete mode 100644 plugins/providers/hyperv/scripts/host_info.ps1 delete mode 100644 plugins/providers/hyperv/scripts/set_smb_share.ps1 rename plugins/{providers/hyperv => synced_folders/smb}/scripts/mount_share.ps1 (89%) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index b1cda402f..845d44ca6 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -71,7 +71,6 @@ module VagrantPlugins b.use WaitForCommunicator, [:running] b.use SyncedFolders - #b.use ShareFolders #b.use SyncFolders end end @@ -138,7 +137,6 @@ module VagrantPlugins autoload :MessageNotRunning, action_root.join('message_not_running') autoload :SyncFolders, action_root.join('sync_folders') autoload :ReadGuestIP, action_root.join('read_guest_ip') - autoload :ShareFolders, action_root.join('share_folders') autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") end end diff --git a/plugins/providers/hyperv/action/share_folders.rb b/plugins/providers/hyperv/action/share_folders.rb deleted file mode 100644 index c46f89162..000000000 --- a/plugins/providers/hyperv/action/share_folders.rb +++ /dev/null @@ -1,123 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -require "debugger" -require "vagrant/util/subprocess" -module VagrantPlugins - module HyperV - module Action - class ShareFolders - - def initialize(app, env) - @app = app - end - - def call(env) - @env = env - smb_shared_folders - prepare_smb_share - # A BIG Clean UP - # There should be a communicator class which branches between windows - # and Linux - if @smb_shared_folders.length > 0 - env[:ui].info('Mounting shared folders with VM, This process may take few minutes.') - end - if env[:machine].config.vm.guest == :windows - env[:ui].info "Mounting shared folders to windows is under development." - # mount_shared_folders_to_windows - elsif env[:machine].config.vm.guest == :linux - mount_shared_folders_to_linux - end - @app.call(env) - end - - def smb_shared_folders - @smb_shared_folders = {} - @env[:machine].config.vm.synced_folders.each do |id, data| - # Ignore disabled shared folders - next if data[:disabled] - - # Collect all SMB shares - next unless data[:smb] - # This to prevent overwriting the actual shared folders data - @smb_shared_folders[id] = data.dup - end - end - - def prepare_smb_share - @smb_shared_folders.each do |id, data| - begin - hostpath = File.expand_path(data[:hostpath], @env[:root_path]) - host_share_username = @env[:machine].provider_config.host_share.username - options = {:path => hostpath, - :share_name => data[:share_name], - :host_share_username => host_share_username} - response = @env[:machine].provider.driver.execute('set_smb_share.ps1', options) - if response["message"] == "OK" - @env[:ui].info "Successfully created SMB share for #{hostpath} with name #{data[:share_name]}" - end - rescue Error::SubprocessError => e - @env[:ui].info e.message - end - end - end - - def ssh_info - @ssh_info || @env[:machine].ssh_info - end - - def mount_shared_folders_to_windows - result = @env[:machine].provider.driver.execute('host_info.ps1', {}) - @smb_shared_folders.each do |id, data| - begin - options = { :share_name => data[:share_name], - :guest_path => data[:guestpath].gsub("/", "\\"), - :guest_ip => ssh_info[:host], - :username => ssh_info[:username], - :host_ip => result["host_ip"], - :password => @env[:machine].provider_config.guest.password, - :host_share_username => @env[:machine].provider_config.host_share.username, - :host_share_password => @env[:machine].provider_config.host_share.password} - @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") - @env[:machine].provider.driver.execute('mount_share.ps1', options) - rescue Error::SubprocessError => e - @env[:ui].info "Failed to link #{data[:share_name]} to Guest" - @env[:ui].info e.message - end - end - end - - def mount_shared_folders_to_linux - # Find Host Machine's credentials - result = @env[:machine].provider.driver.execute('host_info.ps1', {}) - host_share_username = @env[:machine].provider_config.host_share.username - host_share_password = @env[:machine].provider_config.host_share.password - @smb_shared_folders.each do |id, data| - begin - # Mount the Network drive to Guest VM - @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") - - # Create a location in guest to guestpath - @env[:machine].communicate.sudo("mkdir -p #{data[:guestpath]}") - owner = data[:owner] || ssh_info[:username] - group = data[:group] || ssh_info[:username] - - mount_options = "-o rw,username=#{host_share_username},pass=#{host_share_password}," - mount_options += "sec=ntlm,file_mode=0777,dir_mode=0777," - mount_options += "uid=`id -u #{owner}`,gid=`id -g #{group}`,rw #{data[:guestpath]}" - - command = "mount -t cifs //#{result["host_ip"]}/#{data[:share_name]} #{mount_options}" - - @env[:machine].communicate.sudo(command) - - rescue RuntimeError => e - @env[:ui].error(e.message) - end - end - - end - end - end - end -end diff --git a/plugins/providers/hyperv/scripts/host_info.ps1 b/plugins/providers/hyperv/scripts/host_info.ps1 deleted file mode 100644 index 6b655f9dd..000000000 --- a/plugins/providers/hyperv/scripts/host_info.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } - -try { -$hostname = $(whoami) -$ip = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0] - $resultHash = @{ - host_name = "$username" - host_ip = "$ip" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result -} -catch { - Write-Error-Message $_ -} - diff --git a/plugins/providers/hyperv/scripts/set_smb_share.ps1 b/plugins/providers/hyperv/scripts/set_smb_share.ps1 deleted file mode 100644 index fd8c4e795..000000000 --- a/plugins/providers/hyperv/scripts/set_smb_share.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -Param( - [Parameter(Mandatory=$true)] - [string]$path, - [Parameter(Mandatory=$true)] - [string]$share_name, - [Parameter(Mandatory=$true)] - [string]$host_share_username -) - -$ErrorAction = "Stop" - -# See all available shares and check alert user for -# existing/conflicting share name -$shared_folders = net share -$reg = "$share_name(\s+)$path(\s)" -$existing_share = $shared_folders -Match $reg -if ($existing_share) { - # Always clear the existing share name and create a new one - net share $share_name /delete /y -} - -$computer_name = $(Get-WmiObject Win32_Computersystem).name -$grant_permission = "$computer_name\$host_share_username,Full" -$result = net share $share_name=$path /unlimited /GRANT:$grant_permission -if ($result -Match "$share_name was shared successfully.") { - exit 0 -} - -$host.ui.WriteErrorLine("Error: $result") -exit 1 diff --git a/plugins/providers/hyperv/scripts/mount_share.ps1 b/plugins/synced_folders/smb/scripts/mount_share.ps1 similarity index 89% rename from plugins/providers/hyperv/scripts/mount_share.ps1 rename to plugins/synced_folders/smb/scripts/mount_share.ps1 index de099e0a9..fa1f98133 100644 --- a/plugins/providers/hyperv/scripts/mount_share.ps1 +++ b/plugins/synced_folders/smb/scripts/mount_share.ps1 @@ -1,62 +1,56 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - - -param ( - [string]$share_name = $(throw "-share_name is required."), - [string]$guest_path = $(throw "-guest_path is required."), - [string]$guest_ip = $(throw "-guest_ip is required."), - [string]$username = $(throw "-username is required."), - [string]$password = $(throw "-password is required."), - [string]$host_ip = $(throw "-host_ip is required."), - [string]$host_share_username = $(throw "-host_share_username is required."), - [string]$host_share_password = $(throw "-host_share_password is required.") - ) - -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\create_session.ps1" -$modules += $presentDir + "\utils\write_messages.ps1" - -forEach ($module in $modules) { . $module } - -try { - function Mount-File($share_name, $guest_path, $host_path, $host_share_username, $host_share_password) { - try { - # TODO: Check for folder exist. - # Use net use and prompt for password - $guest_path = $guest_path.replace("/", "\") - # Map a network drive to the guest machine - $result = net use * $host_path /user:$host_share_username $host_share_password /persistent:yes - $mapped_drive = (($result -match "\w:") -split (" "))[1] - Write-Host cmd /c mklink /d $guest_path $mapped_drive - # If a folder exist remove it. - if (Test-Path $guest_path) { - $junction = Get-Item $guest_path - $junction.Delete() - } - cmd /c mklink /d $guest_path $mapped_drive - } catch { - return $_ - } - } - - $response = Create-Remote-Session $guest_ip $username $password - - if (!$response["session"] -and $response["error"]) { - Write-Error-Message $response["error"] - return - } - $host_path = "\\$host_ip\$share_name" - $host_share_username = "$host_ip\$host_share_username" - $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Mount-File} -ArgumentList $share_name, $guest_path, $host_path, $host_share_username, $host_share_password -ErrorAction "stop" - Remove-PSSession -Id $response["session"].Id - Write-Error-Message $result -} -catch { - Write-Error-Message "Failed to mount files VM $_" - return -} +param ( + [string]$share_name = $(throw "-share_name is required."), + [string]$guest_path = $(throw "-guest_path is required."), + [string]$guest_ip = $(throw "-guest_ip is required."), + [string]$username = $(throw "-username is required."), + [string]$password = $(throw "-password is required."), + [string]$host_ip = $(throw "-host_ip is required."), + [string]$host_share_username = $(throw "-host_share_username is required."), + [string]$host_share_password = $(throw "-host_share_password is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\create_session.ps1" +$modules += $presentDir + "\utils\write_messages.ps1" + +forEach ($module in $modules) { . $module } + +try { + function Mount-File($share_name, $guest_path, $host_path, $host_share_username, $host_share_password) { + try { + # TODO: Check for folder exist. + # Use net use and prompt for password + $guest_path = $guest_path.replace("/", "\") + # Map a network drive to the guest machine + $result = net use * $host_path /user:$host_share_username $host_share_password /persistent:yes + $mapped_drive = (($result -match "\w:") -split (" "))[1] + Write-Host cmd /c mklink /d $guest_path $mapped_drive + # If a folder exist remove it. + if (Test-Path $guest_path) { + $junction = Get-Item $guest_path + $junction.Delete() + } + cmd /c mklink /d $guest_path $mapped_drive + } catch { + return $_ + } + } + + $response = Create-Remote-Session $guest_ip $username $password + + if (!$response["session"] -and $response["error"]) { + Write-Error-Message $response["error"] + return + } + $host_path = "\\$host_ip\$share_name" + $host_share_username = "$host_ip\$host_share_username" + $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Mount-File} -ArgumentList $share_name, $guest_path, $host_path, $host_share_username, $host_share_password -ErrorAction "stop" + Remove-PSSession -Id $response["session"].Id + Write-Error-Message $result +} +catch { + Write-Error-Message "Failed to mount files VM $_" + return +} diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index df3bab2a2..5a56ac5d3 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -138,6 +138,29 @@ module VagrantPlugins JSON.parse(r.stdout)["ip_addresses"] end + +=begin + def mount_shared_folders_to_windows + result = @env[:machine].provider.driver.execute('host_info.ps1', {}) + @smb_shared_folders.each do |id, data| + begin + options = { :share_name => data[:share_name], + :guest_path => data[:guestpath].gsub("/", "\\"), + :guest_ip => ssh_info[:host], + :username => ssh_info[:username], + :host_ip => result["host_ip"], + :password => @env[:machine].provider_config.guest.password, + :host_share_username => @env[:machine].provider_config.host_share.username, + :host_share_password => @env[:machine].provider_config.host_share.password} + @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") + @env[:machine].provider.driver.execute('mount_share.ps1', options) + rescue Error::SubprocessError => e + @env[:ui].info "Failed to link #{data[:share_name]} to Guest" + @env[:ui].info e.message + end + end + end +=end end end end From 760f3d9b7b70cdea1623d55abf71f9cd0a296edd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 16:12:27 -0800 Subject: [PATCH 24/40] providers/hyperv: that synced folder stuff can go --- plugins/providers/hyperv/action.rb | 2 - .../providers/hyperv/action/sync_folders.rb | 74 ------------------- 2 files changed, 76 deletions(-) delete mode 100644 plugins/providers/hyperv/action/sync_folders.rb diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 845d44ca6..c8acd4a0d 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -70,8 +70,6 @@ module VagrantPlugins b.use WaitForIPAddress b.use WaitForCommunicator, [:running] b.use SyncedFolders - - #b.use SyncFolders end end diff --git a/plugins/providers/hyperv/action/sync_folders.rb b/plugins/providers/hyperv/action/sync_folders.rb deleted file mode 100644 index baf38d76e..000000000 --- a/plugins/providers/hyperv/action/sync_folders.rb +++ /dev/null @@ -1,74 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -require "debugger" -require "log4r" -require "vagrant/util/subprocess" -require "vagrant/util/which" - -module VagrantPlugins - module HyperV - module Action - class SyncFolders - - def initialize(app, env) - @app = app - @logger = Log4r::Logger.new("vagrant_hyperv::action::sync_folders") - end - - def call(env) - @env = env - @app.call(env) - if env[:machine].config.vm.guest == :windows - sync_folders_to_windows - elsif env[:machine].config.vm.guest == :linux - sync_folders_to_linux - end - end - - def ssh_info - @ssh_info ||= @env[:machine].ssh_info - end - - def sync_folders_to_windows - @env[:machine].config.vm.synced_folders.each do |id, data| - # Ignore disabled shared folders - next if data[:disabled] || data[:smb] - hostpath = File.expand_path(data[:hostpath], @env[:root_path]).gsub("/", "\\") - guestpath = data[:guestpath].gsub("/", "\\") - options = { :guest_ip => ssh_info[:host], - :username => ssh_info[:username], - :host_path => hostpath, - :guest_path => guestpath, - :vm_id => @env[:machine].id, - :password => @env[:machine].provider_config.guest.password } - response = @env[:machine].provider.driver.execute('file_sync.ps1', options) - end - end - - def sync_folders_to_linux - if ssh_info.nil? - @env[:ui].info('SSH Info not available, Aborting Sync folder') - return - end - - @env[:machine].config.vm.synced_folders.each do |id, data| - # Ignore disabled shared folders - next if data[:disabled] || data[:smb] - hostpath = File.expand_path(data[:hostpath], @env[:root_path]) - guestpath = data[:guestpath] - @env[:ui].info('Starting Sync folders') - begin - @env[:machine].communicate.upload(hostpath, guestpath) - rescue RuntimeError => e - @env[:ui].error(e.message) - end - - end - end - - end - end - end -end From ef71729c248193a433c3c1a28160650cd1aa187a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 16:12:47 -0800 Subject: [PATCH 25/40] providers/hyperv: remove nonexistent action --- plugins/providers/hyperv/action.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index c8acd4a0d..4912b4b9d 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -133,7 +133,6 @@ module VagrantPlugins autoload :MessageNotCreated, action_root.join('message_not_created') autoload :MessageAlreadyCreated, action_root.join('message_already_created') autoload :MessageNotRunning, action_root.join('message_not_running') - autoload :SyncFolders, action_root.join('sync_folders') autoload :ReadGuestIP, action_root.join('read_guest_ip') autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") end From e93038fd0e100187b8ead730991cc894141566f5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 17:41:02 -0800 Subject: [PATCH 26/40] providers/hyperv: suspend/resume --- lib/vagrant/action.rb | 2 + lib/vagrant/action/builtin/is_state.rb | 30 ++++++++++ lib/vagrant/action/builtin/message.rb | 19 ++++++ plugins/providers/hyperv/action.rb | 59 +++++++++++++++++-- plugins/providers/hyperv/action/resume_vm.rb | 18 ++++++ plugins/providers/hyperv/action/suspend_vm.rb | 18 ++++++ .../providers/hyperv/scripts/resume_vm.ps1 | 7 +++ .../providers/hyperv/scripts/suspend_vm.ps1 | 7 +++ templates/locales/providers_hyperv.yml | 5 ++ .../vagrant/action/builtin/is_state_test.rb | 29 +++++++++ .../vagrant/action/builtin/message_test.rb | 22 +++++++ 11 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 lib/vagrant/action/builtin/is_state.rb create mode 100644 lib/vagrant/action/builtin/message.rb create mode 100644 plugins/providers/hyperv/action/resume_vm.rb create mode 100644 plugins/providers/hyperv/action/suspend_vm.rb create mode 100644 plugins/providers/hyperv/scripts/resume_vm.ps1 create mode 100644 plugins/providers/hyperv/scripts/suspend_vm.ps1 create mode 100644 test/unit/vagrant/action/builtin/is_state_test.rb create mode 100644 test/unit/vagrant/action/builtin/message_test.rb diff --git a/lib/vagrant/action.rb b/lib/vagrant/action.rb index d32a34b99..fcf8d916d 100644 --- a/lib/vagrant/action.rb +++ b/lib/vagrant/action.rb @@ -20,7 +20,9 @@ module Vagrant autoload :HandleBox, "vagrant/action/builtin/handle_box" autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url" autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions" + autoload :IsState, "vagrant/action/builtin/is_state" autoload :Lock, "vagrant/action/builtin/lock" + autoload :Message, "vagrant/action/builtin/message" autoload :Provision, "vagrant/action/builtin/provision" autoload :ProvisionerCleanup, "vagrant/action/builtin/provisioner_cleanup" autoload :SetHostname, "vagrant/action/builtin/set_hostname" diff --git a/lib/vagrant/action/builtin/is_state.rb b/lib/vagrant/action/builtin/is_state.rb new file mode 100644 index 000000000..020ddc3c1 --- /dev/null +++ b/lib/vagrant/action/builtin/is_state.rb @@ -0,0 +1,30 @@ +module Vagrant + module Action + module Builtin + # This middleware is meant to be used with Call and can check if + # a machine is in the given state ID. + class IsState + # Note: Any of the arguments can be arrays as well. + # + # @param [Symbol] target_state The target state ID that means that + # the machine was properly shut down. + # @param [Symbol] source_state The source state ID that the machine + # must be in to be shut down. + def initialize(app, env, check, **opts) + @app = app + @logger = Log4r::Logger.new("vagrant::action::builtin::is_state") + @check = check + end + + def call(env) + @logger.debug("Checking if machine state is '#{@check}'") + state = env[:machine].state.id + @logger.debug("-- Machine state: #{state}") + + env[:result] = @check == state + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant/action/builtin/message.rb b/lib/vagrant/action/builtin/message.rb new file mode 100644 index 000000000..1d0ae65b7 --- /dev/null +++ b/lib/vagrant/action/builtin/message.rb @@ -0,0 +1,19 @@ +module Vagrant + module Action + module Builtin + # This middleware simply outputs a message to the UI. + class Message + def initialize(app, env, message, **opts) + @app = app + @logger = Log4r::Logger.new("vagrant::action::builtin::is_state") + @message = message + end + + def call(env) + env[:ui].output(@message) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 4912b4b9d..81e5dc6ce 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -63,13 +63,44 @@ module VagrantPlugins end end + def self.action_resume + Vagrant::Action::Builder.new.tap do |b| + b.use HandleBox + b.use ConfigValidate + b.use Call, IsCreated do |env1, b1| + if !env1[:result] + b1.use Message, I18n.t("vagrant_hyperv.message_not_created") + next + end + + b1.use ResumeVM + b1.use WaitForIPAddress + b1.use WaitForCommunicator, [:running] + end + end + end + def self.action_start Vagrant::Action::Builder.new.tap do |b| - b.use Provision - b.use StartInstance - b.use WaitForIPAddress - b.use WaitForCommunicator, [:running] - b.use SyncedFolders + b.use Call, IsState, :running do |env1, b1| + if env1[:result] + b1.use Message, I18n.t("vagrant_hyperv.message_already_running") + next + end + + b1.use Call, IsState, :paused do |env2, b2| + if env2[:result] + b2.use action_resume + next + end + + b2.use Provision + b2.use StartInstance + b2.use WaitForIPAddress + b2.use WaitForCommunicator, [:running] + b2.use SyncedFolders + end + end end end @@ -113,6 +144,20 @@ module VagrantPlugins end end + def self.action_suspend + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use SuspendVM + end + end + end + def self.action_read_guest_ip Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate @@ -126,10 +171,12 @@ module VagrantPlugins autoload :DeleteVM, action_root.join("delete_vm") autoload :IsCreated, action_root.join("is_created") autoload :IsStopped, action_root.join("is_stopped") - autoload :ReadState, action_root.join("read_state") autoload :Import, action_root.join("import") + autoload :ReadState, action_root.join("read_state") + autoload :ResumeVM, action_root.join("resume_vm") autoload :StartInstance, action_root.join('start_instance') autoload :StopInstance, action_root.join('stop_instance') + autoload :SuspendVM, action_root.join("suspend_vm") autoload :MessageNotCreated, action_root.join('message_not_created') autoload :MessageAlreadyCreated, action_root.join('message_already_created') autoload :MessageNotRunning, action_root.join('message_not_running') diff --git a/plugins/providers/hyperv/action/resume_vm.rb b/plugins/providers/hyperv/action/resume_vm.rb new file mode 100644 index 000000000..bd3c09dff --- /dev/null +++ b/plugins/providers/hyperv/action/resume_vm.rb @@ -0,0 +1,18 @@ +module VagrantPlugins + module HyperV + module Action + class ResumeVM + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Resuming the machine...") + options = { VmId: env[:machine].id } + env[:machine].provider.driver.execute("resume_vm.ps1", options) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/suspend_vm.rb b/plugins/providers/hyperv/action/suspend_vm.rb new file mode 100644 index 000000000..f3c8a1c34 --- /dev/null +++ b/plugins/providers/hyperv/action/suspend_vm.rb @@ -0,0 +1,18 @@ +module VagrantPlugins + module HyperV + module Action + class SuspendVM + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Suspending the machine...") + options = { VmId: env[:machine].id } + env[:machine].provider.driver.execute("suspend_vm.ps1", options) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/scripts/resume_vm.ps1 b/plugins/providers/hyperv/scripts/resume_vm.ps1 new file mode 100644 index 000000000..cf6079eb3 --- /dev/null +++ b/plugins/providers/hyperv/scripts/resume_vm.ps1 @@ -0,0 +1,7 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$VmId +) + +$VM = Get-VM -Id $VmId -ErrorAction "Stop" +Resume-VM $VM diff --git a/plugins/providers/hyperv/scripts/suspend_vm.ps1 b/plugins/providers/hyperv/scripts/suspend_vm.ps1 new file mode 100644 index 000000000..5630c8c05 --- /dev/null +++ b/plugins/providers/hyperv/scripts/suspend_vm.ps1 @@ -0,0 +1,7 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$VmId +) + +$VM = Get-VM -Id $VmId -ErrorAction "Stop" +Suspend-VM $VM diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index e18fa4136..5d560741c 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -1,5 +1,10 @@ en: vagrant_hyperv: + message_already_running: |- + Hyper-V instance already running. + message_not_created: |- + VM not created. Moving on... + errors: admin_required: |- The Hyper-V provider requires that Vagrant be run with diff --git a/test/unit/vagrant/action/builtin/is_state_test.rb b/test/unit/vagrant/action/builtin/is_state_test.rb new file mode 100644 index 000000000..c2e74d62c --- /dev/null +++ b/test/unit/vagrant/action/builtin/is_state_test.rb @@ -0,0 +1,29 @@ +require "pathname" +require "tmpdir" + +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::IsState do + let(:app) { lambda { |env| } } + let(:env) { { :machine => machine } } + let(:machine) do + double("machine").tap do |machine| + machine.stub(:state).and_return(state) + end + end + + let(:state) { double("state") } + + describe "#call" do + it "sets result to true if is proper state" do + state.stub(id: :foo) + + subject = described_class.new(app, env, :foo) + + app.should_receive(:call).with(env) + + subject.call(env) + expect(env[:result]).to be_true + end + end +end diff --git a/test/unit/vagrant/action/builtin/message_test.rb b/test/unit/vagrant/action/builtin/message_test.rb new file mode 100644 index 000000000..a1ef4d344 --- /dev/null +++ b/test/unit/vagrant/action/builtin/message_test.rb @@ -0,0 +1,22 @@ +require "pathname" +require "tmpdir" + +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::Message do + let(:app) { lambda { |env| } } + let(:env) { { :ui => ui } } + + let(:ui) { double("ui") } + + describe "#call" do + it "outputs the given message" do + subject = described_class.new(app, env, "foo") + + ui.should_receive(:output).with("foo") + app.should_receive(:call).with(env) + + subject.call(env) + end + end +end From c735e81e4dff47bc3ab8505c0b234c3508bf62a6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 17:43:55 -0800 Subject: [PATCH 27/40] core: expand IsState to support inversions --- lib/vagrant/action/builtin/is_state.rb | 2 ++ .../vagrant/action/builtin/is_state_test.rb | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/vagrant/action/builtin/is_state.rb b/lib/vagrant/action/builtin/is_state.rb index 020ddc3c1..9e37b7a21 100644 --- a/lib/vagrant/action/builtin/is_state.rb +++ b/lib/vagrant/action/builtin/is_state.rb @@ -14,6 +14,7 @@ module Vagrant @app = app @logger = Log4r::Logger.new("vagrant::action::builtin::is_state") @check = check + @invert = !!opts[:invert] end def call(env) @@ -22,6 +23,7 @@ module Vagrant @logger.debug("-- Machine state: #{state}") env[:result] = @check == state + env[:result] = !env[:result] if @invert @app.call(env) end end diff --git a/test/unit/vagrant/action/builtin/is_state_test.rb b/test/unit/vagrant/action/builtin/is_state_test.rb index c2e74d62c..68040fb63 100644 --- a/test/unit/vagrant/action/builtin/is_state_test.rb +++ b/test/unit/vagrant/action/builtin/is_state_test.rb @@ -15,6 +15,17 @@ describe Vagrant::Action::Builtin::IsState do let(:state) { double("state") } describe "#call" do + it "sets result to false if is proper state" do + state.stub(id: :foo) + + subject = described_class.new(app, env, :bar) + + app.should_receive(:call).with(env) + + subject.call(env) + expect(env[:result]).to be_false + end + it "sets result to true if is proper state" do state.stub(id: :foo) @@ -25,5 +36,16 @@ describe Vagrant::Action::Builtin::IsState do subject.call(env) expect(env[:result]).to be_true end + + it "inverts the result if specified" do + state.stub(id: :foo) + + subject = described_class.new(app, env, :foo, invert: true) + + app.should_receive(:call).with(env) + + subject.call(env) + expect(env[:result]).to be_false + end end end From af4bc18c143dc2c93adba7b6b4c7823cbbbb1fde Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 17:52:38 -0800 Subject: [PATCH 28/40] providers/hyperv: use IsState and Message built-ins everywhere --- plugins/providers/hyperv/action.rb | 55 +++++++++---------- plugins/providers/hyperv/action/is_created.rb | 22 -------- plugins/providers/hyperv/action/is_stopped.rb | 22 -------- .../hyperv/action/message_already_created.rb | 22 -------- .../hyperv/action/message_not_created.rb | 22 -------- .../hyperv/action/message_not_running.rb | 22 -------- templates/locales/providers_hyperv.yml | 2 + 7 files changed, 28 insertions(+), 139 deletions(-) delete mode 100644 plugins/providers/hyperv/action/is_created.rb delete mode 100644 plugins/providers/hyperv/action/is_stopped.rb delete mode 100644 plugins/providers/hyperv/action/message_already_created.rb delete mode 100644 plugins/providers/hyperv/action/message_not_created.rb delete mode 100644 plugins/providers/hyperv/action/message_not_running.rb diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index 81e5dc6ce..abda706da 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -11,9 +11,9 @@ module VagrantPlugins def self.action_reload Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use Call, IsCreated do |env, b2| - if !env[:result] - b2.use MessageNotCreated + b.use Call, IsState, :not_created do |env, b2| + if env[:result] + b2.use Message, I18n.t("vagrant_hyperv.message_not_created") next end @@ -25,9 +25,9 @@ module VagrantPlugins def self.action_destroy Vagrant::Action::Builder.new.tap do |b| - b.use Call, IsCreated do |env1, b1| - if !env1[:result] - b1.use MessageNotCreated + b.use Call, IsState, :not_created do |env1, b1| + if env1[:result] + b1.use Message, I18n.t("vagrant_hyperv.message_not_created") next end @@ -48,9 +48,9 @@ module VagrantPlugins def self.action_halt Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use Call, IsCreated do |env, b2| - if !env[:result] - b2.use MessageNotCreated + b.use Call, IsState, :not_created do |env, b2| + if env[:result] + b2.use Message, I18n.t("vagrant_hyperv.message_not_created") next end @@ -67,8 +67,8 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use HandleBox b.use ConfigValidate - b.use Call, IsCreated do |env1, b1| - if !env1[:result] + b.use Call, IsState, :not_created do |env, b2| + if env1[:result] b1.use Message, I18n.t("vagrant_hyperv.message_not_created") next end @@ -108,8 +108,8 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use HandleBox b.use ConfigValidate - b.use Call, IsCreated do |env1, b1| - if !env1[:result] + b.use Call, IsState, :not_created do |env1, b1| + if env1[:result] b1.use Import end @@ -128,17 +128,19 @@ module VagrantPlugins def self.action_ssh Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use Call, IsCreated do |env, b2| - if !env[:result] - b2.use MessageNotCreated + b.use Call, IsState, :not_created do |env, b2| + if env[:result] + b2.use Message, I18n.t("vagrant_hyperv.message_not_created") next end - b2.use Call, IsStopped do |env1, b3| - if env1[:result] - b3.use MessageNotRunning - else - b3.use SSHExec + + b2.use Call, IsState, :running do |env1, b3| + if !env1[:result] + b3.use Message, I18n.t("vagrant_hyperv.message_not_running") + next end + + b3.use SSHExec end end end @@ -147,9 +149,9 @@ module VagrantPlugins def self.action_suspend Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use Call, IsCreated do |env, b2| - if !env[:result] - b2.use MessageNotCreated + b.use Call, IsState, :not_created do |env, b2| + if env[:result] + b2.use Message, I18n.t("vagrant_hyperv.message_not_created") next end @@ -169,17 +171,12 @@ module VagrantPlugins # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) autoload :DeleteVM, action_root.join("delete_vm") - autoload :IsCreated, action_root.join("is_created") - autoload :IsStopped, action_root.join("is_stopped") autoload :Import, action_root.join("import") autoload :ReadState, action_root.join("read_state") autoload :ResumeVM, action_root.join("resume_vm") autoload :StartInstance, action_root.join('start_instance') autoload :StopInstance, action_root.join('stop_instance') autoload :SuspendVM, action_root.join("suspend_vm") - autoload :MessageNotCreated, action_root.join('message_not_created') - autoload :MessageAlreadyCreated, action_root.join('message_already_created') - autoload :MessageNotRunning, action_root.join('message_not_running') autoload :ReadGuestIP, action_root.join('read_guest_ip') autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") end diff --git a/plugins/providers/hyperv/action/is_created.rb b/plugins/providers/hyperv/action/is_created.rb deleted file mode 100644 index 1d9934f99..000000000 --- a/plugins/providers/hyperv/action/is_created.rb +++ /dev/null @@ -1,22 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "log4r" -module VagrantPlugins - module HyperV - module Action - class IsCreated - def initialize(app, env) - @app = app - end - - def call(env) - env[:result] = env[:machine].state.id != :not_created - @app.call(env) - end - end - end - end -end diff --git a/plugins/providers/hyperv/action/is_stopped.rb b/plugins/providers/hyperv/action/is_stopped.rb deleted file mode 100644 index 83e87bd8c..000000000 --- a/plugins/providers/hyperv/action/is_stopped.rb +++ /dev/null @@ -1,22 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "log4r" -module VagrantPlugins - module HyperV - module Action - class IsStopped - def initialize(app, env) - @app = app - end - - def call(env) - env[:result] = env[:machine].state.id == :off - @app.call(env) - end - end - end - end -end diff --git a/plugins/providers/hyperv/action/message_already_created.rb b/plugins/providers/hyperv/action/message_already_created.rb deleted file mode 100644 index 72e4b052d..000000000 --- a/plugins/providers/hyperv/action/message_already_created.rb +++ /dev/null @@ -1,22 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "log4r" -module VagrantPlugins - module HyperV - module Action - class MessageAlreadyCreated - def initialize(app, env) - @app = app - end - - def call(env) - env[:ui].info("Machine already created") - @app.call(env) - end - end - end - end -end diff --git a/plugins/providers/hyperv/action/message_not_created.rb b/plugins/providers/hyperv/action/message_not_created.rb deleted file mode 100644 index 5af00f180..000000000 --- a/plugins/providers/hyperv/action/message_not_created.rb +++ /dev/null @@ -1,22 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "log4r" -module VagrantPlugins - module HyperV - module Action - class MessageNotCreated - def initialize(app, env) - @app = app - end - - def call(env) - env[:ui].info("Machine not created") - @app.call(env) - end - end - end - end -end diff --git a/plugins/providers/hyperv/action/message_not_running.rb b/plugins/providers/hyperv/action/message_not_running.rb deleted file mode 100644 index 1a5ca2502..000000000 --- a/plugins/providers/hyperv/action/message_not_running.rb +++ /dev/null @@ -1,22 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "log4r" -module VagrantPlugins - module HyperV - module Action - class MessageNotRunning - def initialize(app, env) - @app = app - end - - def call(env) - env[:ui].info("Machine is not running, Please turn it on.") - @app.call(env) - end - end - end - end -end diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index 5d560741c..92f265b50 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -4,6 +4,8 @@ en: Hyper-V instance already running. message_not_created: |- VM not created. Moving on... + message_not_running: |- + Hyper-V machine isn't running. Can't SSH in! errors: admin_required: |- From d1bdfe492bae0024204c0b30f8bcb29ab0fe495f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 21:29:55 -0800 Subject: [PATCH 29/40] providers/hyperv: style --- .../providers/hyperv/scripts/import_vm.ps1 | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index 5d673456f..c25fff6fc 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -74,15 +74,14 @@ $notes = (Select-Xml -xml $vmconfig -XPath "//notes").node.'#text' $more_vm_params = @{ ProcessorCount = $processors - MemoryStartupBytes = $MemoryStartupBytes + MemoryStartupBytes = $MemoryStartupBytes } If ($dynamicmemory) { $more_vm_params.Add("DynamicMemory",$True) - $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) - $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) -} -else { + $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) + $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) +} else { $more_vm_params.Add("StaticMemory",$True) } @@ -95,36 +94,38 @@ $vm | Set-VM @more_vm_params -Passthru # Add drives to the virtual machine $controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" + # A regular expression pattern to pull the number from controllers [regex]$rx="\d" foreach ($controller in $controllers) { $node = $controller.Node -# Check for SCSI - if ($node.ParentNode.ChannelInstanceGuid) { - $ControllerType = "SCSI" - } - else { - $ControllerType = "IDE" - } + + # Check for SCSI + if ($node.ParentNode.ChannelInstanceGuid) { + $ControllerType = "SCSI" + } else { + $ControllerType = "IDE" + } $drives = $node.ChildNodes | where {$_.pathname."#text"} foreach ($drive in $drives) { -#if drive type is ISO then set DVD Drive accordingly + #if drive type is ISO then set DVD Drive accordingly $driveType = $drive.type."#text" - $addDriveParam = @{ - ControllerNumber = $rx.Match($controller.node.name).value - Path = $vhdx_path - } + $addDriveParam = @{ + ControllerNumber = $rx.Match($controller.node.name).value + Path = $vhdx_path + } + if ($drive.pool_id."#text") { $ResourcePoolName = $drive.pool_id."#text" - $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) + $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) } if ($drivetype -eq 'VHD') { $addDriveParam.add("ControllerType",$ControllerType) - $vm | Add-VMHardDiskDrive @AddDriveparam + $vm | Add-VMHardDiskDrive @AddDriveparam } } } @@ -132,7 +133,8 @@ foreach ($controller in $controllers) { $vm_id = (Get-VM $vm_name).id.guid $resultHash = @{ name = $vm_name - id = $vm_id + id = $vm_id } + $result = ConvertTo-Json $resultHash Write-Output-Message $result From 39bf9db6512208713302393d4b6cfc43e764c518 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 23:13:09 -0800 Subject: [PATCH 30/40] providers/hyperv: remove unused config --- plugins/providers/hyperv/config.rb | 16 ++------- plugins/providers/hyperv/host_share/config.rb | 33 ------------------- 2 files changed, 2 insertions(+), 47 deletions(-) delete mode 100644 plugins/providers/hyperv/host_share/config.rb diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 3a512a945..ef382a45c 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -10,15 +10,8 @@ module VagrantPlugins # @return [Integer] attr_accessor :ip_address_timeout - attr_reader :host_share - def initialize @ip_address_timeout = UNSET_VALUE - @host_share = HostShare::Config.new - end - - def host_config(&block) - block.call(@host_share) end def finalize! @@ -27,15 +20,10 @@ module VagrantPlugins end end - def validate(machine) errors = _detected_errors -=begin - unless host_share.valid_config? - errors << host_share.errors.flatten.join(" ") - end -=end - { "HyperV" => errors } + + { "Hyper-V" => errors } end end end diff --git a/plugins/providers/hyperv/host_share/config.rb b/plugins/providers/hyperv/host_share/config.rb deleted file mode 100644 index 93a4e9cda..000000000 --- a/plugins/providers/hyperv/host_share/config.rb +++ /dev/null @@ -1,33 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -module VagrantPlugins - module HyperV - module HostShare - class Config < Vagrant.plugin("2", :config) - attr_accessor :username, :password - - def errors - @errors - end - - def validate - @errors = [] - if username.nil? - @errors << "Please configure a Windows user account to share folders" - end - if password.nil? - @errors << "Please configure a Windows user account password to share folders" - end - end - - def valid_config? - validate - errors.empty? - end - - end - end - end -end From bdcb92ed075e3f0d0a6727a31f5566939c8dcdb2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 23:13:34 -0800 Subject: [PATCH 31/40] providers/hyperv: disable parallelism --- plugins/providers/hyperv/plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb index 5498a0051..7cfea468b 100644 --- a/plugins/providers/hyperv/plugin.rb +++ b/plugins/providers/hyperv/plugin.rb @@ -10,7 +10,7 @@ module VagrantPlugins machines in Hyper-V. DESC - provider(:hyperv, parallel: true) do + provider(:hyperv) do require_relative "provider" init! Provider From da58f7bdc08312b8be1fcd8fb2ec15cd00b78026 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 23:21:19 -0800 Subject: [PATCH 32/40] providers/hyperv: remove bad reference to file --- plugins/providers/hyperv/config.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index ef382a45c..ad69e3059 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -1,5 +1,4 @@ require "vagrant" -require_relative "host_share/config" module VagrantPlugins module HyperV From 3ddc63c88f5475a8d3e1b08f59617fdf70fae341 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 26 Feb 2014 23:47:28 -0800 Subject: [PATCH 33/40] providers/hyperv: add script to read switches --- plugins/providers/hyperv/scripts/get_switches.ps1 | 8 ++++++++ plugins/providers/hyperv/scripts/import_vm.ps1 | 10 +++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 plugins/providers/hyperv/scripts/get_switches.ps1 diff --git a/plugins/providers/hyperv/scripts/get_switches.ps1 b/plugins/providers/hyperv/scripts/get_switches.ps1 new file mode 100644 index 000000000..ab601468a --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_switches.ps1 @@ -0,0 +1,8 @@ +# This will have a SwitchType property. As far as I know the values are: +# +# 0 - Private +# 1 - Internal +# +$Switches = @(Get-VMSwitch ` + | Select-Object Name,SwitchType,NetAdapterInterfaceDescription) +Write-Output $(ConvertTo-JSON $Switches) diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 index c25fff6fc..d4eb11e08 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -2,7 +2,9 @@ Param( [Parameter(Mandatory=$true)] [string]$vm_xml_config, [Parameter(Mandatory=$true)] - [string]$vhdx_path + [string]$vhdx_path, + + [string]$switchname=$null ) # Include the following modules @@ -41,8 +43,10 @@ $MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB $MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB $MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB -# Get the name of the virtual switch -$switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" +if (!$switchname) { + # Get the name of the virtual switch + $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" +} # Determine boot device Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { From 729f0f5db85c7d5768c3b12ec50780d2b8a46cf8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 27 Feb 2014 08:08:02 -0800 Subject: [PATCH 34/40] providrs/hyperv: ask for the vswitch to use --- plugins/providers/hyperv/action/import.rb | 24 +++++++++++++++++++ plugins/providers/hyperv/errors.rb | 4 ++++ .../providers/hyperv/scripts/get_switches.ps1 | 6 ++++- templates/locales/providers_hyperv.yml | 14 +++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 0808e679b..b2635d6d2 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -40,6 +40,29 @@ module VagrantPlugins end env[:ui].output("Importing a Hyper-V instance") + + switches = env[:machine].provider.driver.execute("get_switches.ps1", {}) + raise Errors::NoSwitches if switches.empty? + + switch = switches[0]["Name"] + if switches.length > 1 + env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") + switches.each_index do |i| + switch = switches[i] + env[:ui].detail("#{i+1}) #{switch["Name"]}") + end + env[:ui].detail(" ") + + switch = nil + while !switch + switch = env[:ui].ask("What switch would you like to use? ") + next if !switch + switch = switch.to_i - 1 + switch = nil if switch < 0 || switch >= switches.length + end + switch = switches[switch]["Name"] + end + env[:ui].detail("Cloning virtual hard drive...") source_path = vhdx_path.to_s dest_path = env[:machine].data_dir.join("disk.vhdx").to_s @@ -52,6 +75,7 @@ module VagrantPlugins vm_xml_config: config_path.to_s.gsub("/", "\\"), vhdx_path: vhdx_path.to_s.gsub("/", "\\") } + options[:switchname] = switch if switch env[:ui].detail("Creating and registering the VM...") server = env[:machine].provider.driver.execute( diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb index cd2519813..b82b0325a 100644 --- a/plugins/providers/hyperv/errors.rb +++ b/plugins/providers/hyperv/errors.rb @@ -18,6 +18,10 @@ module VagrantPlugins error_key(:ip_addr_timeout) end + class NoSwitches < HyperVError + error_key(:no_switches) + end + class PowerShellError < HyperVError error_key(:powershell_error) end diff --git a/plugins/providers/hyperv/scripts/get_switches.ps1 b/plugins/providers/hyperv/scripts/get_switches.ps1 index ab601468a..9c30a170d 100644 --- a/plugins/providers/hyperv/scripts/get_switches.ps1 +++ b/plugins/providers/hyperv/scripts/get_switches.ps1 @@ -3,6 +3,10 @@ # 0 - Private # 1 - Internal # +# Include the following modules +$Dir = Split-Path $script:MyInvocation.MyCommand.Path +. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) + $Switches = @(Get-VMSwitch ` | Select-Object Name,SwitchType,NetAdapterInterfaceDescription) -Write-Output $(ConvertTo-JSON $Switches) +Write-Output-Message $(ConvertTo-JSON $Switches) diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index 92f265b50..6c010cf89 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -1,5 +1,9 @@ en: vagrant_hyperv: + choose_switch: |- + Please choose a switch to attach to your Hyper-V instance. + If none of these are appropriate, please open the Hyper-V manager + to create a new virtual switch. message_already_running: |- Hyper-V instance already running. message_not_created: |- @@ -37,6 +41,16 @@ en: machine doesn't have the latest Hyper-V integration drivers. Please research for your operating system how to install these in order for the VM to properly communicate its IP address to Hyper-V. + no_switches: |- + There are no virtual switches created for Hyper-V! Please open + the Hyper-V Manager, go to the "Virtual Switch Manager", and create + at least one virtual switch. + + A virtual switch is required for Vagrant to create a Hyper-V + machine that is connected to a network so it can access it. + + For more help, please see the documentation on the Vagrant website + for Hyper-V. powershell_error: |- An error occurred while executing a PowerShell script. This error is shown below. Please read the error message and see if this is From 1724d499dc887417177fbef753749e67d8d33d10 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 27 Feb 2014 08:13:39 -0800 Subject: [PATCH 35/40] synced_folders/smb: higher priority than things like NFS and rsync --- plugins/synced_folders/smb/plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/synced_folders/smb/plugin.rb b/plugins/synced_folders/smb/plugin.rb index 82b029a0b..02aca938b 100644 --- a/plugins/synced_folders/smb/plugin.rb +++ b/plugins/synced_folders/smb/plugin.rb @@ -12,7 +12,7 @@ module VagrantPlugins Windows and share them to guest machines. EOF - synced_folder("smb", 5) do + synced_folder("smb", 7) do require_relative "synced_folder" init! SyncedFolder From 736bd8d34e5dd4ccf3490b3e1500c797842c246f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 28 Feb 2014 05:17:52 -0600 Subject: [PATCH 36/40] core: remove unused variable from Message builtin --- lib/vagrant/action/builtin/message.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/action/builtin/message.rb b/lib/vagrant/action/builtin/message.rb index 1d0ae65b7..a340945c4 100644 --- a/lib/vagrant/action/builtin/message.rb +++ b/lib/vagrant/action/builtin/message.rb @@ -4,9 +4,8 @@ module Vagrant # This middleware simply outputs a message to the UI. class Message def initialize(app, env, message, **opts) - @app = app - @logger = Log4r::Logger.new("vagrant::action::builtin::is_state") - @message = message + @app = app + @message = message end def call(env) From fbdca0c8b6990bcf1109ea1a5deec444eb4caff3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 28 Feb 2014 18:27:18 +0100 Subject: [PATCH 37/40] website/docs: document SMB --- website/docs/source/layouts/layout.erb | 1 + .../docs/source/v2/synced-folders/smb.html.md | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 website/docs/source/v2/synced-folders/smb.html.md diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index 1ef236752..2bbfc7f2b 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -177,6 +177,7 @@ >Basic Usage >NFS >RSync + >SMB >VirtualBox <% end %> diff --git a/website/docs/source/v2/synced-folders/smb.html.md b/website/docs/source/v2/synced-folders/smb.html.md new file mode 100644 index 000000000..130a7dabf --- /dev/null +++ b/website/docs/source/v2/synced-folders/smb.html.md @@ -0,0 +1,75 @@ +--- +page_title: "SMB - Synced Folders" +sidebar_current: "syncedfolder-smb" +--- + +# SMB + +**Synced folder type:** `smb` + +Vagrant can use [SMB](http://en.wikipedia.org/wiki/Server_Message_Block) +as a mechanism to create a bi-directional synced folder between the host +machine and the Vagrant machine. + +SMB is built-in to Windows machines and provides a higher performance +alternative to some other mechanisms such as VirtualBox shared folders. + +
+

+ Windows only! SMB is currently only supported + when the host machine is Windows. The guest machine can be Windows + or Linux. +

+
+ +## Prerequisites + +To use the SMB synced folder type, the machine running Vagrant must be +a Windows machine. In addition to this, the command prompt executing Vagrant +must have administrative privileges. Vagrant requires these privileges in +order to create new network folder shares. + +The destination machine must be able to mount SMB filesystems. On Linux +the package to do this is usually called `smbfs` or `cifs`. Vagrant knows +how to automatically install this for some operating systems. + +## Options + +The SMB synced folder type has a variety of options it accepts: + +* `smb_host` (string) - The host IP where the SMB mount is located. If this + isn't specified, Vagrant will attempt to determine this automatically. + +* `smb_password` (string) - The password used for authentication to mount + the SMB mount. This is the password for the username specified by + `smb_username`. If this is not specified, Vagrant will prompt you for it. + It is highly recommended that you do not set this, since it would expose + your password directly in your Vagrantfile. + +* `smb_username` (string) - The username used for authentication to mount + the SMB mount. This is the username to access the mount, _not_ the username + of the account where the folder is being mounted to. This is usually your + Windows username. If this isn't specified, Vagrant will prompt you for + it. + +## Example + +The following is an example of using SMB to sync a folder: + +
+Vagrant.configure("2") do |config|
+  config.vm.synced_folder ".", "/vagrant", type: "smb"
+end
+
+ +## Limitations + +Because SMB is a relatively new synced folder type in Vagrant, it still +has some rough edges. Hopefully, future versions of Vagrant will address +these. + +The primary limitation of SMB synced folders at the moment is that they are +never pruned or cleaned up. Once the folder share is defined, Vagrant never +removes it. To clean up SMB synced folder shares, periodically run +`net share` in a command prompt, find the shares you don't want, then +run `net share NAME /delete` for each, where NAME is the name of the share. From 13f25936e302ab3acb6abbe418994a0effab4327 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 28 Feb 2014 18:53:20 +0100 Subject: [PATCH 38/40] website/docs: hyper-v docs --- website/docs/source/layouts/layout.erb | 10 ++ website/docs/source/v2/hyperv/boxes.html.md | 93 +++++++++++++++++++ .../source/v2/hyperv/configuration.html.md | 13 +++ website/docs/source/v2/hyperv/index.html.md | 25 +++++ .../docs/source/v2/hyperv/limitations.html.md | 30 ++++++ website/docs/source/v2/hyperv/usage.html.md | 17 ++++ 6 files changed, 188 insertions(+) create mode 100644 website/docs/source/v2/hyperv/boxes.html.md create mode 100644 website/docs/source/v2/hyperv/configuration.html.md create mode 100644 website/docs/source/v2/hyperv/index.html.md create mode 100644 website/docs/source/v2/hyperv/limitations.html.md create mode 100644 website/docs/source/v2/hyperv/usage.html.md diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index 2bbfc7f2b..4a3fec6de 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -215,6 +215,16 @@ <% end %> + >Hyper-V + <% if sidebar_section == "hyperv" %> + + <% end %> + >Plugins <% if sidebar_section == "plugins" %>
    diff --git a/website/docs/source/v2/hyperv/boxes.html.md b/website/docs/source/v2/hyperv/boxes.html.md new file mode 100644 index 000000000..a783720c0 --- /dev/null +++ b/website/docs/source/v2/hyperv/boxes.html.md @@ -0,0 +1,93 @@ +--- +page_title: "Creating a Base Box - Hyper-V Provider" +sidebar_current: "hyperv-boxes" +--- + +# Creating a Base Box + +As with [every provider](/v2/providers/basic_usage.html), the Hyper-V +provider has a custom box format that affects how base boxes are made. + +Prior to reading this, you should read the +[general guide to creating base boxes](/v2/boxes/base.html). Actually, +it would probably be most useful to keep this open in a separate tab +as you may be referencing it frequently while creating a base box. That +page contains important information about common software to install +on the box. + +Additionally, it is helpful to understand the +[basics of the box file format](/v2/boxes/format.html). + +
    +

    + Advanced topic! This is a reasonably advanced topic that + a beginning user of Vagrant doesn't need to understand. If you're + just getting started with Vagrant, skip this and use an available + box. If you're an experienced user of Vagrant and want to create + your own custom boxes, this is for you. +

    +
    + +## Additional Software + +In addition to the software that should be installed based on the +[general guide to creating base boxes](/v2/boxes/base.html), +Hyper-V base boxes require some additional software. + +### Hyper-V Kernel Modules + +You'll need to install Hyper-V kernel modules. While this improves performance, +it also enables necessary features such as reporting its IP address so that +Vagrant can access it. + +You can verify Hyper-V kernel modules are properly installed by +running `lsmod` on Linux machines and looking for modules prefixed with +`hv_`. Additionally, you'll need to verify that the "Network" tab for your +virtual machine in the Hyper-V manager is reporting an IP address. If it +is not reporting an IP address, Vagrant will not be able to access it. + +For most newer Linux distributions, the Hyper-V modules will be available +out of the box. + +Ubuntu 12.04 requires some special steps to make networking work. These +are reproduced here in case similar steps are needed with other distributions. +Without these commands, Ubuntu 12.04 will not report an IP address to +Hyper-V: + +``` +$ sudo apt-get install linux-tools-3.11.0-15-generic +$ sudo apt-get install hv-kvp-daemon-init +$ cp/usr/lib/linux-tools-3.11.0-15/hv_* /usr/sbin/ +``` + +## Packaging the Box + +To package a Hyper-V box, export the virtual machine from the +Hyper-V Manager using the "Export" feature. This will create a directory +with a structure similar to the following: + +``` +. +|-- Snapshots +|-- Virtual Hard drives +|-- Virtual Machines +``` + +Delete the "Snapshots" folder. It is of no use to the Vagrant Hyper-V +provider and can only add to the size of the box if there are snapshots +in that folder. + +Then, create the "metadata.json" file necessary for the box, as documented +in [basics of the box file format](/v2/boxes/format.html). The proper +provider value to use for the metadata is "hyperv". + +Finally, create an archive of those contents (but _not_ the parent folder) +using a tool such as `tar`: + +``` +$ tar cvzf ~/custom.box ./* +``` + +A common mistake is to also package the parent folder by accident. Vagrant +will not work in this case. To verify you've packaged it properly, add the +box to Vagrant and try to bring up the machine. diff --git a/website/docs/source/v2/hyperv/configuration.html.md b/website/docs/source/v2/hyperv/configuration.html.md new file mode 100644 index 000000000..2459c7c0d --- /dev/null +++ b/website/docs/source/v2/hyperv/configuration.html.md @@ -0,0 +1,13 @@ +--- +page_title: "Configuration- Hyper-V Provider" +sidebar_current: "hyperv-configuration" +--- + +# Configuration + +The Hyper-V provider has some provider-specific configuration options +you may set. A complete reference is shown below: + + * `ip_address_timeout` (integer) - The time in seconds to wait for the + virtual machine to report an IP address. This defaults to 120 seconds. + This may have to be increased if your VM takes longer to boot. diff --git a/website/docs/source/v2/hyperv/index.html.md b/website/docs/source/v2/hyperv/index.html.md new file mode 100644 index 000000000..c2d31d0b5 --- /dev/null +++ b/website/docs/source/v2/hyperv/index.html.md @@ -0,0 +1,25 @@ +--- +page_title: "Hyper-V Provider" +sidebar_current: "hyperv" +--- + +# Hyper-V + +Vagrant comes with support out of the box for [Hyper-V](http://en.wikipedia.org/wiki/Hyper-V), +a native hypervisor written by Microsoft. Hyper-V is available by default for +almost all Windows 8.1 installs. + +The Hyper-V provider is compatible with Windows 8.1 only. Prior versions +of Hyper-V do not include the necessary APIs for Vagrant to work. + +Hyper-V must be enabled prior to using the provider. Most Windows installations +will not have Hyper-V enabled by default. To enable Hyper-V, go to +"Programs and Features" and check the box next to "Hyper-V." + +
    +Warning: Enabling Hyper-V will cause VirtualBox, VMware, +and any other virtualization technology to no longer work. See +this blog post +for an easy way to create a boot entry to boot Windows without Hyper-V +enabled, if there will be times you'll need other hypervisors. +
    diff --git a/website/docs/source/v2/hyperv/limitations.html.md b/website/docs/source/v2/hyperv/limitations.html.md new file mode 100644 index 000000000..3979cb187 --- /dev/null +++ b/website/docs/source/v2/hyperv/limitations.html.md @@ -0,0 +1,30 @@ +--- +page_title: "Limitations - Hyper-V Provider" +sidebar_current: "hyperv-limitations" +--- + +# Limitations + +The Hyper-V provider works in almost every way like the VirtualBox +or VMware provider would, but has some limitations that are inherent to +Hyper-V itself. + +## Limited Networking + +Vagrant doesn't yet know how to create and configure new networks for +Hyper-V. When launching a machine with Hyper-V, Vagrant will prompt you +asking what virtual switch you want to connect the virtual machine to. + +A result of this is that networking configurations in the Vagrantfile +are completely ignored with Hyper-V. Vagrant can't enforce a static IP +or automatically configure a NAT. + +However, the IP address of the machine will be reported as part of +the `vagrant up`, and you can use that IP address as if it were +a host only network. + +## Packaging + +Vagrant doesn't implement the `vagrant package` command for Hyper-V +yet, though this should be fairly straightforward to add in a Vagrant +release in the near future. diff --git a/website/docs/source/v2/hyperv/usage.html.md b/website/docs/source/v2/hyperv/usage.html.md new file mode 100644 index 000000000..ef87e8d0d --- /dev/null +++ b/website/docs/source/v2/hyperv/usage.html.md @@ -0,0 +1,17 @@ +--- +page_title: "Usage - Hyper-V Provider" +sidebar_current: "hyperv-usage" +--- + +# Usage + +The Hyper-V provider is used just like any other provider. Please +read the general [basic usage](/v2/providers/basic_usage.html) page for +providers. + +The value to use for the `--provider` flag is `hyperv`. + +Hyper-V also requires that you execute Vagrant with administrative +privileges. Creating and managing virtual machines with Hyper-V requires +admin rights. Vagrant will show you an error if it doesn't have the proper +permissions. From 8cb47d01d6a1a60c970ba87e6de0474be9924807 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 28 Feb 2014 18:54:35 +0100 Subject: [PATCH 39/40] website/docs: note where boxes can be found --- website/docs/source/v2/hyperv/usage.html.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/source/v2/hyperv/usage.html.md b/website/docs/source/v2/hyperv/usage.html.md index ef87e8d0d..597294de3 100644 --- a/website/docs/source/v2/hyperv/usage.html.md +++ b/website/docs/source/v2/hyperv/usage.html.md @@ -15,3 +15,7 @@ Hyper-V also requires that you execute Vagrant with administrative privileges. Creating and managing virtual machines with Hyper-V requires admin rights. Vagrant will show you an error if it doesn't have the proper permissions. + +Boxes for Hyper-V can be easily found on +[Vagrant Cloud](http://www.vagrantcloud.com). To get started, you might +want to try the `hashicorp/precise64` box. From 9120339dc71b893e0506468855d912636fc77ed2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 28 Feb 2014 18:58:41 +0100 Subject: [PATCH 40/40] providers/hyperv: set the hostname --- plugins/providers/hyperv/action.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index abda706da..20d48d326 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -99,6 +99,7 @@ module VagrantPlugins b2.use WaitForIPAddress b2.use WaitForCommunicator, [:running] b2.use SyncedFolders + b2.use SetHostname end end end