From 48535f7ef1dc16e895b984585233755a9afab162 Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Mon, 24 Feb 2025 08:48:24 +0300 Subject: [PATCH] NET-1956: Async Node Status API (#3341) * add node status api * upsate node status api to return map data * resolve merge conflicts --- controllers/node.go | 51 ++++++++++++++++++++++++++++++++++++++-- logic/nodes.go | 19 +++++++++++++-- logic/status.go | 4 ++-- models/api_node.go | 20 ++++++++++++++++ pro/controllers/users.go | 4 ++-- 5 files changed, 90 insertions(+), 8 deletions(-) diff --git a/controllers/node.go b/controllers/node.go index 0bb6c13e..2e271827 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -31,6 +31,7 @@ func nodeHandlers(r *mux.Router) { r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost) r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete) r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost) + r.HandleFunc("/api/v1/nodes/{network}/status", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodeStatus))).Methods(http.MethodGet) r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost) } @@ -328,7 +329,7 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { } nodes = logic.AddStaticNodestoList(nodes) - nodes = logic.AddStatusToNodes(nodes) + nodes = logic.AddStatusToNodes(nodes, false) // returns all the nodes in JSON/API format apiNodes := logic.GetAllNodesAPI(nodes[:]) logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName) @@ -368,7 +369,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) { } nodes = logic.AddStaticNodestoList(nodes) - nodes = logic.AddStatusToNodes(nodes) + nodes = logic.AddStatusToNodes(nodes, false) // return all the nodes in JSON/API format apiNodes := logic.GetAllNodesAPI(nodes[:]) logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to") @@ -377,6 +378,52 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(apiNodes) } +// @Summary Get all nodes status on the network +// @Router /api/v1/nodes/{network}/status [get] +// @Tags Nodes +// @Securitydefinitions.oauth2.application OAuth2Application +// @Success 200 {array} models.ApiNode +// @Failure 500 {object} models.ErrorResponse +// Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not +func getNetworkNodeStatus(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + netID := params["network"] + // validate network + _, err := logic.GetNetwork(netID) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to get network %v", err), "badrequest")) + return + } + var nodes []models.Node + nodes, err = logic.GetNetworkNodes(netID) + if err != nil { + logger.Log(0, "error fetching all nodes info: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + username := r.Header.Get("user") + if r.Header.Get("ismaster") == "no" { + user, err := logic.GetUser(username) + if err != nil { + return + } + userPlatformRole, err := logic.GetRole(user.PlatformRoleID) + if err != nil { + return + } + if !userPlatformRole.FullAccess { + nodes = logic.GetFilteredNodesByUserAccess(*user, nodes) + } + + } + nodes = logic.AddStaticNodestoList(nodes) + nodes = logic.AddStatusToNodes(nodes, false) + // return all the nodes in JSON/API format + apiNodesStatusMap := logic.GetNodesStatusAPI(nodes[:]) + logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to") + logic.ReturnSuccessResponseWithJson(w, r, apiNodesStatusMap, "fetched nodes with metric status") +} + // @Summary Get an individual node // @Router /api/nodes/{network}/{nodeid} [get] // @Tags Nodes diff --git a/logic/nodes.go b/logic/nodes.go index f47e6512..1146151e 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -443,7 +443,7 @@ func AddStaticNodestoList(nodes []models.Node) []models.Node { return nodes } -func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) { +func AddStatusToNodes(nodes []models.Node, statusCall bool) (nodesWithStatus []models.Node) { aclDefaultPolicyStatusMap := make(map[string]bool) for _, node := range nodes { if _, ok := aclDefaultPolicyStatusMap[node.Network]; !ok { @@ -451,7 +451,12 @@ func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) { defaultPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy) aclDefaultPolicyStatusMap[node.Network] = defaultPolicy.Enabled } - GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network]) + if statusCall { + GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network]) + } else { + GetNodeCheckInStatus(&node, true) + } + nodesWithStatus = append(nodesWithStatus, node) } return @@ -572,6 +577,16 @@ func GetAllNodesAPI(nodes []models.Node) []models.ApiNode { return apiNodes[:] } +// GetNodesStatusAPI - gets nodes status +func GetNodesStatusAPI(nodes []models.Node) map[string]models.ApiNodeStatus { + apiStatusNodesMap := make(map[string]models.ApiNodeStatus) + for i := range nodes { + newApiNode := nodes[i].ConvertToStatusNode() + apiStatusNodesMap[newApiNode.ID] = *newApiNode + } + return apiStatusNodesMap +} + // DeleteExpiredNodes - goroutine which deletes nodes which are expired func DeleteExpiredNodes(ctx context.Context, peerUpdate chan *models.Node) { // Delete Expired Nodes Every Hour diff --git a/logic/status.go b/logic/status.go index 15d98a31..ec8324a2 100644 --- a/logic/status.go +++ b/logic/status.go @@ -6,9 +6,9 @@ import ( "github.com/gravitl/netmaker/models" ) -var GetNodeStatus = getNodeStatus +var GetNodeStatus = GetNodeCheckInStatus -func getNodeStatus(node *models.Node, t bool) { +func GetNodeCheckInStatus(node *models.Node, t bool) { // On CE check only last check-in time if node.IsStatic { if !node.StaticNode.Enabled { diff --git a/models/api_node.go b/models/api_node.go index 995c4f58..99c752e5 100644 --- a/models/api_node.go +++ b/models/api_node.go @@ -8,6 +8,13 @@ import ( "golang.org/x/exp/slog" ) +type ApiNodeStatus struct { + ID string `json:"id"` + IsStatic bool `json:"is_static"` + IsUserNode bool `json:"is_user_node"` + Status NodeStatus `json:"status"` +} + // ApiNode is a stripped down Node DTO that exposes only required fields to external systems type ApiNode struct { ID string `json:"id,omitempty" validate:"required,min=5,id_unique"` @@ -132,6 +139,19 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node { return &convertedNode } +func (nm *Node) ConvertToStatusNode() *ApiNodeStatus { + apiNode := ApiNodeStatus{} + if nm.IsStatic { + apiNode.ID = nm.StaticNode.ClientID + } else { + apiNode.ID = nm.ID.String() + } + apiNode.IsStatic = nm.IsStatic + apiNode.IsUserNode = nm.IsUserNode + apiNode.Status = nm.Status + return &apiNode +} + // Node.ConvertToAPINode - converts a node to an API node func (nm *Node) ConvertToAPINode() *ApiNode { apiNode := ApiNode{} diff --git a/pro/controllers/users.go b/pro/controllers/users.go index ae6ba675..5cdc767c 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -1102,7 +1102,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { slog.Error("failed to get node network", "error", err) continue } - nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}) + nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}, false) if len(nodesWithStatus) > 0 { node = nodesWithStatus[0] } @@ -1143,7 +1143,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { if err != nil { continue } - nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}) + nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}, false) if len(nodesWithStatus) > 0 { node = nodesWithStatus[0] }