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  }