Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use port groups and vSwitches for networking #108

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/vagrant-vmware-esxi.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'pathname'
require 'vagrant-vmware-esxi/ext'
require 'vagrant-vmware-esxi/plugin'

module VagrantPlugins
Expand Down
16 changes: 16 additions & 0 deletions lib/vagrant-vmware-esxi/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ def self.action_destroy
b1.use Halt unless env1[:machine_state] == 'powered_off'
b1.use ReadState
b1.use Destroy
b1.use DestroyUnusedNetworks
end
end
end

def self.action_destroy_networks
Vagrant::Action::Builder.new.tap do |b|
b.use SetESXiPassword
b.use Call, DestroyUnusedNetworksConfirm do |env1, b1|
if env1[:result]
b1.use DestroyUnusedNetworks
end
end
end
end
Expand All @@ -164,6 +176,7 @@ def self.action_up
b.use ConfigValidate
b.use HandleBox
b.use ReadState
b.use CreateNetwork
b.use CreateVM
b.use ReadState
b.use Boot
Expand Down Expand Up @@ -203,13 +216,16 @@ def self.action_package
action_root = Pathname.new(File.expand_path('../action', __FILE__))
autoload :SetESXiPassword, action_root.join('esxi_password')
autoload :CreateVM, action_root.join('createvm')
autoload :CreateNetwork, action_root.join('create_network')
autoload :ReadState, action_root.join('read_state')
autoload :ReadSSHInfo, action_root.join('read_ssh_info')
autoload :SetNetworkIP, action_root.join('set_network_ip')
autoload :Boot, action_root.join('boot')
autoload :Halt, action_root.join('halt')
autoload :Shutdown, action_root.join('shutdown')
autoload :Destroy, action_root.join('destroy')
autoload :DestroyUnusedNetworks, action_root.join('destroy_unused_networks')
autoload :DestroyUnusedNetworksConfirm, action_root.join('destroy_unused_networks_confirm')
autoload :Suspend, action_root.join('suspend')
autoload :Resume, action_root.join('resume')
autoload :Package, action_root.join('package')
Expand Down
171 changes: 171 additions & 0 deletions lib/vagrant-vmware-esxi/action/create_network.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
require 'json'
require 'vagrant-vmware-esxi/util/esxcli'

module VagrantPlugins
module ESXi
module Action
# Automatically create network (Port group/VLAN) per subnet
#
# For example, when a box given 192.168.1.10/24, create 192.168.1.0/24 port group.
# Then, when another box is given 192.168.1.20/24, use the same port group from
# the previous one.
#
# Example configuration:
# config.vm.network "private_network", ip: "192.168.10.170", netmask: "255.255.255.0",
#
# This will create port group '{vSwitchName}-192.168.10.0-24'.
#
# You can also use manual configurations for the vSwitch and the port group, such as:
# config.vm.network "private_network", ip: "192.168.10.170", netmask: "255.255.255.0",
# esxi__vswitch: "Internal Switch", esxi__port_group: "Internal Network"
#
# Notes:
# 1. If you specify only esxi__port_group, a new port group will be created on the default_vswitch if
# not already created. If you specify only esxi__vswitch, the default_port_group will be used, and
# it will error if there's a mismatch. In this case, you should probably specify both.
# 2. If you specify both esxi__port_group and esxi__vswitch, a new port group will be created
# on that vSwitch if not already created.
#
# For (1) and (2), the vSwitch will also be created if not already created. In any case,
# if esxi__port_group already exists, the esxi__vswitch is ignored (not in the VMX file).
class CreateNetwork
include Util::ESXCLI

CREATE_NETWORK_MUTEX = Mutex.new

MAX_VLAN = 4094
VLANS = Array.new(MAX_VLAN) { |i| i + 1 }.freeze

def initialize(app, env)
@app = app
@logger = Log4r::Logger.new('vagrant_vmware_esxi::action::create_network')
end

def call(env)
@env = env
@default_vswitch = env[:machine].provider_config.default_vswitch
@default_port_group = env[:machine].provider_config.default_port_group
create_network
@app.call(env)
end

