sdwan: add hub node selection and auto fallback to mesh
This commit is contained in:
@@ -343,6 +343,10 @@ func main() {
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.Mode == "hub" && req.HubNode == "" {
|
||||||
|
http.Error(w, "hub mode requires hubNode", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
// tenant filter by session/apikey
|
// tenant filter by session/apikey
|
||||||
tenantID := getTenantID(r)
|
tenantID := getTenantID(r)
|
||||||
if tenantID > 0 {
|
if tenantID > 0 {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
@@ -24,6 +25,15 @@ func (s *Server) SetSDWAN(cfg protocol.SDWANConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetSDWANTenant(tenantID int64, cfg protocol.SDWANConfig) error {
|
func (s *Server) SetSDWANTenant(tenantID int64, cfg protocol.SDWANConfig) error {
|
||||||
|
if cfg.Mode == "hub" {
|
||||||
|
if cfg.HubNode == "" {
|
||||||
|
return errors.New("hub mode requires hubNode")
|
||||||
|
}
|
||||||
|
hub := s.GetNode(cfg.HubNode)
|
||||||
|
if hub == nil || !hub.IsOnline() || hub.TenantID != tenantID || !hub.RelayEnabled {
|
||||||
|
return errors.New("hub node must be online and relay-enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := s.sdwan.saveTenant(tenantID, cfg); err != nil {
|
if err := s.sdwan.saveTenant(tenantID, cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/openp2p-cn/inp2p/pkg/auth"
|
|
||||||
"github.com/openp2p-cn/inp2p/internal/store"
|
"github.com/openp2p-cn/inp2p/internal/store"
|
||||||
|
"github.com/openp2p-cn/inp2p/pkg/auth"
|
||||||
"github.com/openp2p-cn/inp2p/pkg/config"
|
"github.com/openp2p-cn/inp2p/pkg/config"
|
||||||
"github.com/openp2p-cn/inp2p/pkg/protocol"
|
"github.com/openp2p-cn/inp2p/pkg/protocol"
|
||||||
"github.com/openp2p-cn/inp2p/pkg/signal"
|
"github.com/openp2p-cn/inp2p/pkg/signal"
|
||||||
@@ -77,6 +77,15 @@ func New(cfg config.ServerConfig) *Server {
|
|||||||
st, err := store.Open(cfg.DBPath)
|
st, err := store.Open(cfg.DBPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[server] open store failed: %v", err)
|
log.Printf("[server] open store failed: %v", err)
|
||||||
|
} else {
|
||||||
|
// bootstrap default admin/admin in tenant 1
|
||||||
|
if _, gErr := st.GetTenantByID(1); gErr != nil {
|
||||||
|
if _, _, _, cErr := st.CreateTenantWithUsers("default", "admin", "admin"); cErr != nil {
|
||||||
|
log.Printf("[server] bootstrap default tenant failed: %v", cErr)
|
||||||
|
} else {
|
||||||
|
log.Printf("[server] bootstrap default tenant created (tenant=1, admin/admin)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &Server{
|
return &Server{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
@@ -86,7 +95,7 @@ func New(cfg config.ServerConfig) *Server {
|
|||||||
store: st,
|
store: st,
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
upgrader: websocket.Upgrader{
|
upgrader: websocket.Upgrader{
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
ReadBufferSize: 4096,
|
ReadBufferSize: 4096,
|
||||||
WriteBufferSize: 4096,
|
WriteBufferSize: 4096,
|
||||||
},
|
},
|
||||||
@@ -550,7 +559,7 @@ func (s *Server) broadcastNodeOnline(nodeName string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartCleanup periodically removes stale nodes.
|
// StartCleanup periodically removes stale nodes and checks SDWAN hub health.
|
||||||
func (s *Server) StartCleanup() {
|
func (s *Server) StartCleanup() {
|
||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
@@ -567,6 +576,32 @@ func (s *Server) StartCleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
// hub offline -> auto mesh (tenant configs)
|
||||||
|
if s.sdwan != nil {
|
||||||
|
sd := s.sdwan
|
||||||
|
sd.mu.RLock()
|
||||||
|
m := make(map[int64]protocol.SDWANConfig, len(sd.multi))
|
||||||
|
for k, v := range sd.multi {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
sd.mu.RUnlock()
|
||||||
|
for tid, cfg := range m {
|
||||||
|
if cfg.Mode != "hub" || cfg.HubNode == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hub := s.GetNode(cfg.HubNode)
|
||||||
|
if hub != nil && hub.IsOnline() && hub.TenantID == tid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// auto fallback to mesh
|
||||||
|
cfg.Mode = "mesh"
|
||||||
|
cfg.HubNode = ""
|
||||||
|
_ = s.sdwan.saveTenant(tid, cfg)
|
||||||
|
s.broadcastSDWANTenant(tid, cfg)
|
||||||
|
log.Printf("[sdwan] hub offline, auto fallback to mesh (tenant=%d)", tid)
|
||||||
|
}
|
||||||
|
}
|
||||||
case <-s.quit:
|
case <-s.quit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,7 +297,8 @@ type SDWANConfig struct {
|
|||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
GatewayCIDR string `json:"gatewayCIDR"`
|
GatewayCIDR string `json:"gatewayCIDR"`
|
||||||
Mode string `json:"mode,omitempty"` // hub | mesh | fullmesh
|
Mode string `json:"mode,omitempty"` // hub | mesh | fullmesh
|
||||||
IP string `json:"ip,omitempty"` // node self IP if pushed per-node
|
HubNode string `json:"hubNode,omitempty"`
|
||||||
|
IP string `json:"ip,omitempty"` // node self IP if pushed per-node
|
||||||
MTU int `json:"mtu,omitempty"`
|
MTU int `json:"mtu,omitempty"`
|
||||||
Routes []string `json:"routes,omitempty"`
|
Routes []string `json:"routes,omitempty"`
|
||||||
Nodes []SDWANNode `json:"nodes"`
|
Nodes []SDWANNode `json:"nodes"`
|
||||||
|
|||||||
@@ -124,11 +124,16 @@
|
|||||||
<input class="ipt max-w-xs" v-model="sd.name" placeholder="名称">
|
<input class="ipt max-w-xs" v-model="sd.name" placeholder="名称">
|
||||||
<input class="ipt max-w-xs" v-model="sd.gatewayCIDR" placeholder="网段,如 10.10.0.0/24">
|
<input class="ipt max-w-xs" v-model="sd.gatewayCIDR" placeholder="网段,如 10.10.0.0/24">
|
||||||
<select class="ipt max-w-[140px]" v-model="sd.mode"><option value="mesh">mesh</option><option value="hub">hub</option></select>
|
<select class="ipt max-w-[140px]" v-model="sd.mode"><option value="mesh">mesh</option><option value="hub">hub</option></select>
|
||||||
|
<select v-if="sd.mode==='hub'" class="ipt max-w-[220px]" v-model="sd.hubNode">
|
||||||
|
<option value="">选择 Hub 节点</option>
|
||||||
|
<option v-for="n in nodes" :key="'hub'+n.name" :value="n.name">{{ n.alias || n.name }}</option>
|
||||||
|
</select>
|
||||||
<input class="ipt max-w-[120px]" type="number" min="1200" max="9000" v-model.number="sd.mtu" placeholder="MTU">
|
<input class="ipt max-w-[120px]" type="number" min="1200" max="9000" v-model.number="sd.mtu" placeholder="MTU">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button class="btn2" @click="autoAssignIPs">自动分配 IP</button>
|
<button class="btn2" @click="autoAssignIPs">自动分配 IP</button>
|
||||||
<button class="btn" :disabled="busy" @click="saveSDWAN">保存 SDWAN</button>
|
<button class="btn" :disabled="busy" @click="saveSDWAN">保存 SDWAN</button>
|
||||||
|
<div v-if="sd.mode==='hub'" class="text-xs text-slate-400">Hub 离线将自动回 Mesh</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -286,7 +291,7 @@ createApp({
|
|||||||
const refreshSec = ref(15), timer = ref(null);
|
const refreshSec = ref(15), timer = ref(null);
|
||||||
|
|
||||||
const health = ref({}), stats = ref({}), nodes = ref([]), nodeKeyword = ref('');
|
const health = ref({}), stats = ref({}), nodes = ref([]), nodeKeyword = ref('');
|
||||||
const sd = ref({ enabled:false, name:'sdwan-main', gatewayCIDR:'10.10.0.0/24', mode:'mesh', mtu:1420, nodes:[], routes:['10.10.0.0/24'] });
|
const sd = ref({ enabled:false, name:'sdwan-main', gatewayCIDR:'10.10.0.0/24', mode:'mesh', hubNode:'', mtu:1420, nodes:[], routes:['10.10.0.0/24'] });
|
||||||
const connectForm = ref({ from:'', to:'', srcPort:80, dstPort:80, appName:'manual-connect' });
|
const connectForm = ref({ from:'', to:'', srcPort:80, dstPort:80, appName:'manual-connect' });
|
||||||
|
|
||||||
const tenants = ref([]), activeTenant = ref(1), keys = ref([]), users = ref([]), enrolls = ref([]);
|
const tenants = ref([]), activeTenant = ref(1), keys = ref([]), users = ref([]), enrolls = ref([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user