From: Tomas Sedovic<tsedovic(a)redhat.com>
https://bugzilla.redhat.com/show_bug.cgi?id=721351
If all deployment's instances are either stopped or running, the UI will allow
the deployment to be deleted.
All the running instances will be stopped. Once that's done, the deployment
and all its associated instances will be deleted from the database.
Since we don't have a better way to handle arbitrary Condor callbacks, this
relies on dbomatic to set the instance to 'stopped' and the ImageObserver
'after_save' callback to trigger the deletion.
---
src/app/controllers/deployments_controller.rb | 10 +++++--
src/app/models/deployment.rb | 27 ++++++++++++++++++++
src/app/models/instance_observer.rb | 11 ++++++++
src/config/locales/en.yml | 4 +-
...30_add_scheduled_for_deletion_to_deployments.rb | 10 +++++++
src/features/deployment.feature | 11 +++++++-
src/features/step_definitions/instance_steps.rb | 6 ++++
src/spec/models/deployment_spec.rb | 19 ++++++++++++++
8 files changed, 92 insertions(+), 6 deletions(-)
create mode 100644
src/db/migrate/20110719140430_add_scheduled_for_deletion_to_deployments.rb
diff --git a/src/app/controllers/deployments_controller.rb
b/src/app/controllers/deployments_controller.rb
index c10ef01..ee3039e 100644
--- a/src/app/controllers/deployments_controller.rb
+++ b/src/app/controllers/deployments_controller.rb
@@ -166,9 +166,13 @@ class DeploymentsController< ApplicationController
destroyed = []
failed = []
Deployment.find(ids_list).each do |deployment|
- if check_privilege(Privilege::MODIFY, deployment)&&
deployment.destroyable?
- deployment.destroy
- destroyed<< deployment.name
+ if check_privilege(Privilege::MODIFY, deployment)
+ begin
+ deployment.stop_instances_and_destroy!
+ destroyed<< deployment.name
+ rescue
+ failed<< deployment.name
+ end
else
failed<< deployment.name
end
diff --git a/src/app/models/deployment.rb b/src/app/models/deployment.rb
index 49b440a..57b82aa 100644
--- a/src/app/models/deployment.rb
+++ b/src/app/models/deployment.rb
@@ -115,6 +115,33 @@ class Deployment< ActiveRecord::Base
instances.all? {|i| i.destroyable? }
end
+ def stop_instances_and_destroy!
+ if destroyable?
+ destroy
+ return
+ end
+
+ if instances.all? {|i| i.destroyable? or i.state == Instance::STATE_RUNNING}
+ # The deployment will be destroyed from an InstanceObserver callback once
+ # all instances are stopped.
+ self.scheduled_for_deletion = true
+ self.save!
+
+ # stop all deployment's instances
+ instances.each do |instance|
+ break unless instance.state == Instance::STATE_RUNNING
+
+ @task = instance.queue_action(instance.owner, 'stop')
+ unless @task
+ raise ActionError.new("stop cannot be performed on this instance.")
+ end
+ condormatic_instance_stop(@task)
+ end
+ else
+ raise ActionError.new 'all instances must be stopped or running'
+ end
+ end
+
def launch(user)
status = { :errors => {}, :successes => {} }
deployable_xml.assemblies.each do |assembly|
diff --git a/src/app/models/instance_observer.rb b/src/app/models/instance_observer.rb
index 4366b4e..c71454a 100644
--- a/src/app/models/instance_observer.rb
+++ b/src/app/models/instance_observer.rb
@@ -83,6 +83,17 @@ class InstanceObserver< ActiveRecord::Observer
end
end
+ def after_save(instance)
+ if instance.state_changed? and
+ instance.state == Instance::STATE_STOPPED and
+ instance.deployment and
+ instance.deployment.scheduled_for_deletion and
+ instance.deployment.destroyable?
+
+ instance.deployment.destroy
+ end
+ end
+
end
InstanceObserver.instance
diff --git a/src/config/locales/en.yml b/src/config/locales/en.yml
index 519fb9a..8e9b61e 100644
--- a/src/config/locales/en.yml
+++ b/src/config/locales/en.yml
@@ -90,8 +90,8 @@ en:
one: "The deployment %{list} could not be updated."
other: "The deployments %{list} could not be updated."
deleted:
- one: "The deployment %{list} was successfully deleted."
- other: "The deployments %{list} were succesfully deleted."
+ one: "The deployment %{list} was scheduled for deletion."
+ other: "The deployments %{list} were scheduled for deletion."
not_deleted:
one: "The deployment %{list} could not be deleted."
other: "The deployments %{list} could not be deleted."
diff --git a/src/db/migrate/20110719140430_add_scheduled_for_deletion_to_deployments.rb
b/src/db/migrate/20110719140430_add_scheduled_for_deletion_to_deployments.rb
new file mode 100644
index 0000000..3d98889
--- /dev/null
+++ b/src/db/migrate/20110719140430_add_scheduled_for_deletion_to_deployments.rb
@@ -0,0 +1,10 @@
+class AddScheduledForDeletionToDeployments< ActiveRecord::Migration
+ def self.up
+ add_column(:deployments, :scheduled_for_deletion,
+ :boolean, :null => false, :default => false)
+ end
+
+ def self.down
+ remove_column :deployments, :scheduled_for_deletion
+ end
+end
diff --git a/src/features/deployment.feature b/src/features/deployment.feature
index b862ab1..bdd9186 100644
--- a/src/features/deployment.feature
+++ b/src/features/deployment.feature
@@ -181,7 +181,16 @@ Feature: Manage Deployments
And I am on the pools page
When I follow "testdeployment"
And I press "Delete"
- Then I should see "The deployment testdeployment was successfully
deleted"
+ Then I should see "The deployment testdeployment was scheduled for
deletion"
+
+ Scenario: Delete a deployment with running instances
+ Given a deployment "mockdeployment" exists
+ And the deployment "mockdeployment" has an instance named
"myinstance"
+ And the instance "myinstance" is in the running state
+ And I am on the pools page
+ When I follow "mockdeployment"
+ And I press "Delete"
+ Then I should see "The deployment mockdeployment was scheduled for
deletion"
Scenario: Launch a deployment which is not launchable
Given a pool "mockpool" exists
diff --git a/src/features/step_definitions/instance_steps.rb
b/src/features/step_definitions/instance_steps.rb
index a181c62..9a9cb88 100644
--- a/src/features/step_definitions/instance_steps.rb
+++ b/src/features/step_definitions/instance_steps.rb
@@ -18,6 +18,12 @@ Given /^I am viewing the mock instance detail$/ do
visit instance_url(mock_instance)
end
+Given /^the instance "([^"]*)" is in the (\w*) state$/ do |instance,
state|
+ instance = Instance.find_by_name(instance)
+ instance.state = state
+ instance.save!
+end
+
When /^I am viewing the pending instance detail$/ do
visit instance_url(pending_instance)
end
diff --git a/src/spec/models/deployment_spec.rb b/src/spec/models/deployment_spec.rb
index e6c423c..13f922c 100644
--- a/src/spec/models/deployment_spec.rb
+++ b/src/spec/models/deployment_spec.rb
@@ -105,4 +105,23 @@ describe Deployment do
@deployment.pool.pool_family.provider_accounts.destroy_all
@deployment.check_assemblies_matches(user).should_not be_empty
end
+
+ it "should be able to stop running instances on deletion" do
+ @deployment.save!
+ inst1 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
+ inst2 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
+
+ @deployment.stop_instances_and_destroy!
+
+ # this emulates Condor stopping the actual instances
+ # and dbomatic reflecting the changes back to Conductor
+ inst1.state = Instance::STATE_STOPPED; inst1.save!
+ inst2.state = Instance::STATE_STOPPED; inst2.save!
+
+
+ # verify that the deployment and all its instances are deleted
+ lambda {Deployment.find((a)deployment.id)}.should
raise_error(ActiveRecord::RecordNotFound)
+ lambda {Instance.find(inst1.id)}.should raise_error(ActiveRecord::RecordNotFound)
+ lambda {Instance.find(inst2.id)}.should raise_error(ActiveRecord::RecordNotFound)
+ end
end