2 require 'kind_and_etag'
4 class FixCollectionPortableDataHashWithHintedManifest < ActiveRecord::Migration
5 include CurrentApiClient
7 class ArvadosModel < ActiveRecord::Base
8 self.abstract_class = true
9 extend HasUuid::ClassMethods
10 include CurrentApiClient
12 before_create do |record|
13 record.uuid ||= record.class.generate_uuid
14 record.owner_uuid ||= system_user_uuid
16 serialize :properties, Hash
19 # Clean up the name of the stub model class so we generate correct UUIDs.
20 super.sub("FixCollectionPortableDataHashWithHintedManifest::", "")
24 class Collection < ArvadosModel
27 class Log < ArvadosModel
28 def self.log_for(thing, age="old")
29 { "#{age}_etag" => thing.etag,
30 "#{age}_attributes" => thing.attributes,
34 def self.log_create(thing)
35 new_log("create", thing, log_for(thing, "new"))
38 def self.log_update(thing, start_state)
39 new_log("update", thing, start_state.merge(log_for(thing, "new")))
42 def self.log_destroy(thing)
43 new_log("destroy", thing, log_for(thing, "old"))
48 def self.new_log(event_type, thing, properties)
49 create!(event_type: event_type,
51 object_uuid: thing.uuid,
52 object_owner_uuid: thing.owner_uuid,
53 properties: properties)
57 def each_bad_collection
58 Collection.find_each do |coll|
59 next unless (coll.manifest_text =~ /\+[A-Z]/)
60 stripped_manifest = coll.manifest_text.
61 gsub(/( [0-9a-f]{32}(\+\d+)?)(\+\S+)/, '\1')
62 stripped_pdh = sprintf("%s+%i",
63 Digest::MD5.hexdigest(stripped_manifest),
64 stripped_manifest.bytesize)
65 yield [coll, stripped_pdh] if (coll.portable_data_hash != stripped_pdh)
70 Collection.reset_column_information
71 Log.reset_column_information
73 [:owner_uuid, :created_at, :modified_by_client_uuid, :manifest_text,
74 :modified_by_user_uuid, :modified_at, :updated_at, :name,
75 :description, :portable_data_hash, :replication_desired,
76 :replication_confirmed, :replication_confirmed_at, :expires_at]
77 new_expiry = Date.new(2038, 1, 31)
79 each_bad_collection do |coll, stripped_pdh|
80 # Create a copy of the collection including bad portable data hash,
81 # with an expiration. This makes it possible to resolve the bad
82 # portable data hash, but the expiration can hide the Collection
83 # from more user-friendly interfaces like Workbench.
84 start_log = Log.log_for(coll)
85 attributes = Hash[copied_attr_names.map { |key| [key, coll.send(key)] }]
86 attributes[:expires_at] ||= new_expiry
87 attributes[:properties] = (coll.properties.dup rescue {})
88 attributes[:properties]["migrated_from"] ||= coll.uuid
89 coll_copy = Collection.create!(attributes)
90 Log.log_create(coll_copy)
91 coll.update_attributes(portable_data_hash: stripped_pdh)
92 Log.log_update(coll, start_log)
97 Collection.reset_column_information
98 Log.reset_column_information
99 each_bad_collection do |coll, stripped_pdh|
100 if ((src_uuid = coll.properties["migrated_from"]) and
101 (src_coll = Collection.where(uuid: src_uuid).first) and
102 (src_coll.portable_data_hash == stripped_pdh))
103 start_log = Log.log_for(src_coll)
104 src_coll.portable_data_hash = coll.portable_data_hash
106 Log.log_update(src_coll, start_log)
107 coll.destroy or raise Exception.new("failed to destroy old collection")
108 Log.log_destroy(coll)