forked from btcsuite/btcwallet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
manager.go
162 lines (136 loc) · 5.11 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package migration
import (
"errors"
"sort"
"github.com/John-Tonny/vclsuite_vclwallet/walletdb"
)
var (
// ErrReversion is an error returned when an attempt to revert to a
// previous version is detected. This is done to provide safety to users
// as some upgrades may not be backwards-compatible.
ErrReversion = errors.New("reverting to a previous version is not " +
"supported")
)
// Version denotes the version number of the database. A migration can be used
// to bring a previous version of the database to a later one.
type Version struct {
// Number represents the number of this version.
Number uint32
// Migration represents a migration function that modifies the database
// state. Care must be taken so that consequent migrations build off of
// the previous one in order to ensure the consistency of the database.
Migration func(walletdb.ReadWriteBucket) error
}
// Manager is an interface that exposes the necessary methods needed in order to
// migrate/upgrade a service. Each service (i.e., an implementation of this
// interface) can then use the Upgrade function to perform any required database
// migrations.
type Manager interface {
// Name returns the name of the service we'll be attempting to upgrade.
Name() string
// Namespace returns the top-level bucket of the service.
Namespace() walletdb.ReadWriteBucket
// CurrentVersion returns the current version of the service's database.
CurrentVersion(walletdb.ReadBucket) (uint32, error)
// SetVersion sets the version of the service's database.
SetVersion(walletdb.ReadWriteBucket, uint32) error
// Versions returns all of the available database versions of the
// service.
Versions() []Version
}
// GetLatestVersion returns the latest version available from the given slice.
func GetLatestVersion(versions []Version) uint32 {
if len(versions) == 0 {
return 0
}
// Before determining the latest version number, we'll sort the slice to
// ensure it reflects the last element.
sort.Slice(versions, func(i, j int) bool {
return versions[i].Number < versions[j].Number
})
return versions[len(versions)-1].Number
}
// VersionsToApply determines which versions should be applied as migrations
// based on the current version.
func VersionsToApply(currentVersion uint32, versions []Version) []Version {
// Assuming the migration versions are in increasing order, we'll apply
// any migrations that have a version number lower than our current one.
var upgradeVersions []Version
for _, version := range versions {
if version.Number > currentVersion {
upgradeVersions = append(upgradeVersions, version)
}
}
// Before returning, we'll sort the slice by its version number to
// ensure the migrations are applied in their intended order.
sort.Slice(upgradeVersions, func(i, j int) bool {
return upgradeVersions[i].Number < upgradeVersions[j].Number
})
return upgradeVersions
}
// Upgrade attempts to upgrade a group of services exposed through the Manager
// interface. Each service will go through its available versions and determine
// whether it needs to apply any.
//
// NOTE: In order to guarantee fault-tolerance, each service upgrade should
// happen within the same database transaction.
func Upgrade(mgrs ...Manager) error {
for _, mgr := range mgrs {
if err := upgrade(mgr); err != nil {
return err
}
}
return nil
}
// upgrade attempts to upgrade a service expose through its implementation of
// the Manager interface. This function will determine whether any new versions
// need to be applied based on the service's current version and latest
// available one.
func upgrade(mgr Manager) error {
// We'll start by fetching the service's current and latest version.
ns := mgr.Namespace()
currentVersion, err := mgr.CurrentVersion(ns)
if err != nil {
return err
}
versions := mgr.Versions()
latestVersion := GetLatestVersion(versions)
switch {
// If the current version is greater than the latest, then the service
// is attempting to revert to a previous version that's possibly
// backwards-incompatible. To prevent this, we'll return an error
// indicating so.
case currentVersion > latestVersion:
return ErrReversion
// If the current version is behind the latest version, we'll need to
// apply all of the newer versions in order to catch up to the latest.
case currentVersion < latestVersion:
versions := VersionsToApply(currentVersion, versions)
mgrName := mgr.Name()
ns := mgr.Namespace()
for _, version := range versions {
log.Infof("Applying %v migration #%d", mgrName,
version.Number)
// We'll only run a migration if there is one available
// for this version.
if version.Migration != nil {
err := version.Migration(ns)
if err != nil {
log.Errorf("Unable to apply %v "+
"migration #%d: %v", mgrName,
version.Number, err)
return err
}
}
}
// With all of the versions applied, we can now reflect the
// latest version upon the service.
if err := mgr.SetVersion(ns, latestVersion); err != nil {
return err
}
// If the current version matches the latest one, there's no upgrade
// needed and we can safely exit.
case currentVersion == latestVersion:
}
return nil
}