def create_network
CREATE_NETWORK_MUTEX.synchronize do
@env[:ui].info I18n.t("vagrant_vmware_esxi.vagrant_vmware_esxi_message",
message: "Default network on Adapter 1: vSwitch: #{@default_vswitch}, "\
"port group: #{@default_port_group}")
@env[:ui].info I18n.t("vagrant_vmware_esxi.vagrant_vmware_esxi_message",
message: "Creating other networks...")
@created_vswitches = []
@created_port_groups = []

connect_ssh do
@env[:machine].config.vm.networks.each.with_index do |(type, network_options), index|
adapter = index + 2
next if type != :private_network && type != :public_network
set_network_configs(adapter, type, network_options)
create_vswitch_unless_created(network_options)
create_port_group_unless_created(network_options)

details = "vSwitch: #{network_options[:esxi__vswitch]}, "\
"port group: #{network_options[:esxi__port_group]}"
@env[:ui].detail I18n.t("vagrant_vmware_esxi.vagrant_vmware_esxi_message",
message: "Adapter #{adapter}: #{details}")
end
end

save_created_networks
end
end

def set_network_configs(adapter, type, network_options)
# TODO Does this matter? we don't really care where default_vswitch is bridged to anyway
# Assume public_network is using provider_config default_vswitch and default_port_group
private_network_configs = [:esxi__vswitch, :esxi__port_group, :dhcp] & network_options.keys
if type == :public_network && private_network_configs.any?
raise Errors::ESXiError,
message: "Setting #{private_network_configs.join(', ')} not allowed for `public_network`."
end

custom_vswitch = true if network_options[:esxi__vswitch]
dhcp = network_options[:type] == "dhcp" || !network_options[:ip]
network_options[:esxi__vswitch] ||= @default_vswitch
network_options[:netmask] ||= 24 unless dhcp

network_options[:esxi__port_group] ||=
if custom_vswitch || dhcp
@default_port_group
else
# Use the address to generate the port_group name
ip = IPAddr.new("#{network_options[:ip]}/#{network_options[:netmask]}")
"#{network_options[:esxi__vswitch]}-#{ip.to_s}-#{ip.prefix}"
end
end

def create_vswitch_unless_created(network_options)
@logger.info("Creating vSwitch '#{network_options[:esxi__vswitch]}' if not yet created")

vswitch = network_options[:esxi__vswitch]
unless has_vswitch? vswitch
if create_vswitch(vswitch)
@created_vswitches << vswitch
else
raise Errors::ESXiError, message: "Unable create new vSwitch '#{vswitch}'."
end
end
end

def create_port_group_unless_created(network_options)
port_groups = get_port_groups
@logger.debug("Port groups: #{port_groups}")

if port_group = port_groups[network_options[:esxi__port_group]]
# port group already created
unless port_group[:vswitch] == network_options[:esxi__vswitch]
raise Errors::ESXiError, message: "Existing port group '#{network_options[:esxi__port_group]}' "\
"must be in vSwitch '#{network_options[:esxi__vswitch]}'"
end

return
end

# VLAN 0 is bridged to physical NIC by default
vlan_ids = port_groups.values.map { |v| v[:vlan] }.uniq.sort - [0]
vlan = (VLANS - vlan_ids).first
unless vlan
raise Errors::ESXiError,
message: "No more VLAN (max: #{MAX_VLAN}) to assign to the port group"
end

# TODO check max port groups per vSwitch (512)

vswitch = network_options[:esxi__vswitch] || @default_vswitch
@logger.info("Creating port group #{network_options[:esxi__port_group]} on vSwitch '#{vswitch}'")
unless create_port_group(network_options[:esxi__port_group], vswitch, vlan)
raise Errors::ESXiError, message: "Cannot create port group "\
"`#{network_options[:esxi__port_group]}`, VLAN #{vlan}"
end

@created_port_groups << network_options[:esxi__port_group]
end

# Save networks created by this action
def save_created_networks
@logger.debug("Save created networks")
file = @env[:machine].data_dir.join("networks")

if file.exist?
json = JSON.parse(file.read)
@logger.debug("Previously saved networks: #{json}")
json["port_groups"] = json["port_groups"].concat(@created_port_groups.uniq).uniq
json["vswitches"] = json["vswitches"].concat(@created_vswitches.uniq).uniq
else
json = { port_groups: @created_port_groups.uniq, vswitches: @created_vswitches.uniq }
end

