Commit 9b34f6cb authored by Andy Soiron's avatar Andy Soiron

Merge branch 'mk/fix-separate-verification-state-table-method' into 'master'

Geo: Fix undefined separate_verification_state_table?

See merge request gitlab-org/gitlab!78293
parents 55e27263 17bb757f
...@@ -53,12 +53,6 @@ module Geo ...@@ -53,12 +53,6 @@ module Geo
.primary_key_in(range) .primary_key_in(range)
.pluck_primary_key .pluck_primary_key
end end
# @return whether primary checksum data is stored in a table separate
# from the model table
def separate_verification_state_table?
verification_state_table_name != table_name
end
end end
end end
end end
...@@ -200,6 +200,12 @@ module Geo ...@@ -200,6 +200,12 @@ module Geo
verification_state_table_class.arel_table verification_state_table_class.arel_table
end end
# @return whether primary checksum data is stored in a table separate
# from the model table
def separate_verification_state_table?
verification_state_table_name != table_name
end
def verification_timed_out_batch_query def verification_timed_out_batch_query
return verification_timed_out unless separate_verification_state_table? return verification_timed_out unless separate_verification_state_table?
......
...@@ -8,446 +8,460 @@ RSpec.describe Geo::VerificationState do ...@@ -8,446 +8,460 @@ RSpec.describe Geo::VerificationState do
let_it_be(:primary_node) { create(:geo_node, :primary) } let_it_be(:primary_node) { create(:geo_node, :primary) }
let_it_be(:secondary_node) { create(:geo_node) } let_it_be(:secondary_node) { create(:geo_node) }
context 'when verification state is stored in the model table' do context 'for Model classes' do
before(:all) do context 'when verification state is stored in the model table' do
create_dummy_model_table before(:all) do
end create_dummy_model_table
after(:all) do
drop_dummy_model_table
end
before do
stub_dummy_replicator_class
stub_dummy_model_class
end
subject { DummyModel.new }
context 'state machine' do
context 'when failed' do
before do
subject.verification_started
subject.verification_failed_with_message!('foo')
end
context 'and transitioning to pending' do
it 'marks verification as pending' do
subject.verification_pending!
expect(subject.reload.verification_pending?).to be_truthy
end
it 'does not clear retry attributes' do
subject.verification_pending!
expect(subject.reload).to have_attributes(
verification_state: DummyModel.verification_state_value(:verification_pending),
verification_retry_count: 1,
verification_retry_at: be_present
)
end
end
end end
end
describe '.verification_pending_batch' do after(:all) do
# Insert 2 records for a total of 3 with subject drop_dummy_model_table
let!(:other_pending_records) do
DummyModel.insert_all([
{ verification_state: pending_value, verified_at: 7.days.ago },
{ verification_state: pending_value, verified_at: 6.days.ago }
], returning: [:id])
end end
let(:pending_value) { DummyModel.verification_state_value(:verification_pending) }
let(:other_pending_ids) { other_pending_records.map { |result| result['id'] } }
before do before do
subject.save! stub_dummy_replicator_class
end stub_dummy_model_class
it 'returns IDs of rows pending verification' do
expect(subject.class.verification_pending_batch(batch_size: 3)).to include(subject.id)
end end
it 'marks verification as started' do subject { DummyModel.new }
subject.class.verification_pending_batch(batch_size: 3)
expect(subject.reload.verification_started?).to be_truthy context 'state machine' do
expect(subject.verification_started_at).to be_present context 'when failed' do
end before do
subject.verification_started
it 'limits with batch_size and orders records by verified_at with NULLs first' do subject.verification_failed_with_message!('foo')
expected = [subject.id, other_pending_ids.first] end
# `match_array` instead of `eq` because the UPDATE query does not
# guarantee that results are returned in the same order as the subquery
# used to SELECT the correct batch.
expect(subject.class.verification_pending_batch(batch_size: 2)).to match_array(expected)
end
context 'other verification states' do
it 'does not include them' do
subject.verification_started!
expect(subject.class.verification_pending_batch(batch_size: 3)).not_to include(subject.id) context 'and transitioning to pending' do
it 'marks verification as pending' do
subject.verification_pending!
subject.verification_succeeded_with_checksum!('foo', Time.current) expect(subject.reload.verification_pending?).to be_truthy
end
expect(subject.class.verification_pending_batch(batch_size: 3)).not_to include(subject.id)
subject.verification_started it 'does not clear retry attributes' do
subject.verification_failed_with_message!('foo') subject.verification_pending!
expect(subject.class.verification_pending_batch(batch_size: 3)).not_to include(subject.id) expect(subject.reload).to have_attributes(
verification_state: DummyModel.verification_state_value(:verification_pending),
verification_retry_count: 1,
verification_retry_at: be_present
)
end
end
end end
end end
end
describe '.verification_failed_batch' do describe '.verification_pending_batch' do
# Insert 2 records for a total of 3 with subject # Insert 2 records for a total of 3 with subject
let!(:other_failed_records) do let!(:other_pending_records) do
DummyModel.insert_all([ DummyModel.insert_all([
{ verification_state: failed_value, verification_retry_at: 7.days.ago }, { verification_state: pending_value, verified_at: 7.days.ago },
{ verification_state: failed_value, verification_retry_at: 6.days.ago } { verification_state: pending_value, verified_at: 6.days.ago }
], returning: [:id]) ], returning: [:id])
end end
let(:failed_value) { DummyModel.verification_state_value(:verification_failed) } let(:pending_value) { DummyModel.verification_state_value(:verification_pending) }
let(:other_failed_ids) { other_failed_records.map { |result| result['id'] } } let(:other_pending_ids) { other_pending_records.map { |result| result['id'] } }
before do
subject.verification_started
subject.verification_failed_with_message!('foo')
end
context 'with a failed record with retry due' do
before do before do
subject.update!(verification_retry_at: 1.minute.ago) subject.save!
end end
it 'returns IDs of rows pending verification' do it 'returns IDs of rows pending verification' do
expect(subject.class.verification_failed_batch(batch_size: 3)).to include(subject.id) expect(subject.class.verification_pending_batch(batch_size: 3)).to include(subject.id)
end end
it 'marks verification as started' do it 'marks verification as started' do
subject.class.verification_failed_batch(batch_size: 3) subject.class.verification_pending_batch(batch_size: 3)
expect(subject.reload.verification_started?).to be_truthy expect(subject.reload.verification_started?).to be_truthy
expect(subject.verification_started_at).to be_present expect(subject.verification_started_at).to be_present
end end
it 'limits with batch_size and orders records by verification_retry_at with NULLs first' do it 'limits with batch_size and orders records by verified_at with NULLs first' do
expected = other_failed_ids expected = [subject.id, other_pending_ids.first]
# `match_array` instead of `eq` because the UPDATE query does not # `match_array` instead of `eq` because the UPDATE query does not
# guarantee that results are returned in the same order as the subquery # guarantee that results are returned in the same order as the subquery
# used to SELECT the correct batch. # used to SELECT the correct batch.
expect(subject.class.verification_failed_batch(batch_size: 2)).to match_array(expected) expect(subject.class.verification_pending_batch(batch_size: 2)).to match_array(expected)
end end
context 'other verification states' do context 'other verification states' do
it 'does not include them' do it 'does not include them' do
subject.verification_started! subject.verification_started!
expect(subject.class.verification_failed_batch(batch_size: 5)).not_to include(subject.id) expect(subject.class.verification_pending_batch(batch_size: 3)).not_to include(subject.id)
subject.verification_succeeded_with_checksum!('foo', Time.current) subject.verification_succeeded_with_checksum!('foo', Time.current)
expect(subject.class.verification_failed_batch(batch_size: 5)).not_to include(subject.id) expect(subject.class.verification_pending_batch(batch_size: 3)).not_to include(subject.id)
subject.verification_pending! subject.verification_started
subject.verification_failed_with_message!('foo')
expect(subject.class.verification_failed_batch(batch_size: 5)).not_to include(subject.id) expect(subject.class.verification_pending_batch(batch_size: 3)).not_to include(subject.id)
end end
end end
end end
context 'when verification_retry_at is in the future' do describe '.verification_failed_batch' do
it 'does not return the row' do # Insert 2 records for a total of 3 with subject
subject.update!(verification_retry_at: 1.minute.from_now) let!(:other_failed_records) do
DummyModel.insert_all([
{ verification_state: failed_value, verification_retry_at: 7.days.ago },
{ verification_state: failed_value, verification_retry_at: 6.days.ago }
], returning: [:id])
end
expect(subject.class.verification_failed_batch(batch_size: 3)).not_to include(subject.id) let(:failed_value) { DummyModel.verification_state_value(:verification_failed) }
let(:other_failed_ids) { other_failed_records.map { |result| result['id'] } }
before do
subject.verification_started
subject.verification_failed_with_message!('foo')
end end
end
end
describe '.needs_verification' do context 'with a failed record with retry due' do
it 'includes verification_pending' do before do
subject.save! subject.update!(verification_retry_at: 1.minute.ago)
end
expect(subject.class.needs_verification).to include(subject) it 'returns IDs of rows pending verification' do
end expect(subject.class.verification_failed_batch(batch_size: 3)).to include(subject.id)
end
it 'includes verification_failed and verification_retry_due' do it 'marks verification as started' do
subject.verification_started subject.class.verification_failed_batch(batch_size: 3)
subject.verification_failed_with_message!('foo')
subject.update!(verification_retry_at: 1.minute.ago)
expect(subject.class.needs_verification).to include(subject) expect(subject.reload.verification_started?).to be_truthy
end expect(subject.verification_started_at).to be_present
end
it 'excludes verification_failed with future verification_retry_at' do it 'limits with batch_size and orders records by verification_retry_at with NULLs first' do
subject.verification_started expected = other_failed_ids
subject.verification_failed_with_message!('foo')
subject.update!(verification_retry_at: 1.minute.from_now)
expect(subject.class.needs_verification).not_to include(subject) # `match_array` instead of `eq` because the UPDATE query does not
end # guarantee that results are returned in the same order as the subquery
end # used to SELECT the correct batch.
expect(subject.class.verification_failed_batch(batch_size: 2)).to match_array(expected)
end
describe '.needs_reverification' do context 'other verification states' do
before do it 'does not include them' do
stub_current_geo_node(primary_node) subject.verification_started!
end
let(:pending_value) { DummyModel.verification_state_value(:verification_pending) } expect(subject.class.verification_failed_batch(batch_size: 5)).not_to include(subject.id)
let(:failed_value) { DummyModel.verification_state_value(:verification_failed) }
let(:succeeded_value) { DummyModel.verification_state_value(:verification_succeeded) }
it 'includes verification_succeeded with expired checksum' do subject.verification_succeeded_with_checksum!('foo', Time.current)
DummyModel.insert_all([
{ verification_state: succeeded_value, verified_at: 15.days.ago }
])
expect(subject.class.needs_reverification.count).to eq 1 expect(subject.class.verification_failed_batch(batch_size: 5)).not_to include(subject.id)
end
it 'excludes non-success verification states and fresh checksums' do subject.verification_pending!
DummyModel.insert_all([
{ verification_state: pending_value, verified_at: 7.days.ago },
{ verification_state: failed_value, verified_at: 6.days.ago },
{ verification_state: succeeded_value, verified_at: 3.days.ago }
])
expect(subject.class.needs_reverification.count).to eq 0 expect(subject.class.verification_failed_batch(batch_size: 5)).not_to include(subject.id)
end end
end end
end
context 'when verification_retry_at is in the future' do
it 'does not return the row' do
subject.update!(verification_retry_at: 1.minute.from_now)
describe '.reverify_batch' do expect(subject.class.verification_failed_batch(batch_size: 3)).not_to include(subject.id)
let!(:other_verified_records) do end
DummyModel.insert_all([ end
{ verification_state: succeeded_value, verified_at: 3.days.ago },
{ verification_state: succeeded_value, verified_at: 4.days.ago }
])
end end
let(:succeeded_value) { DummyModel.verification_state_value(:verification_succeeded) } describe '.needs_verification' do
it 'includes verification_pending' do
subject.save!
before do expect(subject.class.needs_verification).to include(subject)
stub_current_geo_node(primary_node) end
subject.verification_started it 'includes verification_failed and verification_retry_due' do
subject.verification_started
subject.verification_failed_with_message!('foo')
subject.update!(verification_retry_at: 1.minute.ago)
subject.verification_succeeded_with_checksum!('foo', Time.current) expect(subject.class.needs_verification).to include(subject)
end
subject.update!(verified_at: 15.days.ago) it 'excludes verification_failed with future verification_retry_at' do
end subject.verification_started
subject.verification_failed_with_message!('foo')
subject.update!(verification_retry_at: 1.minute.from_now)
it 'sets pending status to records with outdated verification' do expect(subject.class.needs_verification).not_to include(subject)
expect do end
expect(subject.class.reverify_batch(batch_size: 100)).to eq 1
end.to change { subject.reload.verification_pending? }.to be_truthy
end end
it 'limits the update with batch_size' do describe '.needs_reverification' do
DummyModel.update_all(verified_at: 15.days.ago) before do
stub_current_geo_node(primary_node)
end
expect(subject.class.reverify_batch(batch_size: 2)).to eq 2 let(:pending_value) { DummyModel.verification_state_value(:verification_pending) }
expect(DummyModel.verification_pending.count).to eq 2 let(:failed_value) { DummyModel.verification_state_value(:verification_failed) }
end let(:succeeded_value) { DummyModel.verification_state_value(:verification_succeeded) }
end
describe '.fail_verification_timeouts' do it 'includes verification_succeeded with expired checksum' do
before do DummyModel.insert_all([
subject.verification_started! { verification_state: succeeded_value, verified_at: 15.days.ago }
end ])
context 'when verification has not timed out for a record' do expect(subject.class.needs_reverification.count).to eq 1
it 'does not update verification state' do end
subject.update!(verification_started_at: (described_class::VERIFICATION_TIMEOUT - 1.minute).ago)
DummyModel.fail_verification_timeouts it 'excludes non-success verification states and fresh checksums' do
DummyModel.insert_all([
{ verification_state: pending_value, verified_at: 7.days.ago },
{ verification_state: failed_value, verified_at: 6.days.ago },
{ verification_state: succeeded_value, verified_at: 3.days.ago }
])
expect(subject.reload.verification_started?).to be_truthy expect(subject.class.needs_reverification.count).to eq 0
end end
end end
context 'when verification has timed out for a record' do describe '.reverify_batch' do
it 'sets verification state to failed' do let!(:other_verified_records) do
subject.update!(verification_started_at: (described_class::VERIFICATION_TIMEOUT + 1.minute).ago) DummyModel.insert_all([
{ verification_state: succeeded_value, verified_at: 3.days.ago },
{ verification_state: succeeded_value, verified_at: 4.days.ago }
])
end
DummyModel.fail_verification_timeouts let(:succeeded_value) { DummyModel.verification_state_value(:verification_succeeded) }
expect(subject.reload.verification_failed?).to be_truthy before do
stub_current_geo_node(primary_node)
subject.verification_started
subject.verification_succeeded_with_checksum!('foo', Time.current)
subject.update!(verified_at: 15.days.ago)
end end
end
end
describe '#track_checksum_attempt!', :aggregate_failures do it 'sets pending status to records with outdated verification' do
context 'when verification was not yet started' do
it 'starts verification' do
expect do expect do
subject.track_checksum_attempt! do expect(subject.class.reverify_batch(batch_size: 100)).to eq 1
'a_checksum_value' end.to change { subject.reload.verification_pending? }.to be_truthy
end
end.to change { subject.verification_started_at }.from(nil)
end end
it 'sets verification_succeeded' do it 'limits the update with batch_size' do
expect do DummyModel.update_all(verified_at: 15.days.ago)
subject.track_checksum_attempt! do
'a_checksum_value' expect(subject.class.reverify_batch(batch_size: 2)).to eq 2
end expect(DummyModel.verification_pending.count).to eq 2
end.to change { subject.verification_succeeded? }.from(false).to(true)
end end
end end
context 'when verification was started' do describe '.fail_verification_timeouts' do
it 'does not update verification_started_at' do before do
subject.verification_started! subject.verification_started!
expected = subject.verification_started_at end
subject.track_checksum_attempt! do context 'when verification has not timed out for a record' do
'a_checksum_value' it 'does not update verification state' do
subject.update!(verification_started_at: (described_class::VERIFICATION_TIMEOUT - 1.minute).ago)
DummyModel.fail_verification_timeouts
expect(subject.reload.verification_started?).to be_truthy
end end
end
expect(subject.verification_started_at).to be_within(1.second).of(expected) context 'when verification has timed out for a record' do
it 'sets verification state to failed' do
subject.update!(verification_started_at: (described_class::VERIFICATION_TIMEOUT + 1.minute).ago)
DummyModel.fail_verification_timeouts
expect(subject.reload.verification_failed?).to be_truthy
end
end end
end end
it 'yields to the checksum calculation' do describe '#track_checksum_attempt!', :aggregate_failures do
expect do |probe| context 'when verification was not yet started' do
subject.track_checksum_attempt!(&probe) it 'starts verification' do
end.to yield_with_no_args expect do
end subject.track_checksum_attempt! do
'a_checksum_value'
end
end.to change { subject.verification_started_at }.from(nil)
end
context 'when an error occurs while yielding' do it 'sets verification_succeeded' do
context 'when the record was failed' do expect do
it 'sets verification_failed and increments verification_retry_count' do subject.track_checksum_attempt! do
subject.verification_failed_with_message!('foo') 'a_checksum_value'
end
end.to change { subject.verification_succeeded? }.from(false).to(true)
end
end
context 'when verification was started' do
it 'does not update verification_started_at' do
subject.verification_started!
expected = subject.verification_started_at
subject.track_checksum_attempt! do subject.track_checksum_attempt! do
raise 'an error' 'a_checksum_value'
end end
expect(subject.reload.verification_failed?).to be_truthy expect(subject.verification_started_at).to be_within(1.second).of(expected)
expect(subject.verification_retry_count).to eq(2)
end end
end end
end
context 'when the yielded block returns nil' do it 'yields to the checksum calculation' do
context 'when the record was pending' do expect do |probe|
it 'sets verification_failed and sets verification_retry_count to 1' do subject.track_checksum_attempt!(&probe)
subject.track_checksum_attempt! { nil } end.to yield_with_no_args
end
expect(subject.reload.verification_failed?).to be_truthy context 'when an error occurs while yielding' do
expect(subject.verification_retry_count).to eq(1) context 'when the record was failed' do
it 'sets verification_failed and increments verification_retry_count' do
subject.verification_failed_with_message!('foo')
subject.track_checksum_attempt! do
raise 'an error'
end
expect(subject.reload.verification_failed?).to be_truthy
expect(subject.verification_retry_count).to eq(2)
end
end end
end end
context 'when the record was failed' do context 'when the yielded block returns nil' do
it 'sets verification_failed and increments verification_retry_count' do context 'when the record was pending' do
subject.verification_failed_with_message!('foo') it 'sets verification_failed and sets verification_retry_count to 1' do
subject.track_checksum_attempt! { nil }
subject.track_checksum_attempt! { nil } expect(subject.reload.verification_failed?).to be_truthy
expect(subject.verification_retry_count).to eq(1)
end
end
expect(subject.reload.verification_failed?).to be_truthy context 'when the record was failed' do
expect(subject.verification_retry_count).to eq(2) it 'sets verification_failed and increments verification_retry_count' do
subject.verification_failed_with_message!('foo')
subject.track_checksum_attempt! { nil }
expect(subject.reload.verification_failed?).to be_truthy
expect(subject.verification_retry_count).to eq(2)
end
end end
end end
end end
end
describe '#verification_succeeded_with_checksum!' do describe '#verification_succeeded_with_checksum!' do
before do before do
subject.verification_started! subject.verification_started!
end end
context 'when the resource was updated during checksum calculation' do context 'when the resource was updated during checksum calculation' do
let(:calculation_started_at) { subject.verification_started_at - 1.second } let(:calculation_started_at) { subject.verification_started_at - 1.second }
it 'sets state to pending' do it 'sets state to pending' do
subject.verification_succeeded_with_checksum!('abc123', calculation_started_at) subject.verification_succeeded_with_checksum!('abc123', calculation_started_at)
expect(subject.reload.verification_pending?).to be_truthy expect(subject.reload.verification_pending?).to be_truthy
end
end end
end
context 'when the resource was not updated during checksum calculation' do context 'when the resource was not updated during checksum calculation' do
let(:calculation_started_at) { subject.verification_started_at + 1.second } let(:calculation_started_at) { subject.verification_started_at + 1.second }
it 'saves the checksum' do it 'saves the checksum' do
subject.verification_succeeded_with_checksum!('abc123', calculation_started_at) subject.verification_succeeded_with_checksum!('abc123', calculation_started_at)
expect(subject.reload.verification_succeeded?).to be_truthy expect(subject.reload.verification_succeeded?).to be_truthy
expect(subject.reload.verification_checksum).to eq('abc123') expect(subject.reload.verification_checksum).to eq('abc123')
expect(subject.verified_at).not_to be_nil expect(subject.verified_at).not_to be_nil
end
end end
end
context 'primary node' do context 'primary node' do
it 'calls replicator.handle_after_checksum_succeeded' do it 'calls replicator.handle_after_checksum_succeeded' do
stub_current_geo_node(primary_node) stub_current_geo_node(primary_node)
expect(subject.replicator).to receive(:handle_after_checksum_succeeded) expect(subject.replicator).to receive(:handle_after_checksum_succeeded)
subject.verification_succeeded_with_checksum!('abc123', Time.current) subject.verification_succeeded_with_checksum!('abc123', Time.current)
end
end
context 'secondary node' do
it 'does not call replicator.handle_after_checksum_succeeded' do
stub_current_geo_node(secondary_node)
expect(subject.replicator).not_to receive(:handle_after_checksum_succeeded)
subject.verification_succeeded_with_checksum!('abc123', Time.current)
end
end end
end end
context 'secondary node' do describe '#verification_failed_with_message!' do
it 'does not call replicator.handle_after_checksum_succeeded' do it 'saves the error message and increments retry counter' do
stub_current_geo_node(secondary_node) error = double('error', message: 'An error message')
expect(subject.replicator).not_to receive(:handle_after_checksum_succeeded) subject.verification_started!
subject.verification_failed_with_message!('Failure to calculate checksum', error)
subject.verification_succeeded_with_checksum!('abc123', Time.current) expect(subject.reload.verification_failed?).to be_truthy
expect(subject.reload.verification_failure).to eq 'Failure to calculate checksum: An error message'
expect(subject.verification_retry_count).to be 1
expect(subject.verification_checksum).to be_nil
end end
end end
end end
describe '#verification_failed_with_message!' do context 'when verification state is stored in a separate table' do
it 'saves the error message and increments retry counter' do before(:all) do
error = double('error', message: 'An error message') create_dummy_model_with_separate_state_table
end
subject.verification_started! after(:all) do
subject.verification_failed_with_message!('Failure to calculate checksum', error) drop_dummy_model_with_separate_state_table
end
expect(subject.reload.verification_failed?).to be_truthy before do
expect(subject.reload.verification_failure).to eq 'Failure to calculate checksum: An error message' stub_dummy_replicator_class(model_class: 'TestDummyModelWithSeparateState')
expect(subject.verification_retry_count).to be 1 stub_dummy_model_with_separate_state_class
expect(subject.verification_checksum).to be_nil
end end
end
end
context 'when verification state is stored in a separate table' do subject { TestDummyModelWithSeparateState.new }
before(:all) do
create_dummy_model_with_separate_state_table
end
after(:all) do describe '.fail_verification_timeouts' do
drop_dummy_model_with_separate_state_table it 'sets verification state to failed' do
end state = subject.verification_state_object
state.update!(verification_started_at: (described_class::VERIFICATION_TIMEOUT + 1.minute).ago, verification_state: 1)
before do TestDummyModelWithSeparateState.fail_verification_timeouts
stub_dummy_replicator_class(model_class: 'TestDummyModelWithSeparateState')
stub_dummy_model_with_separate_state_class
end
subject { TestDummyModelWithSeparateState.new } expect(subject.reload.verification_failed?).to be_truthy
end
end
end
end
context 'for registry classes' do
describe '.fail_verification_timeouts' do describe '.fail_verification_timeouts' do
it 'sets verification state to failed' do it 'sets verification state to failed' do
state = subject.verification_state_object state = create(:geo_package_file_registry, :synced, verification_started_at: (described_class::VERIFICATION_TIMEOUT + 1.minute).ago, verification_state: 1)
state.update!(verification_started_at: (described_class::VERIFICATION_TIMEOUT + 1.minute).ago, verification_state: 1)
TestDummyModelWithSeparateState.fail_verification_timeouts state.class.fail_verification_timeouts
expect(subject.reload.verification_failed?).to be_truthy expect(state.reload.verification_failed?).to be_truthy
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment