failover.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. package controllers
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "github.com/google/uuid"
  9. "github.com/gorilla/mux"
  10. controller "github.com/gravitl/netmaker/controllers"
  11. "github.com/gravitl/netmaker/db"
  12. "github.com/gravitl/netmaker/logger"
  13. "github.com/gravitl/netmaker/logic"
  14. "github.com/gravitl/netmaker/models"
  15. "github.com/gravitl/netmaker/mq"
  16. proLogic "github.com/gravitl/netmaker/pro/logic"
  17. "github.com/gravitl/netmaker/schema"
  18. "golang.org/x/exp/slog"
  19. )
  20. // FailOverHandlers - handlers for FailOver
  21. func FailOverHandlers(r *mux.Router) {
  22. r.HandleFunc("/api/v1/node/{nodeid}/failover", controller.Authorize(true, false, "host", http.HandlerFunc(getfailOver))).
  23. Methods(http.MethodGet)
  24. r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(createfailOver))).
  25. Methods(http.MethodPost)
  26. r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(deletefailOver))).
  27. Methods(http.MethodDelete)
  28. r.HandleFunc("/api/v1/node/{network}/failover/reset", logic.SecurityCheck(true, http.HandlerFunc(resetFailOver))).
  29. Methods(http.MethodPost)
  30. r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).
  31. Methods(http.MethodPost)
  32. r.HandleFunc("/api/v1/node/{nodeid}/failover_check", controller.Authorize(true, false, "host", http.HandlerFunc(checkfailOverCtx))).
  33. Methods(http.MethodGet)
  34. }
  35. // @Summary Get failover node
  36. // @Router /api/v1/node/{nodeid}/failover [get]
  37. // @Tags PRO
  38. // @Param nodeid path string true "Node ID"
  39. // @Success 200 {object} models.Node
  40. // @Failure 400 {object} models.ErrorResponse
  41. // @Failure 404 {object} models.ErrorResponse
  42. func getfailOver(w http.ResponseWriter, r *http.Request) {
  43. var params = mux.Vars(r)
  44. nodeid := params["nodeid"]
  45. // confirm host exists
  46. node, err := logic.GetNodeByID(nodeid)
  47. if err != nil {
  48. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  49. return
  50. }
  51. failOverNode, exists := proLogic.FailOverExists(node.Network)
  52. if !exists {
  53. logic.ReturnErrorResponse(
  54. w,
  55. r,
  56. logic.FormatError(errors.New("failover node not found"), "notfound"),
  57. )
  58. return
  59. }
  60. w.Header().Set("Content-Type", "application/json")
  61. logic.ReturnSuccessResponseWithJson(w, r, failOverNode, "get failover node successfully")
  62. }
  63. // @Summary Create failover node
  64. // @Router /api/v1/node/{nodeid}/failover [post]
  65. // @Tags PRO
  66. // @Param nodeid path string true "Node ID"
  67. // @Success 200 {object} models.Node
  68. // @Failure 400 {object} models.ErrorResponse
  69. // @Failure 500 {object} models.ErrorResponse
  70. func createfailOver(w http.ResponseWriter, r *http.Request) {
  71. var params = mux.Vars(r)
  72. nodeid := params["nodeid"]
  73. // confirm host exists
  74. node, err := logic.GetNodeByID(nodeid)
  75. if err != nil {
  76. slog.Error("failed to get node:", "error", err.Error())
  77. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  78. return
  79. }
  80. err = proLogic.CreateFailOver(node)
  81. if err != nil {
  82. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  83. return
  84. }
  85. go mq.PublishPeerUpdate(false)
  86. w.Header().Set("Content-Type", "application/json")
  87. logic.ReturnSuccessResponseWithJson(w, r, node, "created failover successfully")
  88. }
  89. // @Summary Reset failover for a network
  90. // @Router /api/v1/node/{network}/failover/reset [post]
  91. // @Tags PRO
  92. // @Param network path string true "Network ID"
  93. // @Success 200 {object} models.SuccessResponse
  94. // @Failure 500 {object} models.ErrorResponse
  95. func resetFailOver(w http.ResponseWriter, r *http.Request) {
  96. var params = mux.Vars(r)
  97. net := params["network"]
  98. nodes, err := logic.GetNetworkNodes(net)
  99. if err != nil {
  100. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  101. return
  102. }
  103. for _, node := range nodes {
  104. if node.FailedOverBy != uuid.Nil {
  105. node.FailedOverBy = uuid.Nil
  106. node.FailOverPeers = make(map[string]struct{})
  107. logic.UpsertNode(&node)
  108. }
  109. }
  110. go mq.PublishPeerUpdate(false)
  111. w.Header().Set("Content-Type", "application/json")
  112. logic.ReturnSuccessResponse(w, r, "failover has been reset successfully")
  113. }
  114. // @Summary Delete failover node
  115. // @Router /api/v1/node/{nodeid}/failover [delete]
  116. // @Tags PRO
  117. // @Param nodeid path string true "Node ID"
  118. // @Success 200 {object} models.Node
  119. // @Failure 400 {object} models.ErrorResponse
  120. // @Failure 500 {object} models.ErrorResponse
  121. func deletefailOver(w http.ResponseWriter, r *http.Request) {
  122. var params = mux.Vars(r)
  123. nodeid := params["nodeid"]
  124. // confirm host exists
  125. node, err := logic.GetNodeByID(nodeid)
  126. if err != nil {
  127. slog.Error("failed to get node:", "error", err.Error())
  128. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  129. return
  130. }
  131. node.IsFailOver = false
  132. // Reset FailOvered Peers
  133. err = logic.UpsertNode(&node)
  134. if err != nil {
  135. slog.Error("failed to upsert node", "node", node.ID.String(), "error", err)
  136. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  137. return
  138. }
  139. proLogic.RemoveFailOverFromCache(node.Network)
  140. go func() {
  141. proLogic.ResetFailOver(&node)
  142. mq.PublishPeerUpdate(false)
  143. }()
  144. w.Header().Set("Content-Type", "application/json")
  145. logic.ReturnSuccessResponseWithJson(w, r, node, "deleted failover successfully")
  146. }
  147. // @Summary Failover me
  148. // @Router /api/v1/node/{nodeid}/failover_me [post]
  149. // @Tags PRO
  150. // @Param nodeid path string true "Node ID"
  151. // @Accept json
  152. // @Param body body models.FailOverMeReq true "Failover request"
  153. // @Success 200 {object} models.SuccessResponse
  154. // @Failure 400 {object} models.ErrorResponse
  155. // @Failure 500 {object} models.ErrorResponse
  156. func failOverME(w http.ResponseWriter, r *http.Request) {
  157. var params = mux.Vars(r)
  158. nodeid := params["nodeid"]
  159. // confirm host exists
  160. node, err := logic.GetNodeByID(nodeid)
  161. if err != nil {
  162. logger.Log(0, r.Header.Get("user"), "failed to get node:", err.Error())
  163. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  164. return
  165. }
  166. host, err := logic.GetHost(node.HostID.String())
  167. if err != nil {
  168. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  169. return
  170. }
  171. failOverNode, exists := proLogic.FailOverExists(node.Network)
  172. if !exists {
  173. logic.ReturnErrorResponse(
  174. w,
  175. r,
  176. logic.FormatError(
  177. fmt.Errorf("req-from: %s, failover node doesn't exist in the network", host.Name),
  178. "badrequest",
  179. ),
  180. )
  181. return
  182. }
  183. var failOverReq models.FailOverMeReq
  184. err = json.NewDecoder(r.Body).Decode(&failOverReq)
  185. if err != nil {
  186. logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
  187. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  188. return
  189. }
  190. var sendPeerUpdate bool
  191. peerNode, err := logic.GetNodeByID(failOverReq.NodeID)
  192. if err != nil {
  193. slog.Error("peer not found: ", "nodeid", failOverReq.NodeID, "error", err)
  194. logic.ReturnErrorResponse(
  195. w,
  196. r,
  197. logic.FormatError(errors.New("peer not found"), "badrequest"),
  198. )
  199. return
  200. }
  201. eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
  202. logic.GetNodeEgressInfo(&node, eli)
  203. logic.GetNodeEgressInfo(&peerNode, eli)
  204. logic.GetNodeEgressInfo(&failOverNode, eli)
  205. if peerNode.IsFailOver {
  206. logic.ReturnErrorResponse(
  207. w,
  208. r,
  209. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  210. )
  211. return
  212. }
  213. if node.IsFailOver {
  214. logic.ReturnErrorResponse(
  215. w,
  216. r,
  217. logic.FormatError(errors.New("node is acting as failover"), "badrequest"),
  218. )
  219. return
  220. }
  221. if peerNode.IsFailOver {
  222. logic.ReturnErrorResponse(
  223. w,
  224. r,
  225. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  226. )
  227. return
  228. }
  229. if node.IsRelayed && node.RelayedBy == peerNode.ID.String() {
  230. logic.ReturnErrorResponse(
  231. w,
  232. r,
  233. logic.FormatError(errors.New("node is relayed by peer node"), "badrequest"),
  234. )
  235. return
  236. }
  237. if node.IsRelay && peerNode.RelayedBy == node.ID.String() {
  238. logic.ReturnErrorResponse(
  239. w,
  240. r,
  241. logic.FormatError(errors.New("node acting as relay for the peer node"), "badrequest"),
  242. )
  243. return
  244. }
  245. if (node.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && node.EgressDetails.InternetGwID != failOverNode.ID.String()) ||
  246. (peerNode.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID != failOverNode.ID.String()) {
  247. logic.ReturnErrorResponse(
  248. w,
  249. r,
  250. logic.FormatError(
  251. errors.New("node using a internet gw by the peer node"),
  252. "badrequest",
  253. ),
  254. )
  255. return
  256. }
  257. if node.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID == node.ID.String() {
  258. logic.ReturnErrorResponse(
  259. w,
  260. r,
  261. logic.FormatError(
  262. errors.New("node acting as internet gw for the peer node"),
  263. "badrequest",
  264. ),
  265. )
  266. return
  267. }
  268. if node.EgressDetails.InternetGwID != "" && node.EgressDetails.InternetGwID == peerNode.ID.String() {
  269. logic.ReturnErrorResponse(
  270. w,
  271. r,
  272. logic.FormatError(
  273. errors.New("node using a internet gw by the peer node"),
  274. "badrequest",
  275. ),
  276. )
  277. return
  278. }
  279. err = proLogic.SetFailOverCtx(failOverNode, node, peerNode)
  280. if err != nil {
  281. slog.Debug("failed to create failover", "id", node.ID.String(),
  282. "network", node.Network, "error", err)
  283. logic.ReturnErrorResponse(
  284. w,
  285. r,
  286. logic.FormatError(fmt.Errorf("failed to create failover: %v", err), "internal"),
  287. )
  288. return
  289. }
  290. slog.Info(
  291. "[auto-relay] created relay on node",
  292. "node",
  293. node.ID.String(),
  294. "network",
  295. node.Network,
  296. )
  297. sendPeerUpdate = true
  298. if sendPeerUpdate {
  299. go mq.PublishPeerUpdate(false)
  300. }
  301. w.Header().Set("Content-Type", "application/json")
  302. logic.ReturnSuccessResponse(w, r, "relayed successfully")
  303. }
  304. // @Summary checkfailOverCtx
  305. // @Router /api/v1/node/{nodeid}/failover_check [get]
  306. // @Tags PRO
  307. // @Param nodeid path string true "Node ID"
  308. // @Accept json
  309. // @Param body body models.FailOverMeReq true "Failover request"
  310. // @Success 200 {object} models.SuccessResponse
  311. // @Failure 400 {object} models.ErrorResponse
  312. // @Failure 500 {object} models.ErrorResponse
  313. func checkfailOverCtx(w http.ResponseWriter, r *http.Request) {
  314. var params = mux.Vars(r)
  315. nodeid := params["nodeid"]
  316. // confirm host exists
  317. node, err := logic.GetNodeByID(nodeid)
  318. if err != nil {
  319. logger.Log(0, r.Header.Get("user"), "failed to get node:", err.Error())
  320. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  321. return
  322. }
  323. host, err := logic.GetHost(node.HostID.String())
  324. if err != nil {
  325. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  326. return
  327. }
  328. failOverNode, exists := proLogic.FailOverExists(node.Network)
  329. if !exists {
  330. logic.ReturnErrorResponse(
  331. w,
  332. r,
  333. logic.FormatError(
  334. fmt.Errorf("req-from: %s, failover node doesn't exist in the network", host.Name),
  335. "badrequest",
  336. ),
  337. )
  338. return
  339. }
  340. var failOverReq models.FailOverMeReq
  341. err = json.NewDecoder(r.Body).Decode(&failOverReq)
  342. if err != nil {
  343. logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
  344. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  345. return
  346. }
  347. peerNode, err := logic.GetNodeByID(failOverReq.NodeID)
  348. if err != nil {
  349. slog.Error("peer not found: ", "nodeid", failOverReq.NodeID, "error", err)
  350. logic.ReturnErrorResponse(
  351. w,
  352. r,
  353. logic.FormatError(errors.New("peer not found"), "badrequest"),
  354. )
  355. return
  356. }
  357. eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
  358. logic.GetNodeEgressInfo(&node, eli)
  359. logic.GetNodeEgressInfo(&peerNode, eli)
  360. logic.GetNodeEgressInfo(&failOverNode, eli)
  361. if peerNode.IsFailOver {
  362. logic.ReturnErrorResponse(
  363. w,
  364. r,
  365. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  366. )
  367. return
  368. }
  369. if node.IsFailOver {
  370. logic.ReturnErrorResponse(
  371. w,
  372. r,
  373. logic.FormatError(errors.New("node is acting as failover"), "badrequest"),
  374. )
  375. return
  376. }
  377. if peerNode.IsFailOver {
  378. logic.ReturnErrorResponse(
  379. w,
  380. r,
  381. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  382. )
  383. return
  384. }
  385. if node.IsRelayed && node.RelayedBy == peerNode.ID.String() {
  386. logic.ReturnErrorResponse(
  387. w,
  388. r,
  389. logic.FormatError(errors.New("node is relayed by peer node"), "badrequest"),
  390. )
  391. return
  392. }
  393. if node.IsRelay && peerNode.RelayedBy == node.ID.String() {
  394. logic.ReturnErrorResponse(
  395. w,
  396. r,
  397. logic.FormatError(errors.New("node acting as relay for the peer node"), "badrequest"),
  398. )
  399. return
  400. }
  401. if (node.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && node.EgressDetails.InternetGwID != failOverNode.ID.String()) ||
  402. (peerNode.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID != failOverNode.ID.String()) {
  403. logic.ReturnErrorResponse(
  404. w,
  405. r,
  406. logic.FormatError(
  407. errors.New("node using a internet gw by the peer node"),
  408. "badrequest",
  409. ),
  410. )
  411. return
  412. }
  413. if node.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID == node.ID.String() {
  414. logic.ReturnErrorResponse(
  415. w,
  416. r,
  417. logic.FormatError(
  418. errors.New("node acting as internet gw for the peer node"),
  419. "badrequest",
  420. ),
  421. )
  422. return
  423. }
  424. if node.EgressDetails.InternetGwID != "" && node.EgressDetails.InternetGwID == peerNode.ID.String() {
  425. logic.ReturnErrorResponse(
  426. w,
  427. r,
  428. logic.FormatError(
  429. errors.New("node using a internet gw by the peer node"),
  430. "badrequest",
  431. ),
  432. )
  433. return
  434. }
  435. if ok := logic.IsPeerAllowed(node, peerNode, true); !ok {
  436. logic.ReturnErrorResponse(
  437. w,
  438. r,
  439. logic.FormatError(
  440. errors.New("peers are not allowed to communicate"),
  441. "badrequest",
  442. ),
  443. )
  444. return
  445. }
  446. err = proLogic.CheckFailOverCtx(failOverNode, node, peerNode)
  447. if err != nil {
  448. slog.Error("failover ctx cannot be set ", "error", err)
  449. logic.ReturnErrorResponse(
  450. w,
  451. r,
  452. logic.FormatError(fmt.Errorf("failover ctx cannot be set: %v", err), "internal"),
  453. )
  454. return
  455. }
  456. w.Header().Set("Content-Type", "application/json")
  457. logic.ReturnSuccessResponse(w, r, "failover can be set")
  458. }