ipfs-cluster/state/mapstate/migrate.go
Hector Sanjuan 6d77954327 Fix multiple problems with state migration
Additionally, remove persisting the state version to the go-datastore. In the
future versions of the state, there is not a global format anymore (with a
global version). Instead, every pin element can potentially be stored in a
different version.

License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
2019-02-19 19:46:03 +00:00

271 lines
6.3 KiB
Go

package mapstate
// To add a new state format
// - implement the previous format's "next" function to the new format
// - implement the new format's unmarshal function
// - add a case to the switch statement for the previous format version
// - update the code copying the from mapStateVx to mapState
import (
"context"
"errors"
"io"
"github.com/ipfs/ipfs-cluster/api"
msgpack "github.com/multiformats/go-multicodec/msgpack"
)
// Instances of migrateable can be read from a serialized format and migrated
// to other state formats
type migrateable interface {
next() migrateable
unmarshal(io.Reader) error
}
/* V1 */
type mapStateV1 struct {
Version int
PinMap map[string]struct{}
}
// Unmarshal the serialization of a v1 state
func (st *mapStateV1) unmarshal(r io.Reader) error {
dec := msgpack.Multicodec(msgpack.DefaultMsgpackHandle()).Decoder(r)
return dec.Decode(st)
}
// Migrate from v1 to v2
func (st *mapStateV1) next() migrateable {
var mst2 mapStateV2
mst2.PinMap = make(map[string]pinSerialV2)
for k := range st.PinMap {
mst2.PinMap[k] = pinSerialV2{
Cid: k,
Allocations: []string{},
ReplicationFactor: -1,
}
}
return &mst2
}
/* V2 */
type pinSerialV2 struct {
Cid string `json:"cid"`
Name string `json:"name"`
Allocations []string `json:"allocations"`
ReplicationFactor int `json:"replication_factor"`
}
type mapStateV2 struct {
PinMap map[string]pinSerialV2
Version int
}
func (st *mapStateV2) unmarshal(r io.Reader) error {
dec := msgpack.Multicodec(msgpack.DefaultMsgpackHandle()).Decoder(r)
return dec.Decode(st)
}
func (st *mapStateV2) next() migrateable {
var mst3 mapStateV3
mst3.PinMap = make(map[string]pinSerialV3)
for k, v := range st.PinMap {
mst3.PinMap[k] = pinSerialV3{
Cid: v.Cid,
Name: v.Name,
Allocations: v.Allocations,
ReplicationFactorMin: v.ReplicationFactor,
ReplicationFactorMax: v.ReplicationFactor,
}
}
return &mst3
}
/* V3 */
type pinSerialV3 struct {
Cid string `json:"cid"`
Name string `json:"name"`
Allocations []string `json:"allocations"`
ReplicationFactorMin int `json:"replication_factor_min"`
ReplicationFactorMax int `json:"replication_factor_max"`
}
type mapStateV3 struct {
PinMap map[string]pinSerialV3
Version int
}
func (st *mapStateV3) unmarshal(r io.Reader) error {
dec := msgpack.Multicodec(msgpack.DefaultMsgpackHandle()).Decoder(r)
return dec.Decode(st)
}
func (st *mapStateV3) next() migrateable {
var mst4 mapStateV4
mst4.PinMap = make(map[string]pinSerialV4)
for k, v := range st.PinMap {
mst4.PinMap[k] = pinSerialV4{
Cid: v.Cid,
Name: v.Name,
Allocations: v.Allocations,
ReplicationFactorMin: v.ReplicationFactorMin,
ReplicationFactorMax: v.ReplicationFactorMax,
Recursive: true,
}
}
return &mst4
}
/* V4 */
type pinSerialV4 struct {
Cid string `json:"cid"`
Name string `json:"name"`
Allocations []string `json:"allocations"`
ReplicationFactorMin int `json:"replication_factor_min"`
ReplicationFactorMax int `json:"replication_factor_max"`
Recursive bool `json:"recursive"`
}
type mapStateV4 struct {
PinMap map[string]pinSerialV4
Version int
}
func (st *mapStateV4) unmarshal(r io.Reader) error {
dec := msgpack.Multicodec(msgpack.DefaultMsgpackHandle()).Decoder(r)
return dec.Decode(st)
}
func (st *mapStateV4) next() migrateable {
var mst5 mapStateV5
mst5.PinMap = make(map[string]pinSerialV5)
for k, v := range st.PinMap {
pinsv5 := pinSerialV5{}
pinsv5.Cid = v.Cid
pinsv5.Type = uint64(api.DataType)
pinsv5.Allocations = v.Allocations
// Encountered pins with Recursive=false
// in previous states. Since we do not support
// non recursive pins yet, we fix it by
// harcoding MaxDepth.
pinsv5.MaxDepth = -1
// Options
pinsv5.Name = v.Name
pinsv5.ReplicationFactorMin = v.ReplicationFactorMin
pinsv5.ReplicationFactorMax = v.ReplicationFactorMax
mst5.PinMap[k] = pinsv5
}
return &mst5
}
/* V5 */
type pinOptionsV5 struct {
ReplicationFactorMin int `json:"replication_factor_min"`
ReplicationFactorMax int `json:"replication_factor_max"`
Name string `json:"name"`
ShardSize uint64 `json:"shard_size"`
}
type pinSerialV5 struct {
pinOptionsV5
Cid string `json:"cid"`
Type uint64 `json:"type"`
Allocations []string `json:"allocations"`
MaxDepth int `json:"max_depth"`
Reference string `json:"reference"`
}
type mapStateV5 struct {
PinMap map[string]pinSerialV5
Version int
}
func (st *mapStateV5) unmarshal(r io.Reader) error {
dec := msgpack.Multicodec(msgpack.DefaultMsgpackHandle()).Decoder(r)
return dec.Decode(st)
}
func (st *mapStateV5) next() migrateable {
v6 := NewMapState()
for k, v := range st.PinMap {
logger.Infof("migrating", k, v.Cid)
// we need to convert because we added codec struct fields
// and thus serialization is not the same.
p := api.PinSerial{}
p.Cid = v.Cid
p.Type = v.Type
p.Allocations = v.Allocations
p.MaxDepth = v.MaxDepth
p.Reference = v.Reference
p.ReplicationFactorMax = v.ReplicationFactorMax
p.ReplicationFactorMin = v.ReplicationFactorMin
p.Name = v.Name
p.ShardSize = v.ShardSize
v6.Add(context.Background(), p.ToPin())
}
return v6.(*MapState)
}
// Last time we use this migration approach.
func (st *MapState) next() migrateable { return nil }
func (st *MapState) unmarshal(r io.Reader) error {
return st.dst.Unmarshal(r)
}
// Migrate code
func finalCopy(st *MapState, internal *MapState) {
st.dst = internal.dst
}
func (st *MapState) migrateFrom(version int, snap io.Reader) error {
var m, next migrateable
switch version {
case 1:
var mst1 mapStateV1
m = &mst1
case 2:
var mst2 mapStateV2
m = &mst2
case 3:
var mst3 mapStateV3
m = &mst3
case 4:
var mst4 mapStateV4
m = &mst4
case 5:
var mst5 mapStateV5
m = &mst5
default:
return errors.New("version migration not supported")
}
err := m.unmarshal(snap)
if err != nil {
return err
}
for {
next = m.next()
if next == nil {
mst6, ok := m.(*MapState)
if !ok {
return errors.New("migration ended prematurely")
}
finalCopy(st, mst6)
return nil
}
m = next
}
}