diff options
author | danielsdeleo <dan@opscode.com> | 2012-11-05 14:35:57 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2012-11-05 16:27:15 -0800 |
commit | 7184480e7d86f9567e2962ac2dd308e55ea51e8e (patch) | |
tree | 4e571ed6277afa3dd8d50ec1387ac0e0aeda196c /spec | |
parent | c85cb0ecfaeafaa3738ff9950f6fb97db298ae3f (diff) | |
download | chef-7184480e7d86f9567e2962ac2dd308e55ea51e8e.tar.gz |
Merge branch 'OC-4668'
Diffstat (limited to 'spec')
-rw-r--r-- | spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle | bin | 0 -> 3527 bytes | |||
-rw-r--r-- | spec/data/git_bundles/sinatra-test-app.gitbundle | bin | 0 -> 2053 bytes | |||
-rw-r--r-- | spec/functional/resource/deploy_revision_spec.rb | 499 |
3 files changed, 499 insertions, 0 deletions
diff --git a/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle b/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle Binary files differnew file mode 100644 index 0000000000..24e36f7eeb --- /dev/null +++ b/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle diff --git a/spec/data/git_bundles/sinatra-test-app.gitbundle b/spec/data/git_bundles/sinatra-test-app.gitbundle Binary files differnew file mode 100644 index 0000000000..43c54eae5c --- /dev/null +++ b/spec/data/git_bundles/sinatra-test-app.gitbundle diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb new file mode 100644 index 0000000000..a9d5b4800e --- /dev/null +++ b/spec/functional/resource/deploy_revision_spec.rb @@ -0,0 +1,499 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'tmpdir' + +# Deploy relies heavily on symlinks, so it doesn't work on windows. +describe Chef::Resource::DeployRevision, :unix_only => true do + + let(:file_cache_path) { Dir.mktmpdir } + let(:deploy_directory) { Dir.mktmpdir } + + # By making restart or other operations write to this file, we can externally + # track the order in which those operations happened. + let(:observe_order_file) { Tempfile.new("deploy-resource-observe-operations") } + + before do + @old_file_cache_path = Chef::Config[:file_cache_path] + Chef::Config[:file_cache_path] = file_cache_path + end + + after do + Chef::Config[:file_cache_path] = @old_file_cache_path + FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory) + FileUtils.remove_entry_secure file_cache_path + observe_order_file.close + FileUtils.remove_entry_secure observe_order_file + end + + before(:all) do + @ohai = Ohai::System.new + @ohai.require_plugin("os") + end + + let(:node) do + + Chef::Node.new.tap do |n| + n.name "rspec-test" + n.consume_external_attrs(@ohai.data, {}) + end + end + + let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } + + + # These tests use git's bundle feature, which is a way to export an entire + # git repo (or subset of commits) as a single file. + # + # Generally you can treat a git bundle as a regular git remote. + # + # See also: http://git-scm.com/2010/03/10/bundles.html + let(:git_bundle_repo) { File.expand_path("git_bundles/sinatra-test-app.gitbundle", CHEF_SPEC_DATA) } + + let(:git_bundle_with_in_repo_callbacks) { File.expand_path("git_bundles/sinatra-test-app-with-callback-files.gitbundle", CHEF_SPEC_DATA) } + + # This is the fourth version + let(:latest_rev) { "3eb5ca6c353c83d9179dd3b29347539829b401f3" } + + # This is the third version + let(:previous_rev) { "6d19a6dbecc8e37f5b2277345885c0c783eb8fb1" } + + + # This is the sixth version, it is on the "with-deploy-scripts" branch + let(:rev_with_in_repo_callbacks) { "2404d015882659754bdb93ad6e4b4d3d02691a82" } + + # Read values from the +observe_order_file+ and split each line. This way you + # can see in which order things really happened. + def actual_operations_order + IO.read(observe_order_file.path).split("\n").map(&:strip) + end + + # 1. touch `restart.txt` in cwd so we know that the command is run with the + # right cwd. + # 2. Append +tag+ to the `observe_order_file` so we can check the order in + # which operations happen later in the test. + def shell_restart_command(tag) + "touch restart.txt && echo '#{tag}' >> #{observe_order_file.path}" + end + + let(:basic_deploy_resource) do + Chef::Resource::DeployRevision.new(deploy_directory, run_context).tap do |r| + r.repo git_bundle_repo + r.symlink_before_migrate({}) + r.symlinks({}) + end + end + + let(:deploy_to_latest_rev) do + basic_deploy_resource.dup.tap do |r| + r.revision(latest_rev) + r.restart_command shell_restart_command(:deploy_to_latest_rev) + end + end + + let(:deploy_to_previous_rev) do + basic_deploy_resource.dup.tap do |r| + r.revision(previous_rev) + r.restart_command shell_restart_command(:deploy_to_previous_rev) + end + end + + let(:deploy_to_latest_rev_again) do + basic_deploy_resource.dup.tap do |r| + r.revision(latest_rev) + r.restart_command shell_restart_command(:deploy_to_latest_rev_again) + end + end + + # Computes the full path for +path+ relative to the deploy directory + def rel_path(path) + File.expand_path(path, deploy_directory) + end + + def actual_current_rev + Dir.chdir(rel_path("current")) do + `git rev-parse HEAD`.strip + end + end + + def self.the_app_is_deployed_at_revision(target_rev_spec) + it "deploys the app to the target revision (#{target_rev_spec})" do + target_rev = send(target_rev_spec) + + File.should exist(rel_path("current")) + + actual_current_rev.should == target_rev + + # Is the app code actually there? + File.should exist(rel_path("current/app/app.rb")) + end + end + + context "when deploying a simple app" do + describe "for the first time, with the required directory layout precreated" do + before do + FileUtils.mkdir_p(rel_path("releases")) + FileUtils.mkdir_p(rel_path("shared")) + deploy_to_latest_rev.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "restarts the application" do + File.should exist(rel_path("current/restart.txt")) + actual_operations_order.should == %w[deploy_to_latest_rev] + end + + it "is marked as updated" do + deploy_to_latest_rev.should be_updated_by_last_action + end + end + + describe "back to a previously deployed revision, with the directory structure precreated" do + before do + FileUtils.mkdir_p(rel_path("releases")) + FileUtils.mkdir_p(rel_path("shared")) + + deploy_to_latest_rev.run_action(:deploy) + deploy_to_previous_rev.run_action(:deploy) + deploy_to_latest_rev_again.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "restarts the application after rolling back" do + actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again] + end + + it "is marked updated" do + deploy_to_latest_rev_again.should be_updated_by_last_action + end + + it "deploys the right code" do + IO.read(rel_path("current/app/app.rb")).should include("this is the fourth version of the app") + end + end + + describe "for the first time, with no existing directory layout" do + before do + deploy_to_latest_rev.run_action(:deploy) + end + + it "creates the required directory tree" do + File.should be_directory(rel_path("releases")) + File.should be_directory(rel_path("shared")) + File.should be_directory(rel_path("releases/#{latest_rev}")) + + File.should be_directory(rel_path("current/tmp")) + File.should be_directory(rel_path("current/config")) + File.should be_directory(rel_path("current/public")) + + File.should be_symlink(rel_path("current")) + File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}") + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "restarts the application" do + File.should exist(rel_path("current/restart.txt")) + actual_operations_order.should == %w[deploy_to_latest_rev] + end + + it "is marked as updated" do + deploy_to_latest_rev.should be_updated_by_last_action + end + end + + describe "again to the current revision" do + before do + deploy_to_latest_rev.run_action(:deploy) + deploy_to_latest_rev.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "does not restart the app" do + actual_operations_order.should == %w[deploy_to_latest_rev] + end + + it "is not marked updated" do + deploy_to_latest_rev.should_not be_updated_by_last_action + end + + end + + describe "again with force_deploy" do + before do + deploy_to_latest_rev.run_action(:force_deploy) + deploy_to_latest_rev_again.run_action(:force_deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "restarts the app" do + actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_latest_rev_again] + end + + it "is marked updated" do + deploy_to_latest_rev.should be_updated_by_last_action + end + + end + + describe "again to a new revision" do + before do + deploy_to_previous_rev.run_action(:deploy) + deploy_to_latest_rev.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "restarts the application after the new deploy" do + actual_operations_order.should == %w[deploy_to_previous_rev deploy_to_latest_rev] + end + + it "is marked updated" do + deploy_to_previous_rev.should be_updated_by_last_action + end + end + + describe "back to a previously deployed revision (implicit rollback)" do + before do + deploy_to_latest_rev.run_action(:deploy) + deploy_to_previous_rev.run_action(:deploy) + deploy_to_latest_rev_again.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "restarts the application after rolling back" do + actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again] + end + + it "is marked updated" do + deploy_to_latest_rev_again.should be_updated_by_last_action + end + + it "deploys the right code" do + IO.read(rel_path("current/app/app.rb")).should include("this is the fourth version of the app") + end + end + + # CHEF-3435 + describe "to a deploy_to path that does not yet exist" do + + let(:top_level_tmpdir) { Dir.mktmpdir } + + # override top level deploy_directory let block with one that is two + # directories deeper + let(:deploy_directory) { File.expand_path("nested/deeper", top_level_tmpdir) } + + after do + FileUtils.remove_entry_secure top_level_tmpdir + end + + before do + File.should_not exist(deploy_directory) + deploy_to_latest_rev.run_action(:deploy) + end + + it "creates the required directory tree" do + File.should be_directory(rel_path("releases")) + File.should be_directory(rel_path("shared")) + File.should be_directory(rel_path("releases/#{latest_rev}")) + + File.should be_directory(rel_path("current/tmp")) + File.should be_directory(rel_path("current/config")) + File.should be_directory(rel_path("current/public")) + + File.should be_symlink(rel_path("current")) + File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}") + end + + the_app_is_deployed_at_revision(:latest_rev) + + end + end + + context "when deploying an app with inline recipe callbacks" do + + # Use closures to capture and mutate this variable. This allows us to track + # ordering of operations. + callback_order = [] + + let(:deploy_to_latest_with_inline_recipes) do + deploy_to_latest_rev.dup.tap do |r| + r.symlink_before_migrate "config/config.ru" => "config.ru" + r.before_migrate do + callback_order << :before_migrate + + file "#{release_path}/before_migrate.txt" do + # The content here isn't relevant, but it gets printed when running + # the tests. Could be handy for debugging. + content callback_order.inspect + end + end + r.before_symlink do + callback_order << :before_symlink + + current_release_path = release_path + ruby_block "ensure before symlink" do + block do + if File.exist?(File.join(current_release_path, "/tmp")) + raise "Ordering issue with provider, expected symlinks to not have been created" + end + end + end + + file "#{release_path}/before_symlink.txt" do + content callback_order.inspect + end + end + r.before_restart do + callback_order << :before_restart + + current_release_path = release_path + ruby_block "ensure after symlink" do + block do + unless File.exist?(File.join(current_release_path, "/tmp")) + raise "Ordering issue with provider, expected symlinks to have been created" + end + end + end + + file "#{release_path}/tmp/before_restart.txt" do + content callback_order.inspect + end + end + r.after_restart do + callback_order << :after_restart + file "#{release_path}/tmp/after_restart.txt" do + content callback_order.inspect + end + end + end + end + + before do + callback_order.clear # callback_order variable is global for this context group + deploy_to_latest_with_inline_recipes.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:latest_rev) + + it "is marked updated" do + deploy_to_latest_with_inline_recipes.should be_updated_by_last_action + end + + it "calls the callbacks in order" do + callback_order.should == [:before_migrate, :before_symlink, :before_restart, :after_restart] + end + + it "runs chef resources in the callbacks" do + File.should exist(rel_path("current/before_migrate.txt")) + File.should exist(rel_path("current/before_symlink.txt")) + File.should exist(rel_path("current/tmp/before_restart.txt")) + File.should exist(rel_path("current/tmp/after_restart.txt")) + end + end + + context "when deploying an app with in-repo callback scripts" do + let(:deploy_with_in_repo_callbacks) do + basic_deploy_resource.dup.tap do |r| + r.repo git_bundle_with_in_repo_callbacks + r.revision rev_with_in_repo_callbacks + end + end + + before do + deploy_with_in_repo_callbacks.run_action(:deploy) + end + + the_app_is_deployed_at_revision(:rev_with_in_repo_callbacks) + + it "runs chef resources in the callbacks" do + File.should exist(rel_path("current/before_migrate.txt")) + File.should exist(rel_path("current/before_symlink.txt")) + File.should exist(rel_path("current/tmp/before_restart.txt")) + File.should exist(rel_path("current/tmp/after_restart.txt")) + end + + end + + context "when deploying an app with migrations" do + let(:deploy_with_migration) do + basic_deploy_resource.dup.tap do |r| + + # Need this so we can call methods from this test inside the inline + # recipe callbacks + spec_context = self + + r.revision latest_rev + + # enable migrations + r.migrate true + # abuse `shell_restart_command` so we can observe order of when the + # miration command gets run + r.migration_command shell_restart_command("migration") + r.before_migrate do + + # inline recipe callbacks don't cwd, so you have to get the release + # directory as a local and "capture" it in the closure. + current_release = release_path + execute spec_context.shell_restart_command("before_migrate") do + cwd current_release + end + end + r.before_symlink do + current_release = release_path + execute spec_context.shell_restart_command("before_symlink") do + cwd current_release + end + end + + r.before_restart do + current_release = release_path + execute spec_context.shell_restart_command("before_restart") do + cwd current_release + end + end + + r.after_restart do + current_release = release_path + execute spec_context.shell_restart_command("after_restart") do + cwd current_release + end + end + + end + end + + before do + deploy_with_migration.run_action(:deploy) + end + + it "runs migrations in between the before_migrate and before_symlink steps" do + actual_operations_order.should == %w[before_migrate migration before_symlink before_restart after_restart] + end + end + +end + + |