lxd/cluster/membership.go
func Promote(state *state.State, gateway *Gateway, nodes []db.RaftNode) error {
	logger.Info("Promote node to database node")

	// Sanity check that this is not already a database node
	if gateway.IsDatabaseNode() {
		return fmt.Errorf("this node is already a database node")
	}

	// Figure out our own address.
	address := ""
	err := state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		var err error
		address, err = tx.NodeAddress()
		if err != nil {
			return errors.Wrap(err, "failed to fetch the address of this node")
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Sanity check that we actually have an address.
	if address == "" {
		return fmt.Errorf("node is not exposed on the network")
	}

	// Figure out our raft node ID, and an existing target raft node that
	// we'll contact to add ourselves as member.
	id := ""
	target := ""
	for _, node := range nodes {
		if node.Address == address {
			id = strconv.Itoa(int(node.ID))
		} else {
			target = node.Address
		}
	}

	// Sanity check that our address was actually included in the given
	// list of raft nodes.
	if id == "" {
		return fmt.Errorf("this node is not included in the given list of database nodes")
	}

	// Replace our local list of raft nodes with the given one (which
	// includes ourselves). This will make the gateway start a raft node
	// when restarted.
	err = state.Node.Transaction(func(tx *db.NodeTx) error {
		err = tx.RaftNodesReplace(nodes)
		if err != nil {
			return errors.Wrap(err, "failed to set raft nodes")
		}

		return nil
	})
	if err != nil {
		return err
	}

	// Lock regular access to the cluster database since we don't want any
	// other database code to run while we're reconfiguring raft.
	err = state.Cluster.EnterExclusive()
	if err != nil {
		return errors.Wrap(err, "failed to acquire cluster database lock")
	}

	// Wipe all existing raft data, for good measure (perhaps they were
	// somehow leftover).
	err = os.RemoveAll(state.OS.GlobalDatabaseDir())
	if err != nil {
		return errors.Wrap(err, "failed to remove existing raft data")
	}

	// Re-initialize the gateway. This will create a new raft factory an
	// dqlite driver instance, which will be exposed over gRPC by the
	// gateway handlers.
	err = gateway.init()
	if err != nil {
		return errors.Wrap(err, "failed to re-initialize gRPC SQL gateway")
	}

	logger.Info(
		"Joining dqlite raft cluster",
		log15.Ctx{"id": id, "address": address, "target": target})
	changer := gateway.raft.MembershipChanger()
	err = changer.Join(raft.ServerID(id), raft.ServerAddress(target), 5*time.Second)
	if err != nil {
		return err
	}

	// Unlock regular access to our cluster database, and make sure our
	// gateway still works correctly.
	err = state.Cluster.ExitExclusive(func(tx *db.ClusterTx) error {
		_, err := tx.Nodes()
		return err
	})
	if err != nil {
		return errors.Wrap(err, "cluster database initialization failed")
	}
	return nil
}
func Promote(state *state.State, gateway *Gateway, nodes []db.RaftNode) error {
	logger.Info("Promote node to database node")

	// Sanity check that this is not already a database node
	if gateway.IsDatabaseNode() {
		return fmt.Errorf("this node is already a database node")
	}

	// Figure out our own address.
	address := ""
	err := state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		var err error
		address, err = tx.NodeAddress()
		if err != nil {
			return errors.Wrap(err, "failed to fetch the address of this node")
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Sanity check that we actually have an address.
	if address == "" {
		return fmt.Errorf("node is not exposed on the network")
	}

	// Figure out our raft node ID, and an existing target raft node that
	// we'll contact to add ourselves as member.
	id := ""
	target := ""
	for _, node := range nodes {
		if node.Address == address {
			id = strconv.Itoa(int(node.ID))
		} else {
			target = node.Address
		}
	}

	// Sanity check that our address was actually included in the given
	// list of raft nodes.
	if id == "" {
		return fmt.Errorf("this node is not included in the given list of database nodes")
	}

	// Replace our local list of raft nodes with the given one (which
	// includes ourselves). This will make the gateway start a raft node
	// when restarted.
	err = state.Node.Transaction(func(tx *db.NodeTx) error {
		err = tx.RaftNodesReplace(nodes)
		if err != nil {
			return errors.Wrap(err, "failed to set raft nodes")
		}

		return nil
	})
	if err != nil {
		return err
	}

	// Lock regular access to the cluster database since we don't want any
	// other database code to run while we're reconfiguring raft.
	err = state.Cluster.EnterExclusive()
	if err != nil {
		return errors.Wrap(err, "failed to acquire cluster database lock")
	}

	// Wipe all existing raft data, for good measure (perhaps they were
	// somehow leftover).
	err = os.RemoveAll(state.OS.GlobalDatabaseDir())
	if err != nil {
		return errors.Wrap(err, "failed to remove existing raft data")
	}

	// Re-initialize the gateway. This will create a new raft factory an
	// dqlite driver instance, which will be exposed over gRPC by the
	// gateway handlers.
	err = gateway.init()
	if err != nil {
		return errors.Wrap(err, "failed to re-initialize gRPC SQL gateway")
	}

	logger.Info(
		"Joining dqlite raft cluster",
		log15.Ctx{"id": id, "address": address, "target": target})
	changer := gateway.raft.MembershipChanger()
	err = changer.Join(raft.ServerID(id), raft.ServerAddress(target), 5*time.Second)
	if err != nil {
		return err
	}

	// Unlock regular access to our cluster database, and make sure our
	// gateway still works correctly.
	err = state.Cluster.ExitExclusive(func(tx *db.ClusterTx) error {
		_, err := tx.Nodes()
		return err
	})
	if err != nil {
		return errors.Wrap(err, "cluster database initialization failed")
	}
	return nil
}
// Promote makes a LXD node which is not a database node, become part of the
// raft cluster.