closes #9587
[arvados.git] / services / api / lib / salvage_collection.rb
1 module SalvageCollection
2   # Take two input parameters: a collection uuid and reason
3   # Get "src_collection" with the given uuid
4   # Create a new collection with:
5   #   src_collection.manifest_text as "invalid_manifest_text.txt"
6   #   Locators from src_collection.manifest_text as "salvaged_data"
7   # Update src_collection:
8   #   Set src_collection.manifest_text to: ""
9   #   Append to src_collection.name: " (reason; salvaged data at new_collection.uuid)"
10   #   Set portable_data_hash to "d41d8cd98f00b204e9800998ecf8427e+0"
11
12   require File.dirname(__FILE__) + '/../config/environment'
13   include ApplicationHelper
14   require 'tempfile'
15   require 'shellwords'
16
17   def salvage_collection_arv_put cmd
18     new_manifest = %x(#{cmd})
19     if $?.success?
20       new_manifest
21     else
22       raise "Error during arv-put: #{$?} (cmd was #{cmd.inspect})"
23     end
24   end
25
26   # Get all the locators (and perhaps other strings that look a lot
27   # like a locators) from the original manifest, even if they don't
28   # appear in the correct positions with the correct space delimiters.
29   def salvage_collection_locator_data manifest
30     locators = []
31     size = 0
32     manifest.scan(/(^|[^[:xdigit:]])([[:xdigit:]]{32})((\+\d+)(\+|\b))?/) do |_, hash, _, sizehint, _|
33       if sizehint
34         locators << hash.downcase + sizehint
35         size += sizehint.to_i
36       else
37         locators << hash.downcase
38       end
39     end
40     locators << 'd41d8cd98f00b204e9800998ecf8427e+0' if !locators.any?
41     return [locators, size]
42   end
43
44   def salvage_collection uuid, reason='salvaged - see #6277, #6859'
45     act_as_system_user do
46       if !ENV['ARVADOS_API_TOKEN'].present? or !ENV['ARVADOS_API_HOST'].present?
47         raise "ARVADOS environment variables missing. Please set your admin user credentials as ARVADOS environment variables."
48       end
49
50       if !uuid.present?
51         raise "Collection UUID is required."
52       end
53
54       src_collection = Collection.find_by_uuid uuid
55       if !src_collection
56         raise "No collection found for #{uuid}."
57       end
58
59       src_manifest = src_collection.manifest_text || ''
60
61       # create new collection using 'arv-put' with original manifest_text as the data
62       temp_file = Tempfile.new('temp')
63       temp_file.write(src_manifest)
64
65       temp_file.close
66
67       new_manifest = salvage_collection_arv_put "arv-put --as-stream --use-filename invalid_manifest_text.txt #{Shellwords::shellescape(temp_file.path)}"
68
69       temp_file.unlink
70
71       # Get the locator data in the format [[locators], size] from the original manifest
72       locator_data = salvage_collection_locator_data src_manifest
73
74       new_manifest += (". #{locator_data[0].join(' ')} 0:#{locator_data[1]}:salvaged_data\n")
75
76       new_collection = Collection.new
77       new_collection.name = "salvaged from #{src_collection.uuid}, #{src_collection.portable_data_hash}"
78       new_collection.manifest_text = new_manifest
79
80       created = new_collection.save!
81       raise "New collection creation failed." if !created
82
83       $stderr.puts "Salvaged manifest and data for #{uuid} are in #{new_collection.uuid}."
84       puts "Created new collection #{new_collection.uuid}"
85
86       # update src_collection collection name, pdh, and manifest_text
87       src_collection.name = (src_collection.name || '') + ' (' + (reason || '') + '; salvaged data at ' + new_collection.uuid + ')'
88       src_collection.manifest_text = ''
89       src_collection.portable_data_hash = 'd41d8cd98f00b204e9800998ecf8427e+0'
90       src_collection.save!
91       $stderr.puts "Collection #{uuid} emptied and renamed to #{src_collection.name.inspect}."
92     end
93   end
94 end