Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ch89835] Migrate DO subscription information in inter-cloud migrations #16315

Merged
merged 10 commits into from
Jul 28, 2021
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Development
- Use the organization user's data while editing a user from organization settings [#16280](https://github.com/CartoDB/cartodb/pull/16280)
- Fix schema name in layers created by free users [#16307](https://github.com/CartoDB/cartodb/pull/16307)
- Limit start parameter of Dropbox connector [#16264](https://github.com/CartoDB/cartodb/pull/16264)
- Migrate Redis DO subscription information in inter-cloud migrations [#16315](https://github.com/CartoDB/cartodb/pull/16315)
- OauthApps restricted by default [#16304](https://github.com/CartoDB/cartodb/pull/16304)
- Support staging hostname in the catalog [#16258](https://github.com/CartoDB/cartodb/pull/16258)
- Fix user migration export/import logs [#16298](https://github.com/CartoDB/cartodb/pull/16298)
Expand Down
7 changes: 7 additions & 0 deletions app/services/carto/organization_metadata_export_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,13 @@ def import_metadata_from_directory(organization, path)
Carto::UserMetadataExportService.new.import_search_tweets_from_directory(user, "#{path}/user_#{user.id}")
end

organization.users.each do |user|
Carto::UserMetadataExportService.new.import_redis_do_subscriptions(
user,
"#{path}/user_#{user.id}"
)
end

organization
end
end
Expand Down
45 changes: 42 additions & 3 deletions app/services/carto/redis_export_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

# Version History
# 1.0.0: export organization metadata
# 1.0.1: add DO subscriptions
module Carto
module RedisExportServiceConfiguration
CURRENT_VERSION = '1.0.0'.freeze

CURRENT_VERSION = '1.0.1'.freeze

def compatible_version?(version)
version.to_i == CURRENT_VERSION.split('.')[0].to_i
Expand Down Expand Up @@ -34,6 +36,13 @@ def remove_redis_from_hash_export(exported_hash)
remove_redis(exported_hash[:redis])
end

def restore_redis_do_subscriptions_from_json_export(exported_json_string, user)
exported_hash = JSON.parse(exported_json_string).deep_symbolize_keys
raise 'Wrong export version' unless compatible_version?(exported_hash[:version])

restore_do_subscriptions($users_metadata, exported_hash[:redis][:do_subscriptions], user)
end

private

def restore_redis(redis_export)
Expand All @@ -44,6 +53,7 @@ def restore_redis(redis_export)
def remove_redis(redis_export)
remove_keys($users_metadata, redis_export[:users_metadata])
remove_keys($tables_metadata, redis_export[:tables_metadata])
remove_keys($users_metadata, redis_export[:do_subscriptions])
end

def restore_keys(redis_db, redis_keys)
Expand All @@ -60,6 +70,22 @@ def restore_named_maps(redis_db, redis_keys)
end
end

def restore_do_subscriptions(redis_db, redis_keys, user)
redis_keys.each do |key, value|
subscriptions = JSON.parse(value.presence || '[]')

subscriptions.each do |sub|
dataset = user.tables.find_by(name: sub['sync_table'])
next if dataset.nil?

sub['sync_table_id'] = dataset.id
sub['synchronization_id'] = dataset.synchronization.try(:id)
end

redis_db.hset(key, Carto::DoLicensingService::PRESELECTED_STORAGE, subscriptions.to_json)
end
end

def remove_keys(redis_db, redis_keys)
redis_keys.each do |key|
redis_db.del(key)
Expand Down Expand Up @@ -97,14 +123,16 @@ def export_user_json_hash(user)
def export_organization(organization)
{
users_metadata: export_dataservices("org:#{organization.name}"),
tables_metadata: {}
tables_metadata: {},
do_subscriptions: {}
}
end

def export_user(user)
{
users_metadata: export_dataservices("user:#{user.username}"),
tables_metadata: export_named_maps(user)
tables_metadata: export_named_maps(user),
do_subscriptions: export_do_subscriptions(user)
}
end

Expand All @@ -126,6 +154,17 @@ def export_named_maps(user)
{ named_maps_key => named_maps_hash }
end

def export_do_subscriptions(user)
do_subscriptions_key = "do:#{user.username}:datasets"
do_subscriptions =
$users_metadata.hget(do_subscriptions_key, Carto::DoLicensingService::PRESELECTED_STORAGE)
return {} if do_subscriptions.nil?

{
do_subscriptions_key => do_subscriptions
}
end

def matches_user_visualization?(named_map, user)
re = /tpl_(?<viz_id>.+)/
match = re.match(named_map)
Expand Down
9 changes: 9 additions & 0 deletions app/services/carto/user_metadata_export_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ def import_metadata_from_directory(user, meta_path)
import_user_visualizations_from_directory(user, Carto::Visualization::TYPE_DERIVED, meta_path)

import_search_tweets_from_directory(user, meta_path)

import_redis_do_subscriptions(user, meta_path)
end

def import_search_tweets_from_directory(user, meta_path)
Expand All @@ -581,6 +583,13 @@ def import_search_tweets_from_directory(user, meta_path)
search_tweets.each { |st| save_imported_search_tweet(st, user) }
end

def import_redis_do_subscriptions(user, meta_path)
Carto::RedisExportService.new.restore_redis_do_subscriptions_from_json_export(
redis_user_file(meta_path),
user
)
end

private

def user_from_file(path)
Expand Down
2 changes: 1 addition & 1 deletion script/ci/cloudbuild-onprem-dev-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ steps:
fi
waitFor: ['upload-logs-onprem']

timeout: 1800s
timeout: 2100s
options:
machineType: 'E2_HIGHCPU_32'
substitutions:
Expand Down
73 changes: 70 additions & 3 deletions spec/services/carto/redis_export_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def with_visualization
table_visualization.save!
visualization.save!

yield(visualization)
yield(visualization, table_visualization)
ensure
map.destroy
table.destroy
Expand All @@ -48,6 +48,33 @@ def with_named_maps(visualization)
$tables_metadata.del("map_tpl|#{visualization.user.username}")
end

def with_synchronization(visualization)
synchronization = create(:carto_synchronization, visualization: visualization)

yield(synchronization)
ensure
synchronization.destroy
end

def with_do_subscription(synchronization)
redis_key = "do:#{@user.username}:datasets"
$users_metadata.hset(redis_key, 'bq', [{
dataset_id: 'dataset-id',
status: 'active',
available_in: ['bq', 'bq-sample'],
license_type: 'full-access',
type: 'dataset',
sync_status: 'synced',
sync_table: synchronization.visualization.user_table.name,
sync_table_id: synchronization.visualization.user_table.id,
synchronization_id: synchronization.id
}].to_json)

yield
ensure
$users_metadata.del(redis_key)
end

def check_export(export, prefix)
expect(export[:redis][:users_metadata].keys.sort).to eq(["#{prefix}:hash", "#{prefix}:set", "#{prefix}:string"])
end
Expand All @@ -57,6 +84,15 @@ def check_named_maps(export, visualization)
expect(export[:redis][:tables_metadata]["map_tpl|#{visualization.user.username}"]['custom_named']).to eq(Base64.encode64("{:c=>\"custom\"}"))
end

def check_do_subscriptions(export, synchronization)
expect(export[:redis][:do_subscriptions].keys).to eq(["do:#{@user.username}:datasets"])
expect(
JSON.parse(
export[:redis][:do_subscriptions]["do:#{@user.username}:datasets"]
).first['synchronization_id']
).to eq(synchronization.id)
end

def check_redis(prefix)
expect($users_metadata.get("#{prefix}:string")).to eq('something')
expect($users_metadata.zrange("#{prefix}:set", 0, -1, withscores: true)).to eq([['set_key', 5.0]])
Expand All @@ -67,6 +103,13 @@ def check_redis_named_maps
expect($tables_metadata.hgetall("map_tpl|#{@user.username}")). to eq("custom_named" => "{:c=>\"custom\"}")
end

def check_redis_do_subscriptions
expect($users_metadata.hget("do:#{@user.username}:datasets", 'bq').present?).to be_true
expect(
JSON.parse($users_metadata.hget("do:#{@user.username}:datasets", 'bq')).first['dataset_id']
).to eq('dataset-id')
end

let(:service) { Carto::RedisExportService.new }

describe '#export' do
Expand All @@ -93,13 +136,24 @@ def check_redis_named_maps
end

it 'includes non-cartodb-managed named maps' do
with_visualization do |visualization|
with_visualization do |visualization, _|
with_named_maps(visualization) do
export = service.export_user_json_hash(@user)
check_named_maps(export, visualization)
end
end
end

it 'includes DO subscriptions' do
with_visualization do |_, table_visualization|
with_synchronization(table_visualization) do |synchronization|
with_do_subscription(synchronization) do
export = service.export_user_json_hash(@user)
check_do_subscriptions(export, synchronization)
end
end
end
end
end

describe '#export + import' do
Expand All @@ -111,7 +165,7 @@ def check_redis_named_maps
end

it 'copies all keys under user:username for users' do
with_visualization do |visualization|
with_visualization do |visualization, _|
prefix = "user:#{@user.username}"
export = with_redis_keys(prefix) do
with_named_maps(visualization) do
Expand All @@ -123,5 +177,18 @@ def check_redis_named_maps
check_redis_named_maps
end
end

it 'copies DO subscriptions' do
with_visualization do |_, table_visualization|
with_synchronization(table_visualization) do |synchronization|
export = with_do_subscription(synchronization) do
service.export_user_json_hash(@user)
end

service.restore_redis_do_subscriptions_from_json_export(export.to_json, @user)
check_redis_do_subscriptions
end
end
end
end
end
22 changes: 22 additions & 0 deletions spec/services/carto/user_metadata_export_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ def create_user_with_basemaps_assets_visualizations
create(:oauth_access_tokens, oauth_app_user: oauth_app_user, api_key: api_key)
create(:oauth_refresh_tokens, oauth_app_user: oauth_app_user, scopes: ['offline'])

# DO subscriptions
$users_metadata.hset("do:#{@user.username}:datasets", 'bq', [{
dataset_id: 'dataset-id',
status: 'active',
available_in: ['bq', 'bq-sample'],
license_type: 'full-access',
type: 'dataset',
sync_status: 'synced',
sync_table: @table.name,
sync_table_id: @table.id,
synchronization_id: sync.id
}].to_json)

@user.reload
end

Expand Down Expand Up @@ -497,6 +510,12 @@ def expect_export_matches_asset(exported_asset, asset)

def expect_redis_restored(user)
expect(CartoDB::GeocoderUsageMetrics.new(user.username).get(:geocoder_here, :success_responses)).to eq(1)

expect(user.subscriptions.count).to eq(1)
expect(user.subscriptions.first['sync_table']).to eq(@table.name)
if user.tables.exists?(name: user.subscriptions.first['sync_table'])
expect(user.subscriptions.first['sync_table_id']).not_to eq(@table.id)
end
end

def expect_export_matches_search_tweet(exported_search_tweet, search_tweet)
Expand Down Expand Up @@ -745,6 +764,9 @@ def full_export_import(path)
$tables_metadata.del(Carto::Visualization::V2_VISUALIZATIONS_REDIS_KEY)
expect(@visualization.uses_vizjson2?).to be_false

# Clean redis DO subscriptions
$tables_metadata.del("do:#{@user.username}:datasets", 'bq')

@imported_user = service.import_from_directory(path)
service.import_metadata_from_directory(@imported_user, path)

Expand Down