Merge branch 'master' into 6859-fix-invalid-manifests
[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   require 'arvados/keep'
14   include ApplicationHelper
15   require 'tempfile'
16   require 'shellwords'
17
18   def self.salvage_collection_arv_put temp_file
19     %x(arv-put --as-stream --use-filename invalid_manifest_text.txt #{Shellwords::shellescape(temp_file.path)})
20   end
21
22   def self.salvage_collection uuid, reason='salvaged - see #6277, #6859'
23     act_as_system_user do
24       if !ENV['ARVADOS_API_TOKEN'].present? or !ENV['ARVADOS_API_HOST'].present?
25         $stderr.puts "Please set your admin user credentials as ARVADOS environment variables."
26         # exit with a code outside the range of special exit codes; http://tldp.org/LDP/abs/html/exitcodes.html
27         exit 200
28       end
29
30       if !uuid.present?
31         $stderr.puts "Required uuid argument is missing."
32         return false
33       end
34
35       src_collection = Collection.find_by_uuid uuid
36       if !src_collection
37         $stderr.puts "No collection found for #{uuid}. Returning."
38         return false
39       end
40
41       begin
42         src_manifest = src_collection.manifest_text || ''
43
44         # Get all the locators from the original manifest
45         locators = []
46         src_manifest.each_line do |line|
47           line.split(' ').each do |word|
48             if match = Keep::Locator::LOCATOR_REGEXP.match(word)
49               word = word.split('+')[0..1].join('+')  # get rid of any hints
50               locators << word if !word.start_with?('00000000000000000000000000000000')
51             end
52           end
53         end
54         locators << 'd41d8cd98f00b204e9800998ecf8427e+0' if !locators.any?
55
56         # create new collection using 'arv-put' with original manifest_text as the data
57         temp_file = Tempfile.new('temp')
58         temp_file.write(src_manifest)
59         temp_file.close
60
61         new_manifest = salvage_collection_arv_put temp_file
62
63         temp_file.unlink
64
65         if !new_manifest.present?
66           $stderr.puts "arv-put --as-stream failed for #{uuid}"
67           return false
68         end
69
70         words = []
71         new_manifest.split(' ').each do |word|
72           if match = Keep::Locator::LOCATOR_REGEXP.match(word)
73             word = word.split('+')[0..1].join('+')  # get rid of any hints
74             words << word
75           else
76             words << word
77           end
78         end
79
80         new_manifest = words.join(' ') + "\n"
81         new_collection = Collection.new
82
83         total_size = 0
84         locators.each do |locator|
85           total_size += locator.split('+')[1].to_i
86         end
87         new_manifest += (". #{locators.join(' ')} 0:#{total_size}:salvaged_data\n")
88
89         new_collection.name = "salvaged from #{src_collection.uuid}, #{src_collection.portable_data_hash}"
90         new_collection.manifest_text = new_manifest
91         new_collection.portable_data_hash = Digest::MD5.hexdigest(new_collection.manifest_text)
92
93         created = new_collection.save!
94         raise "New collection creation failed." if !created
95
96         $stderr.puts "Salvaged manifest and data for #{uuid} are in #{new_collection.uuid}."
97         puts "Created new collection #{new_collection.uuid}"
98       rescue => error
99         $stderr.puts "Error creating collection for #{uuid}: #{error}"
100         return false
101       end
102
103       begin
104         # update src_collection collection name, pdh, and manifest_text
105         src_collection.name = (src_collection.name || '') + ' (' + (reason || '') + '; salvaged data at ' + new_collection.uuid + ')'
106         src_collection.manifest_text = ''
107         src_collection.portable_data_hash = 'd41d8cd98f00b204e9800998ecf8427e+0'
108         src_collection.save!
109         $stderr.puts "Collection #{uuid} emptied and renamed to #{src_collection.name.inspect}."
110       rescue => error
111         $stderr.puts "Error salvaging collection #{new_collection.uuid}: #{error}"
112         return false
113       end
114     end
115   end
116 end