Permalink
Browse files

Move and forward maintenance page controls to a Maintenance object

EY::Serverside::Maintenance is now responsible for adding and removing
the maintenance page. This simplifies the maintenance code (which was
getting hairy) and makes it more clear what is happening.
  • Loading branch information...
1 parent 80e4ee3 commit 540b03b70576be1bde67b6fe8c223eea40665819 @martinemde martinemde committed Jun 14, 2012
View
19 lib/engineyard-serverside/cli.rb
@@ -34,7 +34,25 @@ def deploy(default_task=:deploy)
EY::Serverside::Deploy.new(servers, config, shell).send(default_task)
end
+ account_app_env_options
+ config_option
+ instances_options
+ verbose_option
+ desc "enable_maintenance", "Enable maintenance page (disables web access)"
+ def enable_maintenance
+ servers, config, shell = init_and_propagate(options, 'enable_maintenance')
+ EY::Serverside::Maintenance.new(servers, config, shell).manually_enable
+ end
+ account_app_env_options
+ config_option
+ instances_options
+ verbose_option
+ desc "disable_maintenance", "Disable maintenance page (enables web access)"
+ def disable_maintenance
+ servers, config, shell = init_and_propagate(options, 'disable_maintenance')
+ EY::Serverside::Maintenance.new(servers, config, shell).manually_disable
+ end
method_option :release_path, :type => :string,
:desc => "Value for #release_path in hooks (mostly for internal coordination)",
@@ -95,7 +113,6 @@ def integrate
desc "restart", "Restart app servers, conditionally enabling maintenance page"
def restart
servers, config, shell = init_and_propagate(options, 'restart')
-
EY::Serverside::Deploy.new(servers, config, shell).restart_with_maintenance_page
end
View
89 lib/engineyard-serverside/deploy.rb
@@ -3,6 +3,7 @@
require 'fileutils'
require 'json'
require 'engineyard-serverside/rails_asset_support'
+require 'engineyard-serverside/maintenance'
module EY
module Serverside
@@ -33,7 +34,7 @@ def cached_deploy
check_for_ey_config
symlink_configs
setup_sqlite3_if_necessary
- conditionally_enable_maintenance_page
+ enable_maintenance_page
run_with_callbacks(:migrate)
run_with_callbacks(:compile_assets) # defined in RailsAssetSupport
callback(:before_symlink)
@@ -44,7 +45,7 @@ def cached_deploy
callback(:after_symlink)
run_with_callbacks(:restart)
- conditionally_disable_maintenance_page
+ disable_maintenance_page
cleanup_old_releases
shell.status "Finished deploy at #{Time.now.asctime}"
@@ -117,79 +118,17 @@ def check_repository
def restart_with_maintenance_page
require_custom_tasks
- with_maintenance_page { restart }
+ enable_maintenance_page
+ restart
+ disable_maintenance_page
end
def enable_maintenance_page
- maintenance_page_candidates = [
- "public/maintenance.html.custom",
- "public/maintenance.html.tmp",
- "public/maintenance.html",
- "public/system/maintenance.html.default",
- ].map do |file|
- File.join(c.latest_release, file)
- end
-
- # this one is guaranteed to exist
- maintenance_page_candidates << File.expand_path("default_maintenance_page.html", File.dirname(__FILE__))
-
- # put in the maintenance page
- maintenance_file = maintenance_page_candidates.detect do |file|
- File.exists?(file)
- end
-
- shell.status "Enabling maintenance page."
- @maintenance_up = true
- roles :app_master, :app, :solo do
- run Escape.shell_command(['mkdir', '-p', File.dirname(c.maintenance_page_enabled_path)])
- run Escape.shell_command(['cp', maintenance_file, c.maintenance_page_enabled_path])
- end
- end
-
- def conditionally_enable_maintenance_page
- if c.enable_maintenance_page?
- enable_maintenance_page
- else
- explain_not_enabling_maintenance_page
- end
- end
-
- def explain_not_enabling_maintenance_page
- if c.migrate?
- if !c.maintenance_on_migrate? && !c.maintenance_on_restart?
- shell.status "Skipping maintenance page. (maintenance_on_migrate is false in ey.yml)"
- shell.notice "[Caution] No maintenance migrations must be non-destructive!"
- shell.notice "Requests may be served during a partially migrated state."
- end
- else
- if c.required_downtime_stack? && !c.maintenance_on_restart?
- shell.status "Skipping maintenance page. (maintenance_on_restart is false in ey.yml, overriding recommended default)"
- unless File.exist?(c.maintenance_page_enabled_path)
- shell.warning <<-WARN
-No maintenance page! Brief downtime may be possible during restart.
-This application stack does not support no-downtime restarts.
- WARN
- end
- elsif !c.required_downtime_stack?
- shell.status "Skipping maintenance page. (no-downtime restarts supported)"
- end
- end
+ maintenance.conditionally_enable
end
def disable_maintenance_page
- shell.status "Removing maintenance page."
- @maintenance_up = false
- roles :app_master, :app, :solo do
- run "rm -f #{c.maintenance_page_enabled_path}"
- end
- end
-
- def conditionally_disable_maintenance_page
- if c.disable_maintenance_page?
- disable_maintenance_page
- elsif File.exists?(c.maintenance_page_enabled_path)
- shell.notice "[Attention] Maintenance page is still up.\nYou must remove it manually using `ey web enable`."
- end
+ maintenance.conditionally_disable
end
def run_with_callbacks(task)
@@ -296,7 +235,9 @@ def rollback
sudo "rm -rf #{rolled_back_release}"
bundle
shell.status "Restarting with previous release."
- with_maintenance_page { run_with_callbacks(:restart) }
+ enable_maintenance_page
+ run_with_callbacks(:restart)
+ disable_maintenance_page
shell.status "Finished rollback at #{Time.now.asctime}"
rescue Exception
shell.status "Failed to rollback at #{Time.now.asctime}"
@@ -483,7 +424,7 @@ def serverside_bin
def puts_deploy_failure
if @cleanup_failed
shell.notice "[Relax] Your site is running new code, but clean up of old deploys failed."
- elsif @maintenance_up
+ elsif maintenance.up?
message = "[Attention] Maintenance page still up, consider the following before removing:\n"
message << " * Deploy hooks ran. This might cause problems for reverting to old code.\n" if @callbacks_reached
message << " * Migrations ran. This might cause problems for reverting to old code.\n" if @migrations_reached
@@ -501,10 +442,8 @@ def puts_deploy_failure
end
end
- def with_maintenance_page
- conditionally_enable_maintenance_page
- yield if block_given?
- conditionally_disable_maintenance_page
+ def maintenance
+ @maintenance ||= Maintenance.new(servers, config, shell)
end
def with_failed_release_cleanup
View
112 lib/engineyard-serverside/maintenance.rb
@@ -0,0 +1,112 @@
+module EY
+ module Serverside
+ class Maintenance
+
+ def initialize(servers, config, shell)
+ @servers, @config, @shell = servers, config, shell
+ end
+
+ def exist?
+ enabled_maintenance_page_pathname.exist?
+ end
+
+ def up?
+ @up
+ end
+
+ def manually_enable
+ if paths.deployed?
+ enable
+ else
+ shell.fatal "Cannot enabled maintenance page. Application #{config.app_name} has never been deployed."
+ false
+ end
+ end
+
+ def manually_disable
+ if paths.deployed?
+ disable
+ else
+ shell.fatal "Cannot enabled maintenance page. Application #{config.app_name} has never been deployed."
+ false
+ end
+ end
+
+ def conditionally_enable
+ if config.enable_maintenance_page?
+ enable
+ else
+ explain_not_enabling
+ end
+ end
+
+ def conditionally_disable
+ if config.disable_maintenance_page?
+ disable
+ elsif exist?
+ shell.notice "[Attention] Maintenance page is still up.\nYou must remove it manually using `ey web enable`."
+ end
+ end
+
+ protected
+
+ attr_reader :config, :shell
+
+ def enable
+ shell.status "Enabling maintenance page."
+ @up = true
+ run "mkdir -p #{maintenance_page_dirname}"
+ run "cp #{source_path} #{enabled_maintenance_page_pathname}"
+ end
+
+ def disable
+ shell.status "Removing maintenance page."
+ @up = false
+ run "rm -f #{enabled_maintenance_page_pathname}"
+ end
+
+ def run(cmd)
+ @servers.roles(:app_master, :app, :solo).run(shell, cmd)
+ end
+
+ def paths
+ config.paths
+ end
+
+ def source_path
+ paths.maintenance_page_candidates.detect {|path| path.exist? }
+ end
+
+ def enabled_maintenance_page_pathname
+ paths.enabled_maintenance_page
+ end
+
+ def maintenance_page_dirname
+ enabled_maintenance_page_pathname.dirname
+ end
+
+ def explain_not_enabling
+ if config.migrate?
+ if !config.maintenance_on_migrate? && !config.maintenance_on_restart?
+ shell.status "Skipping maintenance page. (maintenance_on_migrate is false in ey.yml)"
+ shell.notice "[Caution] No maintenance migrations must be non-destructive!"
+ shell.notice "Requests may be served during a partially migrated state."
+ end
+ else
+ if config.required_downtime_stack? && !config.maintenance_on_restart?
+ shell.status "Skipping maintenance page. (maintenance_on_restart is false in ey.yml, overriding recommended default)"
+ unless exist?
+ shell.warning <<-WARN
+No maintenance page! Brief downtime may be possible during restart.
+This application stack does not support no-downtime restarts.
+ WARN
+ end
+ elsif !config.required_downtime_stack?
+ shell.status "Skipping maintenance page. (no-downtime restarts supported)"
+ end
+ end
+ end
+
+ end
+ end
+end
View
31 lib/engineyard-serverside/paths.rb
@@ -27,10 +27,23 @@ def latest_revision() paths.latest_revision.read.strip
def ssh_identity_file() paths.ssh_identity.to_s end
end
+ # Maintenance page candidates in order of search preference.
+ MAINTENANCE_CANDIDATES = [
+ "public/maintenance.html.custom",
+ "public/maintenance.html.tmp",
+ "public/maintenance.html",
+ "public/system/maintenance.html.default",
+ ]
+
+ # This one is guaranteed to exist.
+ DEFAULT_MAINTENANCE_PAGE = Pathname.new("default_maintenance_page.html").expand_path(File.dirname(__FILE__))
+
+ # Define methods that get us paths
def self.def_path(name, parts)
define_method(name.to_sym) { path(*parts) }
end
+ # Load a path given a root and more parts
def path(root, *parts)
send(root).join(*parts)
end
@@ -93,8 +106,24 @@ def latest_release
all_releases.last
end
+ def deployed?
+ !!latest_release
+ end
+
+ def maintenance_page_candidates
+ if latest_release
+ candidates = MAINTENANCE_CANDIDATES.map do |file|
+ path(:latest_release, file)
+ end
+ else
+ candidates = []
+ end
+ candidates << DEFAULT_MAINTENANCE_PAGE
+ candidates
+ end
+
def rollback
- if previous_release
+ if deployed? && previous_release
self.class.new(@opts.dup.merge(:active_release => previous_release))
else
nil
View
39 spec/custom_deploy_spec.rb
@@ -18,19 +18,19 @@ def initialize(*a)
@call_order = []
end
- def push_code() @call_order << 'push_code' end
- def copy_repository_cache() @call_order << 'copy_repository_cache' end
- def create_revision_file() @call_order << 'create_revision_file' end
- def bundle() @call_order << 'bundle' end
- def setup_services() @call_order << 'setup_services' end
- def symlink_configs() @call_order << 'symlink_configs' end
- def migrate() @call_order << 'migrate' end
- def compile_assets() @call_order << 'compile_assets' end
- def symlink() @call_order << 'symlink' end
- def restart() @call_order << 'restart' end
- def cleanup_old_releases() @call_order << 'cleanup_old_releases' end
- def conditionally_enable_maintenance_page() @call_order << 'conditionally_enable_maintenance_page' end
- def disable_maintenance_page() @call_order << 'disable_maintenance_page' end
+ def push_code() @call_order << 'push_code' end
+ def copy_repository_cache() @call_order << 'copy_repository_cache' end
+ def create_revision_file() @call_order << 'create_revision_file' end
+ def bundle() @call_order << 'bundle' end
+ def setup_services() @call_order << 'setup_services' end
+ def symlink_configs() @call_order << 'symlink_configs' end
+ def migrate() @call_order << 'migrate' end
+ def compile_assets() @call_order << 'compile_assets' end
+ def symlink() @call_order << 'symlink' end
+ def restart() @call_order << 'restart' end
+ def cleanup_old_releases() @call_order << 'cleanup_old_releases' end
+ def enable_maintenance_page() @call_order << 'enable_maintenance_page' end
+ def disable_maintenance_page() @call_order << 'disable_maintenance_page' end
end
config = EY::Serverside::Deploy::Configuration.new({
@@ -40,14 +40,25 @@ def disable_maintenance_page() @call_order << 'disable_maintenance
td = TestDeploy.new(test_servers, config, test_shell)
td.deploy
+
+ ############################# IMPORTANT ####################################
+ #
+ # Call order is referenced in the engineyard gem eydeploy.rb documentation.
+ #
+ # https://support.cloud.engineyard.com/entries/20996661-customize-your-deployment
+ #
+ # Changing call order or removing methods may adversely affect customers
+ # that are using eydeploy.rb and relying on this documentation.
+ #
+ ############################################################################
td.call_order.should == %w(
push_code
copy_repository_cache
create_revision_file
bundle
setup_services
symlink_configs
- conditionally_enable_maintenance_page
+ enable_maintenance_page
migrate
compile_assets
symlink
View
6 spec/restart_spec.rb
@@ -20,12 +20,12 @@ def conditionally_enable_maintenance_page() @call_order << 'conditionally_enabl
end
it "puts up the maintenance page if necessary, restarts, and takes down the maintenance page" do
- config = EY::Serverside::Deploy::Configuration.new('app' => 'app_name')
+ config = EY::Serverside::Deploy::Configuration.new('deploy_to' => deploy_dir, 'app' => 'app_name')
deployer = TestRestartWithMaintenancePage.new(test_servers, config, test_shell)
deployer.restart_with_maintenance_page
deployer.call_order.should == %w(
require_custom_tasks
- conditionally_enable_maintenance_page
+ enable_maintenance_page
restart
disable_maintenance_page
)
@@ -35,7 +35,7 @@ def conditionally_enable_maintenance_page() @call_order << 'conditionally_enabl
describe "glassfish stack" do
it "requires a maintenance page" do
- config = EY::Serverside::Deploy::Configuration.new(:stack => 'glassfish')
+ config = EY::Serverside::Deploy::Configuration.new('deploy_to' => deploy_dir, 'app' => 'app_name', 'stack' => 'glassfish')
deployer = TestRestartDeploy.new(test_servers, config, test_shell)
deployer.restart_with_maintenance_page
deployer.call_order.should include('enable_maintenance_page')

0 comments on commit 540b03b

Please sign in to comment.