Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Use unique app_run_id decoupled from app version

[fixes #43103155]

Change-Id: I111f5035a497bc0940167c1eabf06cfd9e0e8708
  • Loading branch information...
commit e392f22127d4ae39f89c6f4a64dbb839ed801a7b 1 parent 61350e3
Kowshik Prakasam and Nate Clark authored
View
1  .pairs
@@ -4,6 +4,7 @@ pairs:
mb: Max Brunsfeld
bn: Bob Nugmanov
ds: David Sabeti
+ kp: Kowshik Prakasam
email:
prefix: pair
domain: pivotallabs.com
View
1  Gemfile
@@ -38,4 +38,5 @@ group :test do
gem "machinist", "~> 1.0.6"
gem "webmock"
gem "guard-rspec"
+ gem "timecop"
end
View
2  Gemfile.lock
@@ -155,6 +155,7 @@ GEM
rack (>= 1.0.0)
thor (0.15.4)
tilt (1.3.3)
+ timecop (0.5.9.1)
virtus (0.5.2)
backports (~> 2.6.1)
webmock (1.8.11)
@@ -189,6 +190,7 @@ DEPENDENCIES
sqlite3
stager-client (~> 0.0.02)!
steno (~> 1.0.0)
+ timecop
vcap-concurrency!
vcap_common (~> 2.0.8)!
webmock
View
3  lib/cloud_controller/models/app_start_event.rb
@@ -27,6 +27,7 @@ def validate
validates_presence :app_plan_name
validates_presence :app_memory
validates_presence :app_instance_count
+ validates_unique :app_run_id
end
def event_type
@@ -43,7 +44,7 @@ def self.create_from_app(app)
:space_name => app.space.name,
:app_guid => app.guid,
:app_name => app.name,
- :app_run_id => app.version,
+ :app_run_id => SecureRandom.uuid,
:app_plan_name => app.production ? "paid" : "free",
:app_memory => app.memory,
:app_instance_count => app.instances,
View
8 lib/cloud_controller/models/app_stop_event.rb
@@ -1,6 +1,8 @@
# Copyright (c) 2009-2012 VMware, Inc.
module VCAP::CloudController::Models
+ class MissingAppStartEvent < StandardError; end
+
class AppStopEvent < BillingEvent
export_attributes(
:timestamp,
@@ -20,6 +22,7 @@ def validate
validates_presence :space_name
validates_presence :app_guid
validates_presence :app_name
+ validates_unique :app_run_id
end
def event_type
@@ -28,6 +31,9 @@ def event_type
def self.create_from_app(app)
return unless app.space.organization.billing_enabled?
+ app_start_event = AppStartEvent.filter(:app_guid => app.guid).order(Sequel.desc(:timestamp)).first
+ raise MissingAppStartEvent.new(app.guid) if app_start_event.nil?
+
AppStopEvent.create(
:timestamp => Time.now,
:organization_guid => app.space.organization_guid,
@@ -36,7 +42,7 @@ def self.create_from_app(app)
:space_name => app.space.name,
:app_guid => app.guid,
:app_name => app.name,
- :app_run_id => app.version,
+ :app_run_id => app_start_event.app_run_id,
)
end
end
View
1  spec/blueprints.rb
@@ -133,6 +133,7 @@ module VCAP::CloudController::Models
space_name { Sham.name }
app_guid { Sham.guid }
app_name { Sham.name }
+ app_run_id { Sham.guid }
end
ServiceCreateEvent.blueprint do
View
93 spec/models/app_spec.rb
@@ -449,7 +449,7 @@ module VCAP::CloudController
describe "billing" do
context "app state changes" do
context "creating a stopped app" do
- it "should not call AppStartEvent.create_from_app" do
+ it "does not generate a start event or stop event" do
Models::AppStartEvent.should_not_receive(:create_from_app)
Models::AppStopEvent.should_not_receive(:create_from_app)
Models::App.make(:state => "STOPPED")
@@ -457,7 +457,7 @@ module VCAP::CloudController
end
context "creating a started app" do
- it "should not call AppStopEvent.create_from_app" do
+ it "does not generate a stop event" do
Models::AppStartEvent.should_receive(:create_from_app)
Models::AppStopEvent.should_not_receive(:create_from_app)
Models::App.make(:state => "STARTED")
@@ -465,7 +465,7 @@ module VCAP::CloudController
end
context "starting a stopped app" do
- it "should call AppStartEvent.create_from_app" do
+ it "generates a start event" do
app = Models::App.make(:state => "STOPPED")
Models::AppStartEvent.should_receive(:create_from_app).with(app)
Models::AppStopEvent.should_not_receive(:create_from_app)
@@ -474,7 +474,7 @@ module VCAP::CloudController
end
context "updating a stopped app" do
- it "should not call AppStartEvent.create_from_app" do
+ it "does not generate a start event or stop event" do
app = Models::App.make(:state => "STOPPED")
Models::AppStartEvent.should_not_receive(:create_from_app)
Models::AppStopEvent.should_not_receive(:create_from_app)
@@ -483,7 +483,7 @@ module VCAP::CloudController
end
context "stopping a started app" do
- it "should call AppStopEvent.create_from_app" do
+ it "does not generate a start event, but generates a stop event" do
app = Models::App.make(:state => "STARTED")
Models::AppStartEvent.should_not_receive(:create_from_app)
Models::AppStopEvent.should_receive(:create_from_app).with(app)
@@ -492,7 +492,7 @@ module VCAP::CloudController
end
context "updating a started app" do
- it "should not call AppStartEvent.create_from_app" do
+ it "does not generate a start or stop event" do
app = Models::App.make(:state => "STARTED")
Models::AppStartEvent.should_not_receive(:create_from_app)
Models::AppStopEvent.should_not_receive(:create_from_app)
@@ -501,7 +501,7 @@ module VCAP::CloudController
end
context "deleting a started app" do
- it "should call AppStopEvent.create_from_app" do
+ it "generates a start event" do
app = Models::App.make(:state => "STARTED")
VCAP::CloudController::DeaClient.stub(:stop)
Models::AppStopEvent.should_receive(:create_from_app).with(app)
@@ -510,7 +510,7 @@ module VCAP::CloudController
end
context "deleting a stopped app" do
- it "should not call AppStopEvent.create_from_app" do
+ it "does not generate a stop event" do
app = Models::App.make(:state => "STOPPED")
Models::AppStopEvent.should_not_receive(:create_from_app)
app.destroy
@@ -519,50 +519,71 @@ module VCAP::CloudController
end
context "footprint changes" do
+ let(:app) do
+ app = Models::App.make
+ app_org = app.space.organization
+ app_org.billing_enabled = true
+ app_org.save(:validate => false) # because we need to force enable billing
+ app
+ end
+
context "new app" do
- it "should not call AppStartEvent.create_from_app or AppStopEvent.create_from_app" do
+ it "does not generate a start event or stop event" do
Models::AppStartEvent.should_not_receive(:create_from_app)
Models::AppStopEvent.should_not_receive(:create_from_app)
- app = Models::App.make(:state => "STOPPED", :memory => 512)
+ app
end
end
context "no change in footprint" do
- it "should not call AppStartEvent.create_from_app or AppStopEvent.create_from_app" do
+ it "does not generate a start event or stop event" do
Models::AppStartEvent.should_not_receive(:create_from_app)
Models::AppStopEvent.should_not_receive(:create_from_app)
- app = Models::App.make
app.save
end
end
- context "change in memory" do
- it "should call AppStopEvent.create_from_app and AppStartEvent.create_from_app" do
- Models::AppStopEvent.should_receive(:create_from_app).once
- Models::AppStartEvent.should_receive(:create_from_app).twice
- app = Models::App.make(:state => "STARTED")
- app.memory = 512
+ context "started app" do
+ before do
+ app.state = "STARTED"
app.save
end
- end
- context "change in production flag" do
- it "should call AppStopEvent.create_from_app and AppStartEvent.create_from_app" do
- Models::AppStopEvent.should_receive(:create_from_app).once
- Models::AppStartEvent.should_receive(:create_from_app).twice
- app = Models::App.make(:state => "STARTED")
- app.production = true
- app.save
+ def self.it_emits_app_start_and_stop_events(&block)
+ it "generates a stop event for the old run_id, and start events for the new run_id" do
+ original_start_event = Models::AppStartEvent.filter(:app_guid => app.guid).all[0]
+
+ yield(app)
+
+ app.save
+
+ Models::AppStopEvent.filter(
+ :app_guid => app.guid,
+ :app_run_id => original_start_event.app_run_id
+ ).count.should == 1
+
+ Models::AppStartEvent.filter(
+ :app_guid => app.guid
+ ).all.last.app_run_id.should_not == original_start_event.app_run_id
+ end
end
- end
- context "change in instances" do
- it "should call AppStopEvent.create_from_app and AppStartEvent.create_from_app" do
- Models::AppStopEvent.should_receive(:create_from_app).once
- Models::AppStartEvent.should_receive(:create_from_app).twice
- app = Models::App.make(:state => "STARTED")
- app.instances = 5
- app.save
+ context "change in memory" do
+ it_emits_app_start_and_stop_events do |app|
+ app.memory = 512
+ end
+ end
+
+ context "change in production flag" do
+ it_emits_app_start_and_stop_events do |app|
+ app.production = true
+ end
+ end
+
+ context "change in instances" do
+ it_emits_app_start_and_stop_events do |app|
+ app.instances = 5
+ end
end
end
end
@@ -582,7 +603,7 @@ module VCAP::CloudController
it "should raise error when quota is exceeded" do
org = Models::Organization.make(:quota_definition => paid_quota)
space = Models::Space.make(:organization => org)
- expect do
+ expect do
Models::App.make(:space => space,
:production => true,
:memory => 65,
@@ -594,7 +615,7 @@ module VCAP::CloudController
it "should not raise error when quota is not exceeded" do
org = Models::Organization.make(:quota_definition => paid_quota)
space = Models::Space.make(:organization => org)
- expect do
+ expect do
Models::App.make(:space => space,
:production => true,
:memory => 64,
View
6 spec/models/app_start_event_spec.rb
@@ -23,7 +23,11 @@ module VCAP::CloudController
:organization_guid,
:organization_name,
],
- :disable_examples => :deserialization
+ :unique_attributes => [
+ :app_run_id
+ ],
+ :disable_examples => :deserialization,
+ :skip_database_constraints => true
}
describe "create_from_app" do
View
25 spec/models/app_stop_event_spec.rb
@@ -19,7 +19,11 @@ module VCAP::CloudController
:organization_guid,
:organization_name,
],
- :disable_examples => :deserialization
+ :unique_attributes => [
+ :app_run_id
+ ],
+ :disable_examples => :deserialization,
+ :skip_database_constraints => true
}
describe "create_from_app" do
@@ -34,12 +38,23 @@ module VCAP::CloudController
end
context "on an org with billing enabled" do
- it "should create an app stop event" do
- Models::AppStopEvent.should_receive(:create)
- app = Models::App.make
+ let(:app) { Models::App.make }
+
+ before do
app.space.organization.billing_enabled = true
app.space.organization.save(:validate => false)
- Models::AppStopEvent.create_from_app(app)
+ end
+
+
+ it "should create an app stop event using the run id from the latest start event" do
+ Timecop.freeze(Time.now - 3600) { Models::AppStartEvent.create_from_app(app) }
+ start_event_latest = Models::AppStartEvent.create_from_app(app)
+ stop_event = Models::AppStopEvent.create_from_app(app)
+ stop_event.app_run_id.should == start_event_latest.app_run_id
+ end
+
+ it "should raise an exception if a corresponding AppStartEvent is not found" do
+ expect { Models::AppStopEvent.create_from_app(app) }.to raise_error( VCAP::CloudController::Models::MissingAppStartEvent )
end
end
end
View
10 spec/models/helpers/unique_attributes.rb
@@ -94,10 +94,12 @@ module VCAP::CloudController::ModelSpecHelper
}.should raise_error Sequel::ValidationFailed, /#{sequel_exception_match}/
end
- it "should fail due to database integrity checks" do
- lambda {
- described_class.new(dup_opts).save(:validate => false)
- }.should raise_error Sequel::DatabaseError, /#{db_exception_match}/
+ unless opts[:skip_database_constraints]
+ it "should fail due to database integrity checks" do
+ lambda {
+ described_class.new(dup_opts).save(:validate => false)
+ }.should raise_error Sequel::DatabaseError, /#{db_exception_match}/
+ end
end
end
end
View
2  spec/models/organization_spec.rb
@@ -60,7 +60,7 @@ module VCAP::CloudController
Models::Organization.make.billing_enabled.should == false
end
- context "emabling billing" do
+ context "enabling billing" do
let (:org) do
o = Models::Organization.make
2.times do
View
1  spec/spec_helper.rb
@@ -7,6 +7,7 @@
require "machinist/sequel"
require "rack/test"
+require "timecop"
require "steno"
require "cloud_controller"
Please sign in to comment.
Something went wrong with that request. Please try again.