failover.go 12 KB

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