Inspect networks before creating new ones

This commit updates the behavior of how the docker provider creates new
docker networks. It looks at each existing network to see if the
requested subnet has already been configured in the docker engine. If
so, Vagrant will use that network rather than creating a new one. This
includes networks not created by Vagrant. Vagrant will not clean up
these networks if created outside of Vagrant.
This commit is contained in:
Brian Cain 2019-03-12 10:36:57 -07:00
parent 2c25cf8d01
commit 5ed5868067
No known key found for this signature in database
GPG Key ID: 9FC4639B2E4510A0
5 changed files with 67 additions and 9 deletions

View File

@ -27,6 +27,7 @@ module VagrantPlugins
network_name = "vagrant_network" network_name = "vagrant_network"
end end
# Only cleans up networks defined by Vagrant
if machine.provider.driver.existing_network?(network_name) && if machine.provider.driver.existing_network?(network_name) &&
!machine.provider.driver.network_used?(network_name) !machine.provider.driver.network_used?(network_name)
env[:ui].info(I18n.t("docker_provider.network_destroy", network_name: network_name)) env[:ui].info(I18n.t("docker_provider.network_destroy", network_name: network_name))

View File

@ -7,6 +7,7 @@ module VagrantPlugins
def initialize(app, env) def initialize(app, env)
@app = app @app = app
@logger = Log4r::Logger.new('vagrant::plugins::docker::network') @logger = Log4r::Logger.new('vagrant::plugins::docker::network')
@@lock = Mutex.new
end end
# @param[Hash] options - options from the network config # @param[Hash] options - options from the network config
@ -64,7 +65,15 @@ module VagrantPlugins
cli_opts = generate_create_cli_arguments(options) cli_opts = generate_create_cli_arguments(options)
if options[:subnet] if options[:subnet]
existing_network = machine.provider.driver.subnet_defined?(options[:subnet])
if !existing_network
network_name = "vagrant_network_#{options[:subnet]}" network_name = "vagrant_network_#{options[:subnet]}"
else
env[:ui].warn(I18n.t("docker_provider.subnet_exists",
network_name: existing_network,
subnet: options[:subnet]))
network_name = existing_network
end
elsif options[:type] == "dhcp" elsif options[:type] == "dhcp"
network_name = "vagrant_network" network_name = "vagrant_network"
else else
@ -72,12 +81,16 @@ module VagrantPlugins
end end
container_id = machine.id container_id = machine.id
@@lock.synchronize do
machine.env.lock("docker-network-create", retry: true) do
if !machine.provider.driver.existing_network?(network_name) if !machine.provider.driver.existing_network?(network_name)
@logger.debug("Creating network #{network_name}") @logger.debug("Creating network #{network_name}")
machine.provider.driver.create_network(network_name, cli_opts) machine.provider.driver.create_network(network_name, cli_opts)
else else
@logger.debug("Network #{network_name} already created") @logger.debug("Network #{network_name} already created")
end end
end
end
@logger.debug("Connecting network #{network_name} to container guest #{machine.name}") @logger.debug("Connecting network #{network_name} to container guest #{machine.name}")
connect_opts = generate_connect_cli_arguments(options) connect_opts = generate_connect_cli_arguments(options)

View File

