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