Skip to content

Commit 14a9c81

Browse files
authored
Remote kills are absolute (#112)
* Remote kills are absolute * Add ADR about absolute remote kills
1 parent e4f6b6b commit 14a9c81

8 files changed

Lines changed: 69 additions & 74 deletions

File tree

app/models/app_remote_kill.rb

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,14 @@ class AppRemoteKill < ActiveRecord::Base
1313
validate :fixed_version_must_be_greater_than_first_bad_version
1414
validate :must_not_overlap_existing
1515

16-
scope :affecting, ->(app_build, override: false, overridden_at: nil) do
16+
scope :affecting, ->(app_build) do
1717
where(
1818
arel_table[:app_id].eq(app_build.app_id)
1919
.and(arel_table[:first_bad_version].lteq(app_build.version))
2020
.and(arel_fixed_version_is_null_or_greater_than(app_build.version))
21-
.and(
22-
Arel::Nodes::Grouping.new(
23-
arel_is_false(override)
24-
.or(arel_table[:updated_at].gt(overridden_at))
25-
)
26-
)
2721
)
2822
end
2923

30-
class << self
31-
private
32-
33-
def arel_is_false(value_or_arel)
34-
Arel::Nodes::Grouping.new(Arel::Nodes::False.new).eq(value_or_arel)
35-
end
36-
end
37-
3824
scope :overlapping, ->(other) do
3925
where(app_id: other.app_id, split_id: other.split_id)
4026
.where.not(id: other.id)

app/models/assignment.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Assignment < ActiveRecord::Base
4747

4848
scope :excluding_remote_kills_for, ->(app_build) do
4949
joins(:split).where(
50-
Split.arel_excluding_remote_kills_for(app_build, override: arel_table[:force], overridden_at: arel_table[:updated_at])
50+
Split.arel_excluding_remote_kills_for(app_build)
5151
)
5252
end
5353

app/models/split.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ def self.arel_excluding_incomplete_features_for(app_build)
7171
.readonly
7272
end
7373

74-
def self.arel_excluding_remote_kills_for(app_build, override: false, overridden_at: nil)
75-
AppRemoteKill.select(1).affecting(app_build, override: override, overridden_at: overridden_at).arel
74+
def self.arel_excluding_remote_kills_for(app_build)
75+
AppRemoteKill.select(1).affecting(app_build).arel
7676
.where(AppRemoteKill.arel_table[:split_id].eq(arel_table[:id])).exists.not
7777
end
7878

doc/arch/adr-001.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# ADR 001: AppRemoteKill Will Loosely Couple to Override Variant
22

3+
## Status
4+
5+
Accepted
6+
37
## Context
48

59
Splits are accessible to any app in the same ecosystem, but not always
@@ -30,10 +34,6 @@ At this time, we will still strongly couple app_remote_kills to split
3034
existence, however. This may change in the future if it proves to cause
3135
operational issues
3236

33-
## Status
34-
35-
Accepted
36-
3737
## Consequences
3838

3939
The only remaining way to ship an AppRemoteKill that doesn't apply

doc/arch/adr-002.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# ADR 002: Remote kills are absolute
2+
3+
## Status
4+
5+
Accepted
6+
7+
## Context
8+
9+
When rapidly iterating on API for prerelease features, we will commonly
10+
want to make breaking API changes without revving the API endpoints and
11+
maintaining the previous versions of the endpoints indefinitely for
12+
versions of the app that will never be seen by customers.
13+
14+
In that context, remote kills stand to be an incredibly useful tool.
15+
When you decide to make a breaking API change, you simply create/update
16+
a remote kill for the app from version `0` through the first client
17+
version conforming to the new contract. Then, instead of getting crash
18+
bugs on dev builds, the feature simply turns itself off, and testers
19+
will have to upgrade in order to see the feature again.
20+
21+
This stands to eliminate false bug reports of crash bugs from
22+
internal/alpha testers who simply need to upgrade.
23+
24+
But if we want to rely on this as a safety mechanism, we need to make
25+
sure that chrome extension assignment overrides don't have the
26+
opportunity to win-out over a remote kill, because they are too blunt of
27+
a tool. As an alpha tester, you might see that the feature went away and
28+
perform another override in the Chrome extension. If that happens,
29+
you'd be back in crash bug land.
30+
31+
## Decision
32+
33+
On TestTrack server, remote kills will be absolute and won't be
34+
overridden by force assignments regardless of the relative recency of
35+
the override and the remote kill.
36+
37+
## Consequences
38+
39+
As a result, it may be somewhat more difficult for app developers to
40+
"see around" an open-ended remote kill that was dropped because of a bug
41+
as they work on the bugfix.
42+
43+
A hypothetical solution is to bump the app version on their branch and
44+
then mark the remote_kill's fixed_version as that version, so that as
45+
they develop the fix, their local builds will allow them to see their
46+
work. Then merging and releasing the branch will cause the fix to be
47+
available to consumers of that build.
48+
49+
This might introduce version number linearization problems unless we
50+
hold all other merges until the bugfix is released, which may or may not
51+
be an issue the dev team can stomach.
52+
53+
As an alternative, we could make the forthcoming fake TestTrack server
54+
being developed into the Rails client less draconian, and allow
55+
assignment overrides to take precedence over remote kills in that
56+
context only. Then the remote_kill fixed_version could be stamped once
57+
the release version of the bugfix is known, after the developer has
58+
already locally tested the fix.

spec/models/app_remote_kill_spec.rb

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -188,26 +188,6 @@
188188
expect(described_class.affecting(app_build)).to include(existing_remote_kill)
189189
end
190190

191-
it "doesn't return remote kills with newer overrides" do
192-
app_build = app.define_build(version: "1.0", built_at: nil)
193-
194-
expect(described_class.affecting(app_build, override: true, overridden_at: 1.hour.from_now)).not_to include(existing_remote_kill)
195-
end
196-
197-
it "does return remote kills with older overrides" do
198-
app_build = app.define_build(version: "1.0", built_at: nil)
199-
200-
expect(described_class.affecting(app_build, override: true, overridden_at: 1.hour.ago)).to include(existing_remote_kill)
201-
end
202-
203-
it "accepts arel expressions for override and overridden_at and behaves the same" do
204-
app_build = app.define_build(version: "1.0", built_at: nil)
205-
override = Arel::Nodes::True.new
206-
overridden_at = Arel::Nodes.build_quoted(1.hour.ago)
207-
208-
expect(described_class.affecting(app_build, override: override, overridden_at: overridden_at)).to include(existing_remote_kill)
209-
end
210-
211191
it "doesn't return remote kills starting after app version" do
212192
app_build = app.define_build(version: "0.9", built_at: nil)
213193

spec/models/assignment_spec.rb

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -270,33 +270,13 @@
270270
expect(described_class.excluding_remote_kills_for(app_build)).to include(assignment)
271271
end
272272

273-
it "doesn't return assignments for overlapping remote kills" do
274-
split = FactoryBot.create(:split)
275-
assignment = FactoryBot.create(:assignment, split: split)
276-
app = FactoryBot.create(:app)
277-
FactoryBot.create(:app_remote_kill, split: split, app: app, first_bad_version: "0.9", fixed_version: "1.2")
278-
app_build = app.define_build(built_at: Time.zone.now, version: "1.0.0")
279-
280-
expect(described_class.excluding_remote_kills_for(app_build)).not_to include(assignment)
281-
end
282-
283-
it "returns assignments for overlapping older remote kills when forced" do
273+
it "doesn't return assignments for overlapping remote kills, even if forced and more recent" do
284274
split = FactoryBot.create(:split)
285275
app = FactoryBot.create(:app)
286276
FactoryBot.create(:app_remote_kill, split: split, app: app, first_bad_version: "0.9", fixed_version: "1.2")
287277
assignment = FactoryBot.create(:assignment, split: split, force: true)
288278
app_build = app.define_build(built_at: Time.zone.now, version: "1.0.0")
289279

290-
expect(described_class.excluding_remote_kills_for(app_build)).to include(assignment)
291-
end
292-
293-
it "doesn't return forced assignments older than a remote kill" do
294-
split = FactoryBot.create(:split)
295-
assignment = FactoryBot.create(:assignment, split: split, force: true)
296-
app = FactoryBot.create(:app)
297-
FactoryBot.create(:app_remote_kill, split: split, app: app, first_bad_version: "0.9", fixed_version: "1.2")
298-
app_build = app.define_build(built_at: Time.zone.now, version: "1.0.0")
299-
300280
expect(described_class.excluding_remote_kills_for(app_build)).not_to include(assignment)
301281
end
302282

spec/models/split_spec.rb

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -361,21 +361,12 @@
361361
expect(Split.where(Split.arel_excluding_remote_kills_for(app_build))).not_to include(split)
362362
end
363363

364-
it "is backed by AppRemoteKill.affecting with default override args" do
364+
it "is backed by AppRemoteKill.affecting" do
365365
allow(AppRemoteKill).to receive(:affecting).and_call_original
366366

367367
Split.arel_excluding_remote_kills_for(app_build)
368368

369-
expect(AppRemoteKill).to have_received(:affecting).with(app_build, override: false, overridden_at: nil)
370-
end
371-
372-
it "is backed by AppRemoteKill.affecting with explicit override args" do
373-
allow(AppRemoteKill).to receive(:affecting).and_call_original
374-
t = Time.zone.now
375-
376-
Split.arel_excluding_remote_kills_for(app_build, override: true, overridden_at: t)
377-
378-
expect(AppRemoteKill).to have_received(:affecting).with(app_build, override: true, overridden_at: t)
369+
expect(AppRemoteKill).to have_received(:affecting).with(app_build)
379370
end
380371
end
381372

0 commit comments

Comments
 (0)