failover.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  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. acls, _ := logic.ListAclsByNetwork(models.NetworkID(node.Network))
  203. logic.GetNodeEgressInfo(&node, eli, acls)
  204. logic.GetNodeEgressInfo(&peerNode, eli, acls)
  205. logic.GetNodeEgressInfo(&failOverNode, eli, acls)
  206. if peerNode.IsFailOver {
  207. logic.ReturnErrorResponse(
  208. w,
  209. r,
  210. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  211. )
  212. return
  213. }
  214. if node.IsFailOver {
  215. logic.ReturnErrorResponse(
  216. w,
  217. r,
  218. logic.FormatError(errors.New("node is acting as failover"), "badrequest"),
  219. )
  220. return
  221. }
  222. if peerNode.IsFailOver {
  223. logic.ReturnErrorResponse(
  224. w,
  225. r,
  226. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  227. )
  228. return
  229. }
  230. if node.IsRelayed && node.RelayedBy == peerNode.ID.String() {
  231. logic.ReturnErrorResponse(
  232. w,
  233. r,
  234. logic.FormatError(errors.New("node is relayed by peer node"), "badrequest"),
  235. )
  236. return
  237. }
  238. if node.IsRelay && peerNode.RelayedBy == node.ID.String() {
  239. logic.ReturnErrorResponse(
  240. w,
  241. r,
  242. logic.FormatError(errors.New("node acting as relay for the peer node"), "badrequest"),
  243. )
  244. return
  245. }
  246. if (node.InternetGwID != "" && failOverNode.IsInternetGateway && node.InternetGwID != failOverNode.ID.String()) ||
  247. (peerNode.InternetGwID != "" && failOverNode.IsInternetGateway && peerNode.InternetGwID != failOverNode.ID.String()) {
  248. logic.ReturnErrorResponse(
  249. w,
  250. r,
  251. logic.FormatError(
  252. errors.New("node using a internet gw by the peer node"),
  253. "badrequest",
  254. ),
  255. )
  256. return
  257. }
  258. if node.IsInternetGateway && peerNode.InternetGwID == node.ID.String() {
  259. logic.ReturnErrorResponse(
  260. w,
  261. r,
  262. logic.FormatError(
  263. errors.New("node acting as internet gw for the peer node"),
  264. "badrequest",
  265. ),
  266. )
  267. return
  268. }
  269. if node.InternetGwID != "" && node.InternetGwID == peerNode.ID.String() {
  270. logic.ReturnErrorResponse(
  271. w,
  272. r,
  273. logic.FormatError(
  274. errors.New("node using a internet gw by the peer node"),
  275. "badrequest",
  276. ),
  277. )
  278. return
  279. }
  280. err = proLogic.SetFailOverCtx(failOverNode, node, peerNode)
  281. if err != nil {
  282. slog.Debug("failed to create failover", "id", node.ID.String(),
  283. "network", node.Network, "error", err)
  284. logic.ReturnErrorResponse(
  285. w,
  286. r,
  287. logic.FormatError(fmt.Errorf("failed to create failover: %v", err), "internal"),
  288. )
  289. return
  290. }
  291. slog.Info(
  292. "[auto-relay] created relay on node",
  293. "node",
  294. node.ID.String(),
  295. "network",
  296. node.Network,
  297. )
  298. sendPeerUpdate = true
  299. if sendPeerUpdate {
  300. go mq.PublishPeerUpdate(false)
  301. }
  302. w.Header().Set("Content-Type", "application/json")
  303. logic.ReturnSuccessResponse(w, r, "relayed successfully")
  304. }
  305. // @Summary checkfailOverCtx
  306. // @Router /api/v1/node/{nodeid}/failover_check [get]
  307. // @Tags PRO
  308. // @Param nodeid path string true "Node ID"
  309. // @Accept json
  310. // @Param body body models.FailOverMeReq true "Failover request"
  311. // @Success 200 {object} models.SuccessResponse
  312. // @Failure 400 {object} models.ErrorResponse
  313. // @Failure 500 {object} models.ErrorResponse
  314. func checkfailOverCtx(w http.ResponseWriter, r *http.Request) {
  315. var params = mux.Vars(r)
  316. nodeid := params["nodeid"]
  317. // confirm host exists
  318. node, err := logic.GetNodeByID(nodeid)
  319. if err != nil {
  320. logger.Log(0, r.Header.Get("user"), "failed to get node:", err.Error())
  321. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  322. return
  323. }
  324. host, err := logic.GetHost(node.HostID.String())
  325. if err != nil {
  326. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  327. return
  328. }
  329. failOverNode, exists := proLogic.FailOverExists(node.Network)
  330. if !exists {
  331. logic.ReturnErrorResponse(
  332. w,
  333. r,
  334. logic.FormatError(
  335. fmt.Errorf("req-from: %s, failover node doesn't exist in the network", host.Name),
  336. "badrequest",
  337. ),
  338. )
  339. return
  340. }
  341. var failOverReq models.FailOverMeReq
  342. err = json.NewDecoder(r.Body).Decode(&failOverReq)
  343. if err != nil {
  344. logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
  345. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  346. return
  347. }
  348. peerNode, err := logic.GetNodeByID(failOverReq.NodeID)
  349. if err != nil {
  350. slog.Error("peer not found: ", "nodeid", failOverReq.NodeID, "error", err)
  351. logic.ReturnErrorResponse(
  352. w,
  353. r,
  354. logic.FormatError(errors.New("peer not found"), "badrequest"),
  355. )
  356. return
  357. }
  358. eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
  359. acls, _ := logic.ListAclsByNetwork(models.NetworkID(node.Network))
  360. logic.GetNodeEgressInfo(&node, eli, acls)
  361. logic.GetNodeEgressInfo(&peerNode, eli, acls)
  362. logic.GetNodeEgressInfo(&failOverNode, eli, acls)
  363. if peerNode.IsFailOver {
  364. logic.ReturnErrorResponse(
  365. w,
  366. r,
  367. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  368. )
  369. return
  370. }
  371. if node.IsFailOver {
  372. logic.ReturnErrorResponse(
  373. w,
  374. r,
  375. logic.FormatError(errors.New("node is acting as failover"), "badrequest"),
  376. )
  377. return
  378. }
  379. if peerNode.IsFailOver {
  380. logic.ReturnErrorResponse(
  381. w,
  382. r,
  383. logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
  384. )
  385. return
  386. }
  387. if node.IsRelayed && node.RelayedBy == peerNode.ID.String() {
  388. logic.ReturnErrorResponse(
  389. w,
  390. r,
  391. logic.FormatError(errors.New("node is relayed by peer node"), "badrequest"),
  392. )
  393. return
  394. }
  395. if node.IsRelay && peerNode.RelayedBy == node.ID.String() {
  396. logic.ReturnErrorResponse(
  397. w,
  398. r,
  399. logic.FormatError(errors.New("node acting as relay for the peer node"), "badrequest"),
  400. )
  401. return
  402. }
  403. if (node.InternetGwID != "" && failOverNode.IsInternetGateway && node.InternetGwID != failOverNode.ID.String()) ||
  404. (peerNode.InternetGwID != "" && failOverNode.IsInternetGateway && peerNode.InternetGwID != failOverNode.ID.String()) {
  405. logic.ReturnErrorResponse(
  406. w,
  407. r,
  408. logic.FormatError(
  409. errors.New("node using a internet gw by the peer node"),
  410. "badrequest",
  411. ),
  412. )
  413. return
  414. }
  415. if node.IsInternetGateway && peerNode.InternetGwID == node.ID.String() {
  416. logic.ReturnErrorResponse(
  417. w,
  418. r,
  419. logic.FormatError(
  420. errors.New("node acting as internet gw for the peer node"),
  421. "badrequest",
  422. ),
  423. )
  424. return
  425. }
  426. if node.InternetGwID != "" && node.InternetGwID == peerNode.ID.String() {
  427. logic.ReturnErrorResponse(
  428. w,
  429. r,
  430. logic.FormatError(
  431. errors.New("node using a internet gw by the peer node"),
  432. "badrequest",
  433. ),
  434. )
  435. return
  436. }
  437. if ok := logic.IsPeerAllowed(node, peerNode, true); !ok {
  438. logic.ReturnErrorResponse(
  439. w,
  440. r,
  441. logic.FormatError(
  442. errors.New("peers are not allowed to communicate"),
  443. "badrequest",
  444. ),
  445. )
  446. return
  447. }
  448. err = proLogic.CheckFailOverCtx(failOverNode, node, peerNode)
  449. if err != nil {
  450. slog.Error("failover ctx cannot be set ", "error", err)
  451. logic.ReturnErrorResponse(
  452. w,
  453. r,
  454. logic.FormatError(fmt.Errorf("failover ctx cannot be set: %v", err), "internal"),
  455. )
  456. return
  457. }
  458. w.Header().Set("Content-Type", "application/json")
  459. logic.ReturnSuccessResponse(w, r, "failover can be set")
  460. }