File.write(file, JSON.generate(json))
end
end
end
end
end
86 changes: 11 additions & 75 deletions lib/vagrant-vmware-esxi/action/createvm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,82 +218,9 @@ def createvm(env)
end
config.guest_storage = new_guest_storage
end

#
# Figure out network
#
r = ssh.exec!(
'esxcli network vswitch standard list |'\
'grep Portgroups | sed "s/^ Portgroups: //g" |'\
'sed "s/,./\n/g"')
availnetworks = r.split(/\n/)
if config.debug =~ %r{true}i
env[:ui].info I18n.t('vagrant_vmware_esxi.vagrant_vmware_esxi_message',
message: "Avail Networks : #{availnetworks}")
end
if (availnetworks == '') || (r.exitstatus != 0)
raise Errors::ESXiError,
message: "Unable to get list of Virtual Networks:\n"\
"#{r.stderr}"
end

# How many vm.network are there?
vm_network_index = 0
env[:machine].config.vm.networks.each do |type, options|
# I only handle private and public networks
next if type != :private_network && type != :public_network
vm_network_index += 1
end

# If there is more vm.network than esxi_virtual_network's configured
# I need to add more esxi_virtual_networks. Setting each to ---NotSet---
# to give a warning below...
if vm_network_index >= config.esxi_virtual_network.count
config.esxi_virtual_network.count.upto(vm_network_index) do |index|
config.esxi_virtual_network << '--NotSet--'
end
end

# Go through each esxi_virtual_network and make sure it's good. If not
# display a WARNING that we are choosing the first found.
@guestvm_network = []
networkID = 0
for aVirtNet in Array(config.esxi_virtual_network) do
if config.esxi_virtual_network == [''] ||
config.esxi_virtual_network[0] == '--NotSet--'
# First (and only ) interface is not configure or not set
@guestvm_network = [availnetworks.first]
config.esxi_virtual_network = [availnetworks.first]
env[:ui].info I18n.t('vagrant_vmware_esxi.vagrant_vmware_esxi_message',
message: 'WARNING : '\
"esxi_virtual_network[#{networkID}] not "\
"set, using #{availnetworks.first}")
@iswarning = 'true'
elsif availnetworks.include? aVirtNet
# Network interface is good
@guestvm_network << aVirtNet
else
# Network interface is NOT good.
@guestvm_network[networkID] = availnetworks.first
config.esxi_virtual_network[networkID] = availnetworks.first
if aVirtNet == '--NotSet--'
aVirtNet_msg = "esxi_virtual_network[#{networkID}]"
else
aVirtNet_msg = aVirtNet
end
env[:ui].info I18n.t('vagrant_vmware_esxi.vagrant_vmware_esxi_message',
message: 'WARNING : '\
"#{aVirtNet_msg} not "\
"found, using #{availnetworks.first}")
@iswarning = 'true'
end
networkID += 1
break if networkID >= 10
end
end

@logger.info('vagrant-vmware-esxi, createvm: '\
"esxi_virtual_network: #{@guestvm_network}")
collect_networks(env)

# Set some defaults
desired_guest_memsize = config.guest_memsize.to_s.to_i unless config.guest_memsize.nil?
Expand Down Expand Up @@ -779,7 +706,7 @@ def createvm(env)
desired_nic_type = 'e1000' if desired_nic_type.nil?

if config.esxi_virtual_network.is_a? Array
number_of_adapters = config.esxi_virtual_network.count
number_of_adapters = env[:machine].config.vm.networks.count
else
number_of_adapters = 1
end
Expand Down Expand Up @@ -856,6 +783,15 @@ def createvm(env)
# Done
end
end

def collect_networks(env)
@guestvm_network = [env[:machine].provider_config.default_port_group]
env[:machine].config.vm.networks.each do |type, options|
@guestvm_network << options[:esxi__port_group] if options[:esxi__port_group]
end

@logger.info("Networks: #{@guestvm_network}")
end
end
end
end
Expand Down
Loading