failover.go 13 KB

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