2015-07-16 00:52:24 +00:00
|
|
|
package cluster
|
|
|
|
|
|
|
|
import (
|
2015-07-16 03:06:07 +00:00
|
|
|
"encoding/json"
|
2015-07-16 21:27:29 +00:00
|
|
|
"fmt"
|
2015-07-16 03:06:07 +00:00
|
|
|
"io"
|
2015-07-16 21:27:29 +00:00
|
|
|
"math/rand"
|
|
|
|
"net"
|
|
|
|
"time"
|
2015-07-16 03:06:07 +00:00
|
|
|
|
2015-07-16 02:53:10 +00:00
|
|
|
"github.com/influxdb/influxdb/meta"
|
2015-07-16 00:52:24 +00:00
|
|
|
"github.com/influxdb/influxdb/tsdb"
|
2015-07-16 21:27:29 +00:00
|
|
|
"gopkg.in/fatih/pool.v2"
|
2015-07-16 03:06:07 +00:00
|
|
|
)
|
|
|
|
|
2015-07-16 00:52:24 +00:00
|
|
|
// ShardMapper is responsible for providing mappers for requested shards. It is
|
|
|
|
// responsible for creating those mappers from the local store, or reaching
|
|
|
|
// out to another node on the cluster.
|
|
|
|
type ShardMapper struct {
|
2015-07-18 02:02:15 +00:00
|
|
|
ForceRemoteMapping bool // All shards treated as remote. Useful for testing.
|
|
|
|
|
2015-07-16 00:52:24 +00:00
|
|
|
MetaStore interface {
|
|
|
|
NodeID() uint64
|
2015-07-16 21:00:10 +00:00
|
|
|
Node(id uint64) (ni *meta.NodeInfo, err error)
|
2015-07-16 00:52:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TSDBStore interface {
|
|
|
|
CreateMapper(shardID uint64, query string, chunkSize int) (tsdb.Mapper, error)
|
|
|
|
}
|
2015-07-16 21:27:29 +00:00
|
|
|
|
|
|
|
timeout time.Duration
|
|
|
|
pool *clientPool
|
2015-07-16 00:52:24 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 03:06:07 +00:00
|
|
|
// NewShardMapper returns a mapper of local and remote shards.
|
2015-07-16 21:27:29 +00:00
|
|
|
func NewShardMapper(timeout time.Duration) *ShardMapper {
|
|
|
|
return &ShardMapper{
|
|
|
|
pool: newClientPool(),
|
|
|
|
timeout: timeout,
|
|
|
|
}
|
2015-07-16 00:52:24 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 03:06:07 +00:00
|
|
|
// CreateMapper returns a Mapper for the given shard ID.
|
2015-07-16 21:27:29 +00:00
|
|
|
func (s *ShardMapper) CreateMapper(sh meta.ShardInfo, stmt string, chunkSize int) (tsdb.Mapper, error) {
|
2015-07-16 02:53:10 +00:00
|
|
|
var err error
|
|
|
|
var m tsdb.Mapper
|
2015-07-18 02:02:15 +00:00
|
|
|
if sh.OwnedBy(s.MetaStore.NodeID()) && !s.ForceRemoteMapping {
|
2015-07-16 21:27:29 +00:00
|
|
|
m, err = s.TSDBStore.CreateMapper(sh.ID, stmt, chunkSize)
|
2015-07-16 02:53:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
2015-07-16 21:27:29 +00:00
|
|
|
// Pick a node in a pseudo-random manner.
|
|
|
|
conn, err := s.dial(sh.OwnerIDs[rand.Intn(len(sh.OwnerIDs))])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
conn.SetDeadline(time.Now().Add(s.timeout))
|
|
|
|
|
|
|
|
rm := NewRemoteMapper(conn.(*pool.PoolConn), sh.ID, stmt, chunkSize)
|
2015-07-16 21:00:10 +00:00
|
|
|
m = rm
|
2015-07-16 00:52:24 +00:00
|
|
|
}
|
2015-07-16 02:53:10 +00:00
|
|
|
|
2015-07-16 00:52:24 +00:00
|
|
|
return m, nil
|
|
|
|
}
|
2015-07-16 03:06:07 +00:00
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
func (s *ShardMapper) dial(nodeID uint64) (net.Conn, error) {
|
|
|
|
// If we don't have a connection pool for that addr yet, create one
|
|
|
|
_, ok := s.pool.getPool(nodeID)
|
|
|
|
if !ok {
|
|
|
|
factory := &connFactory{nodeID: nodeID, clientPool: s.pool, timeout: s.timeout}
|
|
|
|
factory.metaStore = s.MetaStore
|
|
|
|
|
|
|
|
p, err := pool.NewChannelPool(1, 3, factory.dial)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
s.pool.setPool(nodeID, p)
|
|
|
|
}
|
|
|
|
return s.pool.conn(nodeID)
|
|
|
|
}
|
|
|
|
|
|
|
|
type remoteShardConn interface {
|
|
|
|
io.ReadWriter
|
|
|
|
Close() error
|
|
|
|
MarkUnusable()
|
|
|
|
}
|
|
|
|
|
2015-07-16 03:06:07 +00:00
|
|
|
// RemoteMapper implements the tsdb.Mapper interface. It connects to a remote node,
|
|
|
|
// sends a query, and interprets the stream of data that comes back.
|
|
|
|
type RemoteMapper struct {
|
|
|
|
shardID uint64
|
|
|
|
stmt string
|
|
|
|
chunkSize int
|
|
|
|
|
|
|
|
tagsets []string
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
conn remoteShardConn
|
|
|
|
bufferedResponse *MapShardResponse
|
2015-07-16 03:06:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
// NewRemoteMapper returns a new remote mapper using the given connection.
|
|
|
|
func NewRemoteMapper(c remoteShardConn, shardID uint64, stmt string, chunkSize int) *RemoteMapper {
|
2015-07-16 03:06:07 +00:00
|
|
|
return &RemoteMapper{
|
2015-07-16 21:27:29 +00:00
|
|
|
conn: c,
|
2015-07-16 03:06:07 +00:00
|
|
|
shardID: shardID,
|
|
|
|
stmt: stmt,
|
|
|
|
chunkSize: chunkSize,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open connects to the remote node and starts receiving data.
|
2015-07-16 21:27:29 +00:00
|
|
|
func (r *RemoteMapper) Open() (err error) {
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
r.conn.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
// Build Map request.
|
|
|
|
var request MapShardRequest
|
|
|
|
request.SetShardID(r.shardID)
|
|
|
|
request.SetQuery(r.stmt)
|
|
|
|
request.SetChunkSize(int32(r.chunkSize))
|
|
|
|
|
|
|
|
// Marshal into protocol buffers.
|
|
|
|
buf, err := request.MarshalBinary()
|
2015-07-16 03:06:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
// Write request.
|
|
|
|
if err := WriteTLV(r.conn, mapShardRequestMessage, buf); err != nil {
|
|
|
|
r.conn.MarkUnusable()
|
|
|
|
return err
|
2015-07-16 03:06:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
// Read the response.
|
|
|
|
_, buf, err = ReadTLV(r.conn)
|
|
|
|
if err != nil {
|
|
|
|
r.conn.MarkUnusable()
|
|
|
|
return err
|
2015-07-16 03:06:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
// Unmarshal response.
|
|
|
|
r.bufferedResponse = &MapShardResponse{}
|
|
|
|
if err := r.bufferedResponse.UnmarshalBinary(buf); err != nil {
|
2015-07-16 03:06:07 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
if r.bufferedResponse.Code() != 0 {
|
|
|
|
return fmt.Errorf("error code %d: %s", r.bufferedResponse.Code(), r.bufferedResponse.Message())
|
|
|
|
}
|
2015-07-16 03:06:07 +00:00
|
|
|
|
|
|
|
// Decode the first response to get the TagSets.
|
2015-07-16 21:27:29 +00:00
|
|
|
r.tagsets = r.bufferedResponse.TagSets()
|
2015-07-16 03:06:07 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RemoteMapper) TagSets() []string {
|
|
|
|
return r.tagsets
|
|
|
|
}
|
|
|
|
|
|
|
|
// NextChunk returns the next chunk read from the remote node to the client.
|
2015-07-16 21:27:29 +00:00
|
|
|
func (r *RemoteMapper) NextChunk() (chunk interface{}, err error) {
|
|
|
|
output := &tsdb.MapperOutput{}
|
|
|
|
var response *MapShardResponse
|
2015-07-16 03:06:07 +00:00
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
if r.bufferedResponse != nil {
|
|
|
|
response = r.bufferedResponse
|
|
|
|
r.bufferedResponse = nil
|
|
|
|
} else {
|
|
|
|
response = &MapShardResponse{}
|
|
|
|
|
|
|
|
// Read the response.
|
|
|
|
_, buf, err := ReadTLV(r.conn)
|
|
|
|
if err != nil {
|
|
|
|
r.conn.MarkUnusable()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal response.
|
|
|
|
if err := response.UnmarshalBinary(buf); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.Code() != 0 {
|
|
|
|
return nil, fmt.Errorf("error code %d: %s", response.Code(), response.Message())
|
|
|
|
}
|
2015-07-16 03:06:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
if response.Data() == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(response.Data(), output)
|
|
|
|
return output, err
|
2015-07-16 03:06:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 21:27:29 +00:00
|
|
|
// Close the Mapper
|
2015-07-16 03:06:07 +00:00
|
|
|
func (r *RemoteMapper) Close() {
|
2015-07-16 21:27:29 +00:00
|
|
|
r.conn.Close()
|
2015-07-16 03:06:07 +00:00
|
|
|
}
|