From 18a663a3dda3bd5bbe831ae9ae377071ccde5184 Mon Sep 17 00:00:00 2001 From: Bruce Potter Date: Mon, 16 Oct 2017 15:17:27 -0400 Subject: [PATCH 1/5] added GET /admin/version --- Makefile | 6 ++- README.md | 21 +++++--- src/main/resources/version.txt | 1 + .../com/horizon/exchangeapi/AdminRoutes.scala | 24 +++++++-- .../exchangeapi/MicroservicesRoutes.scala | 54 ++++++------------- .../horizon/exchangeapi/PatternsRoutes.scala | 26 ++++++--- .../horizon/exchangeapi/WorkloadsRoutes.scala | 26 +++++---- 7 files changed, 91 insertions(+), 67 deletions(-) create mode 100644 src/main/resources/version.txt diff --git a/Makefile b/Makefile index ab02e5f1..6a86836c 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ SHELL = /bin/bash -e ARCH ?= x86 DOCKER_NAME ?= exchange-api -DOCKER_TAG ?= v1.39.0 +VERSION = $(shell cat src/main/resources/version.txt) +DOCKER_TAG ?= $(VERSION) DOCKER_OPTS ?= --no-cache COMPILE_CLEAN ?= clean image-string = $(DOCKER_REGISTRY)/$(ARCH)/exchange-api @@ -116,6 +117,9 @@ sync-swagger-ui: testmake: echo $(EXCHANGE_EMAIL) +version: + @echo $(VERSION) + .SECONDARY: .PHONY: default clean clean-exec-image clean-all docker docker-push-only docker-push sync-swagger-ui testmake diff --git a/README.md b/README.md index 4e020571..8f4f53ba 100644 --- a/README.md +++ b/README.md @@ -72,30 +72,35 @@ services in the exchange. - Log output of the exchange svr can be seen via `docker logs -f exchange-api`, or it also goes to `/var/log/syslog` on the exchange docker host - At this point you probably want to `make clean` to stop your local docker container so it stops listening on your 8080 port, or you will be very confused when you go back to running new code in your sandbox, and your testing doesn't seem to be executing it. -## Changes in v1.39.0 +## Changes in v1.40.0 ### External changes -- Updated jetty version to 9.4.7 and fixed build to pull latest bug fixes in the 9.4 range -- Fixed non-pattern node search to not find pattern nodes -- Now filter both pattern and non-pattern node searches to not return nodes with empty publicKey values -- Added `"nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 }` policy to patterns (it is a peer to the dataVerification field). Existing pattern resources in the DB will be converted on the way out. New POST/PUTs must include this new field. -- Added POST /orgs/{orgid}/patterns/{patid}/nodehealth and POST /orgs/{orgid}/search/nodehealth for agbot to get node lastHeartbeat and agreement status - ### Todos left to be finished +- Do consistency checking of patterns and workloads - If maxAgreements>1, for CS, in search don't return node to agbot if agbot from same org already has agreement for same workload. - Add api for wiotp to get number of devices and agreements - Allow random PW creation for user creation - Add ability to change owner of node - Add an unauthenticated admin status rest api - Figure out how to set "Response Class (Status 200)" in swagger -- Do consistency checking of patterns and workloads - See if there is a way to fix the swagger hack for 2 level resources - Consider changing all creates to POST, and update (via put/patch) return codes to 200 - Any other schema changes? +## Changes in v1.39.0 + +### External changes + +- Updated jetty version to 9.4.7 and fixed build to pull latest bug fixes in the 9.4 range +- Fixed non-pattern node search to not find pattern nodes +- Now filter both pattern and non-pattern node searches to not return nodes with empty publicKey values +- Added `"nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 }` policy to patterns (it is a peer to the dataVerification field). Existing pattern resources in the DB will be converted on the way out. New POST/PUTs must include this new field. +- Added POST /orgs/{orgid}/patterns/{patid}/nodehealth and POST /orgs/{orgid}/search/nodehealth for agbot to get node lastHeartbeat and agreement status + + ## Changes in v1.38.0 ### External changes diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt new file mode 100644 index 00000000..ebc91b48 --- /dev/null +++ b/src/main/resources/version.txt @@ -0,0 +1 @@ +1.40.0 \ No newline at end of file diff --git a/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala b/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala index 7df8c279..540f7c16 100644 --- a/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala @@ -9,6 +9,8 @@ import org.json4s.jackson.JsonMethods._ import org.scalatra.swagger._ import org.slf4j._ import java.util.Properties + +import scala.io.Source //import scala.collection.immutable._ //import scala.collection.mutable.ListBuffer import scala.util._ @@ -377,15 +379,31 @@ trait AdminRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi }) }) + // =========== GET /admin/version =============================== + val getAdminVersion = + (apiOperation[GetAdminStatusResponse]("getAdminStatus") + summary "Returns the version of the Exchange server" + notes "Returns the version of the Exchange server. Can be run by anyone." + ) + + get("/admin/version", operation(getAdminVersion)) ({ + credsAndLog(true) // do not need to call authenticate().authorizeTo() because anyone can run this + val versionSource = Source.fromResource("version.txt") // returns BufferedSource + val versionText : String = versionSource.getLines.next() + versionSource.close() + versionText + "\n" + }) + + // =========== GET /admin/status =============================== val getAdminStatus = (apiOperation[GetAdminStatusResponse]("getAdminStatus") summary "Returns status of the Exchange server" notes "Returns a dictionary of statuses/statistics. Can be run by any user." parameters( - Parameter("username", DataType.String, Option[String]("The username. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("The password. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) + Parameter("username", DataType.String, Option[String]("The username. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), + Parameter("password", DataType.String, Option[String]("The password. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) + ) ) get("/admin/status", operation(getAdminStatus)) ({ diff --git a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala index e917814a..22939605 100644 --- a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala @@ -165,9 +165,10 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup val postMicroservices = (apiOperation[ApiResponse]("postMicroservices") summary "Adds a microservice" - notes """Creates a microservice resource. This can only be called by a user. The **request body** structure: + notes """Creates a microservice resource. A microservice provides access to node data or services that can be used by potentially multiple workloads. The microservice resource contains the metadata that Horizon needs to deploy the docker images that implement this microservice. If public is set to true, the microservice can be shared across organizations. This can only be called by a user. The **request body** structure: ``` +// (remove all of the comments like this before using) { "label": "GPS for x86_64", // for the registration UI "description": "blah blah", @@ -176,14 +177,9 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "version": "1.0.0", "arch": "amd64", "sharable": "exclusive", // or: "single", "multiple" - "downloadUrl": "", // not used yet - // Hints to the edge node about how to tell if it has physical sensors supported by the MS - "matchHardware": { - // Normally will only set 1 of these values - "usbNodeIds": ["1546:01a7"], - "devFiles": ["/dev/ttyUSB*", "/dev/ttyACM*"] - }, - // Values the node owner will be prompted for and will be set as env vars to the container. Can override env vars in workloads.deployment. + "downloadUrl": "", // reserved for future use + "matchHardware": {}, // reserved for future use (will be hints to the node about how to tell if it has the physical sensors required by this MS + // Values the node owner will be prompted for and will be set as env vars to the container. "userInput": [ { "name": "foo", @@ -192,19 +188,12 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "defaultValue": "bar" } ], + // The docker images that will be deployed on edge nodes for this microservice "workloads": [ { "deployment": "{\"services\":{\"gps\":{\"image\":\"summit.hovitos.engineering/x86/gps:2.0.3\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSk=", - "torrent": { - "url": "https://images.bluehorizon.network/28f57c91243c56caaf0362deeb6620099a0ba1a3.torrent", - "images": [ - { - "file": "d98bfef9f76dee5b4321c4bc18243d9510f11655.tar.gz", - "signature": "kckH14DUj3bXMu7hnQK=" - } - ] - } + "deployment_signature": "EURzSk=", // filled in by the Horizon signing process + "torrent": "{\"url\":\"https://images.bluehorizon.network/139e5b32f271e43698565ff0a37c525609f86178.json\",\"signature\":\"L6/iZxGXloE=\"}" // filled in by the Horizon signing process } ] } @@ -262,9 +251,10 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup val putMicroservices = (apiOperation[ApiResponse]("putMicroservices") summary "Updates a microservice" - notes """Does a full replace of an existing microservice. This can only be called by a user to create, and then only by that user to update. The **request body** structure: + notes """Does a full replace of an existing microservice. This can only be called by the user that originally created it. The **request body** structure: ``` +// (remove all of the comments like this before using) { "label": "GPS for x86_64", // for the registration UI "description": "blah blah", @@ -273,14 +263,9 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "version": "1.0.0", "arch": "amd64", "sharable": "exclusive", // or: "single", "multiple" - "downloadUrl": "", // not used yet - // Hints to the edge node about how to tell if it has physical sensors supported by the MS - "matchHardware": { - // Normally will only set 1 of these values - "usbNodeIds": ["1546:01a7"], - "devFiles": ["/dev/ttyUSB*", "/dev/ttyACM*"] - }, - // Values the node owner will be prompted for and will be set as env vars to the container. Can override env vars in workloads.deployment. + "downloadUrl": "", // reserved for future use + "matchHardware": {}, // reserved for future use (will be hints to the node about how to tell if it has the physical sensors required by this MS + // Values the node owner will be prompted for and will be set as env vars to the container. "userInput": [ { "name": "foo", @@ -289,19 +274,12 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "defaultValue": "bar" } ], + // The docker images that will be deployed on edge nodes for this microservice "workloads": [ { "deployment": "{\"services\":{\"gps\":{\"image\":\"summit.hovitos.engineering/x86/gps:2.0.3\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSk=", - "torrent": { - "url": "https://images.bluehorizon.network/28f57c91243c56caaf0362deeb6620099a0ba1a3.torrent", - "images": [ - { - "file": "d98bfef9f76dee5b4321c4bc18243d9510f11655.tar.gz", - "signature": "kckH14DUj3bXMu7hnQK=" - } - ] - } + "deployment_signature": "EURzSk=", // filled in by the Horizon signing process + "torrent": "{\"url\":\"https://images.bluehorizon.network/139e5b32f271e43698565ff0a37c525609f86178.json\",\"signature\":\"L6/iZxGXloE=\"}" // filled in by the Horizon signing process } ] } diff --git a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala index f3d32dd7..b7e346fb 100644 --- a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala @@ -146,9 +146,10 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val postPatterns = (apiOperation[ApiResponse]("postPatterns") summary "Adds a pattern" - notes """Creates a pattern resource. This can only be called by a user. The **request body** structure: + notes """Creates a pattern resource. A pattern resource specifies all of the deployment information (workloads and microservices) for a type of node. When a node registers with Horizon, it can specify a pattern name to quickly tell Horizon what should be deployed on it. Patterns are not typically intended to be shared across organizations because they also specify deployment policy. This can only be called by a user. The **request body** structure: ``` +// (remove all of the comments like this before using) { "label": "name of the edge pattern", "description": "descriptive text", @@ -158,23 +159,27 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "workloadUrl": "https://bluehorizon.network/workloads/weather", "workloadOrgid": "myorg", "workloadArch": "amd64", + // If multiple workload versions are listed, Horizon will try to automatically upgrade nodes to the version with the lowest priority_value number "workloadVersions": [ { "version": "1.0.1", "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_overrides_signature": "", + "deployment_overrides_signature": "", // filled in by the Horizon signing process "priority": { "priority_value": 50, "retries": 1, "retry_durations": 3600, "verified_durations": 52 }, + // When Horizon should upgrade nodes to newer workload versions "upgradePolicy": { "lifecycle": "immediate", - "time": "01:00AM" + "time": "01:00AM" // reserved for future use } } ], + // Fill in this section if the Horizon agbot should run a REST API of the cloud data ingest service to confirm the workload is sending data. + // If not using this, the dataVerification field can be set to {}. "dataVerification": { "enabled": true, "URL": "", @@ -189,11 +194,12 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport } }, "nodeHealth": { - "missing_heartbeat_interval": 600, // How long a heartbeat can be missing until node is considered missing (in seconds) + "missing_heartbeat_interval": 600, // How long a node heartbeat can be missing until node is considered missing (in seconds) "check_agreement_status": 120 // How often to check that the node agreement entry still exists in the exchange (in seconds) } } ], + // The Horizon agreement protocol(s) to use. "Basic" means make agreements w/o a blockchain. "Citizen Scientist" means use ethereum to record the agreement. "agreementProtocols": [ { "name": "Basic" @@ -259,6 +265,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport notes """Updates a pattern resource. This can only be called by the user that created it. The **request body** structure: ``` +// (remove all of the comments like this before using) { "label": "name of the edge pattern", "description": "descriptive text", @@ -268,23 +275,27 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "workloadUrl": "https://bluehorizon.network/workloads/weather", "workloadOrgid": "myorg", "workloadArch": "amd64", + // If multiple workload versions are listed, Horizon will try to automatically upgrade nodes to the version with the lowest priority_value number "workloadVersions": [ { "version": "1.0.1", "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_overrides_signature": "", + "deployment_overrides_signature": "", // filled in by the Horizon signing process "priority": { "priority_value": 50, "retries": 1, "retry_durations": 3600, "verified_durations": 52 }, + // When Horizon should upgrade nodes to newer workload versions "upgradePolicy": { "lifecycle": "immediate", - "time": "01:00AM" + "time": "01:00AM" // reserved for future use } } ], + // Fill in this section if the Horizon agbot should run a REST API of the cloud data ingest service to confirm the workload is sending data. + // If not using this, the dataVerification field can be set to {}. "dataVerification": { "enabled": true, "URL": "", @@ -299,11 +310,12 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport } }, "nodeHealth": { - "missing_heartbeat_interval": 600, // How long a heartbeat can be missing until node is considered missing (in seconds) + "missing_heartbeat_interval": 600, // How long a node heartbeat can be missing until node is considered missing (in seconds) "check_agreement_status": 120 // How often to check that the node agreement entry still exists in the exchange (in seconds) } } ], + // The Horizon agreement protocol(s) to use. "Basic" means make agreements w/o a blockchain. "Citizen Scientist" means use ethereum to record the agreement. "agreementProtocols": [ { "name": "Basic" diff --git a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala index 321d1524..1336f360 100644 --- a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala @@ -171,9 +171,10 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val postWorkloads = (apiOperation[ApiResponse]("postWorkloads") summary "Adds a workload" - notes """Creates a workload resource. This can only be called by a user. The **request body** structure: + notes """Creates a workload resource. A workload resource contains the metadata that Horizon needs to deploy the docker images that implement this workload. Think of a workload as an edge application. The workload can require 1 or more microservices that Horizon should also deploy when deploying this workload. If public is set to true, the workload can be shared across organizations. This can only be called by a user. The **request body** structure: ``` +// (remove all of the comments like this before using) { "label": "Location for x86_64", // for the registration UI "description": "blah blah", @@ -181,7 +182,8 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this MS "version": "1.0.0", "arch": "amd64", - "downloadUrl": "", // not used yet + "downloadUrl": "", // reserved for future use + // The microservices used by this workload "apiSpec": [ { "specRef": "https://bluehorizon.network/documentation/microservice/gps", @@ -190,7 +192,7 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "arch": "amd64" } ], - // Values the node owner will be prompted for and will be set as env vars to the container. Can override env vars in workloads.deployment. + // Values the node owner will be prompted for and will be set as env vars to the container. "userInput": [ { "name": "foo", @@ -199,11 +201,12 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "defaultValue": "bar" } ], + // The docker images that will be deployed on edge nodes for this workload "workloads": [ { "deployment": "{\"services\":{\"location\":{\"image\":\"summit.hovitos.engineering/x86/location:2.0.6\",\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" + "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", // filled in by the Horizon signing process + "torrent": "{\"url\":\"https://images.bluehorizon.network/139e5b32f271e43698565ff0a37c525609f86178.json\",\"signature\":\"L6/iZxGXloE=\"}" // filled in by the Horizon signing process } ] } @@ -261,9 +264,10 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val putWorkloads = (apiOperation[ApiResponse]("putWorkloads") summary "Updates a workload" - notes """Does a full replace of an existing workload. This can only be called by a user to create, and then only by that user to update. The **request body** structure: + notes """Does a full replace of an existing workload. This can only be called by the user that originally created it. The **request body** structure: ``` +// (remove all of the comments like this before using) { "label": "Location for x86_64", // for the registration UI "description": "blah blah", @@ -271,7 +275,8 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this MS "version": "1.0.0", "arch": "amd64", - "downloadUrl": "", // not used yet + "downloadUrl": "", // reserved for future use + // The microservices used by this workload "apiSpec": [ { "specRef": "https://bluehorizon.network/documentation/microservice/gps", @@ -280,7 +285,7 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "arch": "amd64" } ], - // Values the node owner will be prompted for and will be set as env vars to the container. Can override env vars in workloads.deployment. + // Values the node owner will be prompted for and will be set as env vars to the container. "userInput": [ { "name": "foo", @@ -289,11 +294,12 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "defaultValue": "bar" } ], + // The docker images that will be deployed on edge nodes for this workload "workloads": [ { "deployment": "{\"services\":{\"location\":{\"image\":\"summit.hovitos.engineering/x86/location:2.0.6\",\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" + "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", // filled in by the Horizon signing process + "torrent": "{\"url\":\"https://images.bluehorizon.network/139e5b32f271e43698565ff0a37c525609f86178.json\",\"signature\":\"L6/iZxGXloE=\"}" // filled in by the Horizon signing process } ] } From dfe0fbd2eb798c9c456592bd0dba4aa1210134d0 Mon Sep 17 00:00:00 2001 From: Bruce Potter Date: Wed, 18 Oct 2017 15:14:56 -0400 Subject: [PATCH 2/5] added consistency checking --- README.md | 11 +- .../com/horizon/exchangeapi/AdminRoutes.scala | 2 +- .../horizon/exchangeapi/AgbotsRoutes.scala | 13 +- .../com/horizon/exchangeapi/ApiUtils.scala | 12 + .../exchangeapi/MicroservicesRoutes.scala | 33 ++- .../com/horizon/exchangeapi/NodesRoutes.scala | 44 ++-- .../horizon/exchangeapi/PatternsRoutes.scala | 102 +++++++-- .../horizon/exchangeapi/WorkloadsRoutes.scala | 105 ++++++--- .../horizon/exchangeapi/tables/Agbots.scala | 2 +- .../exchangeapi/tables/Microservices.scala | 13 +- .../horizon/exchangeapi/tables/Patterns.scala | 22 +- .../exchangeapi/tables/Workloads.scala | 15 +- src/test/bash/patch/nodes/1-node-pattern.sh | 6 + src/test/bash/patch/nodes/1-node-publicKey.sh | 4 +- src/test/bash/post/patterns/p1.sh | 28 +++ src/test/bash/primedb.sh | 214 +++++++++--------- src/test/bash/put/patterns/p1.sh | 28 +++ src/test/scala/exchangeapi/AgbotsSuite.scala | 178 ++++++++------- .../scala/exchangeapi/BlockchainsSuite.scala | 2 +- .../scala/exchangeapi/FrontEndSuite.scala | 125 +++++----- .../exchangeapi/MicroservicesSuite.scala | 29 ++- src/test/scala/exchangeapi/NodesSuite.scala | 63 +++--- .../scala/exchangeapi/PatternsSuite.scala | 94 ++++++-- .../scala/exchangeapi/WorkloadsSuite.scala | 47 +++- 24 files changed, 771 insertions(+), 421 deletions(-) create mode 100755 src/test/bash/patch/nodes/1-node-pattern.sh create mode 100755 src/test/bash/post/patterns/p1.sh create mode 100755 src/test/bash/put/patterns/p1.sh diff --git a/README.md b/README.md index 8f4f53ba..33a06bd0 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,20 @@ services in the exchange. - Log output of the exchange svr can be seen via `docker logs -f exchange-api`, or it also goes to `/var/log/syslog` on the exchange docker host - At this point you probably want to `make clean` to stop your local docker container so it stops listening on your 8080 port, or you will be very confused when you go back to running new code in your sandbox, and your testing doesn't seem to be executing it. -## Changes in v1.40.0 +## Changes in 1.40.0 ### External changes +- Added GET /admin/version +- Modified pattern resource so dataVerification field could be specified as `{}` if you are not using it +- For the references between resources that are not enforced by DB foreign keys, added checking when the resource is created, updated, or patched: + - microservices referenced by a workload + - workloads referenced by a pattern + - patterns referenced by an agbot + - pattern referenced by a node + ### Todos left to be finished -- Do consistency checking of patterns and workloads - If maxAgreements>1, for CS, in search don't return node to agbot if agbot from same org already has agreement for same workload. - Add api for wiotp to get number of devices and agreements - Allow random PW creation for user creation diff --git a/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala b/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala index 540f7c16..e348ff12 100644 --- a/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala @@ -409,7 +409,7 @@ trait AdminRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi get("/admin/status", operation(getAdminStatus)) ({ credsAndLog().authenticate().authorizeTo(TAction(),Access.STATUS) val statusResp = new AdminStatus() - //TODO: use a DBIO.seq instead. It does essentially the same thing, but more efficiently + //TODO: use a DBIO.sequence instead. It does essentially the same thing, but more efficiently db.run(UsersTQ.rows.length.result.asTry.flatMap({ xs => logger.debug("GET /admin/status users length: "+xs) xs match { diff --git a/src/main/scala/com/horizon/exchangeapi/AgbotsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/AgbotsRoutes.scala index 07bee5d7..b5a30f82 100644 --- a/src/main/scala/com/horizon/exchangeapi/AgbotsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/AgbotsRoutes.scala @@ -485,7 +485,14 @@ trait AgbotsRoutes extends ScalatraBase with FutureSupport with SwaggerSupport w catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException pattern.validate(patId) val resp = response - db.run(pattern.toAgbotPatternRow(compositeId, patId).upsert.asTry).map({ xs => + db.run(PatternsTQ.getPattern(OrgAndId(pattern.patternOrgid,pattern.pattern).toString).length.result.asTry.flatMap({ xs => + logger.debug("PUT /orgs/"+orgid+"/agbots/"+id+"/patterns"+patId+" pattern validation: "+xs.toString) + xs match { + case Success(num) => if (num > 0) pattern.toAgbotPatternRow(compositeId, patId).upsert.asTry + else DBIO.failed(new Throwable("the referenced pattern does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + })).map({ xs => logger.debug("PUT /orgs/"+orgid+"/agbots/"+id+"/patterns/"+patId+" result: "+xs.toString) xs match { case Success(_) => resp.setStatus(HttpCode.PUT_OK) @@ -494,8 +501,8 @@ trait AgbotsRoutes extends ScalatraBase with FutureSupport with SwaggerSupport w resp.setStatus(HttpCode.ACCESS_DENIED) ApiResponse(ApiResponseType.ACCESS_DENIED, "pattern '"+patId+"' for agbot '"+compositeId+"' not inserted or updated: "+t.getMessage) } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "pattern '"+patId+"' for agbot '"+compositeId+"' not inserted or updated: "+t.toString) + resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "pattern '"+patId+"' for agbot '"+compositeId+"' not inserted or updated: "+t.getMessage) } } }) diff --git a/src/main/scala/com/horizon/exchangeapi/ApiUtils.scala b/src/main/scala/com/horizon/exchangeapi/ApiUtils.scala index 75a8c9ae..21935ef6 100644 --- a/src/main/scala/com/horizon/exchangeapi/ApiUtils.scala +++ b/src/main/scala/com/horizon/exchangeapi/ApiUtils.scala @@ -301,3 +301,15 @@ case class VersionRange(range: String) { (if (floorInclusive) "[" else "(") + floor + "," + ceiling + (if (ceilingInclusive) "]" else ")") } } + +/** Depending on the given int, returns 1st, 2nd, 3rd, 4th, ... */ +case class Nth(n: Int) { + override def toString: String = { + n match { + case 1 => return n + "st" + case 2 => return n + "nd" + case 3 => return n + "rd" + case _ => return n + "th" + } + } +} diff --git a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala index 22939605..366f2cd8 100644 --- a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala @@ -12,7 +12,7 @@ import com.horizon.exchangeapi.tables._ import scala.collection.immutable._ import scala.collection.mutable.{HashMap => MutableHashMap} import scala.util._ -import java.net._ +//import java.net._ //====== These are the input and output structures for /microservices routes. Swagger and/or json seem to require they be outside the trait. @@ -21,26 +21,23 @@ case class GetMicroservicesResponse(microservices: Map[String,Microservice], las case class GetMicroserviceAttributeResponse(attribute: String, value: String) /** Input format for POST /orgs/{orgid}/microservices or PUT /orgs/{orgid}/microservices/ */ -case class PostPutMicroserviceRequest(label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: String, matchHardware: Map[String,String], userInput: List[Map[String,String]], workloads: List[Map[String,String]]) { +case class PostPutMicroserviceRequest(label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: String, matchHardware: Map[String,String], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { protected implicit val jsonFormats: Formats = DefaultFormats def validate() = { - // Check the specRef is a valid URL - try { - new URL(specRef) - } catch { - case _: MalformedURLException => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "specRef is not valid URL format.")) - } + // Currently we do not want to force that the specRef is a valid URL + //try { new URL(specRef) } + //catch { case _: MalformedURLException => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "specRef is not valid URL format.")) } - if (!Version(version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version is not valid version format.")) - } + if (!Version(version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '"+version+"' is not valid version format.")) - def formId(orgid: String): String = { - // Remove the https:// from the beginning of specRef and replace troublesome chars with a dash. It has already been checked as a valid URL in validate(). - val specRef2 = """^[A-Za-z0-9+.-]*?://""".r replaceFirstIn (specRef, "") - val specRef3 = """[$!*,;/?@&~=%]""".r replaceAllIn (specRef2, "-") // I think possible chars in valid urls are: $_.+!*,;/?:@&~=%- - return OrgAndId(orgid, specRef3 + "_" + version + "_" + arch).toString + // Check that it is signed + for (w <- workloads) { + if (w.deployment != "" && (w.deployment_signature == "" || w.torrent == "")) { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this microservice definition does not appear to be signed.")) } + } } + def formId(orgid: String) = MicroservicesTQ.formId(orgid, specRef, version, arch) + def toMicroserviceRow(microservice: String, orgid: String, owner: String) = MicroserviceRow(microservice, orgid, owner, label, description, public, specRef, version, arch, sharable, downloadUrl, write(matchHardware), write(userInput), write(workloads), ApiTime.nowUTC) } @@ -170,7 +167,7 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup ``` // (remove all of the comments like this before using) { - "label": "GPS for x86_64", // for the registration UI + "label": "GPS for x86_64", // this will be displayed in the node registration UI "description": "blah blah", "public": true, // whether or not it can be viewed by other organizations "specRef": "https://bluehorizon.network/documentation/microservice/gps", // the unique identifier of this MS @@ -184,7 +181,7 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup { "name": "foo", "label": "The Foo Value", - "type": "string", // or: "int", "float", "list of strings" + "type": "string", // or: "int", "float", "string list" "defaultValue": "bar" } ], @@ -256,7 +253,7 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup ``` // (remove all of the comments like this before using) { - "label": "GPS for x86_64", // for the registration UI + "label": "GPS for x86_64", // this will be displayed in the node registration UI "description": "blah blah", "public": true, // whether or not it can be viewed by other organizations "specRef": "https://bluehorizon.network/documentation/microservice/gps", // the unique identifier of this MS diff --git a/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala b/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala index 0bd6a999..2a9e3270 100644 --- a/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala @@ -678,7 +678,7 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi { "token": "abc", // node token, set by user when adding this node. "name": "rpi3", // node name that you pick - "pattern": "myorg/mypattern", // (optional) points to a pattern resource that defines what workloads should be run on this type of node + "pattern": "myorg/mypattern", // (optional) points to a pattern resource that defines what workloads should be deployed to this type of node "registeredMicroservices": [ // list of data microservices you want to make available { "url": "https://bluehorizon.network/documentation/sdr-node-api", @@ -732,15 +732,25 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi val owner = ident match { case IUser(creds) => creds.id; case _ => "" } //val microTmpls = node.getMicroTemplates // do this before creating/updating the entry in db, in case it can not find the templates val resp = response - db.run(NodesTQ.getNumOwned(owner).result.flatMap({ xs => + val patValidateAction = if (node.pattern != "") PatternsTQ.getPattern(node.pattern).length.result else DBIO.successful(1) + db.run(patValidateAction.asTry.flatMap({ xs => + logger.debug("PUT /orgs/"+orgid+"/nodes/"+bareId+" pattern validation: "+xs.toString) + xs match { + case Success(num) => if (num > 0) NodesTQ.getNumOwned(owner).result.asTry + else DBIO.failed(new Throwable("the referenced pattern does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + }).flatMap({ xs => logger.debug("PUT /orgs/"+orgid+"/nodes/"+bareId+" num owned: "+xs) - val numOwned = xs - val maxNodes = ExchConfig.getInt("api.limits.maxNodes") - if (maxNodes == 0 || numOwned <= maxNodes || owner == "") { // when owner=="" we know it is only an update, otherwise we are not sure, but if they are already over the limit, stop them anyway - val action = if (owner == "") node.getDbUpdate(id, orgid, owner) else node.getDbUpsert(id, orgid, owner) - action.transactionally.asTry + xs match { + case Success(numOwned) => val maxNodes = ExchConfig.getInt("api.limits.maxNodes") + if (maxNodes == 0 || numOwned <= maxNodes || owner == "") { // when owner=="" we know it is only an update, otherwise we are not sure, but if they are already over the limit, stop them anyway + val action = if (owner == "") node.getDbUpdate(id, orgid, owner) else node.getDbUpsert(id, orgid, owner) + action.transactionally.asTry + } + else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxNodes+ " nodes")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxNodes+ " nodes")).asTry })).map({ xs => logger.debug("PUT /orgs/"+orgid+"/nodes/"+bareId+" result: "+xs.toString) xs match { @@ -752,8 +762,8 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi resp.setStatus(HttpCode.ACCESS_DENIED) ApiResponse(ApiResponseType.ACCESS_DENIED, "node '"+id+"' not inserted or updated: "+t.getMessage) } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "node '"+id+"' not inserted or updated: "+t.toString) + resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "node '"+id+"' not inserted or updated: "+t.getMessage) } } }) @@ -800,7 +810,15 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi val resp = response val (action, attrName) = node.getDbUpdate(id) if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid node attribute specified")) - db.run(action.transactionally.asTry).map({ xs => + val patValidateAction = if (attrName == "pattern" && node.pattern.get != "") PatternsTQ.getPattern(node.pattern.get).length.result else DBIO.successful(1) + db.run(patValidateAction.asTry.flatMap({ xs => + logger.debug("PATCH /orgs/"+orgid+"/nodes/"+bareId+" pattern validation: "+xs.toString) + xs match { + case Success(num) => if (num > 0) action.transactionally.asTry + else DBIO.failed(new Throwable("the referenced pattern does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + })).map({ xs => logger.debug("PATCH /orgs/"+orgid+"/nodes/"+bareId+" result: "+xs.toString) xs match { case Success(v) => try { @@ -814,8 +832,8 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi ApiResponse(ApiResponseType.NOT_FOUND, "node '"+id+"' not found") } } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "Unexpected result from update: "+e) } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "node '"+id+"' not inserted or updated: "+t.toString) + case Failure(t) => resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "node '"+id+"' not inserted or updated: "+t.getMessage) } }) }) diff --git a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala index b7e346fb..6a011df2 100644 --- a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala @@ -9,10 +9,12 @@ import org.scalatra.swagger._ import org.slf4j._ import slick.jdbc.PostgresProfile.api._ import com.horizon.exchangeapi.tables._ + import scala.collection.immutable._ import scala.collection.mutable.{HashMap => MutableHashMap} import scala.util._ //import java.net._ +import scala.util.control.Breaks._ //====== These are the input and output structures for /orgs/{orgid}/patterns routes. Swagger and/or json seem to require they be outside the trait. @@ -23,7 +25,19 @@ case class GetPatternAttributeResponse(attribute: String, value: String) /** Input format for POST/PUT /orgs/{orgid}/patterns/ */ case class PostPutPatternRequest(label: String, description: String, public: Boolean, workloads: List[PWorkloads], agreementProtocols: List[Map[String,String]]) { protected implicit val jsonFormats: Formats = DefaultFormats - def validate() = {} + def validate(): Unit = { + // Check that it is signed and check the version syntax + for (w <- workloads) { + for (wv <- w.workloadVersions) { + if (!Version(wv.version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '"+wv.version+"' is not valid version format.")) + if (wv.deployment_overrides != "" && wv.deployment_overrides_signature == "") { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this pattern definition does not appear to be signed.")) } + } + } + + } + + // Build a list of db actions to verify that the referenced workloads exist + def validateWorkloadIds: DBIO[Vector[Int]] = PatternsTQ.validateWorkloadIds(workloads) //def toPatternRow(pattern: String, orgid: String, owner: String) = PatternRow(pattern, orgid, owner, label, description, public, write(microservices), write(workloads), write(dataVerification), write(agreementProtocols), write(properties), write(counterPartyProperties), maxAgreements, ApiTime.nowUTC) def toPatternRow(pattern: String, orgid: String, owner: String) = PatternRow(pattern, orgid, owner, label, description, public, write(workloads), write(agreementProtocols), ApiTime.nowUTC) @@ -151,9 +165,10 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ``` // (remove all of the comments like this before using) { - "label": "name of the edge pattern", + "label": "name of the edge pattern", // this will be displayed in the node registration UI "description": "descriptive text", - "public": false, + "public": false, // typically patterns are not appropriate to share across orgs because they contain policy choices + // The workloads that should be deployed to the edge for this pattern. (The workloads must exist before creating this pattern.) "workloads": [ { "workloadUrl": "https://bluehorizon.network/workloads/weather", @@ -229,14 +244,31 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport patternReq.validate() val owner = ident match { case IUser(creds) => creds.id; case _ => "" } val resp = response - db.run(PatternsTQ.getNumOwned(owner).result.flatMap({ xs => - logger.debug("POST /orgs/"+orgid+"/patterns num owned by "+owner+": "+xs) - val numOwned = xs - val maxPatterns = ExchConfig.getInt("api.limits.maxPatterns") - if (maxPatterns == 0 || numOwned <= maxPatterns) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway - patternReq.toPatternRow(pattern, orgid, owner).insert.asTry + db.run(patternReq.validateWorkloadIds.asTry.flatMap({ xs => + logger.debug("POST /orgs/"+orgid+"/patterns"+barePattern+" workload validation: "+xs.toString) + xs match { + case Success(v) => var invalidIndex = -1 // v is a vector of Int (the length of each workload query). If any are zero we should error out. + breakable { for ( (len, index) <- v.zipWithIndex) { + if (len <= 0) { + invalidIndex = index + break + } + } } + if (invalidIndex < 0) PatternsTQ.getNumOwned(owner).result.asTry + else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced workload does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + }).flatMap({ xs => + logger.debug("POST /orgs/"+orgid+"/patterns"+barePattern+" num owned by "+owner+": "+xs) + xs match { + case Success(num) => val numOwned = num + val maxPatterns = ExchConfig.getInt("api.limits.maxPatterns") + if (maxPatterns == 0 || numOwned <= maxPatterns) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway + patternReq.toPatternRow(pattern, orgid, owner).insert.asTry + } + else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxPatterns+ " patterns")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxPatterns+ " patterns")).asTry })).map({ xs => logger.debug("POST /orgs/"+orgid+"/patterns/"+barePattern+" result: "+xs.toString) xs match { @@ -251,8 +283,8 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport resp.setStatus(HttpCode.ALREADY_EXISTS) ApiResponse(ApiResponseType.ALREADY_EXISTS, "pattern '" + pattern + "' already exists: " + t.getMessage) } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "pattern '"+pattern+"' not created: "+t.toString) + resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "pattern '"+pattern+"' not created: "+t.getMessage) } } }) @@ -267,9 +299,10 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ``` // (remove all of the comments like this before using) { - "label": "name of the edge pattern", + "label": "name of the edge pattern", // this will be displayed in the node registration UI "description": "descriptive text", - "public": false, + "public": false, // typically patterns are not appropriate to share across orgs because they contain policy choices + // The workloads that should be deployed to the edge for this pattern. (The workloads must exist before creating this pattern.) "workloads": [ { "workloadUrl": "https://bluehorizon.network/workloads/weather", @@ -345,7 +378,21 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport patternReq.validate() val owner = ident match { case IUser(creds) => creds.id; case _ => "" } val resp = response - db.run(patternReq.toPatternRow(pattern, orgid, owner).update.asTry).map({ xs => + db.run(patternReq.validateWorkloadIds.asTry.flatMap({ xs => + logger.debug("PUT /orgs/"+orgid+"/patterns"+barePattern+" workload validation: "+xs.toString) + xs match { + case Success(v) => var invalidIndex = -1 // v is a vector of Int (the length of each workload query). If any are zero we should error out. + breakable { for ( (len, index) <- v.zipWithIndex) { + if (len <= 0) { + invalidIndex = index + break + } + } } + if (invalidIndex < 0) patternReq.toPatternRow(pattern, orgid, owner).update.asTry + else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced workload does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + })).map({ xs => logger.debug("PUT /orgs/"+orgid+"/patterns/"+barePattern+" result: "+xs.toString) xs match { case Success(n) => try { @@ -360,8 +407,8 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ApiResponse(ApiResponseType.NOT_FOUND, "pattern '"+pattern+"' not found") } } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "pattern '"+pattern+"' not updated: "+e) } // the specific exception is NumberFormatException - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "pattern '"+pattern+"' not updated: "+t.toString) + case Failure(t) => resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "pattern '"+pattern+"' not updated: "+t.getMessage) } }) }) @@ -404,7 +451,22 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val resp = response val (action, attrName) = patternReq.getDbUpdate(pattern, orgid) if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid pattern attribute specified")) - db.run(action.transactionally.asTry).map({ xs => + val patValidateAction = if (attrName == "workloads") PatternsTQ.validateWorkloadIds(patternReq.workloads.get) else DBIO.successful(Vector()) + db.run(patValidateAction.asTry.flatMap({ xs => + logger.debug("PUT /orgs/"+orgid+"/patterns"+barePattern+" workload validation: "+xs.toString) + xs match { + case Success(v) => var invalidIndex = -1 // v is a vector of Int (the length of each workload query). If any are zero we should error out. + breakable { for ( (len, index) <- v.zipWithIndex) { + if (len <= 0) { + invalidIndex = index + break + } + } } + if (invalidIndex < 0) action.transactionally.asTry + else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced workload does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + })).map({ xs => logger.debug("PATCH /orgs/"+orgid+"/patterns/"+barePattern+" result: "+xs.toString) xs match { case Success(v) => try { @@ -418,8 +480,8 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ApiResponse(ApiResponseType.NOT_FOUND, "pattern '"+pattern+"' not found") } } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "Unexpected result from update: "+e) } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "pattern '"+pattern+"' not updated: "+t.toString) + case Failure(t) => resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "pattern '"+pattern+"' not updated: "+t.getMessage) } }) }) diff --git a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala index 1336f360..8e37faea 100644 --- a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala @@ -9,10 +9,12 @@ import org.scalatra.swagger._ import org.slf4j._ import slick.jdbc.PostgresProfile.api._ import com.horizon.exchangeapi.tables._ + import scala.collection.immutable._ -import scala.collection.mutable.{HashMap => MutableHashMap} +import scala.collection.mutable.{ListBuffer, HashMap => MutableHashMap} import scala.util._ -import java.net._ +//import java.net._ +import scala.util.control.Breaks._ //====== These are the input and output structures for /orgs/{orgid}/workloads routes. Swagger and/or json seem to require they be outside the trait. @@ -21,26 +23,34 @@ case class GetWorkloadsResponse(workloads: Map[String,Workload], lastIndex: Int) case class GetWorkloadAttributeResponse(attribute: String, value: String) /** Input format for POST /microservices or PUT /orgs/{orgid}/workloads/ */ -case class PostPutWorkloadRequest(label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: List[Map[String,String]], userInput: List[Map[String,String]], workloads: List[Map[String,String]]) { +case class PostPutWorkloadRequest(label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: List[WMicroservices], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { protected implicit val jsonFormats: Formats = DefaultFormats def validate() = { - // Check the workloadUrl is a valid URL - try { - new URL(workloadUrl) - } catch { - case _: MalformedURLException => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "workloadUrl is not valid URL format.")) - } + // Currently we do not want to force that the workloadUrl is a valid URL + //try { new URL(workloadUrl) } + //catch { case _: MalformedURLException => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "workloadUrl is not valid URL format.")) } + + if (!Version(version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '"+version+"' is not valid version format.")) - if (!Version(version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version is not valid version format.")) + // Check that it is signed + for (w <- workloads) { + if (w.deployment != "" && (w.deployment_signature == "" || w.torrent == "")) { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this workload definition does not appear to be signed.")) } + } } - def formId(orgid: String): String = { - // Remove the https:// from the beginning of workloadUrl and replace troublesome chars with a dash. It has already been checked as a valid URL in validate(). - val workloadUrl2 = """^[A-Za-z0-9+.-]*?://""".r replaceFirstIn (workloadUrl, "") - val workloadUrl3 = """[$!*,;/?@&~=%]""".r replaceAllIn (workloadUrl2, "-") // I think possible chars in valid urls are: $_.+!*,;/?:@&~=%- - return OrgAndId(orgid, workloadUrl3 + "_" + version + "_" + arch).toString + // Build a list of db actions to verify that the referenced workloads exist + def validateMicroserviceIds: DBIO[Vector[Int]] = { + if (apiSpec.isEmpty) return DBIO.successful(Vector()) + val actions = ListBuffer[DBIO[Int]]() + for (m <- apiSpec) { + val microId = MicroservicesTQ.formId(m.org, m.specRef, m.version, m.arch) + actions += MicroservicesTQ.getMicroservice(microId).length.result + } + return DBIO.sequence(actions.toVector) // convert the list of actions to a DBIO sequence because that returns query values } + def formId(orgid: String) = WorkloadsTQ.formId(orgid, workloadUrl, version, arch) + def toWorkloadRow(workload: String, orgid: String, owner: String) = WorkloadRow(workload, orgid, owner, label, description, public, workloadUrl, version, arch, downloadUrl, write(apiSpec), write(userInput), write(workloads), ApiTime.nowUTC) } @@ -176,14 +186,14 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ``` // (remove all of the comments like this before using) { - "label": "Location for x86_64", // for the registration UI + "label": "Location for x86_64", // this will be displayed in the node registration UI "description": "blah blah", "public": true, // whether or not it can be viewed by other organizations - "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this MS + "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this workload "version": "1.0.0", "arch": "amd64", "downloadUrl": "", // reserved for future use - // The microservices used by this workload + // The microservices used by this workload. (The microservices must exist before creating this workload.) "apiSpec": [ { "specRef": "https://bluehorizon.network/documentation/microservice/gps", @@ -231,14 +241,31 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val workload = workloadReq.formId(orgid) val owner = ident match { case IUser(creds) => creds.id; case _ => "" } val resp = response - db.run(WorkloadsTQ.getNumOwned(owner).result.flatMap({ xs => + db.run(workloadReq.validateMicroserviceIds.asTry.flatMap({ xs => + logger.debug("POST /orgs/"+orgid+"/workloads apiSpec validation: "+xs.toString) + xs match { + case Success(v) => var invalidIndex = -1 // v is a vector of Int (the length of each microservice query). If any are zero we should error out. + breakable { for ( (len, index) <- v.zipWithIndex) { + if (len <= 0) { + invalidIndex = index + break + } + } } + if (invalidIndex < 0) WorkloadsTQ.getNumOwned(owner).result.asTry + else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced microservice (apiSpec) does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + }).flatMap({ xs => logger.debug("POST /orgs/"+orgid+"/workloads num owned by "+owner+": "+xs) - val numOwned = xs - val maxWorkloads = ExchConfig.getInt("api.limits.maxWorkloads") - if (maxWorkloads == 0 || numOwned <= maxWorkloads) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway - workloadReq.toWorkloadRow(workload, orgid, owner).insert.asTry + xs match { + case Success(num) => val numOwned = num + val maxWorkloads = ExchConfig.getInt("api.limits.maxWorkloads") + if (maxWorkloads == 0 || numOwned <= maxWorkloads) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway + workloadReq.toWorkloadRow(workload, orgid, owner).insert.asTry + } + else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxWorkloads+ " workloads")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxWorkloads+ " workloads")).asTry })).map({ xs => logger.debug("POST /orgs/"+orgid+"/workloads result: "+xs.toString) xs match { @@ -253,8 +280,8 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport resp.setStatus(HttpCode.ALREADY_EXISTS) ApiResponse(ApiResponseType.ALREADY_EXISTS, "workload '" + workload + "' already exists: " + t.getMessage) } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "workload '"+workload+"' not created: "+t.toString) + resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "workload '"+workload+"' not created: "+t.getMessage) } } }) @@ -269,14 +296,14 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ``` // (remove all of the comments like this before using) { - "label": "Location for x86_64", // for the registration UI + "label": "Location for x86_64", // this will be displayed in the node registration UI "description": "blah blah", "public": true, // whether or not it can be viewed by other organizations - "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this MS + "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this workload "version": "1.0.0", "arch": "amd64", "downloadUrl": "", // reserved for future use - // The microservices used by this workload + // The microservices used by this workload. (The microservices must exist before creating this workload.) "apiSpec": [ { "specRef": "https://bluehorizon.network/documentation/microservice/gps", @@ -326,7 +353,21 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport workloadReq.validate() val owner = ident match { case IUser(creds) => creds.id; case _ => "" } val resp = response - db.run(workloadReq.toWorkloadRow(workload, orgid, owner).update.asTry).map({ xs => + db.run(workloadReq.validateMicroserviceIds.asTry.flatMap({ xs => + logger.debug("POST /orgs/"+orgid+"/workloads apiSpec validation: "+xs.toString) + xs match { + case Success(v) => var invalidIndex = -1 // v is a vector of Int (the length of each microservice query). If any are zero we should error out. + breakable { for ( (len, index) <- v.zipWithIndex) { + if (len <= 0) { + invalidIndex = index + break + } + } } + if (invalidIndex < 0) workloadReq.toWorkloadRow(workload, orgid, owner).update.asTry + else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced microservice (apiSpec) does not exist in the exchange")).asTry + case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry + } + })).map({ xs => logger.debug("PUT /orgs/"+orgid+"/workloads/"+bareWorkload+" result: "+xs.toString) xs match { case Success(n) => try { @@ -341,8 +382,8 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport ApiResponse(ApiResponseType.NOT_FOUND, "workload '"+workload+"' not found") } } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "workload '"+workload+"' not updated: "+e) } // the specific exception is NumberFormatException - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "workload '"+workload+"' not updated: "+t.toString) + case Failure(t) => resp.setStatus(HttpCode.BAD_INPUT) + ApiResponse(ApiResponseType.BAD_INPUT, "workload '"+workload+"' not updated: "+t.getMessage) } }) }) diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala b/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala index f61134ce..7b3469ac 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala @@ -90,7 +90,7 @@ case class AgbotPatternRow(patId: String, agbotId: String, patternOrgid: String, } class AgbotPatterns(tag: Tag) extends Table[AgbotPatternRow](tag, "agbotpatterns") { - def patId = column[String]("patid") // key - this is the pattern's org concatenated with the patter name + def patId = column[String]("patid") // key - this is the pattern's org concatenated with the pattern name def agbotId = column[String]("agbotid") // additional key - the composite orgid/agbotid def patternOrgid = column[String]("patternorgid") def pattern = column[String]("pattern") diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala b/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala index 5ac4404f..d883d143 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala @@ -1,11 +1,13 @@ package com.horizon.exchangeapi.tables +import com.horizon.exchangeapi.OrgAndId import org.json4s._ import org.json4s.jackson.Serialization.read import slick.jdbc.PostgresProfile.api._ /** Contains the object representations of the DB tables related to microservices. */ +case class MDockerImages(deployment: String, deployment_signature: String, torrent: String) case class MicroserviceRow(microservice: String, orgid: String, owner: String, label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: String, matchHardware: String, userInput: String, workloads: String, lastUpdated: String) { protected implicit val jsonFormats: Formats = DefaultFormats @@ -13,7 +15,7 @@ case class MicroserviceRow(microservice: String, orgid: String, owner: String, l def toMicroservice: Microservice = { val mh = if (matchHardware != "") read[Map[String,String]](matchHardware) else Map[String,String]() val input = if (userInput != "") read[List[Map[String,String]]](userInput) else List[Map[String,String]]() - val wrk = if (workloads != "") read[List[Map[String,String]]](workloads) else List[Map[String,String]]() + val wrk = if (workloads != "") read[List[MDockerImages]](workloads) else List[MDockerImages]() new Microservice(owner, label, description, public, specRef, version, arch, sharable, downloadUrl, mh, input, wrk, lastUpdated) } @@ -51,6 +53,13 @@ class Microservices(tag: Tag) extends Table[MicroserviceRow](tag, "microservices object MicroservicesTQ { val rows = TableQuery[Microservices] + def formId(orgid: String, specRef: String, version: String, arch: String): String = { + // Remove the https:// from the beginning of specRef and replace troublesome chars with a dash. It has already been checked as a valid URL in validate(). + val specRef2 = """^[A-Za-z0-9+.-]*?://""".r replaceFirstIn (specRef, "") + val specRef3 = """[$!*,;/?@&~=%]""".r replaceAllIn (specRef2, "-") // I think possible chars in valid urls are: $_.+!*,;/?:@&~=%- + return OrgAndId(orgid, specRef3 + "_" + version + "_" + arch).toString + } + def getAllMicroservices(orgid: String) = rows.filter(_.orgid === orgid) def getMicroservice(microservice: String) = rows.filter(_.microservice === microservice) def getOwner(microservice: String) = rows.filter(_.microservice === microservice).map(_.owner) @@ -95,7 +104,7 @@ object MicroservicesTQ { } // This is the microservice table minus the key - used as the data structure to return to the REST clients -class Microservice(var owner: String, var label: String, var description: String, var public: Boolean, var specRef: String, var version: String, var arch: String, var sharable: String, var downloadUrl: String, var matchHardware: Map[String,String], var userInput: List[Map[String,String]], var workloads: List[Map[String,String]], var lastUpdated: String) { +class Microservice(var owner: String, var label: String, var description: String, var public: Boolean, var specRef: String, var version: String, var arch: String, var sharable: String, var downloadUrl: String, var matchHardware: Map[String,String], var userInput: List[Map[String,String]], var workloads: List[MDockerImages], var lastUpdated: String) { // If we end up needing this, we might have to do deep copies of the variables that are actually structures //def copy = new Microservice(owner, label, description, specRef, version, arch, sharable, downloadUrl, matchHardware, userInput, workloads, lastUpdated) } diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala b/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala index 88839004..bf148f36 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala @@ -10,13 +10,14 @@ import scala.collection.mutable.ListBuffer //case class PPriority(priority_value: Int, retries: Int, retry_durations: Int, verified_durations: Int) //case class PUpgradePolicy(lifecycle: String, time: String) -case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: PDataVerification, nodeHealth: Map[String,Int]) -case class POldWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: PDataVerification) +//case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: PDataVerification, nodeHealth: Map[String,Int]) +case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: Map[String,Any], nodeHealth: Map[String,Int]) +//case class POldWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: PDataVerification) +case class POldWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: Map[String,Any]) case class PWorkloadVersions(version: String, deployment_overrides: String, deployment_overrides_signature: String, priority: Map[String,Int], upgradePolicy: Map[String,String]) //case class PMetering(tokens: Int, per_time_unit: String, notification_interval: Int) case class PDataVerification(enabled: Boolean, URL: String, user: String, password: String, interval: Int, check_rate: Int, metering: Map[String,Any]) -//case class PatternRow(pattern: String, orgid: String, owner: String, label: String, description: String, public: Boolean, microservices: String, workloads: String, dataVerification: String, agreementProtocols: String, properties: String, counterPartyProperties: String, maxAgreements: Int, lastUpdated: String) { case class PatternRow(pattern: String, orgid: String, owner: String, label: String, description: String, public: Boolean, workloads: String, agreementProtocols: String, lastUpdated: String) { protected implicit val jsonFormats: Formats = DefaultFormats @@ -67,6 +68,21 @@ class Patterns(tag: Tag) extends Table[PatternRow](tag, "patterns") { object PatternsTQ { val rows = TableQuery[Patterns] + // Build a list of db actions to verify that the referenced workloads exist + def validateWorkloadIds(workloads: List[PWorkloads]): DBIO[Vector[Int]] = { + // Currently, anax does not support a pattern with no workloads, so do not support that here + val actions = ListBuffer[DBIO[Int]]() + for (w <- workloads) { + for (wv <- w.workloadVersions) { + val workId = WorkloadsTQ.formId(w.workloadOrgid, w.workloadUrl, wv.version, w.workloadArch) + //println("workId: "+workId) + actions += WorkloadsTQ.getWorkload(workId).length.result + } + } + //return DBIO.seq(actions: _*) // convert the list of actions to a DBIO seq + return DBIO.sequence(actions.toVector) // convert the list of actions to a DBIO sequence + } + def getAllPatterns(orgid: String) = rows.filter(_.orgid === orgid) def getPattern(pattern: String) = rows.filter(_.pattern === pattern) def getOwner(pattern: String) = rows.filter(_.pattern === pattern).map(_.owner) diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala b/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala index 75c25041..d1516c29 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala @@ -1,19 +1,21 @@ package com.horizon.exchangeapi.tables +import com.horizon.exchangeapi.OrgAndId import org.json4s._ import org.json4s.jackson.Serialization.read import slick.jdbc.PostgresProfile.api._ /** Contains the object representations of the DB tables related to workloads. */ +case class WMicroservices(specRef: String, org: String, version: String, arch: String) case class WorkloadRow(workload: String, orgid: String, owner: String, label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: String, userInput: String, workloads: String, lastUpdated: String) { protected implicit val jsonFormats: Formats = DefaultFormats def toWorkload: Workload = { - val spec = if (apiSpec != "") read[List[Map[String,String]]](apiSpec) else List[Map[String,String]]() + val spec = if (apiSpec != "") read[List[WMicroservices]](apiSpec) else List[WMicroservices]() val input = if (userInput != "") read[List[Map[String,String]]](userInput) else List[Map[String,String]]() - val wrk = if (workloads != "") read[List[Map[String,String]]](workloads) else List[Map[String,String]]() + val wrk = if (workloads != "") read[List[MDockerImages]](workloads) else List[MDockerImages]() new Workload(owner, label, description, public, workloadUrl, version, arch, downloadUrl, spec, input, wrk, lastUpdated) } @@ -50,6 +52,13 @@ class Workloads(tag: Tag) extends Table[WorkloadRow](tag, "workloads") { object WorkloadsTQ { val rows = TableQuery[Workloads] + def formId(orgid: String, url: String, version: String, arch: String): String = { + // Remove the https:// from the beginning of workloadUrl and replace troublesome chars with a dash. It has already been checked as a valid URL in validate(). + val workloadUrl2 = """^[A-Za-z0-9+.-]*?://""".r replaceFirstIn (url, "") + val workloadUrl3 = """[$!*,;/?@&~=%]""".r replaceAllIn (workloadUrl2, "-") // I think possible chars in valid urls are: $_.+!*,;/?:@&~=%- + return OrgAndId(orgid, workloadUrl3 + "_" + version + "_" + arch).toString + } + def getAllWorkloads(orgid: String) = rows.filter(_.orgid === orgid) def getWorkload(workload: String) = rows.filter(_.workload === workload) def getOwner(workload: String) = rows.filter(_.workload === workload).map(_.owner) @@ -92,7 +101,7 @@ object WorkloadsTQ { } // This is the workload table minus the key - used as the data structure to return to the REST clients -class Workload(var owner: String, var label: String, var description: String, var public: Boolean, var workloadUrl: String, var version: String, var arch: String, var downloadUrl: String, var apiSpec: List[Map[String,String]], var userInput: List[Map[String,String]], var workloads: List[Map[String,String]], var lastUpdated: String) { +class Workload(var owner: String, var label: String, var description: String, var public: Boolean, var workloadUrl: String, var version: String, var arch: String, var downloadUrl: String, var apiSpec: List[WMicroservices], var userInput: List[Map[String,String]], var workloads: List[MDockerImages], var lastUpdated: String) { def copy = new Workload(owner, label, description, public, workloadUrl, version, arch, downloadUrl, apiSpec, userInput, workloads, lastUpdated) } diff --git a/src/test/bash/patch/nodes/1-node-pattern.sh b/src/test/bash/patch/nodes/1-node-pattern.sh new file mode 100755 index 00000000..fa4225fb --- /dev/null +++ b/src/test/bash/patch/nodes/1-node-pattern.sh @@ -0,0 +1,6 @@ +# Update node 1 as node +source `dirname $0`/../../functions.sh PATCH $* + +curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_NODEAUTH" -d '{ + "pattern": "'$EXCHANGE_ORG'/p1x" +}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/nodes/n1 | $parse diff --git a/src/test/bash/patch/nodes/1-node-publicKey.sh b/src/test/bash/patch/nodes/1-node-publicKey.sh index 3b6937ec..da9b01d6 100755 --- a/src/test/bash/patch/nodes/1-node-publicKey.sh +++ b/src/test/bash/patch/nodes/1-node-publicKey.sh @@ -1,6 +1,6 @@ # Update node 1 as node source `dirname $0`/../../functions.sh PATCH $* -curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_NODEAUTH" -d '{ +curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_NODEAUTH" -d '{ "publicKey": "newABCDEF" -}' $EXCHANGE_URL_ROOT/v1/nodes/1 | $parse +}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/nodes/n1 | $parse diff --git a/src/test/bash/post/patterns/p1.sh b/src/test/bash/post/patterns/p1.sh new file mode 100755 index 00000000..b564d92f --- /dev/null +++ b/src/test/bash/post/patterns/p1.sh @@ -0,0 +1,28 @@ +# Adds a workload +source `dirname $0`/../../functions.sh POST $* + +curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ + "label": "My Pattern", "description": "blah blah", "public": true, + "workloads": [ + { + "workloadUrl": "https://bluehorizon.network/workloads/netspeed", + "workloadOrgid": "IBM", + "workloadArch": "amd64", + "workloadVersions": [ + { + "version": "1.0.1", + "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", + "deployment_overrides_signature": "", + "priority": {}, + "upgradePolicy": {} + } + ], + "dataVerification": {}, + "nodeHealth": { + "missing_heartbeat_interval": 600, + "check_agreement_status": 120 + } + } + ], + "agreementProtocols": [{ "name": "Basic" }] +}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/patterns/p1 | $parse diff --git a/src/test/bash/primedb.sh b/src/test/bash/primedb.sh index 04a9295f..f3c5a400 100755 --- a/src/test/bash/primedb.sh +++ b/src/test/bash/primedb.sh @@ -54,9 +54,13 @@ agreementid2="${agreementbase}2" microid="bluehorizon.network-microservices-network_1.0.0_amd64" microurl="https://bluehorizon.network/microservices/network" +microarch="amd64" +microversion="1.0.0" workid="bluehorizon.network-workloads-netspeed_1.0.0_amd64" workurl="https://bluehorizon.network/workloads/netspeed" +workarch="amd64" +workversion="1.0.0" workid2="bluehorizon.network-workloads-weather_1.0.0_amd64" workurl2="https://bluehorizon.network/workloads/weather" @@ -167,6 +171,102 @@ else echo "orgs/$orgid2/users/$user exists" fi +rc=$(curlfind $userauth "orgs/$orgid/microservices/$microid") +checkrc "$rc" 200 404 +if [[ $rc != 200 ]]; then + curlcreate "POST" $userauth "orgs/$orgid/microservices" '{"label": "Network x86_64", "description": "blah blah", "public": true, "specRef": "'$microurl'", + "version": "'$microversion'", "arch": "'$microarch'", "sharable": "single", "downloadUrl": "", + "matchHardware": {}, + "userInput": [], + "workloads": [] }' +else + echo "orgs/$orgid/microservices/$microid exists" +fi + +rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid") +checkrc "$rc" 200 404 +if [[ $rc != 200 ]]; then + curlcreate "POST" $userauth "orgs/$orgid/workloads" '{"label": "Netspeed x86_64", "description": "blah blah", "public": true, "workloadUrl": "'$workurl'", + "version": "'$workversion'", "arch": "'$workarch'", "downloadUrl": "", + "apiSpec": [{ "specRef": "'$microurl'", "org": "'$orgid'", "version": "'$microversion'", "arch": "'$microarch'" }], + "userInput": [], + "workloads": [] }' +else + echo "orgs/$orgid/workloads/$workid exists" +fi + +rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid2") +checkrc "$rc" 200 404 +if [[ $rc != 200 ]]; then + curlcreate "POST" $userauth "orgs/$orgid/workloads" '{"label": "Weather x86_64", "description": "blah blah", "public": true, "workloadUrl": "'$workurl2'", + "version": "1.0.0", "arch": "amd64", "downloadUrl": "", + "apiSpec": [{ "specRef": "'$microurl'", "org": "'$orgid'", "version": "'$microversion'", "arch": "'$microarch'" }], + "userInput": [], + "workloads": [] }' +else + echo "orgs/$orgid/workloads/$workid2 exists" +fi + +rc=$(curlfind $userauth "orgs/$orgid/patterns/$patid") +checkrc "$rc" 200 404 +if [[ $rc != 200 ]]; then + curlcreate "POST" $userauth "orgs/$orgid/patterns/$patid" '{"label": "My Pattern", "description": "blah blah", "public": true, + "workloads": [ + { + "workloadUrl": "'$workurl'", + "workloadOrgid": "'$orgid'", + "workloadArch": "'$workarch'", + "workloadVersions": [ + { + "version": "'$workversion'", + "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", + "deployment_overrides_signature": "a", + "priority": { + "priority_value": 50, + "retries": 1, + "retry_durations": 3600, + "verified_durations": 52 + }, + "upgradePolicy": { + "lifecycle": "immediate", + "time": "01:00AM" + } + } + ], + "dataVerification": { + "enabled": true, + "URL": "", + "user": "", + "password": "", + "interval": 240, + "check_rate": 15, + "metering": { + "tokens": 1, + "per_time_unit": "min", + "notification_interval": 30 + } + }, + "nodeHealth": { + "missing_heartbeat_interval": 600, + "check_agreement_status": 120 + } + } + ], + "agreementProtocols": [{ "name": "Basic" }] }' +else + echo "orgs/$orgid/patterns/$patid exists" +fi + +rc=$(curlfind $userauthorg2 "orgs/$orgid2/patterns/$patid") +checkrc "$rc" 200 404 +if [[ $rc != 200 ]]; then + curlcreate "POST" $userauthorg2 "orgs/$orgid2/patterns/$patid" '{"label": "My other Pattern", "description": "blah blah", "public": true, + "workloads": [], + "agreementProtocols": [{ "name": "Basic" }] }' +else + echo "orgs/$orgid2/patterns/$patid exists" +fi + rc=$(curlfind $userauth "orgs/$orgid/nodes/$nodeid") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then @@ -203,20 +303,20 @@ else echo "orgs/$orgid2/nodes/$nodeid exists" fi -rc=$(curlfind $userauth "orgs/$orgid/agbots/$agbotid") +rc=$(curlfind $userauth "orgs/$orgid/nodes/$nodeid/status") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauth "orgs/$orgid/agbots/$agbotid" '{"token": "'$agbottoken'", "name": "agbot", "msgEndPoint": "whisper-id", "publicKey": "ABC"}' + curlcreate "PUT" $nodeauth "orgs/$orgid/nodes/$nodeid/status" '{ "connectivity": {"firmware.bluehorizon.network": true}, "microservices": [], "workloads": [] }' else - echo "orgs/$orgid/agbots/$agbotid exists" + echo "orgs/$orgid/nodes/$nodeid/status exists" fi -rc=$(curlfind $userauth "orgs/$orgid/agbots/$agbotid/patterns/${orgid}_$patid") +rc=$(curlfind $userauth "orgs/$orgid/agbots/$agbotid") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauth "orgs/$orgid/agbots/$agbotid/patterns/${orgid}_$patid" '{ "patternOrgid": "'$orgid'", "pattern": "'$patid'" }' + curlcreate "PUT" $userauth "orgs/$orgid/agbots/$agbotid" '{"token": "'$agbottoken'", "name": "agbot", "msgEndPoint": "whisper-id", "publicKey": "ABC"}' else - echo "orgs/$orgid/agbots/$agbotid/patterns/${orgid}_$patid exists" + echo "orgs/$orgid/agbots/$agbotid exists" fi rc=$(curlfind $userauthorg2 "orgs/$orgid2/agbots/$agbotid") @@ -227,12 +327,12 @@ else echo "orgs/$orgid2/agbots/$agbotid exists" fi -rc=$(curlfind $userauth "orgs/$orgid/nodes/$nodeid/status") +rc=$(curlfind $userauth "orgs/$orgid/agbots/$agbotid/patterns/${orgid}_$patid") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then - curlcreate "PUT" $nodeauth "orgs/$orgid/nodes/$nodeid/status" '{ "connectivity": {"firmware.bluehorizon.network": true}, "microservices": [], "workloads": [] }' + curlcreate "PUT" $userauth "orgs/$orgid/agbots/$agbotid/patterns/${orgid}_$patid" '{ "patternOrgid": "'$orgid'", "pattern": "'$patid'" }' else - echo "orgs/$orgid/nodes/$nodeid/status exists" + echo "orgs/$orgid/agbots/$agbotid/patterns/${orgid}_$patid exists" fi rc=$(curlfind $userauth "orgs/$orgid/nodes/$nodeid/agreements/$agreementid1") @@ -267,102 +367,6 @@ else echo "orgs/$orgid/agbots/$agbotid/agreements/$agreementid1 exists" fi -rc=$(curlfind $userauth "orgs/$orgid/microservices/$microid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/microservices" '{"label": "Network x86_64", "description": "blah blah", "public": true, "specRef": "'$microurl'", - "version": "1.0.0", "arch": "amd64", "sharable": "single", "downloadUrl": "", - "matchHardware": {}, - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/microservices/$microid exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/workloads" '{"label": "Netspeed x86_64", "description": "blah blah", "public": true, "workloadUrl": "'$workurl'", - "version": "1.0.0", "arch": "amd64", "downloadUrl": "", - "apiSpec": [{ "specRef": "'$microurl'", "org": "IBM", "version": "1.0.0", "arch": "amd64" }], - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/workloads/$workid exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid2") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/workloads" '{"label": "Weather x86_64", "description": "blah blah", "public": true, "workloadUrl": "'$workurl2'", - "version": "1.0.0", "arch": "amd64", "downloadUrl": "", - "apiSpec": [{ "specRef": "'$microurl'", "org": "IBM", "version": "1.0.0", "arch": "amd64" }], - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/workloads/$workid2 exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/patterns/$patid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/patterns/$patid" '{"label": "My Pattern", "description": "blah blah", "public": true, - "workloads": [ - { - "workloadUrl": "'$workurl'", - "workloadOrgid": "'$orgid'", - "workloadArch": "amd64", - "workloadVersions": [ - { - "version": "1.0.1", - "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_overrides_signature": "", - "priority": { - "priority_value": 50, - "retries": 1, - "retry_durations": 3600, - "verified_durations": 52 - }, - "upgradePolicy": { - "lifecycle": "immediate", - "time": "01:00AM" - } - } - ], - "dataVerification": { - "enabled": true, - "URL": "", - "user": "", - "password": "", - "interval": 240, - "check_rate": 15, - "metering": { - "tokens": 1, - "per_time_unit": "min", - "notification_interval": 30 - } - }, - "nodeHealth": { - "missing_heartbeat_interval": 600, - "check_agreement_status": 120 - } - } - ], - "agreementProtocols": [{ "name": "Basic" }] }' -else - echo "orgs/$orgid/patterns/$patid exists" -fi - -rc=$(curlfind $userauthorg2 "orgs/$orgid2/patterns/$patid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauthorg2 "orgs/$orgid2/patterns/$patid" '{"label": "My other Pattern", "description": "blah blah", "public": true, - "workloads": [], - "agreementProtocols": [{ "name": "Basic" }] }' -else - echo "orgs/$orgid2/patterns/$patid exists" -fi - # Do not have a good way to know what msg id they will have, but it is ok to create additional msgs curlputpost "POST" $agbotauth "orgs/$orgid/nodes/$nodeid/msgs" '{"message": "hey there", "ttl": 300}' curlputpost "POST" $nodeauth "orgs/$orgid/agbots/$agbotid/msgs" '{"message": "hey there", "ttl": 300}' diff --git a/src/test/bash/put/patterns/p1.sh b/src/test/bash/put/patterns/p1.sh new file mode 100755 index 00000000..eaf75826 --- /dev/null +++ b/src/test/bash/put/patterns/p1.sh @@ -0,0 +1,28 @@ +# Adds a workload +source `dirname $0`/../../functions.sh POST $* + +curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ + "label": "My Pattern", "description": "blah blah", "public": true, + "workloads": [ + { + "workloadUrl": "https://bluehorizon.network/workloads/netspeed", + "workloadOrgid": "IBM", + "workloadArch": "amd64", + "workloadVersions": [ + { + "version": "1.0.1", + "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", + "deployment_overrides_signature": "", + "priority": {}, + "upgradePolicy": {} + } + ], + "dataVerification": {}, + "nodeHealth": { + "missing_heartbeat_interval": 600, + "check_agreement_status": 120 + } + } + ], + "agreementProtocols": [{ "name": "Basic" }] +}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/patterns/p1 | $parse diff --git a/src/test/scala/exchangeapi/AgbotsSuite.scala b/src/test/scala/exchangeapi/AgbotsSuite.scala index 5beaf13d..861399d1 100644 --- a/src/test/scala/exchangeapi/AgbotsSuite.scala +++ b/src/test/scala/exchangeapi/AgbotsSuite.scala @@ -59,7 +59,12 @@ class AgbotsSuite extends FunSuite { val patId = orgid + "_" + pattern val pattern2 = "mypattern2" val patId2 = orgid + "_" + pattern2 - val workload = "myworkload" + val pattern3 = "mypattern3" + val patId3 = orgid + "_" + pattern3 + val workid = "bluehorizon.network-workloads-netspeed_1.0.0_amd64" + val workurl = "https://bluehorizon.network/workloads/netspeed" + val workarch = "amd64" + val workversion = "1.0.0" val micro = "mymicro" val nodeId = "mynode" val nodeToken = nodeId+"tok" @@ -260,6 +265,94 @@ class AgbotsSuite extends FunSuite { } + test("POST /orgs/"+orgid+"/workloads - add "+workid+" and check that agbot can read it") { + val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map()), List()) + val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + + val response2: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(AGBOTAUTH).asString + info("code: "+response2.code) + assert(response2.code === HttpCode.OK) + val respObj = parse(response2.body).extract[GetWorkloadsResponse] + assert(respObj.workloads.size === 1) + } + // Note: when we delete the org, this workload will get deleted + + test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" and check that agbot can read it") { + val input = PostPutPatternRequest(pattern, "desc", false, + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List[Map[String,String]]() + ) + val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + + val response2: HttpResponse[String] = Http(URL+"/patterns/"+pattern).headers(ACCEPT).headers(AGBOTAUTH).asString + info("code: "+response2.code) + assert(response2.code === HttpCode.OK) + val respObj = parse(response2.body).extract[GetPatternsResponse] + assert(respObj.patterns.size === 1) + } + // Note: when we delete the org, this pattern will get deleted + + test("GET /orgs/"+orgid+"/patterns/"+pattern+" - as IBM agbot (if it exists) and also msg between orgs") { + val ibmAgbotAuth = sys.env.getOrElse("EXCHANGE_AGBOTAUTH", "") + val ibmAgbotId = """^[^:]+""".r.findFirstIn(ibmAgbotAuth).getOrElse("") // get the id before the : + if (ibmAgbotAuth != "") { + val IBMAGBOTAUTH = ("Authorization", "Basic IBM/" + ibmAgbotAuth) + val response: HttpResponse[String] = Http(URL + "/patterns/" + pattern).headers(ACCEPT).headers(IBMAGBOTAUTH).asString + info("code: " + response.code) + assert(response.code === HttpCode.OK) + val respObj = parse(response.body).extract[GetPatternsResponse] + assert(respObj.patterns.size === 1) + + if (ibmAgbotId != "") { + // Also create a node to make sure they can msg each other + val input = PutNodesRequest(nodeToken, "rpi" + nodeId + "-norm", orgid + "/" + pattern, List(), "", Map(), "NODEABC") + var response2 = Http(URL + "/nodes/" + nodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: " + response2.code) + assert(response2.code === HttpCode.PUT_OK) + + val input2 = PostNodesMsgsRequest("{msg from IBM agbot to node in this org}", 300) + response2 = Http(URL + "/nodes/" + nodeId + "/msgs").postData(write(input2)).method("post").headers(CONTENT).headers(ACCEPT).headers(IBMAGBOTAUTH).asString + info("code: " + response2.code + ", response.body: " + response2.body) + assert(response2.code === HttpCode.POST_OK) + + val input3 = PostAgbotsMsgsRequest("{msg from node in this org to IBM agbot}", 300) + response2 = Http(NOORGURL + "/orgs/IBM/agbots/" + ibmAgbotId + "/msgs").postData(write(input3)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString + info("code: " + response2.code + ", response.body: " + response2.body) + assert(response2.code === HttpCode.POST_OK) + } + } + } + + test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2) { + val input = PostPutPatternRequest(pattern2, "desc", false, + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List[Map[String,String]]() + ) + val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + } + // Note: when we delete the org, this pattern will get deleted + + test("POST /orgs/"+orgid+"/microservices - add "+micro+" and check that agbot can read it") { + val input = PostPutMicroserviceRequest(micro+" arm", "desc", false, "https://msurl", "1.0.0", "arm", "singleton", "", Map(), List(Map()), List()) + val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + + val response2: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(AGBOTAUTH).asString + info("code: "+response2.code) + assert(response2.code === HttpCode.OK) + val respObj = parse(response2.body).extract[GetMicroservicesResponse] + assert(respObj.microservices.size === 1) + } + // Note: when we delete the org, this pattern will get deleted + + // Test the pattern sub-resources test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/patterns/"+patId+" - as user") { val input = PutAgbotPatternRequest(orgid, pattern) @@ -275,6 +368,13 @@ class AgbotsSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) } + test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/patterns/"+patId3+" - add pattern that does not exist - should fail") { + val input = PutAgbotPatternRequest(orgid, pattern3) + val response = Http(URL+"/agbots/"+agbotId+"/patterns/"+patId3).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + test("GET /orgs/"+orgid+"/agbots/"+agbotId+"/patterns - as agbot") { val response: HttpResponse[String] = Http(URL+"/agbots/"+agbotId+"/patterns").headers(ACCEPT).headers(AGBOTAUTH).asString info("code: " + response.code) @@ -333,82 +433,6 @@ class AgbotsSuite extends FunSuite { } - test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" and check that agbot can read it") { - val input = PostPutPatternRequest(pattern, "desc", false, - List( PWorkloads("https://wkurl", orgid, "", List(PWorkloadVersions("", "", "", Map(), Map())), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), - List[Map[String,String]]() - ) - val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - - val response2: HttpResponse[String] = Http(URL+"/patterns/"+pattern).headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response2.code) - assert(response2.code === HttpCode.OK) - val respObj = parse(response2.body).extract[GetPatternsResponse] - assert(respObj.patterns.size === 1) - } - // Note: when we delete the org, this pattern will get deleted - - test("GET /orgs/"+orgid+"/patterns/"+pattern+" - as IBM agbot (if it exists) and also msg between orgs") { - val ibmAgbotAuth = sys.env.getOrElse("EXCHANGE_AGBOTAUTH", "") - val ibmAgbotId = """^[^:]+""".r.findFirstIn(ibmAgbotAuth).getOrElse("") // get the id before the : - if (ibmAgbotAuth != "") { - val IBMAGBOTAUTH = ("Authorization", "Basic IBM/" + ibmAgbotAuth) - val response: HttpResponse[String] = Http(URL + "/patterns/" + pattern).headers(ACCEPT).headers(IBMAGBOTAUTH).asString - info("code: " + response.code) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetPatternsResponse] - assert(respObj.patterns.size === 1) - - if (ibmAgbotId != "") { - // Also create a node to make sure they can msg each other - val input = PutNodesRequest(nodeToken, "rpi" + nodeId + "-norm", orgid + "/" + pattern, List(), "", Map(), "NODEABC") - var response2 = Http(URL + "/nodes/" + nodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: " + response2.code) - assert(response2.code === HttpCode.PUT_OK) - - val input2 = PostNodesMsgsRequest("{msg from IBM agbot to node in this org}", 300) - response2 = Http(URL + "/nodes/" + nodeId + "/msgs").postData(write(input2)).method("post").headers(CONTENT).headers(ACCEPT).headers(IBMAGBOTAUTH).asString - info("code: " + response2.code + ", response.body: " + response2.body) - assert(response2.code === HttpCode.POST_OK) - - val input3 = PostAgbotsMsgsRequest("{msg from node in this org to IBM agbot}", 300) - response2 = Http(NOORGURL + "/orgs/IBM/agbots/" + ibmAgbotId + "/msgs").postData(write(input3)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - info("code: " + response2.code + ", response.body: " + response2.body) - assert(response2.code === HttpCode.POST_OK) - } - } - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload+" and check that agbot can read it") { - val input = PostPutWorkloadRequest(workload+" arm", "desc", false, "https://wkurl", "1.0.0", "arm", "", List(Map()), List(Map()), List(Map())) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - - val response2: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response2.code) - assert(response2.code === HttpCode.OK) - val respObj = parse(response2.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 1) - } - // Note: when we delete the org, this pattern will get deleted - - test("POST /orgs/"+orgid+"/microservices - add "+micro+" and check that agbot can read it") { - val input = PostPutMicroserviceRequest(micro+" arm", "desc", false, "https://msurl", "1.0.0", "arm", "singleton", "", Map(), List(Map()), List(Map())) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - - val response2: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response2.code) - assert(response2.code === HttpCode.OK) - val respObj = parse(response2.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - } - // Note: when we delete the org, this pattern will get deleted - /** Try to confirm the agreement that's not there for agbot 9930 */ test("POST /orgs/"+orgid+"/agreements/confirm - not there") { val input = PostAgreementsConfirmRequest(agreementId) diff --git a/src/test/scala/exchangeapi/BlockchainsSuite.scala b/src/test/scala/exchangeapi/BlockchainsSuite.scala index 29d42ab2..08abd480 100644 --- a/src/test/scala/exchangeapi/BlockchainsSuite.scala +++ b/src/test/scala/exchangeapi/BlockchainsSuite.scala @@ -107,7 +107,7 @@ class BlockchainsSuite extends FunSuite { info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - val devInput = PutNodesRequest(nodeToken, "bc dev test", "myorg/mypat", List(RegMicroservice("foo",1,"{}",List( + val devInput = PutNodesRequest(nodeToken, "bc dev test", "", List(RegMicroservice("foo",1,"{}",List( Prop("arch","arm","string","in"), Prop("version","2.0.0","version","in"), Prop("blockchainProtocols","agProto","list","in")))), "whisper-id", Map(), "NODEABC") diff --git a/src/test/scala/exchangeapi/FrontEndSuite.scala b/src/test/scala/exchangeapi/FrontEndSuite.scala index 9519e78d..cc46dd26 100644 --- a/src/test/scala/exchangeapi/FrontEndSuite.scala +++ b/src/test/scala/exchangeapi/FrontEndSuite.scala @@ -54,10 +54,11 @@ class FrontEndSuite extends FunSuite { val msUrl = "http://" + msBase val microservice = msBase + "_1.0.0_arm" val orgmicroservice = authpref+microservice - val wkBase = "wk1" - val wkUrl = "http://" + wkBase - val workload = wkBase + "_1.0.0_arm" - val orgworkload = authpref+workload + val workid = "bluehorizon.network-workloads-netspeed_1.0.0_amd64" + val workurl = "https://bluehorizon.network/workloads/netspeed" + val workarch = "amd64" + val workversion = "1.0.0" + val orgworkload = authpref+workid val ptBase = "pat1" val pattern = ptBase val orgpattern = authpref+pattern @@ -126,6 +127,64 @@ class FrontEndSuite extends FunSuite { assert(response.code === HttpCode.POST_OK) } + test("POST /orgs/"+orgid+"/microservices - create "+microservice) { + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + val respObj = parse(response.body).extract[ApiResponse] + assert(respObj.msg.contains("microservice '"+orgmicroservice+"' created")) + } + + test("GET /orgs/"+orgid+"/microservices") { + val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString + info("code: "+response.code) + // info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.OK) + val respObj = parse(response.body).extract[GetMicroservicesResponse] + assert(respObj.microservices.size === 1) + assert(respObj.microservices.contains(orgmicroservice)) + } + + test("POST /orgs/"+orgid+"/workloads - create "+workid) { + val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + val respObj = parse(response.body).extract[ApiResponse] + assert(respObj.msg.contains("workload '"+orgworkload+"' created")) + } + + test("GET /orgs/"+orgid+"/workloads/"+workid+" - as user") { + val response: HttpResponse[String] = Http(URL + "/workloads/" + workid).headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString + info("code: " + response.code) + // info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.OK) + val respObj = parse(response.body).extract[GetWorkloadsResponse] + assert(respObj.workloads.size === 1) + } + + test("POST /orgs/"+orgid+"/patterns/"+pattern+" - create "+pattern) { + val input = PostPutPatternRequest(ptBase, "desc", false, + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List[Map[String,String]]() + ) + val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + val respObj = parse(response.body).extract[ApiResponse] + assert(respObj.msg.contains("pattern '"+orgpattern+"' created")) + } + + test("GET /orgs/"+orgid+"/patterns") { + val response: HttpResponse[String] = Http(URL + "/patterns").headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString + info("code: " + response.code) + // info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.OK) + val respObj = parse(response.body).extract[GetPatternsResponse] + assert(respObj.patterns.size === 1) + } + test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - create node") { val input = PutNodesRequest(nodeToken, nodeId+"-normal", orgpattern, List( @@ -325,64 +384,6 @@ class FrontEndSuite extends FunSuite { assert(resp.messages.size === 1) } - test("POST /orgs/"+orgid+"/microservices - create "+microservice) { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("microservice '"+orgmicroservice+"' created")) - } - - test("GET /orgs/"+orgid+"/microservices") { - val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - assert(respObj.microservices.contains(orgmicroservice)) - } - - test("POST /orgs/"+orgid+"/workloads - create "+workload) { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("workload '"+orgworkload+"' created")) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+" - as user") { - val response: HttpResponse[String] = Http(URL + "/workloads/" + workload).headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: " + response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 1) - } - - test("POST /orgs/"+orgid+"/patterns/"+pattern+" - create "+pattern) { - val input = PostPutPatternRequest(ptBase, "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), - List[Map[String,String]]() - ) - val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("pattern '"+orgpattern+"' created")) - } - - test("GET /orgs/"+orgid+"/patterns") { - val response: HttpResponse[String] = Http(URL + "/patterns").headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: " + response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetPatternsResponse] - assert(respObj.patterns.size === 1) - } - test("Cleanup - DELETE everything and confirm they are gone") { deleteAllUsers() } diff --git a/src/test/scala/exchangeapi/MicroservicesSuite.scala b/src/test/scala/exchangeapi/MicroservicesSuite.scala index 3af5640d..efc6fe04 100644 --- a/src/test/scala/exchangeapi/MicroservicesSuite.scala +++ b/src/test/scala/exchangeapi/MicroservicesSuite.scala @@ -96,7 +96,7 @@ class MicroservicesSuite extends FunSuite { } /** Add users, node, microservice for future tests */ - test("Add users, node, microservice for future tests") { + test("Add users, node, agbot for future tests") { var userInput = PostPutUsersRequest(pw, false, user+"@hotmail.com") var userResponse = Http(URL+"/users/"+user).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) @@ -107,7 +107,7 @@ class MicroservicesSuite extends FunSuite { info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - val devInput = PutNodesRequest(nodeToken, "bc dev test", "myorg/mypat", List(RegMicroservice("foo",1,"{}",List( + val devInput = PutNodesRequest(nodeToken, "bc dev test", "", List(RegMicroservice("foo",1,"{}",List( Prop("arch","arm","string","in"), Prop("version","2.0.0","version","in"), Prop("blockchainProtocols","agProto","list","in")))), "whisper-id", Map(), "NODEABC") @@ -122,14 +122,21 @@ class MicroservicesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update MS that is not there yet - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "updated", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "updated", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.NOT_FOUND) } + test("POST /orgs/"+orgid+"/microservices - add "+microservice+" that is not signed - should fail") { + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","","a"))) + val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + test("POST /orgs/"+orgid+"/microservices - add "+microservice+" as user") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -138,28 +145,28 @@ class MicroservicesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/microservices - add "+microservice+" again - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ALREADY_EXISTS) } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as same user") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "updated", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "updated", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as 2nd user - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "should not work", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "should not work", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as agbot - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) @@ -175,14 +182,14 @@ class MicroservicesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/microservices - add "+microservice2+" as node - should fail") { - val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", false, msUrl2, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", false, msUrl2, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("POST /orgs/"+orgid+"/microservices - add "+microservice2+" as 2nd user") { - val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", true, msUrl2, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", true, msUrl2, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -202,7 +209,7 @@ test("POST /orgs/"+orgid+"/microservices - with low maxMicroservices - should fa assert(response.code === HttpCode.PUT_OK) // Now try adding another microservice - expect it to be rejected - val input = PostPutMicroserviceRequest(msBase3+" arm", "desc", msUrl3, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutMicroserviceRequest(msBase3+" arm", "desc", msUrl3, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) diff --git a/src/test/scala/exchangeapi/NodesSuite.scala b/src/test/scala/exchangeapi/NodesSuite.scala index 8d1359fc..5389b373 100644 --- a/src/test/scala/exchangeapi/NodesSuite.scala +++ b/src/test/scala/exchangeapi/NodesSuite.scala @@ -61,6 +61,10 @@ class NodesSuite extends FunSuite { val orgnodeId4 = authpref+nodeId4 val patid = "mypat" val compositePatid = orgid+"/"+patid + val workid = "bluehorizon.network-workloads-netspeed_1.0.0_amd64" + val workurl = "https://bluehorizon.network/workloads/netspeed" + val workarch = "amd64" + val workversion = "1.0.0" val agreementId = "9950" val creds = authpref+nodeId+":"+nodeToken val encodedCreds = Base64.getEncoder.encodeToString(creds.getBytes("utf-8")) @@ -156,9 +160,36 @@ class NodesSuite extends FunSuite { assert(response.code === HttpCode.POST_OK) } + test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - before pattern exists - should fail") { + val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, + List(), + "whisper-id", Map("horizon"->"3.2.3"), "NODEABC") + val response = Http(URL+"/nodes/"+nodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code) + assert(response.code === HttpCode.BAD_INPUT) + } + + test("POST /orgs/"+orgid+"/workloads - add "+workid+" so pattern can reference it") { + val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map()), List()) + val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + } + // Note: when we delete the org, this workload will get deleted + + test("POST /orgs/"+orgid+"/patterns/"+patid+" - so nodes can reference it") { + val input = PostPutPatternRequest(patid, "desc", false, + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List[Map[String,String]]() + ) + val response = Http(URL+"/patterns/"+patid).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + } + // Note: when we delete the org, this pattern will get deleted + ExchConfig.load() - /** Add a normal node */ - test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - normal") { + test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - add normal node") { val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, List( RegMicroservice(PWSSPEC,1,"{json policy for "+nodeId+" pws}",List( @@ -570,20 +601,6 @@ class NodesSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) } - /* - test("PATCH /orgs/"+orgid+"/nodes/"+nodeId+" - remove pattern from node so we can search for microservices") { - val jsonInput = """{ "pattern": "" }""" - var response = Http(URL+"/nodes/"+nodeId).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - assert(response.code === HttpCode.PUT_OK) - response = Http(URL+"/nodes/"+nodeId2).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - assert(response.code === HttpCode.PUT_OK) - response = Http(URL+"/nodes/"+nodeId3).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - assert(response.code === HttpCode.PUT_OK) - response = Http(URL+"/nodes/"+nodeId4).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - assert(response.code === HttpCode.PUT_OK) - } - */ - test("POST /orgs/"+orgid+"/search/nodes - all arm nodes") { patchNodePattern("") // remove pattern from nodes so we can search for microservices val input = PostSearchNodesRequest(List(RegMicroserviceSearch(SDRSPEC,List( @@ -878,20 +895,6 @@ class NodesSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) } - /* - test("PATCH /orgs/"+orgid+"/nodes/"+nodeId+" - put pattern back in node so we can search for pattern nodes") { - val jsonInput = """{ "pattern": """"+compositePatid+"""" }""" - var response = Http(URL+"/nodes/"+nodeId).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - assert(response.code === HttpCode.PUT_OK) - response = Http(URL+"/nodes/"+nodeId2).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - assert(response.code === HttpCode.PUT_OK) - response = Http(URL+"/nodes/"+nodeId3).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - assert(response.code === HttpCode.PUT_OK) - response = Http(URL+"/nodes/"+nodeId4).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - assert(response.code === HttpCode.PUT_OK) - } - */ - test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - with "+nodeId+" in agreement") { patchNodePattern(compositePatid) // put pattern back in nodes so we can search for pattern nodes val input = PostPatternSearchRequest(SDRSPEC, 86400, 0, 0) diff --git a/src/test/scala/exchangeapi/PatternsSuite.scala b/src/test/scala/exchangeapi/PatternsSuite.scala index 1e0e5733..936fcd16 100644 --- a/src/test/scala/exchangeapi/PatternsSuite.scala +++ b/src/test/scala/exchangeapi/PatternsSuite.scala @@ -50,6 +50,10 @@ class PatternsSuite extends FunSuite { val agbotId = "9948" val agbotToken = agbotId+"tok" val AGBOTAUTH = ("Authorization","Basic "+authpref+agbotId+":"+agbotToken) + val workid = "bluehorizon.network-workloads-netspeed_1.0.0_amd64" + val workurl = "https://bluehorizon.network/workloads/netspeed" + val workarch = "amd64" + val workversion = "1.0.0" val ptBase = "pt9920" //val ptUrl = "http://" + ptBase val pattern = ptBase @@ -95,33 +99,50 @@ class PatternsSuite extends FunSuite { /** Add users, node, pattern for future tests */ test("Add users, node, pattern for future tests") { - var userInput = PostPutUsersRequest(pw, false, user+"@hotmail.com") - var userResponse = Http(URL+"/users/"+user).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) + var userInput = PostPutUsersRequest(pw, false, user + "@hotmail.com") + var userResponse = Http(URL + "/users/" + user).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString + info("code: " + userResponse.code + ", userResponse.body: " + userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - userInput = PostPutUsersRequest(pw2, false, user2+"@hotmail.com") - userResponse = Http(URL+"/users/"+user2).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) + userInput = PostPutUsersRequest(pw2, false, user2 + "@hotmail.com") + userResponse = Http(URL + "/users/" + user2).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString + info("code: " + userResponse.code + ", userResponse.body: " + userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - val devInput = PutNodesRequest(nodeToken, "bc dev test", "myorg/mypat", List(RegMicroservice("foo",1,"{}",List( - Prop("arch","arm","string","in"), - Prop("version","2.0.0","version","in"), - Prop("blockchainProtocols","agProto","list","in")))), "whisper-id", Map(), "NODEABC") - val devResponse = Http(URL+"/nodes/"+nodeId).postData(write(devInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+devResponse.code) + val devInput = PutNodesRequest(nodeToken, "bc dev test", "", List(RegMicroservice("foo", 1, "{}", List( + Prop("arch", "arm", "string", "in"), + Prop("version", "2.0.0", "version", "in"), + Prop("blockchainProtocols", "agProto", "list", "in")))), "whisper-id", Map(), "NODEABC") + val devResponse = Http(URL + "/nodes/" + nodeId).postData(write(devInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: " + devResponse.code) assert(devResponse.code === HttpCode.PUT_OK) - val agbotInput = PutAgbotsRequest(agbotToken, "agbot"+agbotId+"-norm", /*List[APattern](),*/ "whisper-id", "ABC") - val agbotResponse = Http(URL+"/agbots/"+agbotId).postData(write(agbotInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+agbotResponse.code+", agbotResponse.body: "+agbotResponse.body) + val agbotInput = PutAgbotsRequest(agbotToken, "agbot" + agbotId + "-norm", /*List[APattern](),*/ "whisper-id", "ABC") + val agbotResponse = Http(URL + "/agbots/" + agbotId).postData(write(agbotInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: " + agbotResponse.code + ", agbotResponse.body: " + agbotResponse.body) assert(agbotResponse.code === HttpCode.PUT_OK) } + test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" before workload is exists - should fail") { + val input = PostPutPatternRequest(ptBase, "desc", false, + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List[Map[String,String]]() + ) + val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + + test("Add workload for future tests") { + val workInput = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val workResponse = Http(URL+"/workloads").postData(write(workInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+workResponse.code+", response.body: "+workResponse.body) + assert(workResponse.code === HttpCode.POST_OK) + } + test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update pattern that is not there yet - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -129,9 +150,19 @@ class PatternsSuite extends FunSuite { assert(response.code === HttpCode.NOT_FOUND) } + test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" that is not signed - should fail") { + val input = PostPutPatternRequest(ptBase, "desc", false, + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "{\"services\":{}}", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List[Map[String,String]]() + ) + val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" as user") { val input = PostPutPatternRequest(ptBase, "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "{\"services\":{}}", "a", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -143,7 +174,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" again - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -153,7 +184,7 @@ class PatternsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as same user") { val input = PostPutPatternRequest(ptBase+" amd64", "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -163,7 +194,7 @@ class PatternsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as 2nd user - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString @@ -173,7 +204,7 @@ class PatternsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as agbot - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString @@ -192,7 +223,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2+" as node - should fail") { val input = PostPutPatternRequest("Bad Pattern2", "desc", false, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString @@ -202,7 +233,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2+" as 2nd user") { val input = PostPutPatternRequest(ptBase2+" amd64", "desc", true, - List( PWorkloads("https://wkurl", "myorg", "", List(PWorkloadVersions("", "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), PDataVerification(false, "", "", "", 0, 0, Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString @@ -358,6 +389,23 @@ class PatternsSuite extends FunSuite { assert(getPatternResp.patterns.size === 0) } + test("PATCH /orgs/"+orgid+"/patterns/"+pattern+" - patch the workloads") { + val input = List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map(), Map() )) + val jsonInput = """{ "workloads": """ + write(input) + " }" + //info("jsonInput: "+jsonInput) + val response = Http(URL+"/patterns/"+pattern).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.PUT_OK) + } + + test("PATCH /orgs/"+orgid+"/patterns/"+pattern+" - patch with a nonexistent workload - should fail") { + val input = List( PWorkloads("foo", orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map(), Map() )) + val jsonInput = """{ "workloads": """ + write(input) + " }" + val response = Http(URL+"/patterns/"+pattern).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + test("DELETE /orgs/"+orgid+"/patterns/"+pattern) { val response = Http(URL+"/patterns/"+pattern).method("delete").headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) diff --git a/src/test/scala/exchangeapi/WorkloadsSuite.scala b/src/test/scala/exchangeapi/WorkloadsSuite.scala index 212671f8..50028529 100644 --- a/src/test/scala/exchangeapi/WorkloadsSuite.scala +++ b/src/test/scala/exchangeapi/WorkloadsSuite.scala @@ -61,7 +61,9 @@ class WorkloadsSuite extends FunSuite { val wkBase3 = "wk9922" val wkUrl3 = "http://" + wkBase3 val workload3 = wkBase3 + "_1.0.0_arm" - //var numExistingWorkloads = 0 // this will be set later + val microurl = "https://bluehorizon.network/microservices/network" + val microarch = "amd64" + val microversion = "1.0.0" implicit val formats = DefaultFormats // Brings in default date formats etc. @@ -105,7 +107,7 @@ class WorkloadsSuite extends FunSuite { info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - val devInput = PutNodesRequest(nodeToken, "bc dev test", "myorg/mypat", List(RegMicroservice("foo",1,"{}",List( + val devInput = PutNodesRequest(nodeToken, "bc dev test", "", List(RegMicroservice("foo",1,"{}",List( Prop("arch","arm","string","in"), Prop("version","2.0.0","version","in"), Prop("blockchainProtocols","agProto","list","in")))), "whisper-id", Map(), "NODEABC") @@ -119,16 +121,37 @@ class WorkloadsSuite extends FunSuite { assert(agbotResponse.code === HttpCode.PUT_OK) } + test("POST /orgs/"+orgid+"/workloads - add "+workload+" before the referenced microservice exists - should fail") { + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + + test("POST /orgs/"+orgid+"/microservices - add microservice so workloads can reference it") { + val input = PostPutMicroserviceRequest("testMicro", "desc", false, microurl, microversion, microarch, "single", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.POST_OK) + } + test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update WK that is not there yet - should fail") { // PostPutWorkloadRequest(label: String, description: String, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: List[Map[String,String]], userInput: List[Map[String,String]], workloads: List[Map[String,String]]) { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "updated", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "updated", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.NOT_FOUND) } + test("POST /orgs/"+orgid+"/workloads - add "+workload+" that is not signed - should fail") { + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a",""))) + val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString + info("code: "+response.code+", response.body: "+response.body) + assert(response.code === HttpCode.BAD_INPUT) + } + test("POST /orgs/"+orgid+"/workloads - add "+workload+" as user") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -137,28 +160,28 @@ class WorkloadsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - add "+workload+" again - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ALREADY_EXISTS) } test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as same user") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "updated", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "updated", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as 2nd user - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "should not work", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "should not work", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as agbot - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) @@ -174,14 +197,14 @@ class WorkloadsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - add "+workload2+" as node - should fail") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", false, wkUrl2, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", false, wkUrl2, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("POST /orgs/"+orgid+"/workloads - add "+workload2+" as 2nd user") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", true, wkUrl2, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl2")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", true, wkUrl2, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -201,7 +224,7 @@ class WorkloadsSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) // Now try adding another workload - expect it to be rejected - val input = PostPutWorkloadRequest(wkBase3+" arm", "desc", wkUrl3, "1.0.0", "arm", "", List(Map("specRef" -> "https://msurl")), List(Map("name" -> "foo")), List(Map("deployment" -> "{\"services\":{}}"))) + val input = PostPutWorkloadRequest(wkBase3+" arm", "desc", wkUrl3, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) @@ -237,7 +260,7 @@ class WorkloadsSuite extends FunSuite { } test("GET /orgs/"+orgid+"/workloads - filter owner and workloadUrl") { - val response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(USERAUTH).param("owner",orguser2).param("specRef","%msurl2").asString + val response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(USERAUTH).param("owner",orguser2).param("specRef",microurl).asString info("code: "+response.code) // info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.OK) From e78a7084bba78b1baefa78601bfaf67ae066d697 Mon Sep 17 00:00:00 2001 From: Bruce Potter Date: Wed, 18 Oct 2017 15:19:45 -0400 Subject: [PATCH 3/5] updated readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33a06bd0..82010765 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ services in the exchange. ### External changes - Added GET /admin/version +- Docker tag for exchange docker image no longer has the "v" before the version number - Modified pattern resource so dataVerification field could be specified as `{}` if you are not using it - For the references between resources that are not enforced by DB foreign keys, added checking when the resource is created, updated, or patched: - microservices referenced by a workload @@ -84,7 +85,9 @@ services in the exchange. - patterns referenced by an agbot - pattern referenced by a node -### Todos left to be finished +(No need to upgrade the db if coming from version 1.38) + +### Todos left to be finished in subsequent versions - If maxAgreements>1, for CS, in search don't return node to agbot if agbot from same org already has agreement for same workload. - Add api for wiotp to get number of devices and agreements @@ -107,6 +110,8 @@ services in the exchange. - Added `"nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 }` policy to patterns (it is a peer to the dataVerification field). Existing pattern resources in the DB will be converted on the way out. New POST/PUTs must include this new field. - Added POST /orgs/{orgid}/patterns/{patid}/nodehealth and POST /orgs/{orgid}/search/nodehealth for agbot to get node lastHeartbeat and agreement status +(No need to upgrade the db if coming from version 1.38) + ## Changes in v1.38.0 From a52b457fa4791066a6821ff76706a6bc247e533c Mon Sep 17 00:00:00 2001 From: Bruce Potter Date: Wed, 18 Oct 2017 15:43:52 -0400 Subject: [PATCH 4/5] fixed swagger of admin/version api --- src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala | 4 ++-- src/test/scala/exchangeapi/AdminSuite.scala | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala b/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala index e348ff12..c548c4a5 100644 --- a/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/AdminRoutes.scala @@ -381,9 +381,9 @@ trait AdminRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi // =========== GET /admin/version =============================== val getAdminVersion = - (apiOperation[GetAdminStatusResponse]("getAdminStatus") + (apiOperation[String]("getAdminVersion") summary "Returns the version of the Exchange server" - notes "Returns the version of the Exchange server. Can be run by anyone." + notes "Returns the version of the Exchange server as a simple string (no JSON or quotes). Can be run by anyone." ) get("/admin/version", operation(getAdminVersion)) ({ diff --git a/src/test/scala/exchangeapi/AdminSuite.scala b/src/test/scala/exchangeapi/AdminSuite.scala index 56cd7f90..f43a61b3 100644 --- a/src/test/scala/exchangeapi/AdminSuite.scala +++ b/src/test/scala/exchangeapi/AdminSuite.scala @@ -72,4 +72,10 @@ class AdminSuite extends FunSuite { val postResp = parse(response.body).extract[ApiResponse] assert(postResp.code === ApiResponseType.BAD_INPUT) } + + test("GET /admin/version") { + val response = Http(URL+"/admin/version").headers(ACCEPT).asString + info("code: "+response.code) + assert(response.code === HttpCode.OK) + } } \ No newline at end of file From f20da473a41009e8ada9d2cdd71851c7dfa348c1 Mon Sep 17 00:00:00 2001 From: Bruce Potter Date: Thu, 19 Oct 2017 10:38:10 -0400 Subject: [PATCH 5/5] made some fields optional --- README.md | 11 ++++--- .../exchangeapi/MicroservicesRoutes.scala | 14 ++++----- .../horizon/exchangeapi/PatternsRoutes.scala | 29 +++++++++++++------ .../horizon/exchangeapi/WorkloadsRoutes.scala | 8 ++--- .../horizon/exchangeapi/tables/Patterns.scala | 11 ++++--- src/test/bash/post/patterns/p1.sh | 6 ++-- .../bash/put/patterns/p1-no-nodehealth.sh | 26 +++++++++++++++++ src/test/bash/put/patterns/p1.sh | 13 ++++----- src/test/scala/exchangeapi/AgbotsSuite.scala | 8 ++--- .../scala/exchangeapi/FrontEndSuite.scala | 6 ++-- .../exchangeapi/MicroservicesSuite.scala | 22 +++++++------- src/test/scala/exchangeapi/NodesSuite.scala | 4 +-- .../scala/exchangeapi/PatternsSuite.scala | 28 +++++++++--------- src/test/scala/exchangeapi/UsersSuite.scala | 4 +-- .../scala/exchangeapi/WorkloadsSuite.scala | 22 +++++++------- 15 files changed, 126 insertions(+), 86 deletions(-) create mode 100755 src/test/bash/put/patterns/p1-no-nodehealth.sh diff --git a/README.md b/README.md index 82010765..5a86f7bd 100644 --- a/README.md +++ b/README.md @@ -78,14 +78,17 @@ services in the exchange. - Added GET /admin/version - Docker tag for exchange docker image no longer has the "v" before the version number -- Modified pattern resource so dataVerification field could be specified as `{}` if you are not using it - For the references between resources that are not enforced by DB foreign keys, added checking when the resource is created, updated, or patched: - microservices referenced by a workload - workloads referenced by a pattern - patterns referenced by an agbot - pattern referenced by a node +- Made the following resource fields optional: + - pattern: dataVerification can be omitted or specified as `{}` + - microservice: downloadUrl can be omitted, matchHardware can be omitted or specified as `{}` + - workload: downloadUrl can be omitted -(No need to upgrade the db if coming from version 1.38) +(No need to upgrade the db if coming from version 1.38 or later, altho it won't hurt either) ### Todos left to be finished in subsequent versions @@ -107,10 +110,10 @@ services in the exchange. - Updated jetty version to 9.4.7 and fixed build to pull latest bug fixes in the 9.4 range - Fixed non-pattern node search to not find pattern nodes - Now filter both pattern and non-pattern node searches to not return nodes with empty publicKey values -- Added `"nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 }` policy to patterns (it is a peer to the dataVerification field). Existing pattern resources in the DB will be converted on the way out. New POST/PUTs must include this new field. +- Added `"nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 }` policy to patterns (it is a peer to the dataVerification field). Existing pattern resources in the DB will be converted on the way out. For new POST/PUTs this new field is optional in 1.40. - Added POST /orgs/{orgid}/patterns/{patid}/nodehealth and POST /orgs/{orgid}/search/nodehealth for agbot to get node lastHeartbeat and agreement status -(No need to upgrade the db if coming from version 1.38) +(No need to upgrade the db if coming from version 1.38, altho it won't hurt either) ## Changes in v1.38.0 diff --git a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala index 366f2cd8..782b3076 100644 --- a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala @@ -21,7 +21,7 @@ case class GetMicroservicesResponse(microservices: Map[String,Microservice], las case class GetMicroserviceAttributeResponse(attribute: String, value: String) /** Input format for POST /orgs/{orgid}/microservices or PUT /orgs/{orgid}/microservices/ */ -case class PostPutMicroserviceRequest(label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: String, matchHardware: Map[String,String], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { +case class PostPutMicroserviceRequest(label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: Option[String], matchHardware: Option[Map[String,String]], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { protected implicit val jsonFormats: Formats = DefaultFormats def validate() = { // Currently we do not want to force that the specRef is a valid URL @@ -38,7 +38,7 @@ case class PostPutMicroserviceRequest(label: String, description: String, public def formId(orgid: String) = MicroservicesTQ.formId(orgid, specRef, version, arch) - def toMicroserviceRow(microservice: String, orgid: String, owner: String) = MicroserviceRow(microservice, orgid, owner, label, description, public, specRef, version, arch, sharable, downloadUrl, write(matchHardware), write(userInput), write(workloads), ApiTime.nowUTC) + def toMicroserviceRow(microservice: String, orgid: String, owner: String) = MicroserviceRow(microservice, orgid, owner, label, description, public, specRef, version, arch, sharable, downloadUrl.getOrElse(""), write(matchHardware), write(userInput), write(workloads), ApiTime.nowUTC) } case class PatchMicroserviceRequest(label: Option[String], description: Option[String], public: Option[Boolean], specRef: Option[String], version: Option[String], arch: Option[String], sharable: Option[String], downloadUrl: Option[String]) { @@ -174,8 +174,8 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "version": "1.0.0", "arch": "amd64", "sharable": "exclusive", // or: "single", "multiple" - "downloadUrl": "", // reserved for future use - "matchHardware": {}, // reserved for future use (will be hints to the node about how to tell if it has the physical sensors required by this MS + "downloadUrl": "", // reserved for future use, can be omitted + "matchHardware": {}, // reserved for future use, can be omitted (will be hints to the node about how to tell if it has the physical sensors required by this MS // Values the node owner will be prompted for and will be set as env vars to the container. "userInput": [ { @@ -260,8 +260,8 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "version": "1.0.0", "arch": "amd64", "sharable": "exclusive", // or: "single", "multiple" - "downloadUrl": "", // reserved for future use - "matchHardware": {}, // reserved for future use (will be hints to the node about how to tell if it has the physical sensors required by this MS + "downloadUrl": "", // reserved for future use, can be omitted + "matchHardware": {}, // reserved for future use, can be omitted (will be hints to the node about how to tell if it has the physical sensors required by this MS // Values the node owner will be prompted for and will be set as env vars to the container. "userInput": [ { @@ -339,7 +339,7 @@ trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSup "version": "1.0.0", "arch": "amd64", "sharable": "exclusive", // or: "single", "multiple" - "downloadUrl": "" // not used yet + "downloadUrl": "" // reserved for future use } ```""" parameters( diff --git a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala index 6a011df2..1430439b 100644 --- a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala @@ -39,8 +39,17 @@ case class PostPutPatternRequest(label: String, description: String, public: Boo // Build a list of db actions to verify that the referenced workloads exist def validateWorkloadIds: DBIO[Vector[Int]] = PatternsTQ.validateWorkloadIds(workloads) - //def toPatternRow(pattern: String, orgid: String, owner: String) = PatternRow(pattern, orgid, owner, label, description, public, write(microservices), write(workloads), write(dataVerification), write(agreementProtocols), write(properties), write(counterPartyProperties), maxAgreements, ApiTime.nowUTC) def toPatternRow(pattern: String, orgid: String, owner: String) = PatternRow(pattern, orgid, owner, label, description, public, write(workloads), write(agreementProtocols), ApiTime.nowUTC) + /* This is what to do if we want to fill in a default value for nodeHealth when it is not specified... + def toPatternRow(pattern: String, orgid: String, owner: String): PatternRow = { + // The nodeHealth field is optional, so fill in a default in each element of workloads if not specified. (Otherwise json4s will omit it in the DB and the GETs.) + val workloads2 = workloads.map({ w => + val nodeHealth2 = if (w.nodeHealth.nonEmpty) w.nodeHealth else Some(Map[String,Int]()) + PWorkloads(w.workloadUrl, w.workloadOrgid, w.workloadArch, w.workloadVersions, w.dataVerification, nodeHealth2) + }) + PatternRow(pattern, orgid, owner, label, description, public, write(workloads2), write(agreementProtocols), ApiTime.nowUTC) + } + */ } case class PatchPatternRequest(label: Option[String], description: Option[String], public: Option[Boolean], workloads: Option[List[PWorkloads]], agreementProtocols: Option[List[Map[String,String]]]) { @@ -186,7 +195,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "retry_durations": 3600, "verified_durations": 52 }, - // When Horizon should upgrade nodes to newer workload versions + // When Horizon should upgrade nodes to newer workload versions. Can be set to {} to take the default of immediate. "upgradePolicy": { "lifecycle": "immediate", "time": "01:00AM" // reserved for future use @@ -194,7 +203,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport } ], // Fill in this section if the Horizon agbot should run a REST API of the cloud data ingest service to confirm the workload is sending data. - // If not using this, the dataVerification field can be set to {}. + // If not using this, the dataVerification field can be set to {} or omitted completely. "dataVerification": { "enabled": true, "URL": "", @@ -208,9 +217,10 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "notification_interval": 30 } }, + // If not using agbot node health check, this field can be set to {} or omitted completely. "nodeHealth": { - "missing_heartbeat_interval": 600, // How long a node heartbeat can be missing until node is considered missing (in seconds) - "check_agreement_status": 120 // How often to check that the node agreement entry still exists in the exchange (in seconds) + "missing_heartbeat_interval": 600, // How long a node heartbeat can be missing before cancelling its agreements (in seconds) + "check_agreement_status": 120 // How often to check that the node agreement entry still exists, and cancel agreement if not found (in seconds) } } ], @@ -320,7 +330,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "retry_durations": 3600, "verified_durations": 52 }, - // When Horizon should upgrade nodes to newer workload versions + // When Horizon should upgrade nodes to newer workload versions. Can be set to {} to take the default of immediate. "upgradePolicy": { "lifecycle": "immediate", "time": "01:00AM" // reserved for future use @@ -328,7 +338,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport } ], // Fill in this section if the Horizon agbot should run a REST API of the cloud data ingest service to confirm the workload is sending data. - // If not using this, the dataVerification field can be set to {}. + // If not using this, the dataVerification field can be set to {} or omitted completely. "dataVerification": { "enabled": true, "URL": "", @@ -342,9 +352,10 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "notification_interval": 30 } }, + // If not using agbot node health check, this field can be set to {} or omitted completely. "nodeHealth": { - "missing_heartbeat_interval": 600, // How long a node heartbeat can be missing until node is considered missing (in seconds) - "check_agreement_status": 120 // How often to check that the node agreement entry still exists in the exchange (in seconds) + "missing_heartbeat_interval": 600, // How long a node heartbeat can be missing before cancelling its agreements (in seconds) + "check_agreement_status": 120 // How often to check that the node agreement entry still exists, and cancel agreement if not found (in seconds) } } ], diff --git a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala index 8e37faea..664eb84f 100644 --- a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala @@ -23,7 +23,7 @@ case class GetWorkloadsResponse(workloads: Map[String,Workload], lastIndex: Int) case class GetWorkloadAttributeResponse(attribute: String, value: String) /** Input format for POST /microservices or PUT /orgs/{orgid}/workloads/ */ -case class PostPutWorkloadRequest(label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: List[WMicroservices], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { +case class PostPutWorkloadRequest(label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: Option[String], apiSpec: List[WMicroservices], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { protected implicit val jsonFormats: Formats = DefaultFormats def validate() = { // Currently we do not want to force that the workloadUrl is a valid URL @@ -51,7 +51,7 @@ case class PostPutWorkloadRequest(label: String, description: String, public: Bo def formId(orgid: String) = WorkloadsTQ.formId(orgid, workloadUrl, version, arch) - def toWorkloadRow(workload: String, orgid: String, owner: String) = WorkloadRow(workload, orgid, owner, label, description, public, workloadUrl, version, arch, downloadUrl, write(apiSpec), write(userInput), write(workloads), ApiTime.nowUTC) + def toWorkloadRow(workload: String, orgid: String, owner: String) = WorkloadRow(workload, orgid, owner, label, description, public, workloadUrl, version, arch, downloadUrl.getOrElse(""), write(apiSpec), write(userInput), write(workloads), ApiTime.nowUTC) } case class PatchWorkloadRequest(label: Option[String], description: Option[String], public: Option[Boolean], workloadUrl: Option[String], version: Option[String], arch: Option[String], downloadUrl: Option[String]) { @@ -192,7 +192,7 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this workload "version": "1.0.0", "arch": "amd64", - "downloadUrl": "", // reserved for future use + "downloadUrl": "", // reserved for future use, can be omitted // The microservices used by this workload. (The microservices must exist before creating this workload.) "apiSpec": [ { @@ -302,7 +302,7 @@ trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this workload "version": "1.0.0", "arch": "amd64", - "downloadUrl": "", // reserved for future use + "downloadUrl": "", // reserved for future use, can be omitted // The microservices used by this workload. (The microservices must exist before creating this workload.) "apiSpec": [ { diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala b/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala index bf148f36..2b25ce46 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala @@ -11,7 +11,7 @@ import scala.collection.mutable.ListBuffer //case class PPriority(priority_value: Int, retries: Int, retry_durations: Int, verified_durations: Int) //case class PUpgradePolicy(lifecycle: String, time: String) //case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: PDataVerification, nodeHealth: Map[String,Int]) -case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: Map[String,Any], nodeHealth: Map[String,Int]) +case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: Option[Map[String,Any]], nodeHealth: Option[Map[String,Int]]) //case class POldWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: PDataVerification) case class POldWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PWorkloadVersions], dataVerification: Map[String,Any]) case class PWorkloadVersions(version: String, deployment_overrides: String, deployment_overrides_signature: String, priority: Map[String,Int], upgradePolicy: Map[String,String]) @@ -22,14 +22,17 @@ case class PatternRow(pattern: String, orgid: String, owner: String, label: Stri protected implicit val jsonFormats: Formats = DefaultFormats def toPattern: Pattern = { - val wrk = if (workloads == "") List[PWorkloads]() else { - try { read[List[PWorkloads]](workloads) } //todo: figure out if json4s can be made to be tolerant of missing fields + val wrk = if (workloads == "") List[PWorkloads]() else read[List[PWorkloads]](workloads) + /* Do not need this anymore because putting Option[] around the nodeHealth type makes the json reading and writing tolerant of it not being there + { + try { read[List[PWorkloads]](workloads) } catch { case _: MappingException => val oldWrk = read[List[POldWorkloads]](workloads) // this pattern in the DB does not have the new nodeHealth field, so convert it val newList = new ListBuffer[PWorkloads] - for (w <- oldWrk) { newList += PWorkloads(w.workloadUrl, w.workloadOrgid, w.workloadArch, w.workloadVersions, w.dataVerification, Map()) } + for (w <- oldWrk) { newList += PWorkloads(w.workloadUrl, w.workloadOrgid, w.workloadArch, w.workloadVersions, w.dataVerification, Some(Map())) } newList.toList } } + */ val agproto = if (agreementProtocols != "") read[List[Map[String,String]]](agreementProtocols) else List[Map[String,String]]() new Pattern(owner, label, description, public, wrk, agproto, lastUpdated) } diff --git a/src/test/bash/post/patterns/p1.sh b/src/test/bash/post/patterns/p1.sh index b564d92f..47b0cf70 100755 --- a/src/test/bash/post/patterns/p1.sh +++ b/src/test/bash/post/patterns/p1.sh @@ -6,13 +6,13 @@ curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/ "workloads": [ { "workloadUrl": "https://bluehorizon.network/workloads/netspeed", - "workloadOrgid": "IBM", + "workloadOrgid": "'$EXCHANGE_ORG'", "workloadArch": "amd64", "workloadVersions": [ { - "version": "1.0.1", + "version": "1.0.0", "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_overrides_signature": "", + "deployment_overrides_signature": "a", "priority": {}, "upgradePolicy": {} } diff --git a/src/test/bash/put/patterns/p1-no-nodehealth.sh b/src/test/bash/put/patterns/p1-no-nodehealth.sh new file mode 100755 index 00000000..32bd9bf6 --- /dev/null +++ b/src/test/bash/put/patterns/p1-no-nodehealth.sh @@ -0,0 +1,26 @@ +# Adds a workload +source `dirname $0`/../../functions.sh PUT $* + +curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ + "label": "My Pattern", "description": "blah blah", "public": true, + "workloads": [ + { + "workloadUrl": "https://bluehorizon.network/workloads/netspeed", + "workloadOrgid": "'$EXCHANGE_ORG'", + "workloadArch": "amd64", + "workloadVersions": [ + { + "version": "1.0.0", + "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", + "deployment_overrides_signature": "a", + "priority": {}, + "upgradePolicy": {} + } + ], + "dataVerification": {} + } + ], + "agreementProtocols": [{ "name": "Basic" }] +}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/patterns/p1 | $parse + +# "nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 } diff --git a/src/test/bash/put/patterns/p1.sh b/src/test/bash/put/patterns/p1.sh index eaf75826..06e7e8ea 100755 --- a/src/test/bash/put/patterns/p1.sh +++ b/src/test/bash/put/patterns/p1.sh @@ -1,27 +1,24 @@ # Adds a workload -source `dirname $0`/../../functions.sh POST $* +source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ "label": "My Pattern", "description": "blah blah", "public": true, "workloads": [ { "workloadUrl": "https://bluehorizon.network/workloads/netspeed", - "workloadOrgid": "IBM", + "workloadOrgid": "'$EXCHANGE_ORG'", "workloadArch": "amd64", "workloadVersions": [ { - "version": "1.0.1", + "version": "1.0.0", "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_overrides_signature": "", + "deployment_overrides_signature": "a", "priority": {}, "upgradePolicy": {} } ], "dataVerification": {}, - "nodeHealth": { - "missing_heartbeat_interval": 600, - "check_agreement_status": 120 - } + "nodeHealth": { "missing_heartbeat_interval": 600, "check_agreement_status": 120 } } ], "agreementProtocols": [{ "name": "Basic" }] diff --git a/src/test/scala/exchangeapi/AgbotsSuite.scala b/src/test/scala/exchangeapi/AgbotsSuite.scala index 861399d1..34f807ac 100644 --- a/src/test/scala/exchangeapi/AgbotsSuite.scala +++ b/src/test/scala/exchangeapi/AgbotsSuite.scala @@ -266,7 +266,7 @@ class AgbotsSuite extends FunSuite { test("POST /orgs/"+orgid+"/workloads - add "+workid+" and check that agbot can read it") { - val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map()), List()) + val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, None, List(), List(Map()), List()) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -281,7 +281,7 @@ class AgbotsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" and check that agbot can read it") { val input = PostPutPatternRequest(pattern, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -329,7 +329,7 @@ class AgbotsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2) { val input = PostPutPatternRequest(pattern2, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -339,7 +339,7 @@ class AgbotsSuite extends FunSuite { // Note: when we delete the org, this pattern will get deleted test("POST /orgs/"+orgid+"/microservices - add "+micro+" and check that agbot can read it") { - val input = PostPutMicroserviceRequest(micro+" arm", "desc", false, "https://msurl", "1.0.0", "arm", "singleton", "", Map(), List(Map()), List()) + val input = PostPutMicroserviceRequest(micro+" arm", "desc", false, "https://msurl", "1.0.0", "arm", "single", None, None, List(Map()), List()) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) diff --git a/src/test/scala/exchangeapi/FrontEndSuite.scala b/src/test/scala/exchangeapi/FrontEndSuite.scala index cc46dd26..7959020d 100644 --- a/src/test/scala/exchangeapi/FrontEndSuite.scala +++ b/src/test/scala/exchangeapi/FrontEndSuite.scala @@ -128,7 +128,7 @@ class FrontEndSuite extends FunSuite { } test("POST /orgs/"+orgid+"/microservices - create "+microservice) { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -147,7 +147,7 @@ class FrontEndSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - create "+workid) { - val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, None, List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -166,7 +166,7 @@ class FrontEndSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - create "+pattern) { val input = PostPutPatternRequest(ptBase, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString diff --git a/src/test/scala/exchangeapi/MicroservicesSuite.scala b/src/test/scala/exchangeapi/MicroservicesSuite.scala index efc6fe04..f857e7c6 100644 --- a/src/test/scala/exchangeapi/MicroservicesSuite.scala +++ b/src/test/scala/exchangeapi/MicroservicesSuite.scala @@ -122,21 +122,21 @@ class MicroservicesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update MS that is not there yet - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "updated", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", Some("updated"), Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.NOT_FOUND) } test("POST /orgs/"+orgid+"/microservices - add "+microservice+" that is not signed - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) } - test("POST /orgs/"+orgid+"/microservices - add "+microservice+" as user") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + test("POST /orgs/"+orgid+"/microservices - add "+microservice+" as user, and omit matchHardware") { + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -145,28 +145,28 @@ class MicroservicesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/microservices - add "+microservice+" again - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ALREADY_EXISTS) } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as same user") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "updated", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", Some("updated"), Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as 2nd user - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "should not work", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", Some("should not work"), Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as agbot - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) @@ -182,14 +182,14 @@ class MicroservicesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/microservices - add "+microservice2+" as node - should fail") { - val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", false, msUrl2, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", false, msUrl2, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("POST /orgs/"+orgid+"/microservices - add "+microservice2+" as 2nd user") { - val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", true, msUrl2, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", true, msUrl2, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -209,7 +209,7 @@ test("POST /orgs/"+orgid+"/microservices - with low maxMicroservices - should fa assert(response.code === HttpCode.PUT_OK) // Now try adding another microservice - expect it to be rejected - val input = PostPutMicroserviceRequest(msBase3+" arm", "desc", msUrl3, "1.0.0", "arm", "singleton", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest(msBase3+" arm", "desc", msUrl3, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) diff --git a/src/test/scala/exchangeapi/NodesSuite.scala b/src/test/scala/exchangeapi/NodesSuite.scala index 5389b373..7c97ca96 100644 --- a/src/test/scala/exchangeapi/NodesSuite.scala +++ b/src/test/scala/exchangeapi/NodesSuite.scala @@ -170,7 +170,7 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - add "+workid+" so pattern can reference it") { - val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map()), List()) + val input = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, None, List(), List(Map()), List()) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -179,7 +179,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+patid+" - so nodes can reference it") { val input = PostPutPatternRequest(patid, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+patid).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString diff --git a/src/test/scala/exchangeapi/PatternsSuite.scala b/src/test/scala/exchangeapi/PatternsSuite.scala index 936fcd16..f059fd86 100644 --- a/src/test/scala/exchangeapi/PatternsSuite.scala +++ b/src/test/scala/exchangeapi/PatternsSuite.scala @@ -125,7 +125,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" before workload is exists - should fail") { val input = PostPutPatternRequest(ptBase, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -134,7 +134,7 @@ class PatternsSuite extends FunSuite { } test("Add workload for future tests") { - val workInput = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, "", List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val workInput = PostPutWorkloadRequest("test-workload", "desc", false, workurl, workversion, workarch, None, List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val workResponse = Http(URL+"/workloads").postData(write(workInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+workResponse.code+", response.body: "+workResponse.body) assert(workResponse.code === HttpCode.POST_OK) @@ -142,7 +142,7 @@ class PatternsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update pattern that is not there yet - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -152,7 +152,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" that is not signed - should fail") { val input = PostPutPatternRequest(ptBase, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "{\"services\":{}}", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "{\"services\":{}}", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -162,7 +162,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" as user") { val input = PostPutPatternRequest(ptBase, "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "{\"services\":{}}", "a", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "{\"services\":{}}", "a", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -174,7 +174,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" again - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -182,9 +182,9 @@ class PatternsSuite extends FunSuite { assert(response.code === HttpCode.ALREADY_EXISTS) } - test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as same user") { + test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as same user, w/o dataVerification or nodeHealth fields") { val input = PostPutPatternRequest(ptBase+" amd64", "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), None, None )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -194,7 +194,7 @@ class PatternsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as 2nd user - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString @@ -204,7 +204,7 @@ class PatternsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as agbot - should fail") { val input = PostPutPatternRequest("Bad Pattern", "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString @@ -223,7 +223,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2+" as node - should fail") { val input = PostPutPatternRequest("Bad Pattern2", "desc", false, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString @@ -233,7 +233,7 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2+" as 2nd user") { val input = PostPutPatternRequest(ptBase2+" amd64", "desc", true, - List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]()), Map("check_agreement_status" -> 120) )), + List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map("priority_value" -> 50), Map("lifecycle" -> "immediate"))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), List[Map[String,String]]() ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString @@ -390,7 +390,7 @@ class PatternsSuite extends FunSuite { } test("PATCH /orgs/"+orgid+"/patterns/"+pattern+" - patch the workloads") { - val input = List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map(), Map() )) + val input = List( PWorkloads(workurl, orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Some(Map()), Some(Map()) )) val jsonInput = """{ "workloads": """ + write(input) + " }" //info("jsonInput: "+jsonInput) val response = Http(URL+"/patterns/"+pattern).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -399,7 +399,7 @@ class PatternsSuite extends FunSuite { } test("PATCH /orgs/"+orgid+"/patterns/"+pattern+" - patch with a nonexistent workload - should fail") { - val input = List( PWorkloads("foo", orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Map(), Map() )) + val input = List( PWorkloads("foo", orgid, workarch, List(PWorkloadVersions(workversion, "", "", Map(), Map())), Some(Map()), Some(Map()) )) val jsonInput = """{ "workloads": """ + write(input) + " }" val response = Http(URL+"/patterns/"+pattern).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) diff --git a/src/test/scala/exchangeapi/UsersSuite.scala b/src/test/scala/exchangeapi/UsersSuite.scala index 02d42751..169791c4 100644 --- a/src/test/scala/exchangeapi/UsersSuite.scala +++ b/src/test/scala/exchangeapi/UsersSuite.scala @@ -490,7 +490,7 @@ class UsersSuite extends FunSuite { test("POST /orgs/"+orgid+"/microservices - add "+microservice+" as not public in 1st org") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", "", Map(), List(), List()) + val input = PostPutMicroserviceRequest(msBase+" arm", "desc", false, msUrl, "1.0.0", "arm", "single", None, None, List(), List()) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -537,7 +537,7 @@ class UsersSuite extends FunSuite { test("POST /orgs/"+orgid+"/workloads - add "+workload+" as not public in 1st org") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(), List(), List()) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", None, List(), List(), List()) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) diff --git a/src/test/scala/exchangeapi/WorkloadsSuite.scala b/src/test/scala/exchangeapi/WorkloadsSuite.scala index 50028529..338855a1 100644 --- a/src/test/scala/exchangeapi/WorkloadsSuite.scala +++ b/src/test/scala/exchangeapi/WorkloadsSuite.scala @@ -122,14 +122,14 @@ class WorkloadsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - add "+workload+" before the referenced microservice exists - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) } test("POST /orgs/"+orgid+"/microservices - add microservice so workloads can reference it") { - val input = PostPutMicroserviceRequest("testMicro", "desc", false, microurl, microversion, microarch, "single", "", Map("usbNodeIds" -> "1546:01a7"), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutMicroserviceRequest("testMicro", "desc", false, microurl, microversion, microarch, "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -137,21 +137,21 @@ class WorkloadsSuite extends FunSuite { test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update WK that is not there yet - should fail") { // PostPutWorkloadRequest(label: String, description: String, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: List[Map[String,String]], userInput: List[Map[String,String]], workloads: List[Map[String,String]]) { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "updated", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.NOT_FOUND) } test("POST /orgs/"+orgid+"/workloads - add "+workload+" that is not signed - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a",""))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a",""))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) } test("POST /orgs/"+orgid+"/workloads - add "+workload+" as user") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -160,28 +160,28 @@ class WorkloadsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - add "+workload+" again - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ALREADY_EXISTS) } test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as same user") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "updated", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as 2nd user - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "should not work", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", Some("should not work"), List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as agbot - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase+" arm", "desc", false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) @@ -197,14 +197,14 @@ class WorkloadsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/workloads - add "+workload2+" as node - should fail") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", false, wkUrl2, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", false, wkUrl2, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) } test("POST /orgs/"+orgid+"/workloads - add "+workload2+" as 2nd user") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", true, wkUrl2, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", true, wkUrl2, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK)