github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/migrator/migrator.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package migrator 20 21 import ( 22 "encoding/json" 23 "fmt" 24 "os" 25 "path/filepath" 26 "sort" 27 28 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 29 "github.com/rogpeppe/go-internal/lockedfile" 30 "github.com/rs/zerolog" 31 ) 32 33 const ( 34 statePending = "pending" 35 stateFailed = "failed" 36 stateSucceeded = "succeeded" 37 stateDown = "down" 38 stateSucceededRunAgain = "runagain" 39 ) 40 41 type migration interface { 42 Migrate(*Migrator) (Result, error) 43 Rollback(*Migrator) (Result, error) 44 } 45 46 var migrations = map[string]migration{} 47 48 type migrationStates map[string]MigrationState 49 50 func registerMigration(name string, migration migration) { 51 migrations[name] = migration 52 } 53 54 func allMigrations() []string { 55 ms := []string{} 56 57 for k := range migrations { 58 ms = append(ms, k) 59 } 60 61 sort.Strings(ms) 62 return ms 63 } 64 65 // MigrationState holds the state of a migration 66 type MigrationState struct { 67 State string 68 Message string 69 } 70 71 // Result represents the result of a migration run 72 type Result string 73 74 // Migrator runs migrations on an existing decomposedfs 75 type Migrator struct { 76 lu node.PathLookup 77 states migrationStates 78 log *zerolog.Logger 79 } 80 81 // New returns a new Migrator instance 82 func New(lu node.PathLookup, log *zerolog.Logger) Migrator { 83 return Migrator{ 84 lu: lu, 85 log: log, 86 } 87 } 88 89 // Migrations returns the list of migrations and their states 90 func (m *Migrator) Migrations() (map[string]MigrationState, error) { 91 err := m.readStates() 92 if err != nil { 93 return nil, err 94 } 95 96 states := map[string]MigrationState{} 97 for _, migration := range allMigrations() { 98 if s, ok := m.states[migration]; ok { 99 states[migration] = s 100 } else { 101 states[migration] = MigrationState{ 102 State: statePending, 103 } 104 } 105 } 106 107 return states, nil 108 } 109 110 // RunMigration runs or rolls back a migration 111 func (m *Migrator) RunMigration(id string, rollback bool) error { 112 if _, ok := migrations[id]; !ok { 113 return fmt.Errorf("invalid migration '%s'", id) 114 } 115 116 lock, err := lockedfile.OpenFile(filepath.Join(m.lu.InternalRoot(), ".migrations.lock"), os.O_WRONLY|os.O_CREATE, 0600) 117 if err != nil { 118 return err 119 } 120 defer lock.Close() 121 122 err = m.readStates() 123 if err != nil { 124 return err 125 } 126 127 var res Result 128 if !rollback { 129 m.log.Info().Msg("Running migration " + id + "...") 130 res, err = migrations[id].Migrate(m) 131 } else { 132 m.log.Info().Msg("Rolling back migration " + id + "...") 133 res, err = migrations[id].Rollback(m) 134 } 135 136 // write back state 137 s := m.states[id] 138 s.State = string(res) 139 140 if err != nil { 141 m.log.Error().Err(err).Msg("migration " + id + " failed") 142 s.Message = err.Error() 143 } 144 145 m.states[id] = s 146 err = m.writeStates() 147 if err != nil { 148 return err 149 } 150 m.log.Info().Msg("done") 151 return nil 152 } 153 154 // RunMigrations runs all migrations in sequence. Note this sequence must not be changed or it might 155 // damage existing decomposed fs. 156 func (m *Migrator) RunMigrations() error { 157 lock, err := lockedfile.OpenFile(filepath.Join(m.lu.InternalRoot(), ".migrations.lock"), os.O_WRONLY|os.O_CREATE, 0600) 158 if err != nil { 159 return err 160 } 161 defer lock.Close() 162 163 err = m.readStates() 164 if err != nil { 165 return err 166 } 167 168 for _, migration := range allMigrations() { 169 s := m.states[migration] 170 if s.State == stateSucceeded || s.State == stateDown { 171 continue 172 } 173 174 res, err := migrations[migration].Migrate(m) 175 s.State = string(res) 176 if err != nil { 177 m.log.Error().Err(err).Msg("migration " + migration + " failed") 178 s.Message = err.Error() 179 } 180 181 m.states[migration] = s 182 err = m.writeStates() 183 if err != nil { 184 return err 185 } 186 } 187 return nil 188 } 189 190 func (m *Migrator) readStates() error { 191 m.states = migrationStates{} 192 193 d, err := os.ReadFile(filepath.Join(m.lu.InternalRoot(), ".migrations")) 194 if err != nil { 195 if !os.IsNotExist(err) { 196 return err 197 } 198 } 199 200 if len(d) > 0 { 201 err = json.Unmarshal(d, &m.states) 202 if err != nil { 203 return err 204 } 205 } 206 207 return nil 208 } 209 210 func (m *Migrator) writeStates() error { 211 d, err := json.Marshal(m.states) 212 if err != nil { 213 m.log.Error().Err(err).Msg("could not marshal migration states") 214 return nil 215 } 216 return os.WriteFile(filepath.Join(m.lu.InternalRoot(), ".migrations"), d, 0600) 217 }