code: func (s *Store) Join(id, addr string, metadata map[string]string) error {
  	s.logger.Printf("received request to join node at %s", addr)
  	if s.raft.State() != raft.Leader {
  		return ErrNotLeader
  	}
  
  	configFuture := s.raft.GetConfiguration()
  	if err := configFuture.Error(); err != nil {
  		s.logger.Printf("failed to get raft configuration: %v", err)
  		return err
  	}
  
  	for _, srv := range configFuture.Configuration().Servers {
  		// If a node already exists with either the joining node's ID or address,
  		// that node may need to be removed from the config first.
  		if srv.ID == raft.ServerID(id) || srv.Address == raft.ServerAddress(addr) {
  			// However if *both* the ID and the address are the same, the no
  			// join is actually needed.
  			if srv.Address == raft.ServerAddress(addr) && srv.ID == raft.ServerID(id) {
  				s.logger.Printf("node %s at %s already member of cluster, ignoring join request",
  					id, addr)
  				return nil
  			}
  			if err := s.remove(id); err != nil {
  				s.logger.Printf("failed to remove node: %v", err)
  				return err
  			}
  		}
  	}
  
  	f := s.raft.AddVoter(raft.ServerID(id), raft.ServerAddress(addr), 0, 0)
  	if e := f.(raft.Future); e.Error() != nil {
  		if e.Error() == raft.ErrNotLeader {
  			return ErrNotLeader
  		}
  		return e.Error()
  	}
  
  	if err := s.setMetadata(id, metadata); err != nil {
  		return err
  	}
  
  	s.logger.Printf("node at %s joined successfully", addr)
  	return nil
  }
docs: // Join joins a node, identified by id and located at addr, to this store.
  // The node must be ready to respond to Raft communications at that address.