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)
|
||||
return
|
||||
}
|
||||
if req.Mode == "hub" && req.HubNode == "" {
|
||||
http.Error(w, "hub mode requires hubNode", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/netip"
|
||||
|
||||
@@ -24,6 +25,15 @@ func (s *Server) SetSDWAN(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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/openp2p-cn/inp2p/pkg/auth"
|
||||
"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/protocol"
|
||||
"github.com/openp2p-cn/inp2p/pkg/signal"
|
||||
@@ -77,6 +77,15 @@ func New(cfg config.ServerConfig) *Server {
|
||||
st, err := store.Open(cfg.DBPath)
|
||||
if err != nil {
|
||||
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{
|
||||
cfg: cfg,
|
||||
@@ -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() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
@@ -567,6 +576,32 @@ func (s *Server) StartCleanup() {
|
||||
}
|
||||
}
|
||||
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:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@ type SDWANConfig struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
GatewayCIDR string `json:"gatewayCIDR"`
|
||||
Mode string `json:"mode,omitempty"` // hub | mesh | fullmesh
|
||||
HubNode string `json:"hubNode,omitempty"`
|
||||
IP string `json:"ip,omitempty"` // node self IP if pushed per-node
|
||||
MTU int `json:"mtu,omitempty"`
|
||||
Routes []string `json:"routes,omitempty"`
|
||||
|
||||
@@ -124,11 +124,16 @@
|
||||
<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">
|
||||
<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">
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn2" @click="autoAssignIPs">自动分配 IP</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>
|
||||
|
||||
@@ -286,7 +291,7 @@ createApp({
|
||||
const refreshSec = ref(15), timer = ref(null);
|
||||
|
||||
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 tenants = ref([]), activeTenant = ref(1), keys = ref([]), users = ref([]), enrolls = ref([]);
|
||||
|
||||
Reference in New Issue
Block a user