diff --git a/README.md b/README.md index 958dec8a99..c6bf387fed 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ install_dir | Installation directory for docker binary | String | auto-detected install_type | Installation type for docker ("binary", "package" or "source") | String | "package" options | Additional options to pass to docker. These could be flags like "-api-enable-cors". | String | nil registry_cmd_timeout | registry LWRP default cmd_timeout seconds | Fixnum | 60 +docker_daemon_timeout | Timeout to wait for the docker daemon to start in seconds | Fixnum | 10 storage_type | Storage driver for docker (nil, "aufs", or "devmapper") | String | auto-detected (see attributes/default.rb) version | Version of docker | String | nil virtualization_type | Virtualization driver for docker (nil or "lxc") | String | auto-detected (see attributes/default.rb) diff --git a/attributes/default.rb b/attributes/default.rb index 94acdb2b7e..8e84963af4 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -14,6 +14,7 @@ default['docker']['http_proxy'] = nil default['docker']['image_cmd_timeout'] = 300 default['docker']['registry_cmd_timeout'] = 60 +default['docker']['docker_daemon_timeout'] = 10 default['docker']['init_type'] = value_for_platform( %w{ centos debian oracle redhat } => { diff --git a/libraries/helpers.rb b/libraries/helpers.rb index c1f1a608d3..0693d9c361 100644 --- a/libraries/helpers.rb +++ b/libraries/helpers.rb @@ -1,7 +1,27 @@ +require 'chef/mixin/shell_out' +include Chef::Mixin::ShellOut + # Helpers module module Helpers # Helpers::Docker module module Docker + # Exception to signify that the Docker daemon is not yet ready to handle + # docker commands. + class DockerNotReady < StandardError + def initialize(timeout) + super <<-EOH +The Docker daemon did not become ready within #{timeout} seconds. +This most likely means that Docker failed to start. +Docker can fail to start if: + + - a configuration file is invalid + - permissions are incorrect for the root directory of the docker runtime. + +If this problem persists, check your service log files. +EOH + end + end + def cli_args(spec) cli_line = '' spec.each_pair do |arg, value| @@ -28,5 +48,75 @@ def docker_inspect_id(id) inspect = docker_inspect(id) inspect['id'] if inspect end + + def timeout + node['docker']['docker_daemon_timeout'] + end + + # This is based upon wait_until_ready! from the opscode jenkins cookbook. + # + # Since the docker service returns immediately and the actual docker + # process is started as a daemon, we block the Chef Client run until the + # daemon is actually ready. + # + # This method will effectively "block" the current thread until the docker + # daemon is ready + # + # @raise [DockerNotReady] + # if the Docker master does not respond within (+timeout+) seconds + # + def wait_until_ready! + Timeout.timeout(timeout) do + loop do + result = shell_out('docker info') + break if Array(result.valid_exit_codes).include?(result.exitstatus) + Chef::Log.debug("Docker daemon is not running - #{result.stdout}\n#{result.stderr}") + sleep(0.5) + end + end + rescue Timeout::Error + raise DockerNotReady.new(timeout), 'docker timeout exceeded' + end + + # the Error message to display if a command times out. Subclasses + # may want to override this to provide more details on the timeout. + def command_timeout_error_message + <<-EOM + +Command timed out: +#{cmd} + +EOM + end + + # Runs a docker command. Does not raise exception on non-zero exit code. + def docker_cmd(cmd, timeout = new_resource.cmd_timeout) + execute_cmd('docker ' + cmd, timeout) + end + + # Executes the given command with the specified timeout. Does not raise an + # exception on a non-zero exit code. + def execute_cmd(cmd, timeout = new_resource.cmd_timeout) + Chef::Log.debug('Executing: ' + cmd) + begin + shell_out(cmd, :timeout => timeout) + rescue Mixlib::ShellOut::CommandTimeout + raise CommandTimeout, command_timeout_error_message + end + end + + # Executes the given docker command with the specified timeout. Raises an + # exception if the command returns a non-zero exit code. + def docker_cmd!(cmd, timeout = new_resource.cmd_timeout) + execute_cmd!('docker ' + cmd, timeout) + end + + # Executes teh given command with the specified timeout. Raises an + # exception if the command returns a non-zero exit code. + def execute_cmd!(cmd, timeout = new_resource.cmd_timeout) + cmd = execute_cmd(cmd, timeout) + cmd.error! + cmd + end end end diff --git a/providers/container.rb b/providers/container.rb index 1f8d7ef278..fe08807641 100644 --- a/providers/container.rb +++ b/providers/container.rb @@ -6,7 +6,8 @@ class CommandTimeout < RuntimeError; end def load_current_resource @current_resource = Chef::Resource::DockerContainer.new(new_resource) - dps = docker_cmd('ps -a -notrunc') + wait_until_ready! + dps = docker_cmd!('ps -a -notrunc') dps.stdout.each_line do |dps_line| ps = dps(dps_line) unless container_id_matches?(ps['id']) @@ -121,7 +122,7 @@ def commit commit_end_args = new_resource.repository commit_end_args += ":#{new_resource.tag}" if new_resource.tag end - docker_cmd("commit #{commit_args} #{current_resource.id} #{commit_end_args}") + docker_cmd!("commit #{commit_args} #{current_resource.id} #{commit_end_args}") end def container_command_matches_if_exists?(command) @@ -151,7 +152,7 @@ def container_name end def cp - docker_cmd("cp #{current_resource.id}:#{new_resource.source} #{new_resource.destination}") + docker_cmd!("cp #{current_resource.id}:#{new_resource.source} #{new_resource.destination}") end def docker_cmd(cmd, timeout = new_resource.cmd_timeout) @@ -175,19 +176,14 @@ def dps(dps_line) ps end -def execute_cmd(cmd, timeout = new_resource.cmd_timeout) - Chef::Log.debug('Executing: ' + cmd) - begin - shell_out(cmd, :timeout => timeout) - rescue Mixlib::ShellOut::CommandTimeout - raise CommandTimeout, <<-EOM +def command_timeout_error_message + <<-EOM Command timed out: #{cmd} Please adjust node container_cmd_timeout attribute or this docker_container cmd_timeout attribute if necessary. EOM - end end def exists? @@ -195,14 +191,14 @@ def exists? end def export - docker_cmd("export #{current_resource.id} > #{new_resource.destination}") + docker_cmd!("export #{current_resource.id} > #{new_resource.destination}") end def kill if service? service_stop else - docker_cmd("kill #{current_resource.id}") + docker_cmd!("kill #{current_resource.id}") end end @@ -221,7 +217,7 @@ def remove rm_args = cli_args( 'link' => new_resource.link ) - docker_cmd("rm #{rm_args} #{current_resource.id}") + docker_cmd!("rm #{rm_args} #{current_resource.id}") service_remove if service? end @@ -229,7 +225,7 @@ def restart if service? service_restart else - docker_cmd("restart #{current_resource.id}") + docker_cmd!("restart #{current_resource.id}") end end @@ -258,7 +254,7 @@ def run 'volumes-from' => new_resource.volumes_from, 'w' => new_resource.working_directory ) - dr = docker_cmd("run #{run_args} #{new_resource.image} #{new_resource.command}") + dr = docker_cmd!("run #{run_args} #{new_resource.image} #{new_resource.command}") dr.error! new_resource.id(dr.stdout.chomp) service_create if service? @@ -442,7 +438,7 @@ def start if service? service_create else - docker_cmd("start #{start_args} #{current_resource.id}") + docker_cmd!("start #{start_args} #{current_resource.id}") end end @@ -453,10 +449,10 @@ def stop if service? service_stop else - docker_cmd("stop #{stop_args} #{current_resource.id}", (new_resource.cmd_timeout + 15)) + docker_cmd!("stop #{stop_args} #{current_resource.id}", (new_resource.cmd_timeout + 15)) end end def wait - docker_cmd("wait #{current_resource.id}") + docker_cmd!("wait #{current_resource.id}") end diff --git a/providers/image.rb b/providers/image.rb index caaa227816..98a732c047 100644 --- a/providers/image.rb +++ b/providers/image.rb @@ -5,6 +5,7 @@ class CommandTimeout < RuntimeError; end def load_current_resource + wait_until_ready! @current_resource = Chef::Resource::DockerImage.new(new_resource) dimages = docker_cmd('images -a -notrunc') if dimages.stdout.include?(new_resource.image_name) @@ -122,7 +123,7 @@ def build command = new_resource.source end - docker_cmd("build #{build_args} #{command}") + docker_cmd!("build #{build_args} #{command}") end def di(di_line) @@ -136,23 +137,14 @@ def di(di_line) image end -def docker_cmd(cmd, timeout = new_resource.cmd_timeout) - execute_cmd('docker ' + cmd, timeout) -end - -def execute_cmd(cmd, timeout = new_resource.cmd_timeout) - Chef::Log.debug('Executing: ' + cmd) - begin - shell_out(cmd, :timeout => timeout) - rescue Mixlib::ShellOut::CommandTimeout - raise CommandTimeout, <<-EOM +def command_timeout_error_message + <<-EOM Command timed out: #{cmd} Please adjust node image_cmd_timeout attribute or this docker_image cmd_timeout attribute if necessary. EOM - end end def image_id_matches?(id) @@ -183,12 +175,12 @@ def import import_args += new_resource.source import_args += " #{new_resource.image_name}" end - docker_cmd("import #{import_args} #{repository_and_tag_args}") + docker_cmd!("import #{import_args} #{repository_and_tag_args}") end end def insert - docker_cmd("insert #{new_resource.image_name} #{new_resource.source} #{new_resource.destination}") + docker_cmd!("insert #{new_resource.image_name} #{new_resource.source} #{new_resource.destination}") end def installed? @@ -196,7 +188,7 @@ def installed? end def load - docker_cmd("load < #{new_resource.source}") + docker_cmd!("load < #{new_resource.source}") end def pull @@ -204,15 +196,17 @@ def pull 'registry' => new_resource.registry, 't' => new_resource.tag ) - docker_cmd("pull #{new_resource.image_name} #{pull_args}") + docker_cmd!("pull #{new_resource.image_name} #{pull_args}") end def push - docker_cmd("push #{new_resource.image_name}") + docker_cmd!("push #{new_resource.image_name}") end def remove - docker_cmd("rmi #{new_resource.image_name}") + image_name = new_resource.image_name + image_name = "#{image_name}:#{new_resource.tag}" if new_resource.tag + docker_cmd!("rmi #{image_name}") end def repository_and_tag_args @@ -225,12 +219,12 @@ def repository_and_tag_args end def save - docker_cmd("save #{new_resource.image_name} > #{new_resource.destination}") + docker_cmd!("save #{new_resource.image_name} > #{new_resource.destination}") end def tag tag_args = cli_args( 'f' => new_resource.force ) - docker_cmd("tag #{tag_args} #{new_resource.image_name} #{repository_and_tag_args}") + docker_cmd!("tag #{tag_args} #{new_resource.image_name} #{repository_and_tag_args}") end diff --git a/providers/registry.rb b/providers/registry.rb index b5d71014f2..77979f4079 100644 --- a/providers/registry.rb +++ b/providers/registry.rb @@ -6,6 +6,7 @@ class CommandTimeout < RuntimeError; end def load_current_resource @current_resource = Chef::Resource::DockerRegistry.new(new_resource) + wait_until_ready! # TODO: load current resource? @current_resource end @@ -17,23 +18,14 @@ def load_current_resource end end -def docker_cmd(cmd, timeout = new_resource.cmd_timeout) - execute_cmd('docker ' + cmd, timeout) -end - -def execute_cmd(cmd, timeout = new_resource.cmd_timeout) - Chef::Log.debug('Executing: ' + cmd) - begin - shell_out(cmd, :timeout => timeout) - rescue Mixlib::ShellOut::CommandTimeout - raise CommandTimeout, <<-EOM +def command_timeout_error_message + <<-EOM Command timed out: #{cmd} Please adjust node registry_cmd_timeout attribute or this docker_registry cmd_timeout attribute if necessary. EOM - end end def logged_in? @@ -46,5 +38,5 @@ def login 'p' => new_resource.password, 'u' => new_resource.username ) - docker_cmd("login #{new_resource.server} #{login_args}") + docker_cmd!("login #{new_resource.server} #{login_args}") end diff --git a/test/cookbooks/docker_test/files/default/tests/minitest/image_lwrp_test.rb b/test/cookbooks/docker_test/files/default/tests/minitest/image_lwrp_test.rb index f8e9a91de1..823ea045f9 100644 --- a/test/cookbooks/docker_test/files/default/tests/minitest/image_lwrp_test.rb +++ b/test/cookbooks/docker_test/files/default/tests/minitest/image_lwrp_test.rb @@ -2,7 +2,7 @@ describe_recipe "docker_test::image_lwrp_test" do include Helpers::DockerTest - + it "has base image not installed" do refute image_exists?("base") end @@ -14,4 +14,8 @@ it "has bflad/testcontainerd image installed" do assert image_exists?("bflad/testcontainerd") end + + it "has myImage image not installed" do + refute image_exists?("myImage") + end end diff --git a/test/cookbooks/docker_test/recipes/image_lwrp.rb b/test/cookbooks/docker_test/recipes/image_lwrp.rb index 1b94913b51..832ee3b27b 100644 --- a/test/cookbooks/docker_test/recipes/image_lwrp.rb +++ b/test/cookbooks/docker_test/recipes/image_lwrp.rb @@ -19,5 +19,6 @@ end docker_image "myImage" do + tag "myTag" action :remove end