github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/upgrades/raft.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package upgrades 5 6 import ( 7 "bytes" 8 "io/ioutil" 9 "net" 10 "os" 11 "path/filepath" 12 "strconv" 13 "time" 14 15 "github.com/hashicorp/go-msgpack/codec" 16 "github.com/hashicorp/raft" 17 "github.com/juju/errors" 18 "github.com/juju/replicaset" 19 20 "github.com/juju/juju/agent" 21 "github.com/juju/juju/core/raftlease" 22 "github.com/juju/juju/feature" 23 raftworker "github.com/juju/juju/worker/raft" 24 ) 25 26 // jujuMachineKey is the key for the replset member tag where we 27 // store the member's corresponding machine id. 28 const jujuMachineKey = "juju-machine-id" 29 30 // BootstrapRaft initialises the raft cluster in a controller that is 31 // being upgraded. 32 func BootstrapRaft(context Context) error { 33 agentConfig := context.AgentConfig() 34 storageDir := raftDir(agentConfig) 35 _, err := os.Stat(storageDir) 36 // If the storage dir already exists we shouldn't run again. (If 37 // we statted the dir successfully, this will return nil.) 38 if !os.IsNotExist(err) { 39 return err 40 } 41 _, transport := raft.NewInmemTransport(raft.ServerAddress("notused")) 42 defer transport.Close() 43 44 conf, err := raftworker.NewRaftConfig(raftworker.Config{ 45 LocalID: raft.ServerID(agentConfig.Tag().Id()), 46 Logger: logger, 47 Transport: transport, 48 FSM: raftworker.BootstrapFSM{}, 49 }) 50 if err != nil { 51 return errors.Annotate(err, "getting raft config") 52 } 53 logStore, err := raftworker.NewLogStore(storageDir) 54 if err != nil { 55 return errors.Annotate(err, "making log store") 56 } 57 defer logStore.Close() 58 59 snapshotStore, err := raftworker.NewSnapshotStore(storageDir, 2, logger) 60 if err != nil { 61 return errors.Annotate(err, "making snapshot store") 62 } 63 64 st := context.State() 65 members, err := st.ReplicaSetMembers() 66 if err != nil { 67 return errors.Annotate(err, "getting replica set members") 68 } 69 info, err := st.StateServingInfo() 70 if err != nil { 71 return errors.Annotate(err, "getting state serving info") 72 } 73 servers, err := makeRaftServers(members, info.APIPort) 74 if err != nil { 75 return errors.Trace(err) 76 } 77 err = raft.BootstrapCluster(conf, logStore, logStore, snapshotStore, transport, servers) 78 return errors.Annotate(err, "bootstrapping raft cluster") 79 } 80 81 func raftDir(agentConfig agent.ConfigSetter) string { 82 return filepath.Join(agentConfig.DataDir(), "raft") 83 } 84 85 func makeRaftServers(members []replicaset.Member, apiPort int) (raft.Configuration, error) { 86 var empty raft.Configuration 87 var servers []raft.Server 88 for _, member := range members { 89 id, ok := member.Tags[jujuMachineKey] 90 if !ok { 91 return empty, errors.NotFoundf("juju machine id for replset member %d", member.Id) 92 } 93 baseAddress, _, err := net.SplitHostPort(member.Address) 94 if err != nil { 95 return empty, errors.Annotatef(err, "getting base address for replset member %d", member.Id) 96 } 97 apiAddress := net.JoinHostPort(baseAddress, strconv.Itoa(apiPort)) 98 suffrage := raft.Voter 99 if member.Votes != nil && *member.Votes < 1 { 100 suffrage = raft.Nonvoter 101 } 102 server := raft.Server{ 103 ID: raft.ServerID(id), 104 Address: raft.ServerAddress(apiAddress), 105 Suffrage: suffrage, 106 } 107 servers = append(servers, server) 108 } 109 return raft.Configuration{Servers: servers}, nil 110 } 111 112 // MigrateLegacyLeases converts leases in the legacy store into 113 // corresponding ones in the raft store. 114 func MigrateLegacyLeases(context Context) error { 115 // We know at this point in time that the raft workers aren't 116 // running - they're all guarded by the upgrade-steps gate. 117 118 // We need to migrate leases if: 119 // * legacy-leases is off, 120 // * there are some legacy leases, 121 // * and there are no snapshots in the snapshot store (which shows 122 // that the raft-lease store is already in use). 123 st := context.State() 124 controllerConfig, err := st.ControllerConfig() 125 if err != nil { 126 return errors.Annotate(err, "getting controller config") 127 } 128 if controllerConfig.Features().Contains(feature.LegacyLeases) { 129 logger.Debugf("legacy-leases flag is set, not migrating leases") 130 return nil 131 } 132 133 var zero time.Time 134 legacyLeases, err := st.LegacyLeases(zero) 135 if err != nil { 136 return errors.Annotate(err, "getting legacy leases") 137 } 138 if len(legacyLeases) == 0 { 139 logger.Debugf("no legacy leases to migrate") 140 return nil 141 } 142 143 storageDir := raftDir(context.AgentConfig()) 144 snapshotStore, err := raftworker.NewSnapshotStore( 145 storageDir, 2, logger) 146 if err != nil { 147 return errors.Annotate(err, "opening snapshot store") 148 } 149 snapshots, err := snapshotStore.List() 150 if err != nil { 151 return errors.Annotate(err, "listing snapshots") 152 } 153 if len(snapshots) != 0 { 154 logger.Debugf("snapshots found in store - raft leases in use") 155 return nil 156 } 157 158 // We need the last term and index, latest configuration and 159 // configuration index from the log store. 160 logStore, err := raftworker.NewLogStore(storageDir) 161 if err != nil { 162 return errors.Annotate(err, "opening log store") 163 } 164 defer logStore.Close() 165 166 latest, configEntry, err := collectLogEntries(logStore) 167 if err != nil { 168 return errors.Trace(err) 169 } 170 171 configuration, err := decodeConfiguration(configEntry.Data) 172 if err != nil { 173 return errors.Annotate(err, "decoding configuration") 174 } 175 176 entries := make(map[raftlease.SnapshotKey]raftlease.SnapshotEntry, len(legacyLeases)) 177 target := st.LeaseNotifyTarget(ioutil.Discard, logger) 178 179 // Populate the snapshot and the leaseholders collection. 180 for key, info := range legacyLeases { 181 if key.Lease == "" || info.Holder == "" { 182 logger.Debugf("not migrating blank lease %#v holder %q", key, info.Holder) 183 continue 184 } 185 entries[raftlease.SnapshotKey{ 186 Namespace: key.Namespace, 187 ModelUUID: key.ModelUUID, 188 Lease: key.Lease, 189 }] = raftlease.SnapshotEntry{ 190 Holder: info.Holder, 191 Start: zero, 192 Duration: info.Expiry.Sub(zero), 193 } 194 target.Claimed(key, info.Holder) 195 } 196 197 newSnapshot := raftlease.Snapshot{ 198 Version: raftlease.SnapshotVersion, 199 Entries: entries, 200 GlobalTime: zero, 201 } 202 // Store the snapshot. 203 _, transport := raft.NewInmemTransport(raft.ServerAddress("notused")) 204 defer transport.Close() 205 sink, err := snapshotStore.Create( 206 raft.SnapshotVersionMax, 207 latest.Index, 208 latest.Term, 209 configuration, 210 configEntry.Index, 211 transport, 212 ) 213 if err != nil { 214 return errors.Annotate(err, "creating snapshot sink") 215 } 216 defer sink.Close() 217 err = newSnapshot.Persist(sink) 218 if err != nil { 219 sink.Cancel() 220 return errors.Annotate(err, "persisting snapshot") 221 } 222 223 return nil 224 } 225 226 // collectLogEntries returns two log entries: the latest one, and the 227 // most recent configuration entry. (These might be the same.) 228 func collectLogEntries(store raft.LogStore) (*raft.Log, *raft.Log, error) { 229 var latest raft.Log 230 231 lastIndex, err := store.LastIndex() 232 if err != nil { 233 return nil, nil, errors.Annotate(err, "getting last index") 234 } 235 236 if lastIndex == 0 { 237 return nil, nil, errors.Errorf("no log entries, expected at least one for configuration") 238 } 239 240 err = store.GetLog(lastIndex, &latest) 241 if err != nil { 242 return nil, nil, errors.Annotate(err, "getting last log entry") 243 } 244 245 if latest.Type == raft.LogConfiguration { 246 return &latest, &latest, nil 247 } 248 249 firstIndex, err := store.FirstIndex() 250 if err != nil { 251 return nil, nil, errors.Annotate(err, "getting first index`") 252 } 253 current := lastIndex 254 for current > firstIndex { 255 current-- 256 var entry raft.Log 257 err := store.GetLog(current, &entry) 258 if errors.Cause(err) == raft.ErrLogNotFound { 259 continue 260 } else if err != nil { 261 return nil, nil, errors.Annotatef(err, "getting log index %d", current) 262 } 263 if entry.Type == raft.LogConfiguration { 264 return &latest, &entry, nil 265 } 266 } 267 268 return nil, nil, errors.Errorf("no configuration entry in log") 269 } 270 271 func decodeConfiguration(data []byte) (raft.Configuration, error) { 272 var hd codec.MsgpackHandle 273 dec := codec.NewDecoder(bytes.NewBuffer(data), &hd) 274 var config raft.Configuration 275 err := dec.Decode(&config) 276 return config, errors.Trace(err) 277 }