wip: migrate to mono-repo. SPN has already been moved to spn/
This commit is contained in:
221
spn/navigator/route.go
Normal file
221
spn/navigator/route.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package navigator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Routes holds a collection of Routes.
|
||||
type Routes struct {
|
||||
All []*Route
|
||||
randomizeTopPercent float32
|
||||
maxCost float32 // automatic
|
||||
maxRoutes int // manual setting
|
||||
}
|
||||
|
||||
// Len is the number of elements in the collection.
|
||||
func (r *Routes) Len() int {
|
||||
return len(r.All)
|
||||
}
|
||||
|
||||
// Less reports whether the element with index i should sort before the element
|
||||
// with index j.
|
||||
func (r *Routes) Less(i, j int) bool {
|
||||
return r.All[i].TotalCost < r.All[j].TotalCost
|
||||
}
|
||||
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
func (r *Routes) Swap(i, j int) {
|
||||
r.All[i], r.All[j] = r.All[j], r.All[i]
|
||||
}
|
||||
|
||||
// isGoodEnough reports whether the route would survive a clean process.
|
||||
func (r *Routes) isGoodEnough(route *Route) bool {
|
||||
if r.maxCost > 0 && route.TotalCost > r.maxCost {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// add adds a Route if it is good enough.
|
||||
func (r *Routes) add(route *Route) {
|
||||
if !r.isGoodEnough(route) {
|
||||
return
|
||||
}
|
||||
r.All = append(r.All, route.CopyUpTo(0))
|
||||
r.clean()
|
||||
}
|
||||
|
||||
// clean sort and shortens the list to the configured maximum.
|
||||
func (r *Routes) clean() {
|
||||
// Sort Routes so that the best ones are on top.
|
||||
sort.Sort(r)
|
||||
// Remove all remaining from the list.
|
||||
if len(r.All) > r.maxRoutes {
|
||||
r.All = r.All[:r.maxRoutes]
|
||||
}
|
||||
// Set new maximum total cost.
|
||||
if len(r.All) >= r.maxRoutes {
|
||||
r.maxCost = r.All[len(r.All)-1].TotalCost
|
||||
}
|
||||
}
|
||||
|
||||
// randomizeTop randomized to the top nearest pins for balancing the network.
|
||||
func (r *Routes) randomizeTop() {
|
||||
switch {
|
||||
case r.randomizeTopPercent == 0:
|
||||
// Check if randomization is enabled.
|
||||
return
|
||||
case len(r.All) < 2:
|
||||
// Check if we have enough pins to work with.
|
||||
return
|
||||
}
|
||||
|
||||
// Find randomization set.
|
||||
randomizeUpTo := len(r.All)
|
||||
threshold := r.All[0].TotalCost * (1 + r.randomizeTopPercent)
|
||||
for i, r := range r.All {
|
||||
// Find first value above the threshold to stop.
|
||||
if r.TotalCost > threshold {
|
||||
randomizeUpTo = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle top set.
|
||||
if randomizeUpTo >= 2 {
|
||||
mr := mrand.New(mrand.NewSource(time.Now().UnixNano())) //nolint:gosec
|
||||
mr.Shuffle(randomizeUpTo, r.Swap)
|
||||
}
|
||||
}
|
||||
|
||||
// Route is a path through the map.
|
||||
type Route struct {
|
||||
// Path is a list of Transit Hubs and the Destination Hub, including the Cost
|
||||
// for each Hop.
|
||||
Path []*Hop
|
||||
|
||||
// DstCost is the calculated cost between the Destination Hub and the destination IP.
|
||||
DstCost float32
|
||||
|
||||
// TotalCost is the sum of all costs of this Route.
|
||||
TotalCost float32
|
||||
|
||||
// Algorithm is the ID of the algorithm used to calculate the route.
|
||||
Algorithm string
|
||||
}
|
||||
|
||||
// Hop is one hop of a route's path.
|
||||
type Hop struct {
|
||||
pin *Pin
|
||||
|
||||
// HubID is the Hub ID.
|
||||
HubID string
|
||||
|
||||
// Cost is the cost for both Lane to this Hub and the Hub itself.
|
||||
Cost float32
|
||||
}
|
||||
|
||||
// addHop adds a hop to the route.
|
||||
func (r *Route) addHop(pin *Pin, cost float32) {
|
||||
r.Path = append(r.Path, &Hop{
|
||||
pin: pin,
|
||||
Cost: cost,
|
||||
})
|
||||
r.recalculateTotalCost()
|
||||
}
|
||||
|
||||
// completeRoute completes the route by adding the destination cost of the
|
||||
// connection between the last hop and the destination IP.
|
||||
func (r *Route) completeRoute(dstCost float32) {
|
||||
r.DstCost = dstCost
|
||||
r.recalculateTotalCost()
|
||||
}
|
||||
|
||||
// removeHop removes the last hop from the Route.
|
||||
func (r *Route) removeHop() {
|
||||
// Reset DstCost, as the route might have been completed.
|
||||
r.DstCost = 0
|
||||
|
||||
if len(r.Path) >= 1 {
|
||||
r.Path = r.Path[:len(r.Path)-1]
|
||||
}
|
||||
r.recalculateTotalCost()
|
||||
}
|
||||
|
||||
// recalculateTotalCost recalculates to total cost of this route.
|
||||
func (r *Route) recalculateTotalCost() {
|
||||
r.TotalCost = r.DstCost
|
||||
for _, hop := range r.Path {
|
||||
if hop.pin.HasActiveTerminal() {
|
||||
// If we have an active connection, only take 80% of the cost.
|
||||
r.TotalCost += hop.Cost * 0.8
|
||||
} else {
|
||||
r.TotalCost += hop.Cost
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CopyUpTo makes a somewhat deep copy of the Route up to the specified amount
|
||||
// and returns it. Hops themselves are not copied, because their data does not
|
||||
// change. Therefore, returned Hops may not be edited.
|
||||
// Specify an amount of 0 to copy all.
|
||||
func (r *Route) CopyUpTo(n int) *Route {
|
||||
// Check amount.
|
||||
if n == 0 || n > len(r.Path) {
|
||||
n = len(r.Path)
|
||||
}
|
||||
|
||||
newRoute := &Route{
|
||||
Path: make([]*Hop, n),
|
||||
DstCost: r.DstCost,
|
||||
TotalCost: r.TotalCost,
|
||||
}
|
||||
copy(newRoute.Path, r.Path)
|
||||
return newRoute
|
||||
}
|
||||
|
||||
// makeExportReady fills in all the missing data fields which are meant for
|
||||
// exporting only.
|
||||
func (r *Routes) makeExportReady(algorithm string) {
|
||||
for _, route := range r.All {
|
||||
route.makeExportReady(algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
// makeExportReady fills in all the missing data fields which are meant for
|
||||
// exporting only.
|
||||
func (r *Route) makeExportReady(algorithm string) {
|
||||
r.Algorithm = algorithm
|
||||
for _, hop := range r.Path {
|
||||
hop.makeExportReady()
|
||||
}
|
||||
}
|
||||
|
||||
// makeExportReady fills in all the missing data fields which are meant for
|
||||
// exporting only.
|
||||
func (hop *Hop) makeExportReady() {
|
||||
hop.HubID = hop.pin.Hub.ID
|
||||
}
|
||||
|
||||
// Pin returns the Pin of the Hop.
|
||||
func (hop *Hop) Pin() *Pin {
|
||||
return hop.pin
|
||||
}
|
||||
|
||||
func (r *Route) String() string {
|
||||
s := make([]string, 0, len(r.Path)+2)
|
||||
s = append(s, fmt.Sprintf("route with %.2fc:", r.TotalCost))
|
||||
for i, hop := range r.Path {
|
||||
if i == 0 {
|
||||
s = append(s, hop.pin.String())
|
||||
} else {
|
||||
s = append(s, fmt.Sprintf("--> %.2fc %s", hop.Cost, hop.pin))
|
||||
}
|
||||
}
|
||||
s = append(s, fmt.Sprintf("--> %.2fc", r.DstCost))
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user