summaryrefslogtreecommitdiff
path: root/internal/routing/router.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/routing/router.go')
-rw-r--r--internal/routing/router.go81
1 files changed, 81 insertions, 0 deletions
diff --git a/internal/routing/router.go b/internal/routing/router.go
new file mode 100644
index 0000000..53e5261
--- /dev/null
+++ b/internal/routing/router.go
@@ -0,0 +1,81 @@
+package routing
+
+import (
+ "errors"
+ "sort"
+ "strconv"
+ "sync"
+ "sync/atomic"
+
+ "aigw/internal/catalog"
+ "aigw/internal/domain"
+)
+
+var ErrNoRoute = errors.New("no compatible upstream route")
+
+type Router struct {
+ catalog *catalog.Catalog
+ counters sync.Map
+}
+
+func New(catalog *catalog.Catalog) *Router {
+ return &Router{catalog: catalog}
+}
+
+func (r *Router) Plan(modelID string, protocol domain.Protocol) ([]domain.Route, error) {
+ model, err := r.catalog.Model(modelID)
+ if err != nil {
+ return nil, err
+ }
+ routes := make([]domain.Route, 0, len(model.Routes))
+ for _, route := range model.Routes {
+ if route.Provider.Protocol == protocol {
+ routes = append(routes, route)
+ }
+ }
+ if len(routes) == 0 {
+ return nil, ErrNoRoute
+ }
+
+ sort.SliceStable(routes, func(i, j int) bool { return routes[i].Priority < routes[j].Priority })
+ result := make([]domain.Route, 0, len(routes))
+ for start := 0; start < len(routes); {
+ end := start + 1
+ for end < len(routes) && routes[end].Priority == routes[start].Priority {
+ end++
+ }
+ result = append(result, r.rotate(modelID, protocol, routes[start:end])...)
+ start = end
+ }
+ return result, nil
+}
+
+func (r *Router) rotate(modelID string, protocol domain.Protocol, routes []domain.Route) []domain.Route {
+ if len(routes) < 2 {
+ return append([]domain.Route(nil), routes...)
+ }
+ key := modelID + "\x00" + string(protocol) + "\x00" + strconv.Itoa(routes[0].Priority)
+ counterValue, _ := r.counters.LoadOrStore(key, &atomic.Uint64{})
+ counter := counterValue.(*atomic.Uint64).Add(1) - 1
+
+ totalWeight := 0
+ for _, route := range routes {
+ totalWeight += route.Weight
+ }
+ position := int(counter % uint64(totalWeight))
+ selected := 0
+ for i, route := range routes {
+ if position < route.Weight {
+ selected = i
+ break
+ }
+ position -= route.Weight
+ }
+
+ result := make([]domain.Route, 0, len(routes))
+ result = append(result, routes[selected])
+ for offset := 1; offset < len(routes); offset++ {
+ result = append(result, routes[(selected+offset)%len(routes)])
+ }
+ return result
+}