@ -205,7 +205,8 @@ module VagrantPlugins
# @param [Array] networks - list of networks to inspect # @param [Array] networks - list of networks to inspect
# @param [Array] opts - An array of flags used for listing networks # @param [Array] opts - An array of flags used for listing networks
def inspect_network(network, opts=nil) def inspect_network(network, opts=nil)
command = ['docker', 'network', 'inspect', network].push(*opts) command = ['docker', 'network', 'inspect'] + Array(network)
command = command.push(*opts)
output = execute(*command) output = execute(*command)
output output
end end
@ -246,13 +247,35 @@ module VagrantPlugins
# Docker network helpers # Docker network helpers
# ###################### # ######################
# @param [String] subnet_string - Subnet to look for
def subnet_defined?(subnet_string)
all_networks = list_network(["--format={{.Name}}"])
all_networks = all_networks.split("\n")
results = inspect_network(all_networks)
begin
networks_info = JSON.parse(results)
networks_info.each do |network|
config = network["IPAM"]["Config"]
if (config.size > 0 &&
config.first["Subnet"] == subnet_string)
@logger.debug("Found existing network #{network["Name"]} already configured with #{subnet_string}")
return network["Name"]
end
end
rescue JSON::ParserError => e
@logger.warn("Could not properly parse response from `docker network inspect #{all_networks.join(" ")}`")
end
return nil
end
# Looks to see if a docker network has already been defined # Looks to see if a docker network has already been defined
# #
# @param [String] network - name of network to look for # @param [String] network - name of network to look for
def existing_network?(network) def existing_network?(network)
result = list_network(["--format='{{json .Name}}'"]) result = list_network(["--format={{.Name}}"])
#TODO: we should be more explicit here if we can #TODO: we should be more explicit here if we can
result.match?(/\"#{network}\"/) result.match?(/#{network}/)
end end
# Returns true or false if network is in use or not. # Returns true or false if network is in use or not.

View File

@ -70,6 +70,9 @@ en:
ssh_through_host_vm: |- ssh_through_host_vm: |-
SSH will be proxied through the Docker virtual machine since we're SSH will be proxied through the Docker virtual machine since we're
not running Docker natively. This is just a notice, and not an error. not running Docker natively. This is just a notice, and not an error.
subnet_exists: |-
A network called '%{network_name}' using subnet '%{subnet}' is already in use.
Using '%{network_name}' instead of creating a new network...
synced_folders_changed: |- synced_folders_changed: |-
Vagrant has noticed that the synced folder definitions have changed. Vagrant has noticed that the synced folder definitions have changed.
With Docker, these synced folder changes won't take effect until you With Docker, these synced folder changes won't take effect until you

View File

@ -61,6 +61,7 @@ describe VagrantPlugins::DockerProvider::Action::Network do
allow(driver).to receive(:existing_network?).and_return(false) allow(driver).to receive(:existing_network?).and_return(false)
allow(driver).to receive(:create_network).and_return(true) allow(driver).to receive(:create_network).and_return(true)
allow(driver).to receive(:connect_network).and_return(true) allow(driver).to receive(:connect_network).and_return(true)
allow(driver).to receive(:subnet_defined?).and_return(nil)
called = false called = false
app = ->(*args) { called = true } app = ->(*args) { called = true }
@ -76,6 +77,8 @@ describe VagrantPlugins::DockerProvider::Action::Network do
allow(driver).to receive(:existing_network?).and_return(false) allow(driver).to receive(:existing_network?).and_return(false)
allow(driver).to receive(:create_network).and_return(true) allow(driver).to receive(:create_network).and_return(true)
allow(driver).to receive(:connect_network).and_return(true) allow(driver).to receive(:connect_network).and_return(true)
allow(driver).to receive(:subnet_defined?).and_return(nil)
expect(subject).to receive(:generate_create_cli_arguments). expect(subject).to receive(:generate_create_cli_arguments).
with(networks[0][1]).and_return(["--subnet=172.20.0.0/16", "--driver=bridge", "--internal=true"]) with(networks[0][1]).and_return(["--subnet=172.20.0.0/16", "--driver=bridge", "--internal=true"])
@ -86,6 +89,21 @@ describe VagrantPlugins::DockerProvider::Action::Network do
expect(subject).to receive(:generate_connect_cli_arguments). expect(subject).to receive(:generate_connect_cli_arguments).
with(networks[1][1]).and_return([]) with(networks[1][1]).and_return([])
expect(driver).to receive(:create_network).twice
expect(driver).to receive(:connect_network).twice
subject.call(env)
end
it "uses an existing network if a matching subnet is found" do
allow(driver).to receive(:host_vm?).and_return(false)
allow(driver).to receive(:existing_network?).and_return(true)
allow(driver).to receive(:create_network).and_return(true)
allow(driver).to receive(:connect_network).and_return(true)
allow(driver).to receive(:subnet_defined?).and_return("my_cool_subnet_network")
expect(driver).not_to receive(:create_network)
subject.call(env) subject.call(env)
end end