blob: bb7e7a771f81cc19462c17f55fe13e47d4b08d4b [file]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// Package node provides node selector for liaison.
package node
import (
"context"
"fmt"
"sync"
"github.com/pkg/errors"
"golang.org/x/exp/slices"
databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
"github.com/apache/skywalking-banyandb/banyand/metadata/schema"
"github.com/apache/skywalking-banyandb/banyand/queue/pub"
"github.com/apache/skywalking-banyandb/pkg/run"
)
var (
_ Selector = (*pickFirstSelector)(nil)
// ErrNoAvailableNode will be returned if no node is available.
ErrNoAvailableNode = errors.New("selector: no available node")
)
// Selector keeps all data nodes in the memory and can provide different algorithm to pick an available node.
//
//go:generate mockgen -destination=mock/node_selector_mock.go -package=mock github.com/apache/skywalking-banyandb/pkg/node Selector
type Selector interface {
OnInit(kinds []schema.Kind) (bool, []int64)
AddNode(node *databasev1.Node)
RemoveNode(node *databasev1.Node)
SetNodeSelector(selector *pub.LabelSelector)
Pick(group, name string, shardID, replicaID uint32) (string, error)
run.PreRunner
fmt.Stringer
}
// NewPickFirstSelector returns a simple selector that always returns the first node if exists.
func NewPickFirstSelector() (Selector, error) {
return &pickFirstSelector{
nodeIDMap: make(map[string]struct{}),
}, nil
}
// pickFirstSelector always pick the first node in the sorted node ids list.
type pickFirstSelector struct {
nodeIDMap map[string]struct{}
nodeIDs []string
mu sync.RWMutex
}
// OnInit implements Selector.
func (p *pickFirstSelector) OnInit(_ []schema.Kind) (bool, []int64) {
return true, []int64{0}
}
// SetNodeSelector implements Selector.
func (p *pickFirstSelector) SetNodeSelector(_ *pub.LabelSelector) {}
// String implements Selector.
func (p *pickFirstSelector) String() string {
n, err := p.Pick("", "", 0, 0)
if err != nil {
return fmt.Sprintf("%v", err)
}
p.mu.Lock()
defer p.mu.Unlock()
return fmt.Sprintf("pick [%s] from %s", n, p.nodeIDs)
}
func (p *pickFirstSelector) PreRun(context.Context) error {
return nil
}
func (p *pickFirstSelector) Name() string {
return "pick-first"
}
func (p *pickFirstSelector) AddNode(node *databasev1.Node) {
nodeID := node.GetMetadata().GetName()
p.mu.RLock()
if _, ok := p.nodeIDMap[nodeID]; ok {
p.mu.RUnlock()
return
}
p.mu.RUnlock()
p.mu.Lock()
defer p.mu.Unlock()
p.nodeIDMap[nodeID] = struct{}{}
p.nodeIDs = append(p.nodeIDs, nodeID)
slices.Sort(p.nodeIDs)
}
func (p *pickFirstSelector) RemoveNode(node *databasev1.Node) {
nodeID := node.GetMetadata().GetName()
p.mu.RLock()
if _, ok := p.nodeIDMap[nodeID]; !ok {
p.mu.RUnlock()
return
}
p.mu.RUnlock()
p.mu.Lock()
defer p.mu.Unlock()
delete(p.nodeIDMap, nodeID)
idx, ok := slices.BinarySearch(p.nodeIDs, nodeID)
if !ok {
return
}
p.nodeIDs = slices.Delete(p.nodeIDs, idx, idx+1)
}
func (p *pickFirstSelector) Pick(_, _ string, _, _ uint32) (string, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if len(p.nodeIDs) == 0 {
return "", ErrNoAvailableNode
}
return p.nodeIDs[0], nil
}