From 3b7f5db5ba9d4db23593fe116499f9583fed271f Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Wed, 18 Dec 2019 11:21:57 +0530 Subject: [PATCH] FIX: parallel spec system needs a dedicated upload folder for each worker. (#8547) --- app/services/inline_uploads.rb | 18 ++--- config/routes.rb | 3 + lib/backup_restore/backuper.rb | 4 +- lib/backup_restore/restorer.rb | 15 ++-- lib/discourse.rb | 4 + lib/disk_space.rb | 2 +- lib/file_store/base_store.rb | 6 +- lib/s3_inventory.rb | 2 +- spec/components/cooked_post_processor_spec.rb | 75 ++++++++++--------- .../components/file_store/local_store_spec.rb | 27 +++---- spec/components/url_helper_spec.rb | 2 +- spec/fabricators/post_fabricator.rb | 38 +++++----- .../helpers/user_notifications_helper_spec.rb | 6 +- spec/jobs/pull_hotlinked_images_spec.rb | 5 +- spec/models/post_spec.rb | 18 +++-- spec/multisite/s3_store_spec.rb | 38 +++++----- 16 files changed, 142 insertions(+), 121 deletions(-) diff --git a/app/services/inline_uploads.rb b/app/services/inline_uploads.rb index 8168543b243..30727ec2b8e 100644 --- a/app/services/inline_uploads.rb +++ b/app/services/inline_uploads.rb @@ -54,10 +54,8 @@ class InlineUploads raw_matches << [match, href, replacement, index] end - db = RailsMultisite::ConnectionManagement.current_db - regexps = [ - /(https?:\/\/[a-zA-Z0-9\.\/-]+\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/, + /(https?:\/\/[a-zA-Z0-9\.\/-]+\/#{Discourse.store.upload_path}#{UPLOAD_REGEXP_PATTERN})/, ] if Discourse.store.external? @@ -219,28 +217,28 @@ class InlineUploads end def self.matched_uploads(node) - db = RailsMultisite::ConnectionManagement.current_db + upload_path = Discourse.store.upload_path base_url = Discourse.base_url.sub(/https?:\/\//, "(https?://)") regexps = [ /(upload:\/\/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)/, /(\/uploads\/short-url\/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)/, /(#{base_url}\/uploads\/short-url\/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)/, - /(#{GlobalSetting.relative_url_root}\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/, - /(#{base_url}\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/, + /(#{GlobalSetting.relative_url_root}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/, + /(#{base_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/, ] if GlobalSetting.cdn_url && (cdn_url = GlobalSetting.cdn_url.sub(/https?:\/\//, "(https?://)")) - regexps << /(#{cdn_url}\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/ + regexps << /(#{cdn_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ if GlobalSetting.relative_url_root.present? - regexps << /(#{cdn_url}#{GlobalSetting.relative_url_root}\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/ + regexps << /(#{cdn_url}#{GlobalSetting.relative_url_root}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ end end if Discourse.store.external? if Rails.configuration.multisite - regexps << /((https?:)?#{SiteSetting.Upload.s3_base_url}\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/ - regexps << /(#{SiteSetting.Upload.s3_cdn_url}\/uploads\/#{db}#{UPLOAD_REGEXP_PATTERN})/ + regexps << /((https?:)?#{SiteSetting.Upload.s3_base_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ + regexps << /(#{SiteSetting.Upload.s3_cdn_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ else regexps << /((https?:)?#{SiteSetting.Upload.s3_base_url}#{UPLOAD_REGEXP_PATTERN})/ regexps << /(#{SiteSetting.Upload.s3_cdn_url}#{UPLOAD_REGEXP_PATTERN})/ diff --git a/config/routes.rb b/config/routes.rb index 21e536ce9c6..feaf544f18e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -507,6 +507,9 @@ Discourse::Application.routes.draw do get "uploads/short-url/:base62(.:extension)" => "uploads#show_short", constraints: { site: /\w+/, base62: /[a-zA-Z0-9]+/, extension: /[a-z0-9\.]+/i }, as: :upload_short # used to download attachments get "uploads/:site/original/:tree:sha(.:extension)" => "uploads#show", constraints: { site: /\w+/, tree: /([a-z0-9]+\/)+/i, sha: /\h{40}/, extension: /[a-z0-9\.]+/i } + if Discourse.is_parallel_test? + get "uploads/:site/:index/original/:tree:sha(.:extension)" => "uploads#show", constraints: { site: /\w+/, index: /\d+/, tree: /([a-z0-9]+\/)+/i, sha: /\h{40}/, extension: /[a-z0-9\.]+/i } + end # used to download attachments (old route) get "uploads/:site/:id/:sha" => "uploads#show", constraints: { site: /\w+/, id: /\d+/, sha: /\h{16}/, format: /.*/ } get "secure-media-uploads/*path(.:extension)" => "uploads#show_secure", constraints: { extension: /[a-z0-9\.]+/i } diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb index 0770af2c86f..3068ecb2a49 100644 --- a/lib/backup_restore/backuper.rb +++ b/lib/backup_restore/backuper.rb @@ -265,7 +265,7 @@ module BackupRestore def add_local_uploads_to_archive(tar_filename) log "Archiving uploads..." - upload_directory = "uploads/" + @current_db + upload_directory = Discourse.store.upload_path if File.directory?(File.join(Rails.root, "public", upload_directory)) exclude_optimized = SiteSetting.include_thumbnails_in_backups ? '' : "--exclude=#{upload_directory}/optimized" @@ -289,7 +289,7 @@ module BackupRestore log "Downloading uploads from S3. This may take a while..." store = FileStore::S3Store.new - upload_directory = File.join("uploads", @current_db) + upload_directory = Discourse.store.upload_path count = 0 Upload.find_each do |upload| diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index c76685c6595..6d1e1f0c179 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -431,36 +431,36 @@ module BackupRestore log "Extracting uploads..." public_uploads_path = File.join(Rails.root, "public") + upload_path = Discourse.store.upload_path FileUtils.mkdir_p(File.join(public_uploads_path, "uploads")) tmp_uploads_path = Dir.glob(File.join(@tmp_directory, "uploads", "*")).first return if tmp_uploads_path.blank? previous_db_name = BackupMetadata.value_for("db_name") || File.basename(tmp_uploads_path) - current_db_name = RailsMultisite::ConnectionManagement.current_db optimized_images_exist = File.exist?(File.join(tmp_uploads_path, 'optimized')) Discourse::Utils.execute_command( - 'rsync', '-avp', '--safe-links', "#{tmp_uploads_path}/", "uploads/#{current_db_name}/", + 'rsync', '-avp', '--safe-links', "#{tmp_uploads_path}/", "#{upload_path}/", failure_message: "Failed to restore uploads.", chdir: public_uploads_path ) - remap_uploads(previous_db_name, current_db_name) + remap_uploads(previous_db_name, upload_path) if SiteSetting.Upload.enable_s3_uploads migrate_to_s3 - remove_local_uploads(File.join(public_uploads_path, "uploads/#{current_db_name}")) + remove_local_uploads(File.join(public_uploads_path, upload_path)) end generate_optimized_images unless optimized_images_exist end - def remap_uploads(previous_db_name, current_db_name) + def remap_uploads(previous_db_name, upload_path) log "Remapping uploads..." was_multisite = BackupMetadata.value_for("multisite") == "t" - uploads_folder = was_multisite ? "/" : "/uploads/#{current_db_name}/" + uploads_folder = was_multisite ? "/" : "/#{upload_path}/" if (old_base_url = BackupMetadata.value_for("base_url")) && old_base_url != Discourse.base_url remap(old_base_url, Discourse.base_url) @@ -490,8 +490,9 @@ module BackupRestore remap(old_host, new_host) end + current_db_name = RailsMultisite::ConnectionManagement.current_db if previous_db_name != current_db_name - remap("uploads/#{previous_db_name}", "uploads/#{current_db_name}") + remap("uploads/#{previous_db_name}", upload_path) end rescue => ex diff --git a/lib/discourse.rb b/lib/discourse.rb index d4b6b4eb68c..f4819a210e6 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -837,6 +837,10 @@ module Discourse def self.redis $redis end + + def self.is_parallel_test? + ENV['RAILS_ENV'] == "test" && ENV['TEST_ENV_NUMBER'] + end end # rubocop:enable Style/GlobalVars diff --git a/lib/disk_space.rb b/lib/disk_space.rb index c817c68f3bb..96121b0cc73 100644 --- a/lib/disk_space.rb +++ b/lib/disk_space.rb @@ -20,7 +20,7 @@ class DiskSpace end def self.uploads_path - "#{Rails.root}/public/uploads/#{RailsMultisite::ConnectionManagement.current_db}" + "#{Rails.root}/public/#{Discourse.store.upload_path}" end private_class_method :uploads_path end diff --git a/lib/file_store/base_store.rb b/lib/file_store/base_store.rb index 0c4c14d2be5..8fbf45d5b7c 100644 --- a/lib/file_store/base_store.rb +++ b/lib/file_store/base_store.rb @@ -31,7 +31,11 @@ module FileStore end def upload_path - File.join("uploads", RailsMultisite::ConnectionManagement.current_db) + path = File.join("uploads", RailsMultisite::ConnectionManagement.current_db) + return path unless Discourse.is_parallel_test? + + n = ENV['TEST_ENV_NUMBER'].presence || '1' + File.join(path, n) end def has_been_uploaded?(url) diff --git a/lib/s3_inventory.rb b/lib/s3_inventory.rb index b98e2e99094..3f3735b2323 100644 --- a/lib/s3_inventory.rb +++ b/lib/s3_inventory.rb @@ -39,7 +39,7 @@ class S3Inventory decompress_inventory_file(file) end - multisite_prefix = "uploads/#{RailsMultisite::ConnectionManagement.current_db}/" + multisite_prefix = Discourse.store.upload_path ActiveRecord::Base.transaction do begin connection.exec("CREATE TEMP TABLE #{table_name}(url text UNIQUE, etag text, PRIMARY KEY(etag, url))") diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index b9627b6cdb1..25749d133a3 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -17,6 +17,7 @@ end describe CookedPostProcessor do fab!(:upload) { Fabricate(:upload) } + let(:upload_path) { Discourse.store.upload_path } context "#post_process" do fab!(:post) do @@ -290,7 +291,7 @@ describe CookedPostProcessor do # fake some optimized images OptimizedImage.create!( - url: '/uploads/default/666x500.jpg', + url: "/#{upload_path}/666x500.jpg", width: 666, height: 500, upload_id: upload.id, @@ -302,7 +303,7 @@ describe CookedPostProcessor do # fake 3x optimized image, we lose 2 pixels here over original due to rounding on downsize OptimizedImage.create!( - url: '/uploads/default/1998x1500.jpg', + url: "/#{upload_path}/1998x1500.jpg", width: 1998, height: 1500, upload_id: upload.id, @@ -313,7 +314,7 @@ describe CookedPostProcessor do # Fake a loading image _optimized_image = OptimizedImage.create!( - url: '/uploads/default/10x10.png', + url: "/#{upload_path}/10x10.png", width: CookedPostProcessor::LOADING_SIZE, height: CookedPostProcessor::LOADING_SIZE, upload_id: upload.id, @@ -329,10 +330,10 @@ describe CookedPostProcessor do html = cpp.html - expect(html).to include(%Q|data-small-upload="//test.localhost/uploads/default/10x10.png"|) + expect(html).to include(%Q|data-small-upload="//test.localhost/#{upload_path}/10x10.png"|) # 1.5x is skipped cause we have a missing thumb - expect(html).to include('srcset="//test.localhost/uploads/default/666x500.jpg, //test.localhost/uploads/default/1998x1500.jpg 3x"') - expect(html).to include('src="//test.localhost/uploads/default/666x500.jpg"') + expect(html).to include("srcset=\"//test.localhost/#{upload_path}/666x500.jpg, //test.localhost/#{upload_path}/1998x1500.jpg 3x\"") + expect(html).to include("src=\"//test.localhost/#{upload_path}/666x500.jpg\"") # works with CDN set_cdn_url("http://cdn.localhost") @@ -343,9 +344,9 @@ describe CookedPostProcessor do html = cpp.html - expect(html).to include(%Q|data-small-upload="//cdn.localhost/uploads/default/10x10.png"|) - expect(html).to include('srcset="//cdn.localhost/uploads/default/666x500.jpg, //cdn.localhost/uploads/default/1998x1500.jpg 3x"') - expect(html).to include('src="//cdn.localhost/uploads/default/666x500.jpg"') + expect(html).to include(%Q|data-small-upload="//cdn.localhost/#{upload_path}/10x10.png"|) + expect(html).to include("srcset=\"//cdn.localhost/#{upload_path}/666x500.jpg, //cdn.localhost/#{upload_path}/1998x1500.jpg 3x\"") + expect(html).to include("src=\"//cdn.localhost/#{upload_path}/666x500.jpg\"") end it "doesn't include response images for cropped images" do @@ -450,7 +451,7 @@ describe CookedPostProcessor do cpp.post_process expect(cpp.html).to match_html <<~HTML -