Skip to content

Commit a89e976

Browse files
Merge pull request #62 from ipfs/kevina/5to6
repo migration from 5 to 6
2 parents 564fc64 + 8e22fc0 commit a89e976

File tree

12 files changed

+1156
-5
lines changed

12 files changed

+1156
-5
lines changed

.travis.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ env:
1414
- TEST_VERBOSE=1
1515

1616
script:
17-
- cd sharness
18-
- make
17+
- make -k test
1918

2019
# For docker containers
2120

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
install:
22
go install
33
@echo "fs-repo-migrations now installed, type 'fs-repo-migrations' to run"
4+
5+
test: test_go sharness
6+
7+
test_go:
8+
go test ./ipfs-5-to-6/... # go test ./... fails see #66
9+
10+
sharness:
11+
make -C sharness
12+
13+
.PHONY: test test_go sharness

ipfs-5-to-6/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
import (
4+
migrate "github.com/ipfs/fs-repo-migrations/go-migrate"
5+
mg5 "github.com/ipfs/fs-repo-migrations/ipfs-5-to-6/migration"
6+
)
7+
8+
func main() {
9+
m := mg5.Migration{}
10+
migrate.Main(&m)
11+
}

ipfs-5-to-6/migration/ci_config.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package mg5
2+
3+
import (
4+
"strings"
5+
)
6+
7+
// ciConfig is a case insensitive map to represent a IPFS
8+
// configuration when unmarshaled as a map[string]interface{} instead
9+
// of specialized structures. It is needed becuase JSON library is
10+
// case insensitive when matching json values to struct fields
11+
type ciConfig struct {
12+
conf map[string]interface{}
13+
fromLC map[string]string
14+
}
15+
16+
func newCiConfig(conf map[string]interface{}) ciConfig {
17+
return ciConfig{
18+
conf: conf,
19+
fromLC: lcMap(conf),
20+
}
21+
}
22+
23+
func lcMap(conf map[string]interface{}) map[string]string {
24+
fromLC := make(map[string]string)
25+
for key, _ := range conf {
26+
fromLC[strings.ToLower(key)] = key
27+
}
28+
return fromLC
29+
}
30+
31+
// get gets a key, returns nil if the key doesn't exist
32+
func (c ciConfig) get(origkey string) interface{} {
33+
lckey := strings.ToLower(origkey)
34+
key, ok := c.fromLC[lckey]
35+
if !ok {
36+
return nil
37+
}
38+
return c.conf[key] // should not panic, key should exist
39+
}
40+
41+
func (c ciConfig) set(origkey string, val interface{}) {
42+
lckey := strings.ToLower(origkey)
43+
key, ok := c.fromLC[lckey]
44+
if ok {
45+
c.conf[key] = val
46+
}
47+
c.fromLC[lckey] = origkey
48+
c.conf[origkey] = val
49+
}
50+
51+
func (c ciConfig) del(origkey string) {
52+
lckey := strings.ToLower(origkey)
53+
key, ok := c.fromLC[lckey]
54+
if ok {
55+
delete(c.conf, key)
56+
}
57+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package mg5
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"os"
9+
"reflect"
10+
)
11+
12+
// convFunc does an inplace conversion of the "datastore"
13+
// configuration (represted as a map[string]interface{}) from one
14+
// version to another
15+
type convFunc func(ds ciConfig) error
16+
17+
// convertFile converts a config file from one version to another, the
18+
// converted config is stored in
19+
func convertFile(orig string, new string, convFunc convFunc) (ciConfig, error) {
20+
in, err := os.Open(orig)
21+
if err != nil {
22+
return ciConfig{}, err
23+
}
24+
out, err := os.Create(new)
25+
if err != nil {
26+
return ciConfig{}, err
27+
}
28+
return convert(in, out, convFunc)
29+
}
30+
31+
// convert converts the config from one version to another, returns
32+
// the converted config as a map[string]interface{}
33+
func convert(in io.Reader, out io.Writer, convFunc convFunc) (ciConfig, error) {
34+
data, err := ioutil.ReadAll(in)
35+
if err != nil {
36+
return ciConfig{}, err
37+
}
38+
confMap := make(map[string]interface{})
39+
err = json.Unmarshal(data, &confMap)
40+
if err != nil {
41+
return ciConfig{}, err
42+
}
43+
conf := newCiConfig(confMap)
44+
ds, _ := conf.get("datastore").(map[string]interface{})
45+
if ds == nil {
46+
return ciConfig{}, fmt.Errorf("Datastore field missing or of the wrong type")
47+
}
48+
err = convFunc(newCiConfig(ds))
49+
if err != nil {
50+
return ciConfig{}, err
51+
}
52+
fixed, err := json.MarshalIndent(confMap, "", " ")
53+
if err != nil {
54+
return ciConfig{}, err
55+
}
56+
out.Write(fixed)
57+
out.Write([]byte("\n"))
58+
return conf, nil
59+
}
60+
61+
func ver5to6(ds ciConfig) error {
62+
noSyncVal := ds.get("nosync")
63+
if noSyncVal == nil {
64+
noSyncVal = interface{}(false)
65+
}
66+
noSync, ok := noSyncVal.(bool)
67+
if !ok {
68+
return fmt.Errorf("unsupported value for Datastore.NoSync fields: %v", noSyncVal)
69+
}
70+
ds.del("nosync")
71+
72+
dsTypeVal := ds.get("type")
73+
if dsTypeVal == nil {
74+
dsTypeVal = interface{}("")
75+
}
76+
dsType, ok := dsTypeVal.(string)
77+
if !ok || (dsType != "default" && dsType != "leveldb" && dsType != "") {
78+
return fmt.Errorf("unsupported value for Datastore.Type fields: %s", dsType)
79+
}
80+
ds.del("type")
81+
82+
// Path and Params never appear to have been used so just delete them
83+
ds.del("path")
84+
ds.del("params")
85+
86+
ds.set("Spec", datastoreSpec(!noSync))
87+
return nil
88+
}
89+
90+
func ver6to5(ds ciConfig) (err error) {
91+
defer func() {
92+
if r := recover(); r != nil {
93+
err = fmt.Errorf("incompatible config detected, downgrade not possible: %v", r.(error))
94+
}
95+
}()
96+
spec := ds.get("Spec").(map[string]interface{})
97+
if spec == nil {
98+
return fmt.Errorf("Datastore.Spec field missing or not a json object, can't downgrade")
99+
}
100+
mounts, _ := spec["mounts"].([]interface{})
101+
if mounts == nil {
102+
return fmt.Errorf("Datastore.Spec.mounts field is missing or not an array")
103+
}
104+
var root, blocks interface{}
105+
sync := true
106+
for _, mount := range mounts {
107+
switch mountpoint := mount.(map[string]interface{})["mountpoint"].(string); mountpoint {
108+
case "/blocks":
109+
sync = mount.(map[string]interface{})["child"].(map[string]interface{})["sync"].(bool)
110+
blocks = mount
111+
case "/":
112+
root = mount
113+
default:
114+
return fmt.Errorf("unknown mountpoint: %s", mountpoint)
115+
}
116+
}
117+
// normalize spec
118+
spec["mounts"] = []interface{}{blocks, root}
119+
expected := datastoreSpec(sync)
120+
if !reflect.DeepEqual(spec, expected) {
121+
return fmt.Errorf("Datastore.Spec field not of a supported value, can't downgrade")
122+
}
123+
ds.del("Spec")
124+
ds.set("Type", "leveldb")
125+
ds.set("Params", nil)
126+
ds.set("NoSync", !sync)
127+
println("ver6to5 done!")
128+
return nil
129+
}
130+
131+
func datastoreSpec(sync bool) map[string]interface{} {
132+
return map[string]interface{}{
133+
"type": "mount",
134+
"mounts": []interface{}{
135+
map[string]interface{}{
136+
"mountpoint": "/blocks",
137+
"type": "measure",
138+
"prefix": "flatfs.datastore",
139+
"child": map[string]interface{}{
140+
"type": "flatfs",
141+
"path": "blocks",
142+
"sync": sync,
143+
"shardFunc": "/repo/flatfs/shard/v1/next-to-last/2",
144+
},
145+
},
146+
map[string]interface{}{
147+
"mountpoint": "/",
148+
"type": "measure",
149+
"prefix": "leveldb.datastore",
150+
"child": map[string]interface{}{
151+
"type": "levelds",
152+
"path": "datastore",
153+
"compression": "none",
154+
},
155+
},
156+
},
157+
}
158+
}

0 commit comments

Comments
 (0)