Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

feature: define the interface SupernodeLocator #1294

Merged
merged 1 commit into from
May 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dfget/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ const (
ServerAliveTime = 5 * time.Minute
DefaultDownloadTimeout = 5 * time.Minute

DefaultSupernodePort = 8002
DefaultSupernodeSchema = "http"
DefaultSupernodePort = 8002
)

/* errors code */
Expand Down
77 changes: 77 additions & 0 deletions dfget/locator/locator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright The Dragonfly Authors.
*
* Licensed 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 locator

// SupernodeLocator defines the way how to get available supernodes.
// Developers can implement their own locator more flexibly , not just get the
// supernode list from configuration or CLI.
type SupernodeLocator interface {
// Get returns the current selected supernode, it should be idempotent.
Get() *Supernode

// Next chooses the next available supernode for retrying or other
// purpose. The current supernode should be set as this result.
Next() *Supernode

// GetGroup returns the group with the giving name.
GetGroup(name string) *SupernodeGroup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, why consider add a supernode group concept here? And the SupernodeGroup can only be retrieved for viewing. Is it expected that I can choose a group to use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, we can group supernodes by region, business, version and so on. The implementation of SupernodeLocator can select a supernode based on the group. The caller does not need to care about the specific selection logic, but if it is really needed, the supernode list can also be obtained through this interface.


// All returns all the supernodes.
All() []*SupernodeGroup

// Report records the metrics of the current supernode in order to choose a
// more appropriate supernode for the next time if necessary.
Report(node string, metrics *SupernodeMetrics)

// Refresh refreshes all the supernodes.
Refresh() bool
}

// SupernodeGroup groups supernodes which have same attributes.
// For example, we can group supernodes by region, business, version and so on.
// The implementation of SupernodeLocator can select a supernode based on the
// group.
type SupernodeGroup struct {
Name string
Nodes []*Supernode

// Infos stores other information that user can customized.
Infos map[string]string
}

// GetNode return the node with the giving index.
func (sg *SupernodeGroup) GetNode(idx int) *Supernode {
if idx < 0 || idx >= len(sg.Nodes) {
return nil
}
return sg.Nodes[idx]
}

// Supernode holds the basic information of supernodes.
type Supernode struct {
Schema string
IP string
Port int
Weight int
GroupName string
Metrics *SupernodeMetrics
}

// SupernodeMetrics holds metrics used for the locator to choose supernode.
type SupernodeMetrics struct {
Metrics map[string]interface{}
}
147 changes: 147 additions & 0 deletions dfget/locator/static_locator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright The Dragonfly Authors.
*
* Licensed 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 locator

import (
"math/rand"
"sync/atomic"
"time"

"github.com/dragonflyoss/Dragonfly/dfget/config"
"github.com/dragonflyoss/Dragonfly/pkg/algorithm"
"github.com/dragonflyoss/Dragonfly/pkg/netutils"
)

func init() {
rand.Seed(time.Now().UnixNano())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can consider moving rand.Seed(time.Now().UnixNano()) to the beginning of the main function to ensure that it always work and also avoids calling it everywhere. 😕

func main() {
	rand.Seed(time.Now().UnixNano())
	
	app.Execute()
}

Copy link
Member Author

@lowzj lowzj Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about move rand.Seed to the top package of dfget rather than main function? I think this operation should be transparent to the caller.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. That's OK with me. Just don't like calling rand.Seed everywhere!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to ensure the independence of this pr, I open another pr #1318 to do this, please take a look.

}

const staticLocatorGroupName = "config"

// StaticLocator uses the nodes passed from configuration or CLI.
type StaticLocator struct {
idx int32
Group *SupernodeGroup
}

// ----------------------------------------------------------------------------
// constructors

// NewStaticLocator constructs StaticLocator which uses the nodes passed from
// configuration or CLI.
func NewStaticLocator(nodes []*config.NodeWeight) *StaticLocator {
locator := &StaticLocator{}
if len(nodes) == 0 {
return locator
}
group := &SupernodeGroup{
Name: staticLocatorGroupName,
}
for _, node := range nodes {
ip, port := netutils.GetIPAndPortFromNode(node.Node, config.DefaultSupernodePort)
if ip == "" {
continue
}
supernode := &Supernode{
Schema: config.DefaultSupernodeSchema,
IP: ip,
Port: port,
Weight: node.Weight,
GroupName: staticLocatorGroupName,
}
for i := 0; i < supernode.Weight; i++ {
group.Nodes = append(group.Nodes, supernode)
}
}
shuffleNodes(group.Nodes)
locator.Group = group
return locator
}

// NewStaticLocatorFromStr constructs StaticLocator from string list.
// The format of nodes is: ip:port=weight
func NewStaticLocatorFromStr(nodes []string) (*StaticLocator, error) {
nodeWeight, err := config.ParseNodesSlice(nodes)
if err != nil {
return nil, err
}
return NewStaticLocator(nodeWeight), nil
}

// ----------------------------------------------------------------------------
// implement api methods

func (s *StaticLocator) Get() *Supernode {
if s.Group == nil {
return nil
}
return s.Group.GetNode(s.load())
}

func (s *StaticLocator) Next() *Supernode {
if s.Group == nil || s.load() >= len(s.Group.Nodes) {
return nil
}
return s.Group.GetNode(s.inc())
}

func (s *StaticLocator) GetGroup(name string) *SupernodeGroup {
if s.Group == nil || s.Group.Name != name {
return nil
}
return s.Group
}

func (s *StaticLocator) All() []*SupernodeGroup {
if s.Group == nil {
return nil
}
return []*SupernodeGroup{s.Group}
}

func (s *StaticLocator) Report(node string, metrics *SupernodeMetrics) {
// unnecessary to implement this method
return
}

func (s *StaticLocator) Refresh() bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks more like Reset 😅 How about rename as Reset.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation looks more like Reset. But considering the different behaviors of other implementations, it may get a new list of supernodes via invoking this method.
So IMOP, Reresh is more appropriate. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense!

atomic.StoreInt32(&s.idx, 0)
return true
}

// ----------------------------------------------------------------------------
// private methods of StaticLocator

func (s *StaticLocator) load() int {
return int(atomic.LoadInt32(&s.idx))
}

func (s *StaticLocator) inc() int {
return int(atomic.AddInt32(&s.idx, 1))
}

// ----------------------------------------------------------------------------
// helper functions

func shuffleNodes(nodes []*Supernode) []*Supernode {
if length := len(nodes); length > 1 {
algorithm.Shuffle(length, func(i, j int) {
nodes[i], nodes[j] = nodes[j], nodes[i]
})
}
return nodes
}
Loading