Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 365: PUT /orgs/{orgid}/nodes/{nodeId} changes a node's lastHeartbeat #367

Merged
merged 1 commit into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ Now you can disable root by setting `api.root.enabled` to `false` in `/etc/horiz
- detect if a pattern is updated with service that has userInput w/o default values, and give warning
- Consider changing all creates to POST, and update (via put/patch) return codes to 200


## Changes in 2.34.0

- Issue 365: The PUT /orgs/{orgid}/nodes/{nodeId} route will no longer set/update a node's last heartbeat.

## Changes in 2.33.0

- Issue 313: Expanded unit testing structure to cover some of the Exchange.
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.33.0
2.34.0
2 changes: 1 addition & 1 deletion src/main/scala/com/horizon/exchangeapi/ApiUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ object RouteUtils {
/** From the given db joined node/agreement rows, build the output node health hash and return it.
This is shared between POST /org/{orgid}/patterns/{pat-id}/nodehealth and POST /org/{orgid}/search/nodehealth
*/
def buildNodeHealthHash(list: scala.Seq[(String, String, Option[String], Option[String])]): Map[String,NodeHealthHashElement] = {
def buildNodeHealthHash(list: scala.Seq[(String, Option[String], Option[String], Option[String])]): Map[String,NodeHealthHashElement] = {
// Go thru the rows and build a hash of the nodes, adding the agreement to its value as we encounter them
val nodeHash = new MutableHashMap[String,NodeHealthHashElement] // key is node id, value has lastHeartbeat and the agreements map
for ( (nodeId, lastHeartbeat, agrId, agrLastUpdated) <- list ) {
Expand Down
121 changes: 90 additions & 31 deletions src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/main/scala/com/horizon/exchangeapi/OrgsRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ final case class PostNodeHealthRequest(lastTime: String, nodeOrgids: Option[List
}

final case class NodeHealthAgreementElement(lastUpdated: String)
class NodeHealthHashElement(var lastHeartbeat: String, var agreements: Map[String,NodeHealthAgreementElement])
final case class PostNodeHealthResponse(nodes: Map[String,NodeHealthHashElement])
class NodeHealthHashElement(var lastHeartbeat: Option[String], var agreements: Map[String, NodeHealthAgreementElement])
final case class PostNodeHealthResponse(nodes: Map[String, NodeHealthHashElement])

/** Case class for request body for ResourceChanges route */
final case class ResourceChangesRequest(changeId: Long, lastUpdated: Option[String], maxRecords: Int, orgList: Option[List[String]]) {
Expand Down Expand Up @@ -974,7 +974,7 @@ trait OrgsRoutes extends JacksonSupport with AuthenticationSupport {
val id = orgId + "/" + ident.getIdentity
ident match {
case _: INode =>
NodesTQ.getLastHeartbeat(id).update(ApiTime.nowUTC).asTry
NodesTQ.getLastHeartbeat(id).update(Some(ApiTime.nowUTC)).asTry
case _: IAgbot =>
AgbotsTQ.getLastHeartbeat(id).update(ApiTime.nowUTC).asTry
case _ =>
Expand Down
67 changes: 57 additions & 10 deletions src/main/scala/com/horizon/exchangeapi/tables/Nodes.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
package com.horizon.exchangeapi.tables

import com.horizon.exchangeapi._
import org.json4s._
import scala.collection.mutable.{HashMap => MutableHashMap, // Renaming this to not have to qualify every use of a immutable collection
ListBuffer}

import org.json4s.{DefaultFormats,
Formats}
import org.json4s.jackson.Serialization.read
import slick.jdbc.PostgresProfile.api._

import scala.collection.mutable.{ListBuffer, HashMap => MutableHashMap} //renaming this so i do not have to qualify every use of a immutable collection
import com.horizon.exchangeapi.{ApiTime,
ExchMsg,
Role,
StrConstants,
Version,
VersionRange}

import slick.jdbc.PostgresProfile.api.{DBIO,
ForeignKeyAction,
Query,
Table,
TableQuery,
Tag,
anyToShapedValue,
booleanColumnExtensionMethods,
booleanColumnType,
columnExtensionMethods,
intColumnType,
queryInsertActionExtensionMethods,
queryUpdateActionExtensionMethods,
recordQueryActionExtensionMethods,
stringColumnExtensionMethods,
stringColumnType,
valueToConstColumn}


/** We define this trait because services in the DB and in the search criteria need the same methods, but have slightly different constructor args */
trait RegServiceTrait {
Expand Down Expand Up @@ -63,7 +89,22 @@ class Node(var token: String, var name: String, var owner: String, var nodeType:
def copy = new Node(token, name, owner, nodeType, pattern, registeredServices, userInput, msgEndPoint, softwareVersions, lastHeartbeat, publicKey, arch, heartbeatIntervals, lastUpdated)
}

final case class NodeRow(id: String, orgid: String, token: String, name: String, owner: String, nodeType: String, pattern: String, regServices: String, userInput: String, msgEndPoint: String, softwareVersions: String, lastHeartbeat: String, publicKey: String, arch: String, heartbeatIntervals: String, lastUpdated: String) {
final case class NodeRow(id: String,
orgid: String,
token: String,
name: String,
owner: String,
nodeType: String,
pattern: String,
regServices: String,
userInput: String,
msgEndPoint: String,
softwareVersions: String,
lastHeartbeat: Option[String],
publicKey: String,
arch: String,
heartbeatIntervals: String,
lastUpdated: String) {
protected implicit val jsonFormats: Formats = DefaultFormats

def toNode(superUser: Boolean): Node = {
Expand All @@ -75,7 +116,7 @@ final case class NodeRow(id: String, orgid: String, token: String, name: String,
val rsvc2 = rsvc.map(rs => RegService(rs.url,rs.numAgreements, rs.configState.orElse(Some("active")), rs.policy, rs.properties))
val input = if (userInput != "") read[List[OneUserInputService]](userInput) else List[OneUserInputService]()
val hbInterval = if (heartbeatIntervals != "") read[NodeHeartbeatIntervals](heartbeatIntervals) else NodeHeartbeatIntervals(0, 0, 0)
new Node(tok, name, owner, nt, pattern, rsvc2, input, msgEndPoint, swv, lastHeartbeat, publicKey, arch, hbInterval, lastUpdated)
new Node(tok, name, owner, nt, pattern, rsvc2, input, msgEndPoint, swv, lastHeartbeat.orNull, publicKey, arch, hbInterval, lastUpdated)
}

/* Not needed anymore, because node properties are no longer in a separate table that needs to be joined...
Expand All @@ -89,13 +130,19 @@ final case class NodeRow(id: String, orgid: String, token: String, name: String,

def upsert: DBIO[_] = {
//val tok = if (token == "") "" else if (Password.isHashed(token)) token else Password.hash(token) <- token is already hashed
if (Role.isSuperUser(owner)) NodesTQ.rows.map(d => (d.id, d.orgid, d.token, d.name, d.nodeType, d.pattern, d.regServices, d.userInput, d.msgEndPoint, d.softwareVersions, d.lastHeartbeat, d.publicKey, d.arch, d.heartbeatIntervals, d.lastUpdated)).insertOrUpdate((id, orgid, token, name, nodeType, pattern, regServices, userInput, msgEndPoint, softwareVersions, lastHeartbeat, publicKey, arch, heartbeatIntervals, lastUpdated))
if (Role.isSuperUser(owner)) NodesTQ.rows.map(d => (d.id, d.orgid, d.token, d.name, d.nodeType, d.pattern, d.regServices, d.userInput, d.msgEndPoint, d.softwareVersions, d.lastHeartbeat, d.publicKey, d.arch, d.heartbeatIntervals, d.lastUpdated)).insertOrUpdate((id, orgid, token, name, nodeType, pattern, regServices, userInput, msgEndPoint, softwareVersions, lastHeartbeat.orElse(None), publicKey, arch, heartbeatIntervals, lastUpdated))
else NodesTQ.rows.insertOrUpdate(NodeRow(id, orgid, token, name, owner, nodeType, pattern, regServices, userInput, msgEndPoint, softwareVersions, lastHeartbeat, publicKey, arch, heartbeatIntervals, lastUpdated))
}

def update: DBIO[_] = {
//val tok = if (token == "") "" else if (Password.isHashed(token)) token else Password.hash(token) <- token is already hashed
if (owner == "") (for { d <- NodesTQ.rows if d.id === id } yield (d.id,d.orgid,d.token,d.name,d.nodeType,d.pattern,d.regServices,d.userInput,d.msgEndPoint,d.softwareVersions,d.lastHeartbeat,d.publicKey, d.arch, d.heartbeatIntervals, d.lastUpdated)).update((id, orgid, token, name, nodeType, pattern, regServices, userInput, msgEndPoint, softwareVersions, lastHeartbeat, publicKey, arch, heartbeatIntervals, lastUpdated))
if (owner == "") (
for {
d <- NodesTQ.rows if d.id === id
} yield (d.id,d.orgid,d.token,d.name,d.nodeType,d.pattern,d.regServices,d.userInput,
d.msgEndPoint,d.softwareVersions,d.lastHeartbeat,d.publicKey, d.arch, d.heartbeatIntervals, d.lastUpdated))
.update((id, orgid, token, name, nodeType, pattern, regServices, userInput, msgEndPoint, softwareVersions,
lastHeartbeat.orElse(None), publicKey, arch, heartbeatIntervals, lastUpdated))
else (for { d <- NodesTQ.rows if d.id === id } yield d).update(NodeRow(id, orgid, token, name, owner, nodeType, pattern, regServices, userInput, msgEndPoint, softwareVersions, lastHeartbeat, publicKey, arch, heartbeatIntervals, lastUpdated))
}
}
Expand All @@ -115,7 +162,7 @@ class Nodes(tag: Tag) extends Table[NodeRow](tag, "nodes") {
def msgEndPoint = column[String]("msgendpoint")
def softwareVersions = column[String]("swversions")
def publicKey = column[String]("publickey") // this is last because that is where alter table in upgradedb puts it
def lastHeartbeat = column[String]("lastheartbeat")
def lastHeartbeat = column[Option[String]]("lastheartbeat")
def arch = column[String]("arch")
def heartbeatIntervals = column[String]("heartbeatintervals")
def lastUpdated = column[String]("lastupdated")
Expand Down Expand Up @@ -174,7 +221,7 @@ object NodesTQ {
def getLastUpdated(id: String) = rows.filter(_.id === id).map(_.lastUpdated)
def getNodeUsingPolicy(id: String) = rows.filter(_.id === id).map(x => (x.pattern, x.publicKey))

def setLastHeartbeat(id: String, lastHeartbeat: String) = rows.filter(_.id === id).map(_.lastHeartbeat).update(lastHeartbeat)
def setLastHeartbeat(id: String, lastHeartbeat: String) = rows.filter(_.id === id).map(_.lastHeartbeat).update(Some(lastHeartbeat))
def setLastUpdated(id: String, lastUpdated: String) = rows.filter(_.id === id).map(_.lastUpdated).update(lastUpdated)


Expand Down
Loading